diff --git a/step-8/README.md b/step-8/README.md index 626c7eb..322b38a 100644 --- a/step-8/README.md +++ b/step-8/README.md @@ -57,3 +57,58 @@ distanza coseno. La directory è ignorata da git (generata automaticamente). Stessi modelli raccomandati nel [README di step-7](../step-7/README.md). Il modello deve essere scaricato in Ollama prima di eseguire questo script (`ollama pull `). + +--- + +## Regole d'oro per parametri ottimali + +### Modello di embedding + +**Usa un modello multilingue per testi italiani.** +I modelli English-first (`nomic-embed-text`, `mxbai-embed-large`, `all-minilm`) +producono vettori di qualità inferiore su italiano, con retrieval meno preciso. +Prima scelta: `qwen3-embedding:0.6b`. + +**Più dimensioni = retrieval più preciso, ma più spazio su disco.** + +| Dimensioni | Modelli | Quando usarlo | +|---|---|---| +| 1024 | `qwen3-embedding:0.6b`, `bge-m3` | documenti tecnici, testi lunghi | +| 768 | `nomic-embed-text-v2-moe` | buon compromesso | +| 384 | `all-minilm` | solo per test rapidi | + +**Usa la stessa famiglia LLM + embedding quando possibile.** +`qwen3-embedding` + `qwen3.5` condividono tokenizer e spazio semantico — +il retrieval è più coerente rispetto a modelli di famiglie diverse. + +### Coerenza tra step-8 e step-9 + +**`EMBED_MODEL` deve essere identico in step-8 e step-9.** +ChromaDB memorizza i vettori generati con un certo modello. Se step-9 usa un +modello diverso per la query di ricerca, gli spazi vettoriali non corrispondono +e il retrieval restituisce risultati casuali — senza alcun errore visibile. + +**Dopo aver cambiato `EMBED_MODEL`, riesegui sempre con `--force`.** +Senza `--force` lo script salta la collection già esistente — i vecchi vettori +(generati col modello precedente) restano e continuano a essere usati da step-9. + +```bash +# Cambio modello → ricrea sempre la collection +python step-8/ingest.py --stem --force +``` + +### Quando usare `--force` + +| Situazione | `--force` necessario? | +|---|---| +| Prima esecuzione | No | +| Hai cambiato `EMBED_MODEL` | **Sì** | +| Hai migliorato i chunk in step-6 | **Sì** | +| Hai aggiunto nuovi documenti (stem diverso) | No | +| Vuoi solo verificare che funzioni | No | + +### Distanza vettoriale + +Lo script usa **distanza coseno** (hardcoded), che è la scelta corretta per +embedding testuali — misura l'angolo tra vettori indipendentemente dalla loro +lunghezza. Non cambiare questo parametro. diff --git a/step-9/rag.py b/step-9/rag.py index 2c4ca1f..b4617f0 100644 --- a/step-9/rag.py +++ b/step-9/rag.py @@ -60,10 +60,11 @@ def embed(text: str) -> list[float]: # ─── Generazione ────────────────────────────────────────────────────────────── -def call_ollama(prompt: str) -> str: +def call_ollama(prompt: str, system: str = "") -> str: """Chiama Ollama /api/generate e ritorna la risposta.""" payload = json.dumps({ "model": LLM_MODEL, + "system": system, "prompt": prompt, "stream": False, "think": not NO_THINK, @@ -110,6 +111,7 @@ def retrieve(collection: chromadb.Collection, question: str) -> list[dict]: # ─── Prompt ─────────────────────────────────────────────────────────────────── def build_prompt(question: str, chunks: list[dict]) -> str: + """Ritorna (system, user_prompt) separati per l'API Ollama.""" context_parts = [] for i, c in enumerate(chunks, start=1): header = f"[Contesto {i}" @@ -121,11 +123,8 @@ def build_prompt(question: str, chunks: list[dict]) -> str: context_parts.append(f"{header}\n{c['text']}") context = "\n\n".join(context_parts) - return ( - f"{SYSTEM_PROMPT}\n\n" - f"{context}\n\n" - f"Domanda: {question}" - ) + user_prompt = f"{context}\n\nDomanda: {question}" + return SYSTEM_PROMPT, user_prompt # ─── Loop interattivo ───────────────────────────────────────────────────────── @@ -148,10 +147,10 @@ def answer(question: str, collection: chromadb.Collection, verbose: bool) -> Non print(f" {c['text'][:120].replace(chr(10), ' ')}...") print("──────────────────────────────────────────────────────────────\n") - prompt = build_prompt(question, chunks) + system, prompt = build_prompt(question, chunks) try: - response = call_ollama(prompt) + response = call_ollama(prompt, system=system) except (urllib.error.URLError, OSError) as e: print(f"❌ Errore generazione: {e}") return