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:
+54
-15
@@ -18,11 +18,36 @@ Uso:
|
|||||||
import shutil
|
import shutil
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
# Modelli riconosciuti come embedding (basta che uno sia presente)
|
# ─── Lista canonica di modelli embedding supportati ───────────────────────────
|
||||||
# Tutto il resto viene considerato LLM
|
# Ordine: prima scelta → ultima scelta (come da README step-7)
|
||||||
EMBED_MODELS = ["bge-m3", "nomic-embed-text", "mxbai-embed-large", "all-minilm"]
|
EMBED_MODELS = [
|
||||||
|
"qwen3-embedding",
|
||||||
|
"nomic-embed-text-v2-moe",
|
||||||
|
"bge-m3",
|
||||||
|
"nomic-embed-text",
|
||||||
|
"mxbai-embed-large",
|
||||||
|
"paraphrase-multilingual",
|
||||||
|
"all-minilm",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def _is_embed(model_name: str) -> bool:
|
||||||
|
"""True se il modello è riconosciuto come embedding (lista canonica o keyword)."""
|
||||||
|
base = model_name.split(":")[0].lower()
|
||||||
|
return any(base == e or base.startswith(e) for e in EMBED_MODELS) or "embed" in base
|
||||||
|
|
||||||
|
|
||||||
|
# ─── Modelli configurati in step-9/config.py ─────────────────────────────────
|
||||||
|
# Per spostare config.py alla root: cambia solo la riga qui sotto.
|
||||||
|
sys.path.insert(0, str(Path(__file__).parent.parent / "step-9"))
|
||||||
|
try:
|
||||||
|
from config import EMBED_MODEL as CONFIGURED_EMBED, OLLAMA_MODEL as CONFIGURED_LLM
|
||||||
|
except Exception:
|
||||||
|
CONFIGURED_EMBED = None
|
||||||
|
CONFIGURED_LLM = None
|
||||||
|
|
||||||
REQUIRED_LIBS = ["chromadb"]
|
REQUIRED_LIBS = ["chromadb"]
|
||||||
|
|
||||||
@@ -83,25 +108,39 @@ def _match(model_name: str, available: list[str]) -> str | None:
|
|||||||
|
|
||||||
|
|
||||||
def check_embed_model(available: list[str]) -> bool:
|
def check_embed_model(available: list[str]) -> bool:
|
||||||
"""Verifica che almeno un modello di embedding sia presente."""
|
"""Verifica che il modello di embedding configurato sia disponibile."""
|
||||||
for candidate in EMBED_MODELS:
|
if CONFIGURED_EMBED:
|
||||||
found = _match(candidate, available)
|
print(f" modello configurato (step-9/config.py): {CONFIGURED_EMBED}")
|
||||||
|
found = _match(CONFIGURED_EMBED, available)
|
||||||
if found:
|
if found:
|
||||||
print(f"✅ modello embedding trovato: {found}")
|
print(f"✅ embedding disponibile: {found}")
|
||||||
return True
|
return True
|
||||||
|
print(f"❌ {CONFIGURED_EMBED} non trovato in Ollama")
|
||||||
|
print(f" → ollama pull {CONFIGURED_EMBED}")
|
||||||
|
return False
|
||||||
|
# fallback: config.py non leggibile
|
||||||
|
found = next((m for m in available if _is_embed(m)), None)
|
||||||
|
if found:
|
||||||
|
print(f"✅ modello embedding trovato: {found}")
|
||||||
|
return True
|
||||||
print("❌ nessun modello di embedding trovato")
|
print("❌ nessun modello di embedding trovato")
|
||||||
print(f" → Consigliato per italiano: ollama pull bge-m3")
|
print(f" → Prima scelta: ollama pull qwen3-embedding:0.6b")
|
||||||
print(f" → Alternativa leggera: ollama pull nomic-embed-text")
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def check_llm_model(available: list[str]) -> bool:
|
def check_llm_model(available: list[str]) -> bool:
|
||||||
"""Verifica che almeno un modello non-embedding sia presente."""
|
"""Verifica che il modello LLM configurato sia disponibile."""
|
||||||
llm_candidates = [
|
if CONFIGURED_LLM:
|
||||||
m for m in available
|
print(f" modello configurato (step-9/config.py): {CONFIGURED_LLM}")
|
||||||
if not any(m == e or m.startswith(e + ":") or m.startswith(e + "-")
|
found = _match(CONFIGURED_LLM, available)
|
||||||
for e in EMBED_MODELS)
|
if found:
|
||||||
]
|
print(f"✅ LLM disponibile: {found}")
|
||||||
|
return True
|
||||||
|
print(f"❌ {CONFIGURED_LLM} non trovato in Ollama")
|
||||||
|
print(f" → ollama pull {CONFIGURED_LLM}")
|
||||||
|
return False
|
||||||
|
# fallback: config.py non leggibile
|
||||||
|
llm_candidates = [m for m in available if not _is_embed(m)]
|
||||||
if llm_candidates:
|
if llm_candidates:
|
||||||
print(f"✅ modello LLM trovato: {llm_candidates[0]}")
|
print(f"✅ modello LLM trovato: {llm_candidates[0]}")
|
||||||
return True
|
return True
|
||||||
|
|||||||
@@ -0,0 +1,59 @@
|
|||||||
|
# Step 8 — Vettorizzazione
|
||||||
|
|
||||||
|
Legge i chunk prodotti da step-6, genera gli embedding tramite Ollama e li
|
||||||
|
salva in ChromaDB (vector store persistente su disco).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Prerequisiti
|
||||||
|
|
||||||
|
- Step-6 completato (esiste `step-6/<stem>/chunks.json`)
|
||||||
|
- Ollama attivo con il modello di embedding scaricato
|
||||||
|
- `chromadb` installato (`pip install -r requirements.txt`)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Configurazione modello
|
||||||
|
|
||||||
|
Il modello di embedding viene letto da **`step-9/config.py`**:
|
||||||
|
|
||||||
|
```python
|
||||||
|
# step-9/config.py
|
||||||
|
EMBED_MODEL = "nomic-embed-text" # ← cambia qui
|
||||||
|
```
|
||||||
|
|
||||||
|
> Il modello scelto qui deve corrispondere a quello usato in step-9.
|
||||||
|
> Se lo cambi dopo aver già vettorizzato, devi rieseguire step-8 con `--force`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Uso
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Vettorizza un singolo documento
|
||||||
|
python step-8/ingest.py --stem <nome>
|
||||||
|
|
||||||
|
# Vettorizza tutti i documenti trovati in step-6/
|
||||||
|
python step-8/ingest.py
|
||||||
|
|
||||||
|
# Sovrascrive una collection già esistente
|
||||||
|
python step-8/ingest.py --stem <nome> --force
|
||||||
|
|
||||||
|
# Override modello (senza modificare config.py)
|
||||||
|
python step-8/ingest.py --stem <nome> --model bge-m3
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Output
|
||||||
|
|
||||||
|
I vettori vengono salvati in `chroma_db/<stem>/` come collection ChromaDB con
|
||||||
|
distanza coseno. La directory è ignorata da git (generata automaticamente).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Modelli supportati
|
||||||
|
|
||||||
|
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 <modello>`).
|
||||||
+32
-22
@@ -3,15 +3,20 @@
|
|||||||
Step 8 — Vettorizzazione
|
Step 8 — Vettorizzazione
|
||||||
|
|
||||||
Legge i chunk prodotti da step-6, genera gli embedding tramite Ollama
|
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
|
Input: step-6/<stem>/chunks.json
|
||||||
Output: chroma_db/<stem> (collection ChromaDB)
|
Output: chroma_db/<stem> (collection ChromaDB)
|
||||||
|
|
||||||
Uso:
|
Uso:
|
||||||
python step-8/ingest.py --stem <nome> # singolo documento
|
python step-8/ingest.py --stem <nome> # singolo documento
|
||||||
python step-8/ingest.py # tutti gli stem trovati
|
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> --force # sovrascrive collection
|
||||||
|
python step-8/ingest.py --model bge-m3 # override modello
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
@@ -24,23 +29,26 @@ from pathlib import Path
|
|||||||
|
|
||||||
import chromadb
|
import chromadb
|
||||||
|
|
||||||
# ─── Costanti ─────────────────────────────────────────────────────────────────
|
# ─── Configurazione ────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
project_root = Path(__file__).parent.parent
|
project_root = Path(__file__).parent.parent
|
||||||
|
|
||||||
CHUNKS_DIR = project_root / "step-6"
|
CHUNKS_DIR = project_root / "step-6"
|
||||||
CHROMA_DIR = project_root / "chroma_db"
|
CHROMA_DIR = project_root / "chroma_db"
|
||||||
|
|
||||||
OLLAMA_URL = "http://localhost:11434"
|
# Legge EMBED_MODEL e OLLAMA_URL da step-9/config.py (fonte di verità).
|
||||||
EMBED_MODEL = "nomic-embed-text"
|
# Per spostare config.py alla root: cambia solo la riga qui sotto.
|
||||||
EMBED_ENDPOINT = f"{OLLAMA_URL}/api/embeddings"
|
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 ────────────────────────────────────────────────────────────────────
|
# ─── Ollama ────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
def embed(text: str) -> list[float]:
|
def embed(text: str, model: str) -> list[float]:
|
||||||
"""Chiama Ollama /api/embeddings e ritorna il vettore."""
|
"""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(
|
req = urllib.request.Request(
|
||||||
EMBED_ENDPOINT,
|
EMBED_ENDPOINT,
|
||||||
data=payload,
|
data=payload,
|
||||||
@@ -52,22 +60,22 @@ def embed(text: str) -> list[float]:
|
|||||||
return data["embedding"]
|
return data["embedding"]
|
||||||
|
|
||||||
|
|
||||||
def check_ollama() -> bool:
|
def check_ollama(model: str) -> bool:
|
||||||
"""Verifica che Ollama sia attivo e che nomic-embed-text sia disponibile."""
|
"""Verifica che Ollama sia attivo e che il modello di embedding sia disponibile."""
|
||||||
try:
|
try:
|
||||||
req = urllib.request.Request(f"{OLLAMA_URL}/api/tags", method="GET")
|
req = urllib.request.Request(f"{OLLAMA_URL}/api/tags", method="GET")
|
||||||
with urllib.request.urlopen(req, timeout=10) as resp:
|
with urllib.request.urlopen(req, timeout=10) as resp:
|
||||||
data = json.loads(resp.read())
|
data = json.loads(resp.read())
|
||||||
models = [m["name"] for m in data.get("models", [])]
|
models = [m["name"] for m in data.get("models", [])]
|
||||||
found = any(
|
found = any(
|
||||||
m == EMBED_MODEL or m.startswith(EMBED_MODEL + ":")
|
m == model or m.startswith(model + ":")
|
||||||
for m in models
|
for m in models
|
||||||
)
|
)
|
||||||
if found:
|
if found:
|
||||||
print(f"✅ Ollama OK — {EMBED_MODEL} disponibile")
|
print(f"✅ Ollama OK — {model} disponibile")
|
||||||
return True
|
return True
|
||||||
print(f"❌ Modello {EMBED_MODEL} non trovato in Ollama")
|
print(f"❌ Modello {model} non trovato in Ollama")
|
||||||
print(f" → ollama pull {EMBED_MODEL}")
|
print(f" → ollama pull {model}")
|
||||||
return False
|
return False
|
||||||
except (urllib.error.URLError, OSError):
|
except (urllib.error.URLError, OSError):
|
||||||
print("❌ Ollama non raggiungibile — assicurati che sia in esecuzione")
|
print("❌ Ollama non raggiungibile — assicurati che sia in esecuzione")
|
||||||
@@ -88,7 +96,7 @@ def collection_exists(client: chromadb.PersistentClient, stem: str) -> bool:
|
|||||||
|
|
||||||
# ─── Ingestione ───────────────────────────────────────────────────────────────
|
# ─── 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.
|
Legge step-6/<stem>/chunks.json, genera embedding e popola ChromaDB.
|
||||||
Ritorna True se completato con successo, False altrimenti.
|
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):
|
for i, chunk in enumerate(chunks, start=1):
|
||||||
t0 = time.monotonic()
|
t0 = time.monotonic()
|
||||||
vector = embed(chunk["text"])
|
vector = embed(chunk["text"], model)
|
||||||
t1 = time.monotonic()
|
t1 = time.monotonic()
|
||||||
durations.append(t1 - t0)
|
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("--stem", help="Nome del documento (senza --stem = tutti)")
|
||||||
parser.add_argument("--force", action="store_true",
|
parser.add_argument("--force", action="store_true",
|
||||||
help="Sovrascrive la collection se già esistente")
|
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()
|
args = parser.parse_args()
|
||||||
|
|
||||||
print("─── Step 8 — Vettorizzazione ─────────────────────────────────────────\n")
|
print("─── Step 8 — Vettorizzazione ─────────────────────────────────────────\n")
|
||||||
|
|
||||||
if not check_ollama():
|
if not check_ollama(args.model):
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
stems = [args.stem] if args.stem else find_stems()
|
stems = [args.stem] if args.stem else find_stems()
|
||||||
@@ -213,7 +223,7 @@ def main() -> int:
|
|||||||
for stem in stems:
|
for stem in stems:
|
||||||
if len(stems) > 1:
|
if len(stems) > 1:
|
||||||
print(f"── {stem} ──")
|
print(f"── {stem} ──")
|
||||||
results.append(ingest(stem, force=args.force))
|
results.append(ingest(stem, force=args.force, model=args.model))
|
||||||
if len(stems) > 1:
|
if len(stems) > 1:
|
||||||
print()
|
print()
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user