From 0f6a7dabb6e6675193707841653dc42b34d762b1 Mon Sep 17 00:00:00 2001 From: Simone Garau <20005068@studenti.uniupo.it> Date: Tue, 16 Sep 2025 14:46:15 +0200 Subject: [PATCH] Ho fatto funzionare ollama sia con docer che con uv. Aggiornato il readme --- .env.example | 7 ++ Dockerfile | 4 +- README.md | 29 +++++++- docker-compose.yaml | 22 ++++++ pyproject.toml | 4 +- src/example.py | 22 ++++-- src/ollama_demo.py | 161 ++++++++++++++++++++++++++++++++++++++++++++ uv.lock | 15 +++++ 8 files changed, 252 insertions(+), 12 deletions(-) create mode 100644 src/ollama_demo.py diff --git a/.env.example b/.env.example index 2e1f87a..be19490 100644 --- a/.env.example +++ b/.env.example @@ -13,3 +13,10 @@ GOOGLE_API_KEY= ANTHROPIC_API_KEY= DEEPSEEK_API_KEY= OPENAI_API_KEY= + +# Dipende dal sistema operativo +# windows: C:\Users\\.ollama +# mac: /Users//.ollama +# linux: /home//.ollama +# wsl: /usr/share/ollama/.ollama +OLLAMA_MODELS_PATH= \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index ebb38d8..d7f15df 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,7 +2,7 @@ # Infatti scegliamo l'immagine ufficiale di uv che ha già tutto configurato # Nel caso in cui si volesse usare un'altra immagine di base che ha magari CUDA # bisognerebbe installare uv manualmente come descritto nel README -#FROM pytorch/pytorch:2.6.0-cuda12.6-cudnn9-devel # Lo lascio qui nel caso + FROM ghcr.io/astral-sh/uv:python3.12-alpine # Dopo aver definito la workdir mi trovo già in essa @@ -22,4 +22,4 @@ COPY LICENSE . COPY src ./src # Comando di default all'avvio dell'applicazione -CMD ["python", "src/app.py"] +CMD ["python", "src/example.py"] diff --git a/README.md b/README.md index 04d7eab..b7d374f 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,9 @@ L'obiettivo di questo progetto è creare un sistema basato su **LLM Agents** e d # Installazione Per l'installazione si può utilizzare un approccio tramite **uv** (manuale) oppure utilizzare un ambiente **Docker** già pronto (automatico). -Prima di avviare l'applicazione è però necessario configurare correttamente le API keys, altrimenti il progetto, anche se installato correttamente, non riuscirà a partire. +Prima di avviare l'applicazione è però necessario configurare correttamente le API keys e installare Ollama per l'utilizzo dei modelli locali, altrimenti il progetto, anche se installato correttamente, non riuscirà a partire. + +## API Keys Le API Keys puoi ottenerle tramite i seguenti servizi: - **Google AI**: [Google AI Studio](https://makersuite.google.com/app/apikey) (gratuito con limiti) - **Anthropic**: [Anthropic Console](https://console.anthropic.com/) @@ -19,6 +21,27 @@ Le API Keys puoi ottenerle tramite i seguenti servizi: Nota che alcune API sono gratuite con limiti di utilizzo, altre sono a pagamento. Google offre attualmente l'accesso gratuito con limiti ragionevoli. +## Ollama (Modelli Locali) +Per utilizzare modelli AI localmente, è necessario installare Ollama: + +**1. Installazione Ollama**: +- **Linux**: + ```sh + curl -fsSL https://ollama.com/install.sh | sh + ``` +- **macOS/Windows**: Scarica l'installer da [https://ollama.com/download/windows](https://ollama.com/download/windows) + +**2. GPU Support (Raccomandato)**: +Per utilizzare la GPU con Ollama, assicurati di avere NVIDIA CUDA Toolkit installato: +- **Download**: [NVIDIA CUDA Downloads](https://developer.nvidia.com/cuda-downloads?target_os=Windows&target_arch=x86_64&target_version=11&target_type=exe_local) +- **Documentazione WSL**: [CUDA WSL User Guide](https://docs.nvidia.com/cuda/wsl-user-guide/index.html) + +**3. Installazione Modelli**: +Esempio per installare un modello locale: +```sh +ollama pull gemma3:4b +``` + ### Variabili d'Ambiente **1. Copia il file di esempio**: @@ -26,12 +49,14 @@ Nota che alcune API sono gratuite con limiti di utilizzo, altre sono a pagamento cp .env.example .env ``` -**2. Modifica il file .env** creato con le tue API keys, inserendole nella variabile opportuna dopo l'uguale e ***senza*** spazi: +**2. Modifica il file .env** creato con le tue API keys e il path dei modelli Ollama, inserendoli nelle variabili opportune dopo l'uguale e ***senza*** spazi: ```dotenv GOOGLE_API_KEY= ANTHROPIC_API_KEY= DEEPSEEK_API_KEY= OPENAI_API_KEY= +# Path dove Ollama salva i modelli (es. /home/username/.ollama su Linux) +OLLAMA_MODELS_PATH= ``` ### Opzione 1 UV diff --git a/docker-compose.yaml b/docker-compose.yaml index ef127ff..fffd043 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -8,8 +8,30 @@ services: - .:/app env_file: - .env + # Aggiunte chiave: environment: + # Questa variabile dice alla tua app dove trovare il servizio Ollama + - OLLAMA_HOST=http://ollama:11434 + # Le tue API keys esistenti - GOOGLE_API_KEY=${GOOGLE_API_KEY} - ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY} - DEEPSEEK_API_KEY=${DEEPSEEK_API_KEY} - OPENAI_API_KEY=${OPENAI_API_KEY} + # Assicura che ollama parta prima della tua app + depends_on: + - ollama + + # Nuovo servizio per Ollama + ollama: + image: ollama/ollama + container_name: ollama + # Aggiungi il runtime NVIDIA per GPU support + runtime: nvidia + environment: + - NVIDIA_VISIBLE_DEVICES=all + ports: + - "11434:11434" + volumes: + # Mappa la cartella dei modelli del tuo PC a quella interna del container + # ${OLLAMA_MODELS_PATH} sarà letto dal file .env + - ${OLLAMA_MODELS_PATH}:/root/.ollama \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index 2f11715..52049c3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -27,5 +27,7 @@ dependencies = [ # altamente consigliata dato che ha anche tools integrati per fare scraping, calcoli e molto altro # oltre a questa è necessario installare anche le librerie specifiche per i modelli che si vogliono usare "agno", - "google-genai" + "google-genai", + # ☑️ per usare modelli in locale + "ollama", ] diff --git a/src/example.py b/src/example.py index 5bcf878..7595560 100644 --- a/src/example.py +++ b/src/example.py @@ -2,19 +2,27 @@ from agno.agent import Agent from agno.models.google import Gemini from agno.tools.reasoning import ReasoningTools from dotenv import load_dotenv +import ollama +from ollama_demo import generate_text -try: +def run_gemini_poem(): load_dotenv() - reasoning_agent = Agent( model=Gemini(), - tools=[ - ReasoningTools(), - ], + tools=[ReasoningTools()], instructions="Use tables to display data.", markdown=True, ) result = reasoning_agent.run("Scrivi una poesia su un gatto. Sii breve.") print(result.content) -except Exception as e: - pass + +def run_ollama_codegemma_poem(): + prompt = "Scrivi una poesia su un gatto. Sii breve." + response = generate_text(model="gpt-oss:latest", prompt=prompt) + print(response) + +if __name__ == "__main__": + print("Risposta Gemini:") + run_gemini_poem() + print("\nRisposta Ollama GPT-OSS:") + run_ollama_codegemma_poem() diff --git a/src/ollama_demo.py b/src/ollama_demo.py new file mode 100644 index 0000000..1e52f5f --- /dev/null +++ b/src/ollama_demo.py @@ -0,0 +1,161 @@ +#!/usr/bin/env python3 +""" +Demo di Ollama (Python) – mostra: + 1. Elenco dei modelli disponibili + 2. Generazione di testo semplice + 3. Chat con streaming + 4. Calcolo di embeddings + 5. Esempio (opzionale) di function calling / tools + +Uso: + python ollama_demo.py + +Requisiti: + pip install ollama + Avviare il server Ollama (es. 'ollama serve' o l'app desktop) e avere i modelli già pullati. +""" + +import ollama + +# Configurazione modelli +MODEL = 'gpt-oss:latest' # modello principale per testo/chat +EMBEDDING_MODEL = 'mxbai-embed-large:latest' # modello dedicato embeddings (richiede supporto embeddings) + +# 1. Elenco dei modelli ------------------------------------------------------- + +def list_models(): + """Stampa i modelli caricati nel server Ollama.""" + print("\n[1] Modelli disponibili:") + try: + response = ollama.list() + models = getattr(response, 'models', []) or (response.get('models', []) if isinstance(response, dict) else []) + if not models: + print(" (Nessun modello trovato)") + return + for m in models: + name = getattr(m, 'model', None) or (m.get('model') if isinstance(m, dict) else 'sconosciuto') + details = getattr(m, 'details', None) + fmt = getattr(details, 'format', None) if details else 'unknown' + print(f" • {name} – {fmt}") + except Exception as e: + print(f" ❌ Errore durante il listing: {e}") + + +# 2. Generazione di testo ------------------------------------------------------ + +def generate_text(model: str, prompt: str, max_tokens: int = 200) -> str: + """Genera testo dal modello indicato.""" + print(f"\n[2] Generazione testo con '{model}'") + response = ollama.chat( + model=model, + messages=[{"role": "user", "content": prompt}] + ) + text = response['message']['content'] + print("Risposta:\n" + text + "\n") + return text + + +# 3. Chat con streaming -------------------------------------------------------- + +def chat_streaming(model: str, messages: list) -> str: + """Esegue una chat mostrando progressivamente la risposta.""" + print(f"\n[3] Chat (streaming) con '{model}'") + stream = ollama.chat(model=model, messages=messages, stream=True) + full = "" + for chunk in stream: + if 'message' in chunk and 'content' in chunk['message']: + part = chunk['message']['content'] + full += part + print(part, end="", flush=True) + print("\n") + return full + + +# 4. Embeddings ---------------------------------------------------------------- + +def get_embedding(model: str, text: str): + """Calcola embedding del testo col modello specificato (se supportato).""" + print(f"\n[4] Embedding con '{model}'") + try: + r = ollama.embeddings(model=model, prompt=text) + except ollama.ResponseError as e: + print(f" ⚠️ Il modello '{model}' non supporta embeddings o errore API: {e}") + return None + emb = r['embedding'] + print(f"Dimensione embedding: {len(emb)} (prime 5: {emb[:5]})") + return emb + + +# 5. Function calling / Tools (opzionale) -------------------------------------- + +def try_tools(model: str): + """Esempio di function calling; se non supportato mostra messaggio informativo.""" + print(f"\n[5] Function calling / tools con '{model}'") + tools = [ + { + "type": "function", + "function": { + "name": "get_current_weather", + "description": "Ottiene condizioni meteo sintetiche di una località (demo)", + "parameters": { + "type": "object", + "properties": { + "location": {"type": "string", "description": "Città"}, + "unit": {"type": "string", "enum": ["celsius", "fahrenheit"]} + }, + "required": ["location"] + } + } + } + ] + try: + response = ollama.chat( + model=model, + messages=[{"role": "user", "content": "Che tempo fa a Milano?"}], + tools=tools + ) + msg = response['message'] + if 'tool_calls' in msg and msg['tool_calls']: + tool_call = msg['tool_calls'][0] + print("Richiesta funzione:", tool_call['function']['name']) + print("Argomenti:", tool_call['function']['arguments']) + else: + print("Risposta modello senza tool call:", msg.get('content', '')) + except ollama.ResponseError as e: + if 'does not support tools' in str(e).lower(): + print("Il modello non supporta i tools.") + else: + print("Errore API:", e) + + +# Main ------------------------------------------------------------------------- +if __name__ == '__main__': + # 1. Elenco modelli + list_models() + + # 2. Prompt semplice + generate_text( + model=MODEL, + prompt="Scrivi una poesia breve su un tramonto al mare. Usa circa 40 parole." + ) + + # 3. Chat con streaming + chat_streaming( + model=MODEL, + messages=[ + {"role": "system", "content": "Sei un assistente conciso e utile."}, + {"role": "user", "content": "Suggerisci 3 consigli per un cappuccino cremoso a casa."} + ] + ) + + # 4. Embedding (usa modello dedicato se diverso) + if EMBEDDING_MODEL: + get_embedding( + model=EMBEDDING_MODEL, + text="L'intelligenza artificiale accelera l'innovazione in molti settori." + ) + else: + print("\n[4] Salto embedding: nessun modello embedding configurato.") + + # 5. Function calling (opzionale) + try_tools(MODEL) \ No newline at end of file diff --git a/uv.lock b/uv.lock index 37b669e..53c7f1b 100644 --- a/uv.lock +++ b/uv.lock @@ -477,6 +477,19 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/54/cd/7b5f49d5d78db7badab22d8323c1b6ae458fbf86c4fdfa194ab3cd4eb39b/numpy-2.3.2-cp312-cp312-win_arm64.whl", hash = "sha256:ee807923782faaf60d0d7331f5e86da7d5e3079e28b291973c545476c2b00d07", size = 10194071, upload-time = "2025-07-24T20:42:36.657Z" }, ] +[[package]] +name = "ollama" +version = "0.5.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "httpx" }, + { name = "pydantic" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/72/62/a36be4555e4218d6c8b35e72e0dfe0823845400097275cd81c9aec4ddf39/ollama-0.5.4.tar.gz", hash = "sha256:75857505a5d42e5e58114a1b78cc8c24596d8866863359d8a2329946a9b6d6f3", size = 45233, upload-time = "2025-09-16T00:25:25.785Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1b/af/d0a23c8fdec4c8ddb771191d9b36a57fbce6741835a78f1b18ab6d15ae7d/ollama-0.5.4-py3-none-any.whl", hash = "sha256:6374c9bb4f2a371b3583c09786112ba85b006516745689c172a7e28af4d4d1a2", size = 13548, upload-time = "2025-09-16T00:25:24.186Z" }, +] + [[package]] name = "orjson" version = "3.11.3" @@ -937,6 +950,7 @@ dependencies = [ { name = "dotenv" }, { name = "google-genai" }, { name = "gradio" }, + { name = "ollama" }, ] [package.metadata] @@ -945,6 +959,7 @@ requires-dist = [ { name = "dotenv" }, { name = "google-genai" }, { name = "gradio" }, + { name = "ollama" }, ] [[package]]