docs(step-8): aggiungi regole per parametri ottimali
fix(step-9): passa SYSTEM_PROMPT come campo system nell'API Ollama anziche concatenato nel prompt — risolve risposte di fallback errate con modelli piccoli
This commit is contained in:
@@ -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).
|
Stessi modelli raccomandati nel [README di step-7](../step-7/README.md).
|
||||||
Il modello deve essere scaricato in Ollama prima di eseguire questo script
|
Il modello deve essere scaricato in Ollama prima di eseguire questo script
|
||||||
(`ollama pull <modello>`).
|
(`ollama pull <modello>`).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 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 <nome> --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.
|
||||||
|
|||||||
+7
-8
@@ -60,10 +60,11 @@ def embed(text: str) -> list[float]:
|
|||||||
|
|
||||||
# ─── Generazione ──────────────────────────────────────────────────────────────
|
# ─── Generazione ──────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
def call_ollama(prompt: str) -> str:
|
def call_ollama(prompt: str, system: str = "") -> str:
|
||||||
"""Chiama Ollama /api/generate e ritorna la risposta."""
|
"""Chiama Ollama /api/generate e ritorna la risposta."""
|
||||||
payload = json.dumps({
|
payload = json.dumps({
|
||||||
"model": LLM_MODEL,
|
"model": LLM_MODEL,
|
||||||
|
"system": system,
|
||||||
"prompt": prompt,
|
"prompt": prompt,
|
||||||
"stream": False,
|
"stream": False,
|
||||||
"think": not NO_THINK,
|
"think": not NO_THINK,
|
||||||
@@ -110,6 +111,7 @@ def retrieve(collection: chromadb.Collection, question: str) -> list[dict]:
|
|||||||
# ─── Prompt ───────────────────────────────────────────────────────────────────
|
# ─── Prompt ───────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
def build_prompt(question: str, chunks: list[dict]) -> str:
|
def build_prompt(question: str, chunks: list[dict]) -> str:
|
||||||
|
"""Ritorna (system, user_prompt) separati per l'API Ollama."""
|
||||||
context_parts = []
|
context_parts = []
|
||||||
for i, c in enumerate(chunks, start=1):
|
for i, c in enumerate(chunks, start=1):
|
||||||
header = f"[Contesto {i}"
|
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_parts.append(f"{header}\n{c['text']}")
|
||||||
|
|
||||||
context = "\n\n".join(context_parts)
|
context = "\n\n".join(context_parts)
|
||||||
return (
|
user_prompt = f"{context}\n\nDomanda: {question}"
|
||||||
f"{SYSTEM_PROMPT}\n\n"
|
return SYSTEM_PROMPT, user_prompt
|
||||||
f"{context}\n\n"
|
|
||||||
f"Domanda: {question}"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
# ─── Loop interattivo ─────────────────────────────────────────────────────────
|
# ─── 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(f" {c['text'][:120].replace(chr(10), ' ')}...")
|
||||||
print("──────────────────────────────────────────────────────────────\n")
|
print("──────────────────────────────────────────────────────────────\n")
|
||||||
|
|
||||||
prompt = build_prompt(question, chunks)
|
system, prompt = build_prompt(question, chunks)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
response = call_ollama(prompt)
|
response = call_ollama(prompt, system=system)
|
||||||
except (urllib.error.URLError, OSError) as e:
|
except (urllib.error.URLError, OSError) as e:
|
||||||
print(f"❌ Errore generazione: {e}")
|
print(f"❌ Errore generazione: {e}")
|
||||||
return
|
return
|
||||||
|
|||||||
Reference in New Issue
Block a user