feat(step-7,8): leggi modello da config.py, allinea EMBED_MODELS al README

- step-8/ingest.py: rimuove EMBED_MODEL e OLLAMA_URL hardcoded;
  li importa da step-9/config.py (fonte di verita unica)
- step-7/check_env.py: aggiorna EMBED_MODELS con tutti i modelli
  del README (aggiunge qwen3-embedding, nomic-embed-text-v2-moe,
  paraphrase-multilingual); mostra il modello configurato in config.py
  e verifica proprio quello, non un qualsiasi modello embedding
- step-8/README.md: creato
This commit is contained in:
2026-04-14 18:22:05 +02:00
parent 92182a2cd4
commit 05946bb93f
3 changed files with 145 additions and 37 deletions
+32 -22
View File
@@ -3,15 +3,20 @@
Step 8 — Vettorizzazione
Legge i chunk prodotti da step-6, genera gli embedding tramite Ollama
(nomic-embed-text) e li indicizza in ChromaDB (persistente).
e li indicizza in ChromaDB (persistente).
Il modello di embedding viene letto da step-9/config.py (EMBED_MODEL).
Puoi sovrascriverlo con --model, ma deve corrispondere al modello che
userai in step-9 — altrimenti riesegui con --force dopo aver cambiato.
Input: step-6/<stem>/chunks.json
Output: chroma_db/<stem> (collection ChromaDB)
Uso:
python step-8/ingest.py --stem <nome> # singolo documento
python step-8/ingest.py # tutti gli stem trovati
python step-8/ingest.py --stem <nome> --force # sovrascrive collection
python step-8/ingest.py --stem <nome> # singolo documento
python step-8/ingest.py # tutti gli stem trovati
python step-8/ingest.py --stem <nome> --force # sovrascrive collection
python step-8/ingest.py --model bge-m3 # override modello
"""
import argparse
@@ -24,23 +29,26 @@ from pathlib import Path
import chromadb
# ─── Costanti ─────────────────────────────────────────────────────────────────
# ─── Configurazione ────────────────────────────────────────────────────────────
project_root = Path(__file__).parent.parent
CHUNKS_DIR = project_root / "step-6"
CHROMA_DIR = project_root / "chroma_db"
CHUNKS_DIR = project_root / "step-6"
CHROMA_DIR = project_root / "chroma_db"
OLLAMA_URL = "http://localhost:11434"
EMBED_MODEL = "nomic-embed-text"
EMBED_ENDPOINT = f"{OLLAMA_URL}/api/embeddings"
# Legge EMBED_MODEL e OLLAMA_URL da step-9/config.py (fonte di verità).
# Per spostare config.py alla root: cambia solo la riga qui sotto.
sys.path.insert(0, str(project_root / "step-9"))
from config import EMBED_MODEL, OLLAMA_URL # noqa: E402
EMBED_ENDPOINT = f"{OLLAMA_URL}/api/embeddings"
# ─── Ollama ────────────────────────────────────────────────────────────────────
def embed(text: str) -> list[float]:
def embed(text: str, model: str) -> list[float]:
"""Chiama Ollama /api/embeddings e ritorna il vettore."""
payload = json.dumps({"model": EMBED_MODEL, "prompt": text}).encode()
payload = json.dumps({"model": model, "prompt": text}).encode()
req = urllib.request.Request(
EMBED_ENDPOINT,
data=payload,
@@ -52,22 +60,22 @@ def embed(text: str) -> list[float]:
return data["embedding"]
def check_ollama() -> bool:
"""Verifica che Ollama sia attivo e che nomic-embed-text sia disponibile."""
def check_ollama(model: str) -> bool:
"""Verifica che Ollama sia attivo e che il modello di embedding sia disponibile."""
try:
req = urllib.request.Request(f"{OLLAMA_URL}/api/tags", method="GET")
with urllib.request.urlopen(req, timeout=10) as resp:
data = json.loads(resp.read())
models = [m["name"] for m in data.get("models", [])]
found = any(
m == EMBED_MODEL or m.startswith(EMBED_MODEL + ":")
m == model or m.startswith(model + ":")
for m in models
)
if found:
print(f"✅ Ollama OK — {EMBED_MODEL} disponibile")
print(f"✅ Ollama OK — {model} disponibile")
return True
print(f"❌ Modello {EMBED_MODEL} non trovato in Ollama")
print(f" → ollama pull {EMBED_MODEL}")
print(f"❌ Modello {model} non trovato in Ollama")
print(f" → ollama pull {model}")
return False
except (urllib.error.URLError, OSError):
print("❌ Ollama non raggiungibile — assicurati che sia in esecuzione")
@@ -88,7 +96,7 @@ def collection_exists(client: chromadb.PersistentClient, stem: str) -> bool:
# ─── Ingestione ───────────────────────────────────────────────────────────────
def ingest(stem: str, force: bool) -> bool:
def ingest(stem: str, force: bool, model: str = EMBED_MODEL) -> bool:
"""
Legge step-6/<stem>/chunks.json, genera embedding e popola ChromaDB.
Ritorna True se completato con successo, False altrimenti.
@@ -133,7 +141,7 @@ def ingest(stem: str, force: bool) -> bool:
for i, chunk in enumerate(chunks, start=1):
t0 = time.monotonic()
vector = embed(chunk["text"])
vector = embed(chunk["text"], model)
t1 = time.monotonic()
durations.append(t1 - t0)
@@ -196,11 +204,13 @@ def main() -> int:
parser.add_argument("--stem", help="Nome del documento (senza --stem = tutti)")
parser.add_argument("--force", action="store_true",
help="Sovrascrive la collection se già esistente")
parser.add_argument("--model", default=EMBED_MODEL,
help=f"Modello embedding Ollama (default da step-9/config.py: {EMBED_MODEL})")
args = parser.parse_args()
print("─── Step 8 — Vettorizzazione ─────────────────────────────────────────\n")
if not check_ollama():
if not check_ollama(args.model):
return 1
stems = [args.stem] if args.stem else find_stems()
@@ -213,7 +223,7 @@ def main() -> int:
for stem in stems:
if len(stems) > 1:
print(f"── {stem} ──")
results.append(ingest(stem, force=args.force))
results.append(ingest(stem, force=args.force, model=args.model))
if len(stems) > 1:
print()