Files

1 line
186 KiB
JSON
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{"repo": "attilio81/blueprint-rag-", "files": {"README.md": {"88cc0ab0270a5ebaa6b40406b8270954f8c98e01": {"t": 1785746599.5957718, "n": 30942, "h": "8fd6b7c36cd042b9", "c": "# Agente RAG per i Preventivi — Guida Completa\n\n> Questa guida è pensata per chi non ha mai sentito parlare di RAG o di AI applicata ai documenti.\n> La leggi dall'inizio alla fine e alla fine sai usare il sistema.\n\n---\n\n## Indice\n\n1. [Cos'è un RAG? (spiegazione per neofiti)](#1-cosè-un-rag-spiegazione-per-neofiti)\n2. [Il problema che risolve questo progetto](#2-il-problema-che-risolve-questo-progetto)\n3. [Come funziona — architettura semplificata](#3-come-funziona--architettura-semplificata)\n4. [I componenti del sistema](#4-i-componenti-del-sistema)\n5. [Come è strutturato il progetto](#5-come-è-strutturato-il-progetto)\n6. [Prima installazione — passo passo](#6-prima-installazione--passo-passo)\n7. [Configurazione](#7-configurazione)\n8. [Indicizzare i PDF](#8-indicizzare-i-pdf)\n9. [Fare domande all'agente](#9-fare-domande-allagente)\n10. [Aggiungere nuovi documenti](#10-aggiungere-nuovi-documenti)\n11. [Adattare il sistema a un nuovo progetto](#11-adattare-il-sistema-a-un-nuovo-progetto)\n12. [Domande frequenti](#12-domande-frequenti)\n\n---\n\n## 1. Cos'è un RAG? (spiegazione per neofiti)\n\n**RAG** sta per **Retrieval-Augmented Generation**, che in italiano possiamo chiamare\n\"generazione aumentata dal recupero\". È una tecnica per far rispondere un'AI a domande\nsu documenti reali, senza \"inventare\" risposte.\n\n### Il problema delle AI classiche\n\nUn modello di linguaggio come ChatGPT è addestrato su miliardi di testi trovati su internet.\nSa moltissime cose di carattere generale. Ma **non conosce i tuoi documenti** —\ni tuoi preventivi, le tue offerte, i tuoi schemi tecnici.\n\nSe gli chiedi \"Qual è il prezzo del prodotto X nel preventivo del fornitore Y?\",\nnon sa rispondere. O peggio: inventa una risposta plausibile ma sbagliata.\nQuesto si chiama **allucinazione**.\n\n### Come funziona il RAG\n\nIl RAG risolve il problema in due passaggi:\n\n```\n[FASE 1 — INDICIZZAZIONE]\n\nI tuoi PDF ──► Estrai il testo ──► Dividi in piccoli pezzi (chunk)\n ──► Converti ogni pezzo in un numero (embedding)\n ──► Salva in un database vettoriale\n\n[FASE 2 — RISPOSTA]\n\nLa tua domanda ──► Converti anche lei in un numero (embedding)\n ──► Trova i pezzi più \"vicini\" per significato\n ──► Passa quei pezzi + la domanda all'AI\n ──► L'AI risponde basandosi SOLO su quei pezzi\n```\n\n### Il trucco degli \"embedding\"\n\nUn **embedding** è la traduzione di un testo in un array di numeri (un vettore).\nTesti con significato simile producono numeri simili. Quindi:\n\n- \"Prezzo fioriera grande\" e \"costo vaso esterno Ø1200\" producono numeri vicini\n- \"Preventivo facciata nord\" e \"offerta insegne fronte edificio\" producono numeri vicini\n\nQuesto permette di trovare documenti pertinenti anche se non usano le stesse parole\nesatte della domanda.\n\n---\n\n## 2. Il problema che risolve questo progetto\n\nUn progetto edilizio tipico accumula diversi preventivi da fornitori diversi, in formati diversi:\n\n| Documento | Contenuto |\n|-----------|-----------|\n| `scheda_prodotto.pdf` | Scheda prodotto con prezzi e dimensioni |\n| `preventivo_fornitore_a.pdf` | Preventivo Fornitore A per forniture |\n| `offerta_insegne.pdf` | Offerta insegne e segnaletica |\n| `offerta_facciata.pdf` | Offerta facciata continua |\n| `preventivo_edile.pdf` | Preventivo lavori edili |\n| `disegni_tecnici.pdf` | Disegni tecnici di facciata |\n| `offerta_verde.pdf` | Offerta verde/giardino |\n\nSenza il sistema, per rispondere a domande come \"Qual è il totale delle insegne?\"\nbisognava aprire ogni PDF, cercare manualmente, fare calcoli.\n\n**Con il sistema**, si scrive la domanda in italiano e si ottiene la risposta in pochi secondi,\ncon indicato il documento sorgente.\n\n---\n\n## 3. Come funziona — architettura semplificata\n\n```\n ┌─────────────────────────────────────────┐\n │ FASE DI INDICIZZAZIONE │\n │ (si fa una volta sola) │\n └─────────────────────────────────────────┘\n\nPreventivi/\n├── scheda_prodotto.pdf ──────────────────────────────────────────────────┐\n├── offerta_facciata.pdf ─────────────────────────────────────────────── │\n├── preventivo_fornitore.pdf ─────────────────────────────────────────── │\n└── ... altri PDF ... │\n ▼\n ┌────────────────────────────────┐\n │ PIPELINE INGESTION │\n │ │\n │ Passaggio 1 — TESTO │\n │ pymupdf estrae il testo │\n │ grezzo da ogni pagina │\n │ → chunk separator-aware │\n │ (paragrafo › riga › parola) │\n │ │\n │ Passaggio 2 — VISIONE │\n │ Ogni pagina viene │\n │ \"fotografata\" e mandata al │\n │ vision provider configurato │\n │ che la descrive in Markdown │\n │ → chunk separator-aware │\n └─────────────┬──────────────────┘\n │\n ▼\n ┌────────────────────────────────┐\n │ gemini-embedding-2-preview │\n │ │\n │ Ogni pezzo di testo diventa │\n │ un vettore di 3072 numeri │\n └─────────────┬──────────────────┘\n │\n ▼\n ┌────────────────────────────────┐\n │ ChromaDB │\n │ (database vettoriale) │\n │ salvato in chroma_db/ │\n └────────────────────────────────┘\n\n\n ┌─────────────────────────────────────────┐\n │ FASE DI RISPOSTA │\n │ (ogni volta che fai domande) │\n └─────────────────────────────────────────┘\n\nTu: \"Qual è il prezzo del prodotto X nel preventivo?\"\n │\n ▼\n┌─────────────────────┐ ┌──────────────────────────────────────────┐\n│ La domanda diventa │ │ ChromaDB cerca i 6 chunk più simili │\n│ anche lei un vettore│──────► │ per significato alla domanda │\n│ di 3072 numeri │ │ (\"prodotto X\", \"prezzo\", \"preventivo\") │\n└─────────────────────┘ └───────────────────┬──────────────────────┘\n │\n │ Restituisce i chunk più rilevanti:\n │ \"Codice prodotto X, dimensioni...\"\n │ \"Prezzo: € XXX,XX IVA esclusa...\"\n │ ...\n ▼\n ┌───────────────────────────────┐\n │ DeepSeek Chat │\n │ │\n │ Riceve: │\n │ - I chunk trovati │\n │ - La tua domanda │\n │ - Le istruzioni del progetto │\n │ │\n │ Risponde in italiano, │\n │ citando la fonte │\n └───────────────────────────────┘\n │\n ▼\n \"Il prodotto X costa € XXX,XX IVA esclusa.\n Fonte: Fornitore A (preventivo_fornitore_a.pdf)\"\n```\n\n---\n\n## 4. I componenti del sistema\n\n| Componente | Ruolo | Tecnologia |\n|------------|-------|------------|\n| **Estrazione testo** | Legge il testo dai PDF | `pymupdf` |\n| **Chunking** | Divide il testo rispettando paragrafi, righe e parole | separator-aware (`\\n\\n` › `\\n` › ` `) |\n| **Vision provider** | \"Guarda\" le pagine e le descrive in Markdown strutturato | configurabile: LM Studio, Gemini, OpenAI (vedi §7) |\n| **Embedding** | Trasforma testi in numeri | `gemini-embedding-2-preview` (3072 dim.) |\n| **Database vettoriale** | Archivia e cerca per similarità | `ChromaDB` (locale, nessun server) |\n| **Knowledge base** | Collega embedding e ricerca | `Agno` framework |\n| **Modello di chat** | Genera la risposta finale | `DeepSeek Chat` |\n| **Interfaccia web** | Chat + gestione documenti nel browser | `Streamlit` |\n| **Ricerca web** | Trova fornitori e alternative online | `DuckDuckGo` via `ddgs` (no API key) |\n| **CLI** | Interfaccia alternativa a riga di comando | Python `argparse` + `input()` |\n| **Deduplicazione** | Evita di re-indicizzare PDF già visti | SHA-256 hash + `indexed.json` |\n| **MCP Server** | Espone 7 tool di ricerca sul DB ordini/fornitori | `FastMCP` + `pyodbc` (read-only) |\n| **CatalogoTools** | Integra la ricerca DB nell'agente Agno (sync) | wrapper diretto su `search.py` |\n| **Embedding articoli** | Ricerca semantica nel catalogo prodotti | ChromaDB `iab_articoli` + GeminiEmbedder |\n\n---\n\n## 5. Come è strutturato il progetto\n\n```\nC:\\Progetti Pilota\\EsploraPreventivi\\\n│\n├── Preventivi\\ ← I tuoi PDF (metti qui i nuovi)\n│ ├── scheda_prodotto.pdf\n│ ├── preventivo_fornitore.pdf\n│ └── ...\n│\n├── rag_preventivi\\ ← Il codice del sistema\n│ ├── config.py ← Impostazioni (modelli, soglie, path, vision provider, prompt)\n│ ├── knowledge.py ← Configura ChromaDB + GeminiEmbedder\n│ ├── agent.py ← Crea l'agente DeepSeek + WebSearchTools + CatalogoTools\n│ ├── catalogo_tools.py ← Toolkit Agno per ricerca nel catalogo (wrapper sync su search.py)\n│ ├── chat_app.py ← Interfaccia web Streamlit (avvia con streamlit run)\n│ ├── admin_tab.py ← Tab \"Gestione\": lista, upload, indicizza, elimina\n│ ├── main.py ← CLI alternativa (argparse + input())\n│ │\n│ ├── ingestion\\ ← Pipeline di indicizzazione\n│ │ ├── text_extractor.py ← Estrae testo con pymupdf + chunking separator-aware\n│ │ ├── image_extractor.py ← Visione pagine con provider configurabile (LM Studio / Gemini / OpenAI)\n│ │ └── pipeline.py ← Orchestrazione + deduplicazione + streaming log\n│ │\n│ └── tests\\ ← Test automatici (23 test)\n│ ├── conftest.py\n│ ├── test_text_extractor.py\n│ ├── test_image_extractor.py\n│ ├── test_pipeline.py\n│ ├── test_agent.py\n│ └── test_admin_tab.py\n│\n├── mcp_preventivi\\ ← Server MCP + ricerca nel catalogo\n│ ├── server.py ← FastMCP server con 7 tool read-only\n│ ├── search.py ← Funzioni di ricerca: articoli, fornitori, prezzi, codici\n│ ├── db.py ← Helper connessione pyodbc (contextlib.closing)\n│ ├── embeddings.py ← GeminiEmbedder + ChromaDB per ricerca semantica articoli\n│ ├── index_articoli.py ← Script per indicizzare il catalogo (--limit N per parziale)\n│ ├── visualizza_embeddings.py ← Mappa UMAP 2D interattiva degli embedding (HTML)\n│ ├── requirements.txt\n│ └── tests\\\n│ ├── test_search.py ← Test TDD per tutte le funzioni di ricerca\n│ └── test_embeddings.py ← Test TDD per la ricerca semantica\n│\n├── chroma_db\\ ← Database vettoriale preventivi (auto-creato)\n├── indexed.json ← Registro dei PDF già indicizzati\n├── .env ← Chiavi API + stringa di connessione DB (non condividere!)\n├── .gitignore ← Protegge .env, chroma_db e .chroma_iab da git\n├── start.bat ← Avvia tutto con un doppio clic\n└── README.md ← Questo file\n```\n\n---\n\n## 6. Prima installazione — passo passo\n\n### Prerequisiti\n\n- Python 3.11 o superiore installato\n- Connessione internet (per le API di embedding e chat)\n- Le chiavi API necessarie in base al vision provider scelto (vedi §7)\n\n### Passo 1 — Installa le dipendenze\n\n```bash\npip install -r rag_preventivi/requirements.txt\npip install -r mcp_preventivi/requirements.txt\n```\n\n`rag_preventivi`: agno, chromadb, pymupdf, google-generativeai, openai, python-dotenv, Pillow, pytest, streamlit, ddgs.\n\n`mcp_preventivi`: fastmcp, pyodbc, chromadb, google-generativeai, python-dotenv.\n\n### Passo 2 — Ottieni le chiavi API\n\nHai sempre bisogno di queste due:\n\n**Google API Key** (per l'embedding):\n1. Vai su [aistudio.google.com](https://aistudio.google.com)\n2. Clicca su \"Get API Key\" e copia la chiave (inizia con `AIza...`)\n\n**DeepSeek API Key** (per il modello di chat):\n1. Vai su [platform.deepseek.com](https://platform.deepseek.com/api_keys)\n2. Crea un account e genera una chiave API\n\nIn base al **vision provider** scelto (vedi §7), potrebbero servire chiavi aggiuntive:\n\n| Provider | Chiave aggiuntiva |\n|----------|-------------------|\n| `lmstudio` | Nessuna — gira in locale |\n| `gemini` | `GOOGLE_API_KEY` (già usata per l'embedding) |\n| `openai` | `OPENAI_API_KEY` |\n\n### Passo 3 — Crea il file .env\n\nNella cartella `C:\\Progetti Pilota\\EsploraPreventivi\\` crea un file `.env`:\n\n```\nGOOGLE_API_KEY=AIzaSy...la_tua_chiave...\nDEEPSEEK_API_KEY=sk-...la_tua_chiave...\n\n# Connessione al database ordini/fornitori (per mcp_preventivi)\nIAB_DB_CONNECTION_STRING=Driver={ODBC Driver 17 for SQL Server};Server=...;Database=...;UID=...;PWD=...\n\n# Solo se usi VISION_PROVIDER = \"openai\"\n# OPENAI_API_KEY=sk-...la_tua_chiave...\n```\n\n> **Importante**: non condividere mai questo file. È già protetto da `.gitignore`.\n\n### Passo 4 — (Solo per `lmstudio`) Configura LM Studio\n\n1. Scarica e installa [LM Studio](https://lmstudio.ai)\n2. Scarica il modello `qwen/qwen3.5-9b` dalla scheda \"Discover\"\n3. Avvia il server locale: vai in \"Developer\" → \"Start Server\"\n4. Il server gira su `http://localhost:1234`\n\n### Passo 5 — Indicizza il catalogo articoli (opzionale, per la ricerca semantica)\n\nSe vuoi che l'agente possa trovare articoli nel catalogo anche per sinonimo o concetto,\nesegui l'indicizzazione degli embedding:\n\n```bash\ncd mcp_preventivi\npython index_articoli.py\n```\n\nPer un test rapido con i primi 100 articoli:\n\n```bash\npython index_articoli.py --limit 100\n```\n\n> Richiede `IAB_DB_CONNECTION_STRING` nel `.env`. Se non configurata, il resto del sistema\n> funziona comunque — solo la ricerca semantica non sarà disponibile.\n\n### Passo 6 — Verifica l'installazione\n\n```bash\ncd rag_preventivi\npython -c \"from knowledge import build_knowledge; kb, vdb = build_knowledge(); print('OK!')\"\n```\n\nSe stampa `OK!` senza errori, sei pronto.\n\n---\n\n## 7. Configurazione\n\nTutto si configura nel file `rag_preventivi/config.py`. Non è mai necessario toccare altri file.\n\n### Parametri principali\n\n```python\n# ── Progetto ───────────────────────────────────────────────────────────────\nPROJECT_CONTEXT = \"preventivi edilizi del Centro Commerciale Leonardo di Imola\"\nCHROMA_COLLECTION = \"preventivi_leonardo_v2\" # nome univoco per questo progetto\n\n# ── Vision provider ────────────────────────────────────────────────────────\n# \"lmstudio\" — locale, gratis, privacy totale (richiede LM Studio in esecuzione)\n# \"gemini\" — API Google, qualità alta, richiede GOOGLE_API_KEY\n# \"openai\" — API OpenAI, richiede OPENAI_API_KEY\nVISION_PROVIDER = \"lmstudio\"\n\n# ── Vision prompt ──────────────────────────────────────────────────────────\n# Il testo inviato al modello vision per descrivere ogni pagina.\n# Personalizzalo per il tipo di documenti che indicizzi.\nVISION_PROMPT = \"\"\"Analizza questa pagina di documento commerciale.\nRestituisci SOLO il contenuto in Markdown strutturato:\n...\"\"\"\n\n# ── Chunking ───────────────────────────────────────────────────────────────\nCHUNK_SIZE = 1000 # caratteri massimi per chunk\nCHUNK_OVERLAP = 200 # caratteri di sovrapposizione tra chunk adiacenti\n```\n\n### Vision provider — dettaglio\n\n| Provider | Costo | Privacy | Qualità | Requisiti |\n|----------|-------|---------|---------|-----------|\n| `lmstudio` | Gratis | Locale | Alta (dipende dal modello) | LM Studio + GPU/RAM |\n| `gemini` | Basso | Dati a Google | Ottima | `GOOGLE_API_KEY` |\n| `openai` | Medio | Dati a OpenAI | Ottima | `OPENAI_API_KEY` |\n\nModelli consigliati per LM Studio:\n\n| Modello | VRAM | Note |\n|---------|------|------|\n| `qwen/qwen3.5-9b` | ~8 GB | Ottimo per testi tecnici e tabelle, multilingue |\n| `llava-v1.6` | ~6 GB | Buono per pagine con grafica |\n| `minicpm-v` | ~4 GB | Leggero, per GPU con poca VRAM |\n\n### Chunking — come funziona\n\nIl sistema divide il testo in chunk cercando il confine più naturale disponibile:\n\n```\n1° tentativo: taglia su \\n\\n (fine paragrafo) ← preferito\n2° tentativo: taglia su \\n (fine riga)\n3° tentativo: taglia su spazio (fine parola)\n4° tentativo: taglia sui caratteri ← ultimo resort\n```\n\nQuesto garantisce che una riga di tabella tipo `| P001 | Porta legno | 3 | €400 |`\nnon venga mai spezzata a metà.\n\n---\n\n## 8. Indicizzare i PDF\n\nL'indicizzazione è il processo che legge i PDF, li analizza e li salva nel database\nvettoriale. Si fa **una volta sola** per ogni set di documenti. I documenti già\nindicizzati vengono automaticamente saltati alle esecuzioni successive.\n\n### Comando base\n\n```bash\ncd \"C:\\Progetti Pilota\\EsploraPreventivi\\rag_preventivi\"\npython main.py --ingest-only\n```\n\n**Cosa succede:**\n\n```\n[INGEST] scheda_prodotto.pdf\n → 8 chunk testo\n → 4 chunk visione\n ✓ scheda_prodotto.pdf indicizzato\n\n[INGEST] preventivo_fornitore.pdf\n → 20 chunk testo\n → 8 chunk visione\n ✓ preventivo_fornitore.pdf indicizzato\n\n[SKIP] offerta_insegne.pdf già indicizzato ← PDF non modificato, viene saltato\n...\nIngestion completata.\n```\n\n**Quanto ci vuole?**\n- Con `lmstudio`: ~2-5 sec/pagina (dipende dalla GPU), nessun rate limit\n- Con `gemini`: ~1 pagina/secondo (rate limit integrato)\n- Con `openai`: veloce, dipende dalla rete\n\n### Re-indicizzare tutto da zero\n\nSe vuoi rielaborare tutti i PDF (es. dopo aver cambiato il vision provider o il prompt):\n\n```bash\npython main.py --reindex --ingest-only\n```\n\n> **Attenzione**: `--reindex` cancella e ricostruisce tutti gli embedding.\n> I PDF non vengono toccati, solo il database vettoriale.\n\n### Cosa analizza il sistema per ogni pagina\n\nPer ogni pagina di ogni PDF, il sistema fa **due passaggi**:\n\n1. **Testo**: estrae il testo grezzo con `pymupdf`. Veloce, gratuito, funziona bene\n per PDF con testo selezionabile. Il testo viene diviso in chunk rispettando\n i confini naturali (paragrafi, righe).\n\n2. **Visione**: rasterizza la pagina come immagine (150 DPI) e la invia al\n vision provider configurato, che la \"guarda\" e produce una descrizione Markdown\n strutturata. Questo recupera informazioni da tabelle, schemi, loghi, elementi\n grafici che il testo grezzo non cattura. Anche la descrizione viene chunckata\n rispettando i confini del Markdown.\n\n> **Se un PDF è solo immagine** (scansionato, senza testo selezionabile),\n> il sistema salta il passaggio testo e usa solo la visione.\n\n---\n\n## 9. Fare domande all'agente\n\nHai due modalità: **interfaccia web** (raccomandato) e **CLI** (terminale).\n\n---\n\n### Avvio rapido — start.bat (raccomandato)\n\nFai doppio clic su `start.bat` nella cartella principale. Apre automaticamente:\n1. Il server MCP Preventivi in una finestra separata\n2. L'interfaccia Streamlit nel browser su `http://localhost:8501`\n\nPer fermare tutto: chiudi le due finestre del terminale.\n\n---\n\n### Interfaccia web — Streamlit (manuale)\n\n```bash\ncd \"C:\\Progetti Pilota\\EsploraPreventivi\\rag_preventivi\"\nstreamlit run chat_app.py\n```\n\nSi apre automaticamente il browser su `http://localhost:8501`. Trovi due tab:\n\n**Tab \"💬 Chat\"** — scrivi le domande nella casella in basso e ottieni risposte in streaming:\n- Le fonti RAG appaiono nell'expander **📄 Fonti documenti**\n- Se hai chiesto una ricerca web, gli URL appaiono nell'expander **🌐 Fonti web**\n- La conversazione ha memoria multi-turno (puoi fare domande di seguito)\n\n**Tab \"🗂 Gestione\"** — gestisci i PDF senza toccare il filesystem:\n- Lista documenti con stato (✅ Indicizzato / ⏳ Non indicizzato / ⚠️ Corrotto)\n- Upload di nuovi PDF tramite drag & drop\n- Bottone \"▶️ Indicizza nuovi\" con log in tempo reale\n- Bottone \"🔄 Re-indicizza tutto\"\n- Pulsante 🗑️ per eliminare un documento\n\n---\n\n### Ricerca web su richiesta\n\nL'agente può cercare sul web quando glielo chiedi esplicitamente. Usa parole come:\n- \"Cercami sul web fornitori di serramenti in PVC\"\n- \"Trova alternative online per questo prodotto\"\n- \"Quali aziende vendono lo stesso articolo?\"\n\nPer domande sui preventivi, usa sempre il knowledge base (non va su internet da solo).\n\n---\n\n### CLI — riga di comando (alternativa)\n\n```bash\ncd \"C:\\Progetti Pilota\\EsploraPreventivi\\rag_preventivi\"\npython main.py\n```\n\n```\n=== Agente RAG Preventivi ===\nDigita 'exit' per uscire.\n\nTu: _\n```\n\nDigita la tua domanda in italiano e premi Invio. Digita `exit` o `quit` per uscire.\n\n---\n\n### Esempi di domande\n\n**Prezzi e quantità dai preventivi:**\n```\nQual è il prezzo del prodotto X?\nQual è il totale del preventivo del fornitore Y?\nElenca tutti i prodotti con il loro prezzo\n```\n\n**Fornitori:**\n```\nChi sono i fornitori del progetto?\nChi fornisce le insegne luminose?\nQuali sono i contatti del fornitore X?\n```\n\n**Elementi tecnici:**\n```\nChe sistema di facciata continua è previsto?\nDimensioni del prodotto X dalla scheda tecnica\n```\n\n**Confronto con il catalogo interno:**\n```\nQuesto articolo è nel nostro catalogo? C'è un prezzo migliore?\nConfronta i fornitori per il codice articolo VITE-M6\nTrova il codice fornitore ROS-VM6-20 nel catalogo\nCerca fornitori alternativi per \"bullone inox M8\"\n```\n\n**Ricerca web:**\n```\nTrovami fornitori alternativi per questo tipo di infissi\nCerca sul web il prezzo medio del calcestruzzo C25/30\n```\n\n### Come leggere la risposta\n\nL'agente cita sempre la fonte:\n\n```\nIl prodotto X ha un prezzo di **€ XXX,XX IVA esclusa**.\n\nFonte: Fornitore A, preventivo_fornitore_a.pdf\n\nSono disponibili anche le taglie:\n- Taglia S: dimensioni, peso → € [prezzo]\n(da analisi immagine)\n```\n\n> La dicitura **\"(da analisi immagine)\"** indica che l'informazione viene\n> da un chunk visione — il modello ha visto la pagina come immagine, non come testo.\n\n---\n\n## 10. Aggiungere nuovi documenti\n\n### Metodo 1 — Interfaccia web (più semplice)\n\n1. Apri l'app Streamlit (`streamlit run chat_app.py`)\n2. Vai nella tab \"🗂 Gestione\"\n3. Trascina il PDF nel campo upload — viene copiato automaticamente in `Preventivi\\`\n4. Clicca \"▶️ Indicizza nuovi\" — il log mostra il progresso in tempo reale\n\n### Metodo 2 — CLI\n\n1. Copia il PDF nella cartella `Preventivi\\`\n2. Esegui l'indicizzazione:\n\n```bash\ncd rag_preventivi\npython main.py --ingest-only\n```\n\nIl sistema rileva automaticamente il nuovo file (hash SHA-256 non presente\nin `indexed.json`) e lo indicizza. I file già indicizzati vengono saltati.\n\n### Aggiornare un PDF esistente\n\nSe modifichi un PDF già indicizzato:\n\n1. Sovrascrivi il file nella cartella `Preventivi\\`\n2. Esegui `python main.py --ingest-only`\n\nIl sistema rileva che l'hash è cambiato e re-indicizza automaticamente solo\nquel file.\n\n### Formati supportati\n\n| Formato | Supportato | Note |\n|---------|-----------|------|\n| `.pdf` con testo selezionabile | ✅ | Testo + visione |\n| `.pdf` scansionato (solo immagine) | ✅ | Solo visione |\n| `.pdf` con encoding corrotto | ✅ | Salta testo, usa solo visione |\n| `.xlsx` / `.xls` (Excel) | ❌ | Non ancora supportato |\n| `.docx` (Word) | ❌ | Non ancora supportato |\n\n---\n\n## 11. Adattare il sistema a un nuovo progetto\n\nIl sistema è progettato per funzionare con qualsiasi tipo di preventivo o documento\ncommerciale. Per adattarlo, modifica **solo `config.py`**:\n\n```python\n# 1. Dai un nome al progetto e alla sua collection ChromaDB\nPROJECT_CONTEXT = \"preventivi forniture hardware e software\"\nCHROMA_COLLECTION = \"preventivi_informatica\"\n\n# 2. Scegli il vision provider più adatto\nVISION_PROVIDER = \"lmstudio\" # o \"gemini\" o \"openai\"\n\n# 3. Personalizza il prompt per il tipo di documento\nVISION_PROMPT = \"\"\"Analizza questa pagina di offerta commerciale IT.\nRestituisci SOLO il contenuto in Markdown strutturato:\n- Usa tabelle Markdown (| Part Number | Descrizione | Qt | Prezzo unitario | Totale |)\n- Usa ## per sezioni (hardware, software, servizi, licenze)\n- Riporta: part number, SKU, versioni software, canoni annui, sconti\nNon aggiungere testo non presente. Solo estrarre.\"\"\"\n```\n\nPoi cancella `chroma_db/` e `indexed.json` (appartengono al progetto precedente)\ne ri-indicizza i nuovi PDF.\n\n**Esempi di prompt per altri domini:**\n\n```python\n# Forniture medicali\nVISION_PROMPT = \"\"\"Analizza questa pagina di offerta per forniture sanitarie.\nRestituisci SOLO il contenuto in Markdown strutturato:\n- Tabelle con (| Codice CND | Descrizione | Fabbricante | Qt | Prezzo |)\n- ## per sezioni (dispositivi, farmaci, consumabili, servizi)\n- Riporta: codici CND/RDM, fabbricante, numero di riferimento\nNon aggiungere testo non presente. Solo estrarre.\"\"\"\n\n# Lavori edili generici\nVISION_PROMPT = \"\"\"Analizza questa pagina di computo metrico o preventivo edile.\nRestituisci SOLO il contenuto in Markdown strutturato:\n- Tabelle con (| Voce | Unità | Quantità | Prezzo unit. | Importo |)\n- ## per categorie di lavoro (opere murarie, impianti, finiture...)\n- Riporta: codici voce, unità di misura, prezzi unitari, totali parziali\nNon aggiungere testo non presente. Solo estrarre.\"\"\"\n```\n\n---\n\n## 12. Domande frequenti\n\n### \"L'agente ha risposto una cosa sbagliata\"\n\nPuò succedere se l'informazione non è nei documenti indicizzati, o se è in una\nparte del documento non estratta correttamente. Prova a:\n- Riformulare la domanda con termini più vicini a quelli nel documento\n- Verificare manualmente nel PDF di origine\n- Cambiare il vision provider o migliorare il `VISION_PROMPT` in `config.py`\n\n### \"Ho ricevuto un errore 429\"\n\nSignifica che hai superato il limite di chiamate all'API Gemini.\nIl sistema ha già un rate limiting interno per il provider Gemini.\nSe persiste, attendi qualche minuto e riprova. Considera di passare a `lmstudio`\nper eliminare il problema alla radice.\n\n### \"LM Studio non risponde\"\n\nVerifica che LM Studio sia in esecuzione e che il server locale sia avviato\n(\"Developer\" → \"Start Server\"). Il modello deve essere caricato prima di avviare\nl'indicizzazione. Verifica che `LMSTUDIO_BASE_URL` in `config.py` corrisponda\nalla porta mostrata da LM Studio (default: `http://localhost:1234/v1`).\n\n### \"Il modello in LM Studio ha un nome diverso\"\n\nApri un terminale e lancia:\n```bash\ncurl http://localhost:1234/v1/models\n```\nCopia il campo `id` del modello caricato e incollalo in `config.py` come `LMSTUDIO_VISION_MODEL`.\n\n### \"Il database vettoriale sembra corrotto\"\n\nElimina la cartella `chroma_db\\` e il file `indexed.json`, poi esegui:\n```bash\npython main.py --reindex --ingest-only\n```\n\n### \"Voglio cambiare il modello di chat\"\n\nModifica `agent.py`, riga con `DeepSeek(id=\"deepseek-chat\")`.\nAgno supporta anche: OpenAI GPT-4, Anthropic Claude, Google Gemini, ecc.\n\n### \"Come aggiungo Excel o Word?\"\n\nBisogna aggiungere un nuovo estrattore in `ingestion/`. Per Excel con `openpyxl`:\n- Crea `ingestion/excel_extractor.py`\n- Estendi il glob in `pipeline.py` da `*.pdf` a `*.pdf` + `*.xlsx`\n\n---\n\n## Riepilogo dei comandi\n\n```bash\n# Prima installazione\npip install -r rag_preventivi/requirements.txt\npip install -r mcp_preventivi/requirements.txt\n\n# Avviare tutto con un clic (server MCP + Streamlit)\nstart.bat\n\n# Avviare solo l'interfaccia web (manuale)\nstreamlit run rag_preventivi/chat_app.py\n\n# Avviare solo il server MCP (manuale)\ncd mcp_preventivi && python server.py\n\n# Indicizzare i PDF via CLI (prima volta o nuovi PDF)\npython rag_preventivi/main.py --ingest-only\n\n# Re-indicizzare tutto da zero via CLI\npython rag_preventivi/main.py --reindex --ingest-only\n\n# Avviare la chat CLI (alternativa alla web)\npython rag_preventivi/main.py\n\n# Indicizzare il catalogo articoli per ricerca semantica\ncd mcp_preventivi && python index_articoli.py\n\n# Visualizzare la mappa UMAP degli embedding articoli\ncd mcp_preventivi && python visualizza_embeddings.py\n\n# Eseguire i test automatici\ncd rag_preventivi && python -m pytest tests/ -v\ncd mcp_preventivi && python -m pytest tests/ -v\n\n# Verificare il modello caricato in LM Studio\ncurl http://localhost:1234/v1/models\n```\n\n---\n\n## Roadmap\n\n| Stato | Feature | Note |\n|-------|---------|------|\n| ✅ | **Interfaccia web Streamlit** | Chat streaming + tab Gestione documenti |\n| ✅ | **Ricerca web su richiesta** | DuckDuckGo via `ddgs`, solo su richiesta esplicita |\n| ✅ | **Vision provider configurabile** | LM Studio (locale) / Gemini / OpenAI — si cambia una riga in `config.py` |\n| ✅ | **Chunking separator-aware** | Rispetta paragrafi, righe di tabella e parole — nessun taglio a metà riga |\n| ✅ | **Vision prompt parametrizzabile** | Personalizzabile per dominio in `config.py` |\n| ✅ | **MCP SQL — confronto catalogo fornitori** | FastMCP server con 7 tool read-only su DB ordini/fornitori. `CatalogoTools` integra la ricerca nell'agente Agno (sync). Ricerca semantica degli articoli via ChromaDB + GeminiEmbedder. Visualizzazione UMAP 2D degli embedding. |\n| 🔜 | **Supporto Excel (.xlsx)** | Aggiungere `excel_extractor.py` con `openpyxl` per indicizzare anche i fogli di calcolo. |\n| 💡 | **Supporto Word (.docx)** | Estendere la pipeline con `python-docx`. |\n| 💡 | **Embedding locale** | Sostituire Gemini embedding con FastEmbed `bge-m3` per privacy totale e zero costi. |\n\n---\n\n*Stack: Python · Agno · DeepSeek · Gemini Embedding · ChromaDB · pymupdf · Streamlit · DuckDuckGo · FastMCP · pyodbc · UMAP · LM Studio (Qwen3.5)*\n"}, "c0a2d2a20bb3e6b07aae21d37657b84afb009b97": {"t": 1785766193.1855073, "n": 30942, "h": "8fd6b7c36cd042b9", "c": "# Agente RAG per i Preventivi — Guida Completa\n\n> Questa guida è pensata per chi non ha mai sentito parlare di RAG o di AI applicata ai documenti.\n> La leggi dall'inizio alla fine e alla fine sai usare il sistema.\n\n---\n\n## Indice\n\n1. [Cos'è un RAG? (spiegazione per neofiti)](#1-cosè-un-rag-spiegazione-per-neofiti)\n2. [Il problema che risolve questo progetto](#2-il-problema-che-risolve-questo-progetto)\n3. [Come funziona — architettura semplificata](#3-come-funziona--architettura-semplificata)\n4. [I componenti del sistema](#4-i-componenti-del-sistema)\n5. [Come è strutturato il progetto](#5-come-è-strutturato-il-progetto)\n6. [Prima installazione — passo passo](#6-prima-installazione--passo-passo)\n7. [Configurazione](#7-configurazione)\n8. [Indicizzare i PDF](#8-indicizzare-i-pdf)\n9. [Fare domande all'agente](#9-fare-domande-allagente)\n10. [Aggiungere nuovi documenti](#10-aggiungere-nuovi-documenti)\n11. [Adattare il sistema a un nuovo progetto](#11-adattare-il-sistema-a-un-nuovo-progetto)\n12. [Domande frequenti](#12-domande-frequenti)\n\n---\n\n## 1. Cos'è un RAG? (spiegazione per neofiti)\n\n**RAG** sta per **Retrieval-Augmented Generation**, che in italiano possiamo chiamare\n\"generazione aumentata dal recupero\". È una tecnica per far rispondere un'AI a domande\nsu documenti reali, senza \"inventare\" risposte.\n\n### Il problema delle AI classiche\n\nUn modello di linguaggio come ChatGPT è addestrato su miliardi di testi trovati su internet.\nSa moltissime cose di carattere generale. Ma **non conosce i tuoi documenti** —\ni tuoi preventivi, le tue offerte, i tuoi schemi tecnici.\n\nSe gli chiedi \"Qual è il prezzo del prodotto X nel preventivo del fornitore Y?\",\nnon sa rispondere. O peggio: inventa una risposta plausibile ma sbagliata.\nQuesto si chiama **allucinazione**.\n\n### Come funziona il RAG\n\nIl RAG risolve il problema in due passaggi:\n\n```\n[FASE 1 — INDICIZZAZIONE]\n\nI tuoi PDF ──► Estrai il testo ──► Dividi in piccoli pezzi (chunk)\n ──► Converti ogni pezzo in un numero (embedding)\n ──► Salva in un database vettoriale\n\n[FASE 2 — RISPOSTA]\n\nLa tua domanda ──► Converti anche lei in un numero (embedding)\n ──► Trova i pezzi più \"vicini\" per significato\n ──► Passa quei pezzi + la domanda all'AI\n ──► L'AI risponde basandosi SOLO su quei pezzi\n```\n\n### Il trucco degli \"embedding\"\n\nUn **embedding** è la traduzione di un testo in un array di numeri (un vettore).\nTesti con significato simile producono numeri simili. Quindi:\n\n- \"Prezzo fioriera grande\" e \"costo vaso esterno Ø1200\" producono numeri vicini\n- \"Preventivo facciata nord\" e \"offerta insegne fronte edificio\" producono numeri vicini\n\nQuesto permette di trovare documenti pertinenti anche se non usano le stesse parole\nesatte della domanda.\n\n---\n\n## 2. Il problema che risolve questo progetto\n\nUn progetto edilizio tipico accumula diversi preventivi da fornitori diversi, in formati diversi:\n\n| Documento | Contenuto |\n|-----------|-----------|\n| `scheda_prodotto.pdf` | Scheda prodotto con prezzi e dimensioni |\n| `preventivo_fornitore_a.pdf` | Preventivo Fornitore A per forniture |\n| `offerta_insegne.pdf` | Offerta insegne e segnaletica |\n| `offerta_facciata.pdf` | Offerta facciata continua |\n| `preventivo_edile.pdf` | Preventivo lavori edili |\n| `disegni_tecnici.pdf` | Disegni tecnici di facciata |\n| `offerta_verde.pdf` | Offerta verde/giardino |\n\nSenza il sistema, per rispondere a domande come \"Qual è il totale delle insegne?\"\nbisognava aprire ogni PDF, cercare manualmente, fare calcoli.\n\n**Con il sistema**, si scrive la domanda in italiano e si ottiene la risposta in pochi secondi,\ncon indicato il documento sorgente.\n\n---\n\n## 3. Come funziona — architettura semplificata\n\n```\n ┌─────────────────────────────────────────┐\n │ FASE DI INDICIZZAZIONE │\n │ (si fa una volta sola) │\n └─────────────────────────────────────────┘\n\nPreventivi/\n├── scheda_prodotto.pdf ──────────────────────────────────────────────────┐\n├── offerta_facciata.pdf ─────────────────────────────────────────────── │\n├── preventivo_fornitore.pdf ─────────────────────────────────────────── │\n└── ... altri PDF ... │\n ▼\n ┌────────────────────────────────┐\n │ PIPELINE INGESTION │\n │ │\n │ Passaggio 1 — TESTO │\n │ pymupdf estrae il testo │\n │ grezzo da ogni pagina │\n │ → chunk separator-aware │\n │ (paragrafo › riga › parola) │\n │ │\n │ Passaggio 2 — VISIONE │\n │ Ogni pagina viene │\n │ \"fotografata\" e mandata al │\n │ vision provider configurato │\n │ che la descrive in Markdown │\n │ → chunk separator-aware │\n └─────────────┬──────────────────┘\n │\n ▼\n ┌────────────────────────────────┐\n │ gemini-embedding-2-preview │\n │ │\n │ Ogni pezzo di testo diventa │\n │ un vettore di 3072 numeri │\n └─────────────┬──────────────────┘\n │\n ▼\n ┌────────────────────────────────┐\n │ ChromaDB │\n │ (database vettoriale) │\n │ salvato in chroma_db/ │\n └────────────────────────────────┘\n\n\n ┌─────────────────────────────────────────┐\n │ FASE DI RISPOSTA │\n │ (ogni volta che fai domande) │\n └─────────────────────────────────────────┘\n\nTu: \"Qual è il prezzo del prodotto X nel preventivo?\"\n │\n ▼\n┌─────────────────────┐ ┌──────────────────────────────────────────┐\n│ La domanda diventa │ │ ChromaDB cerca i 6 chunk più simili │\n│ anche lei un vettore│──────► │ per significato alla domanda │\n│ di 3072 numeri │ │ (\"prodotto X\", \"prezzo\", \"preventivo\") │\n└─────────────────────┘ └───────────────────┬──────────────────────┘\n │\n │ Restituisce i chunk più rilevanti:\n │ \"Codice prodotto X, dimensioni...\"\n │ \"Prezzo: € XXX,XX IVA esclusa...\"\n │ ...\n ▼\n ┌───────────────────────────────┐\n │ DeepSeek Chat │\n │ │\n │ Riceve: │\n │ - I chunk trovati │\n │ - La tua domanda │\n │ - Le istruzioni del progetto │\n │ │\n │ Risponde in italiano, │\n │ citando la fonte │\n └───────────────────────────────┘\n │\n ▼\n \"Il prodotto X costa € XXX,XX IVA esclusa.\n Fonte: Fornitore A (preventivo_fornitore_a.pdf)\"\n```\n\n---\n\n## 4. I componenti del sistema\n\n| Componente | Ruolo | Tecnologia |\n|------------|-------|------------|\n| **Estrazione testo** | Legge il testo dai PDF | `pymupdf` |\n| **Chunking** | Divide il testo rispettando paragrafi, righe e parole | separator-aware (`\\n\\n` › `\\n` › ` `) |\n| **Vision provider** | \"Guarda\" le pagine e le descrive in Markdown strutturato | configurabile: LM Studio, Gemini, OpenAI (vedi §7) |\n| **Embedding** | Trasforma testi in numeri | `gemini-embedding-2-preview` (3072 dim.) |\n| **Database vettoriale** | Archivia e cerca per similarità | `ChromaDB` (locale, nessun server) |\n| **Knowledge base** | Collega embedding e ricerca | `Agno` framework |\n| **Modello di chat** | Genera la risposta finale | `DeepSeek Chat` |\n| **Interfaccia web** | Chat + gestione documenti nel browser | `Streamlit` |\n| **Ricerca web** | Trova fornitori e alternative online | `DuckDuckGo` via `ddgs` (no API key) |\n| **CLI** | Interfaccia alternativa a riga di comando | Python `argparse` + `input()` |\n| **Deduplicazione** | Evita di re-indicizzare PDF già visti | SHA-256 hash + `indexed.json` |\n| **MCP Server** | Espone 7 tool di ricerca sul DB ordini/fornitori | `FastMCP` + `pyodbc` (read-only) |\n| **CatalogoTools** | Integra la ricerca DB nell'agente Agno (sync) | wrapper diretto su `search.py` |\n| **Embedding articoli** | Ricerca semantica nel catalogo prodotti | ChromaDB `iab_articoli` + GeminiEmbedder |\n\n---\n\n## 5. Come è strutturato il progetto\n\n```\nC:\\Progetti Pilota\\EsploraPreventivi\\\n│\n├── Preventivi\\ ← I tuoi PDF (metti qui i nuovi)\n│ ├── scheda_prodotto.pdf\n│ ├── preventivo_fornitore.pdf\n│ └── ...\n│\n├── rag_preventivi\\ ← Il codice del sistema\n│ ├── config.py ← Impostazioni (modelli, soglie, path, vision provider, prompt)\n│ ├── knowledge.py ← Configura ChromaDB + GeminiEmbedder\n│ ├── agent.py ← Crea l'agente DeepSeek + WebSearchTools + CatalogoTools\n│ ├── catalogo_tools.py ← Toolkit Agno per ricerca nel catalogo (wrapper sync su search.py)\n│ ├── chat_app.py ← Interfaccia web Streamlit (avvia con streamlit run)\n│ ├── admin_tab.py ← Tab \"Gestione\": lista, upload, indicizza, elimina\n│ ├── main.py ← CLI alternativa (argparse + input())\n│ │\n│ ├── ingestion\\ ← Pipeline di indicizzazione\n│ │ ├── text_extractor.py ← Estrae testo con pymupdf + chunking separator-aware\n│ │ ├── image_extractor.py ← Visione pagine con provider configurabile (LM Studio / Gemini / OpenAI)\n│ │ └── pipeline.py ← Orchestrazione + deduplicazione + streaming log\n│ │\n│ └── tests\\ ← Test automatici (23 test)\n│ ├── conftest.py\n│ ├── test_text_extractor.py\n│ ├── test_image_extractor.py\n│ ├── test_pipeline.py\n│ ├── test_agent.py\n│ └── test_admin_tab.py\n│\n├── mcp_preventivi\\ ← Server MCP + ricerca nel catalogo\n│ ├── server.py ← FastMCP server con 7 tool read-only\n│ ├── search.py ← Funzioni di ricerca: articoli, fornitori, prezzi, codici\n│ ├── db.py ← Helper connessione pyodbc (contextlib.closing)\n│ ├── embeddings.py ← GeminiEmbedder + ChromaDB per ricerca semantica articoli\n│ ├── index_articoli.py ← Script per indicizzare il catalogo (--limit N per parziale)\n│ ├── visualizza_embeddings.py ← Mappa UMAP 2D interattiva degli embedding (HTML)\n│ ├── requirements.txt\n│ └── tests\\\n│ ├── test_search.py ← Test TDD per tutte le funzioni di ricerca\n│ └── test_embeddings.py ← Test TDD per la ricerca semantica\n│\n├── chroma_db\\ ← Database vettoriale preventivi (auto-creato)\n├── indexed.json ← Registro dei PDF già indicizzati\n├── .env ← Chiavi API + stringa di connessione DB (non condividere!)\n├── .gitignore ← Protegge .env, chroma_db e .chroma_iab da git\n├── start.bat ← Avvia tutto con un doppio clic\n└── README.md ← Questo file\n```\n\n---\n\n## 6. Prima installazione — passo passo\n\n### Prerequisiti\n\n- Python 3.11 o superiore installato\n- Connessione internet (per le API di embedding e chat)\n- Le chiavi API necessarie in base al vision provider scelto (vedi §7)\n\n### Passo 1 — Installa le dipendenze\n\n```bash\npip install -r rag_preventivi/requirements.txt\npip install -r mcp_preventivi/requirements.txt\n```\n\n`rag_preventivi`: agno, chromadb, pymupdf, google-generativeai, openai, python-dotenv, Pillow, pytest, streamlit, ddgs.\n\n`mcp_preventivi`: fastmcp, pyodbc, chromadb, google-generativeai, python-dotenv.\n\n### Passo 2 — Ottieni le chiavi API\n\nHai sempre bisogno di queste due:\n\n**Google API Key** (per l'embedding):\n1. Vai su [aistudio.google.com](https://aistudio.google.com)\n2. Clicca su \"Get API Key\" e copia la chiave (inizia con `AIza...`)\n\n**DeepSeek API Key** (per il modello di chat):\n1. Vai su [platform.deepseek.com](https://platform.deepseek.com/api_keys)\n2. Crea un account e genera una chiave API\n\nIn base al **vision provider** scelto (vedi §7), potrebbero servire chiavi aggiuntive:\n\n| Provider | Chiave aggiuntiva |\n|----------|-------------------|\n| `lmstudio` | Nessuna — gira in locale |\n| `gemini` | `GOOGLE_API_KEY` (già usata per l'embedding) |\n| `openai` | `OPENAI_API_KEY` |\n\n### Passo 3 — Crea il file .env\n\nNella cartella `C:\\Progetti Pilota\\EsploraPreventivi\\` crea un file `.env`:\n\n```\nGOOGLE_API_KEY=AIzaSy...la_tua_chiave...\nDEEPSEEK_API_KEY=sk-...la_tua_chiave...\n\n# Connessione al database ordini/fornitori (per mcp_preventivi)\nIAB_DB_CONNECTION_STRING=Driver={ODBC Driver 17 for SQL Server};Server=...;Database=...;UID=...;PWD=...\n\n# Solo se usi VISION_PROVIDER = \"openai\"\n# OPENAI_API_KEY=sk-...la_tua_chiave...\n```\n\n> **Importante**: non condividere mai questo file. È già protetto da `.gitignore`.\n\n### Passo 4 — (Solo per `lmstudio`) Configura LM Studio\n\n1. Scarica e installa [LM Studio](https://lmstudio.ai)\n2. Scarica il modello `qwen/qwen3.5-9b` dalla scheda \"Discover\"\n3. Avvia il server locale: vai in \"Developer\" → \"Start Server\"\n4. Il server gira su `http://localhost:1234`\n\n### Passo 5 — Indicizza il catalogo articoli (opzionale, per la ricerca semantica)\n\nSe vuoi che l'agente possa trovare articoli nel catalogo anche per sinonimo o concetto,\nesegui l'indicizzazione degli embedding:\n\n```bash\ncd mcp_preventivi\npython index_articoli.py\n```\n\nPer un test rapido con i primi 100 articoli:\n\n```bash\npython index_articoli.py --limit 100\n```\n\n> Richiede `IAB_DB_CONNECTION_STRING` nel `.env`. Se non configurata, il resto del sistema\n> funziona comunque — solo la ricerca semantica non sarà disponibile.\n\n### Passo 6 — Verifica l'installazione\n\n```bash\ncd rag_preventivi\npython -c \"from knowledge import build_knowledge; kb, vdb = build_knowledge(); print('OK!')\"\n```\n\nSe stampa `OK!` senza errori, sei pronto.\n\n---\n\n## 7. Configurazione\n\nTutto si configura nel file `rag_preventivi/config.py`. Non è mai necessario toccare altri file.\n\n### Parametri principali\n\n```python\n# ── Progetto ───────────────────────────────────────────────────────────────\nPROJECT_CONTEXT = \"preventivi edilizi del Centro Commerciale Leonardo di Imola\"\nCHROMA_COLLECTION = \"preventivi_leonardo_v2\" # nome univoco per questo progetto\n\n# ── Vision provider ────────────────────────────────────────────────────────\n# \"lmstudio\" — locale, gratis, privacy totale (richiede LM Studio in esecuzione)\n# \"gemini\" — API Google, qualità alta, richiede GOOGLE_API_KEY\n# \"openai\" — API OpenAI, richiede OPENAI_API_KEY\nVISION_PROVIDER = \"lmstudio\"\n\n# ── Vision prompt ──────────────────────────────────────────────────────────\n# Il testo inviato al modello vision per descrivere ogni pagina.\n# Personalizzalo per il tipo di documenti che indicizzi.\nVISION_PROMPT = \"\"\"Analizza questa pagina di documento commerciale.\nRestituisci SOLO il contenuto in Markdown strutturato:\n...\"\"\"\n\n# ── Chunking ───────────────────────────────────────────────────────────────\nCHUNK_SIZE = 1000 # caratteri massimi per chunk\nCHUNK_OVERLAP = 200 # caratteri di sovrapposizione tra chunk adiacenti\n```\n\n### Vision provider — dettaglio\n\n| Provider | Costo | Privacy | Qualità | Requisiti |\n|----------|-------|---------|---------|-----------|\n| `lmstudio` | Gratis | Locale | Alta (dipende dal modello) | LM Studio + GPU/RAM |\n| `gemini` | Basso | Dati a Google | Ottima | `GOOGLE_API_KEY` |\n| `openai` | Medio | Dati a OpenAI | Ottima | `OPENAI_API_KEY` |\n\nModelli consigliati per LM Studio:\n\n| Modello | VRAM | Note |\n|---------|------|------|\n| `qwen/qwen3.5-9b` | ~8 GB | Ottimo per testi tecnici e tabelle, multilingue |\n| `llava-v1.6` | ~6 GB | Buono per pagine con grafica |\n| `minicpm-v` | ~4 GB | Leggero, per GPU con poca VRAM |\n\n### Chunking — come funziona\n\nIl sistema divide il testo in chunk cercando il confine più naturale disponibile:\n\n```\n1° tentativo: taglia su \\n\\n (fine paragrafo) ← preferito\n2° tentativo: taglia su \\n (fine riga)\n3° tentativo: taglia su spazio (fine parola)\n4° tentativo: taglia sui caratteri ← ultimo resort\n```\n\nQuesto garantisce che una riga di tabella tipo `| P001 | Porta legno | 3 | €400 |`\nnon venga mai spezzata a metà.\n\n---\n\n## 8. Indicizzare i PDF\n\nL'indicizzazione è il processo che legge i PDF, li analizza e li salva nel database\nvettoriale. Si fa **una volta sola** per ogni set di documenti. I documenti già\nindicizzati vengono automaticamente saltati alle esecuzioni successive.\n\n### Comando base\n\n```bash\ncd \"C:\\Progetti Pilota\\EsploraPreventivi\\rag_preventivi\"\npython main.py --ingest-only\n```\n\n**Cosa succede:**\n\n```\n[INGEST] scheda_prodotto.pdf\n → 8 chunk testo\n → 4 chunk visione\n ✓ scheda_prodotto.pdf indicizzato\n\n[INGEST] preventivo_fornitore.pdf\n → 20 chunk testo\n → 8 chunk visione\n ✓ preventivo_fornitore.pdf indicizzato\n\n[SKIP] offerta_insegne.pdf già indicizzato ← PDF non modificato, viene saltato\n...\nIngestion completata.\n```\n\n**Quanto ci vuole?**\n- Con `lmstudio`: ~2-5 sec/pagina (dipende dalla GPU), nessun rate limit\n- Con `gemini`: ~1 pagina/secondo (rate limit integrato)\n- Con `openai`: veloce, dipende dalla rete\n\n### Re-indicizzare tutto da zero\n\nSe vuoi rielaborare tutti i PDF (es. dopo aver cambiato il vision provider o il prompt):\n\n```bash\npython main.py --reindex --ingest-only\n```\n\n> **Attenzione**: `--reindex` cancella e ricostruisce tutti gli embedding.\n> I PDF non vengono toccati, solo il database vettoriale.\n\n### Cosa analizza il sistema per ogni pagina\n\nPer ogni pagina di ogni PDF, il sistema fa **due passaggi**:\n\n1. **Testo**: estrae il testo grezzo con `pymupdf`. Veloce, gratuito, funziona bene\n per PDF con testo selezionabile. Il testo viene diviso in chunk rispettando\n i confini naturali (paragrafi, righe).\n\n2. **Visione**: rasterizza la pagina come immagine (150 DPI) e la invia al\n vision provider configurato, che la \"guarda\" e produce una descrizione Markdown\n strutturata. Questo recupera informazioni da tabelle, schemi, loghi, elementi\n grafici che il testo grezzo non cattura. Anche la descrizione viene chunckata\n rispettando i confini del Markdown.\n\n> **Se un PDF è solo immagine** (scansionato, senza testo selezionabile),\n> il sistema salta il passaggio testo e usa solo la visione.\n\n---\n\n## 9. Fare domande all'agente\n\nHai due modalità: **interfaccia web** (raccomandato) e **CLI** (terminale).\n\n---\n\n### Avvio rapido — start.bat (raccomandato)\n\nFai doppio clic su `start.bat` nella cartella principale. Apre automaticamente:\n1. Il server MCP Preventivi in una finestra separata\n2. L'interfaccia Streamlit nel browser su `http://localhost:8501`\n\nPer fermare tutto: chiudi le due finestre del terminale.\n\n---\n\n### Interfaccia web — Streamlit (manuale)\n\n```bash\ncd \"C:\\Progetti Pilota\\EsploraPreventivi\\rag_preventivi\"\nstreamlit run chat_app.py\n```\n\nSi apre automaticamente il browser su `http://localhost:8501`. Trovi due tab:\n\n**Tab \"💬 Chat\"** — scrivi le domande nella casella in basso e ottieni risposte in streaming:\n- Le fonti RAG appaiono nell'expander **📄 Fonti documenti**\n- Se hai chiesto una ricerca web, gli URL appaiono nell'expander **🌐 Fonti web**\n- La conversazione ha memoria multi-turno (puoi fare domande di seguito)\n\n**Tab \"🗂 Gestione\"** — gestisci i PDF senza toccare il filesystem:\n- Lista documenti con stato (✅ Indicizzato / ⏳ Non indicizzato / ⚠️ Corrotto)\n- Upload di nuovi PDF tramite drag & drop\n- Bottone \"▶️ Indicizza nuovi\" con log in tempo reale\n- Bottone \"🔄 Re-indicizza tutto\"\n- Pulsante 🗑️ per eliminare un documento\n\n---\n\n### Ricerca web su richiesta\n\nL'agente può cercare sul web quando glielo chiedi esplicitamente. Usa parole come:\n- \"Cercami sul web fornitori di serramenti in PVC\"\n- \"Trova alternative online per questo prodotto\"\n- \"Quali aziende vendono lo stesso articolo?\"\n\nPer domande sui preventivi, usa sempre il knowledge base (non va su internet da solo).\n\n---\n\n### CLI — riga di comando (alternativa)\n\n```bash\ncd \"C:\\Progetti Pilota\\EsploraPreventivi\\rag_preventivi\"\npython main.py\n```\n\n```\n=== Agente RAG Preventivi ===\nDigita 'exit' per uscire.\n\nTu: _\n```\n\nDigita la tua domanda in italiano e premi Invio. Digita `exit` o `quit` per uscire.\n\n---\n\n### Esempi di domande\n\n**Prezzi e quantità dai preventivi:**\n```\nQual è il prezzo del prodotto X?\nQual è il totale del preventivo del fornitore Y?\nElenca tutti i prodotti con il loro prezzo\n```\n\n**Fornitori:**\n```\nChi sono i fornitori del progetto?\nChi fornisce le insegne luminose?\nQuali sono i contatti del fornitore X?\n```\n\n**Elementi tecnici:**\n```\nChe sistema di facciata continua è previsto?\nDimensioni del prodotto X dalla scheda tecnica\n```\n\n**Confronto con il catalogo interno:**\n```\nQuesto articolo è nel nostro catalogo? C'è un prezzo migliore?\nConfronta i fornitori per il codice articolo VITE-M6\nTrova il codice fornitore ROS-VM6-20 nel catalogo\nCerca fornitori alternativi per \"bullone inox M8\"\n```\n\n**Ricerca web:**\n```\nTrovami fornitori alternativi per questo tipo di infissi\nCerca sul web il prezzo medio del calcestruzzo C25/30\n```\n\n### Come leggere la risposta\n\nL'agente cita sempre la fonte:\n\n```\nIl prodotto X ha un prezzo di **€ XXX,XX IVA esclusa**.\n\nFonte: Fornitore A, preventivo_fornitore_a.pdf\n\nSono disponibili anche le taglie:\n- Taglia S: dimensioni, peso → € [prezzo]\n(da analisi immagine)\n```\n\n> La dicitura **\"(da analisi immagine)\"** indica che l'informazione viene\n> da un chunk visione — il modello ha visto la pagina come immagine, non come testo.\n\n---\n\n## 10. Aggiungere nuovi documenti\n\n### Metodo 1 — Interfaccia web (più semplice)\n\n1. Apri l'app Streamlit (`streamlit run chat_app.py`)\n2. Vai nella tab \"🗂 Gestione\"\n3. Trascina il PDF nel campo upload — viene copiato automaticamente in `Preventivi\\`\n4. Clicca \"▶️ Indicizza nuovi\" — il log mostra il progresso in tempo reale\n\n### Metodo 2 — CLI\n\n1. Copia il PDF nella cartella `Preventivi\\`\n2. Esegui l'indicizzazione:\n\n```bash\ncd rag_preventivi\npython main.py --ingest-only\n```\n\nIl sistema rileva automaticamente il nuovo file (hash SHA-256 non presente\nin `indexed.json`) e lo indicizza. I file già indicizzati vengono saltati.\n\n### Aggiornare un PDF esistente\n\nSe modifichi un PDF già indicizzato:\n\n1. Sovrascrivi il file nella cartella `Preventivi\\`\n2. Esegui `python main.py --ingest-only`\n\nIl sistema rileva che l'hash è cambiato e re-indicizza automaticamente solo\nquel file.\n\n### Formati supportati\n\n| Formato | Supportato | Note |\n|---------|-----------|------|\n| `.pdf` con testo selezionabile | ✅ | Testo + visione |\n| `.pdf` scansionato (solo immagine) | ✅ | Solo visione |\n| `.pdf` con encoding corrotto | ✅ | Salta testo, usa solo visione |\n| `.xlsx` / `.xls` (Excel) | ❌ | Non ancora supportato |\n| `.docx` (Word) | ❌ | Non ancora supportato |\n\n---\n\n## 11. Adattare il sistema a un nuovo progetto\n\nIl sistema è progettato per funzionare con qualsiasi tipo di preventivo o documento\ncommerciale. Per adattarlo, modifica **solo `config.py`**:\n\n```python\n# 1. Dai un nome al progetto e alla sua collection ChromaDB\nPROJECT_CONTEXT = \"preventivi forniture hardware e software\"\nCHROMA_COLLECTION = \"preventivi_informatica\"\n\n# 2. Scegli il vision provider più adatto\nVISION_PROVIDER = \"lmstudio\" # o \"gemini\" o \"openai\"\n\n# 3. Personalizza il prompt per il tipo di documento\nVISION_PROMPT = \"\"\"Analizza questa pagina di offerta commerciale IT.\nRestituisci SOLO il contenuto in Markdown strutturato:\n- Usa tabelle Markdown (| Part Number | Descrizione | Qt | Prezzo unitario | Totale |)\n- Usa ## per sezioni (hardware, software, servizi, licenze)\n- Riporta: part number, SKU, versioni software, canoni annui, sconti\nNon aggiungere testo non presente. Solo estrarre.\"\"\"\n```\n\nPoi cancella `chroma_db/` e `indexed.json` (appartengono al progetto precedente)\ne ri-indicizza i nuovi PDF.\n\n**Esempi di prompt per altri domini:**\n\n```python\n# Forniture medicali\nVISION_PROMPT = \"\"\"Analizza questa pagina di offerta per forniture sanitarie.\nRestituisci SOLO il contenuto in Markdown strutturato:\n- Tabelle con (| Codice CND | Descrizione | Fabbricante | Qt | Prezzo |)\n- ## per sezioni (dispositivi, farmaci, consumabili, servizi)\n- Riporta: codici CND/RDM, fabbricante, numero di riferimento\nNon aggiungere testo non presente. Solo estrarre.\"\"\"\n\n# Lavori edili generici\nVISION_PROMPT = \"\"\"Analizza questa pagina di computo metrico o preventivo edile.\nRestituisci SOLO il contenuto in Markdown strutturato:\n- Tabelle con (| Voce | Unità | Quantità | Prezzo unit. | Importo |)\n- ## per categorie di lavoro (opere murarie, impianti, finiture...)\n- Riporta: codici voce, unità di misura, prezzi unitari, totali parziali\nNon aggiungere testo non presente. Solo estrarre.\"\"\"\n```\n\n---\n\n## 12. Domande frequenti\n\n### \"L'agente ha risposto una cosa sbagliata\"\n\nPuò succedere se l'informazione non è nei documenti indicizzati, o se è in una\nparte del documento non estratta correttamente. Prova a:\n- Riformulare la domanda con termini più vicini a quelli nel documento\n- Verificare manualmente nel PDF di origine\n- Cambiare il vision provider o migliorare il `VISION_PROMPT` in `config.py`\n\n### \"Ho ricevuto un errore 429\"\n\nSignifica che hai superato il limite di chiamate all'API Gemini.\nIl sistema ha già un rate limiting interno per il provider Gemini.\nSe persiste, attendi qualche minuto e riprova. Considera di passare a `lmstudio`\nper eliminare il problema alla radice.\n\n### \"LM Studio non risponde\"\n\nVerifica che LM Studio sia in esecuzione e che il server locale sia avviato\n(\"Developer\" → \"Start Server\"). Il modello deve essere caricato prima di avviare\nl'indicizzazione. Verifica che `LMSTUDIO_BASE_URL` in `config.py` corrisponda\nalla porta mostrata da LM Studio (default: `http://localhost:1234/v1`).\n\n### \"Il modello in LM Studio ha un nome diverso\"\n\nApri un terminale e lancia:\n```bash\ncurl http://localhost:1234/v1/models\n```\nCopia il campo `id` del modello caricato e incollalo in `config.py` come `LMSTUDIO_VISION_MODEL`.\n\n### \"Il database vettoriale sembra corrotto\"\n\nElimina la cartella `chroma_db\\` e il file `indexed.json`, poi esegui:\n```bash\npython main.py --reindex --ingest-only\n```\n\n### \"Voglio cambiare il modello di chat\"\n\nModifica `agent.py`, riga con `DeepSeek(id=\"deepseek-chat\")`.\nAgno supporta anche: OpenAI GPT-4, Anthropic Claude, Google Gemini, ecc.\n\n### \"Come aggiungo Excel o Word?\"\n\nBisogna aggiungere un nuovo estrattore in `ingestion/`. Per Excel con `openpyxl`:\n- Crea `ingestion/excel_extractor.py`\n- Estendi il glob in `pipeline.py` da `*.pdf` a `*.pdf` + `*.xlsx`\n\n---\n\n## Riepilogo dei comandi\n\n```bash\n# Prima installazione\npip install -r rag_preventivi/requirements.txt\npip install -r mcp_preventivi/requirements.txt\n\n# Avviare tutto con un clic (server MCP + Streamlit)\nstart.bat\n\n# Avviare solo l'interfaccia web (manuale)\nstreamlit run rag_preventivi/chat_app.py\n\n# Avviare solo il server MCP (manuale)\ncd mcp_preventivi && python server.py\n\n# Indicizzare i PDF via CLI (prima volta o nuovi PDF)\npython rag_preventivi/main.py --ingest-only\n\n# Re-indicizzare tutto da zero via CLI\npython rag_preventivi/main.py --reindex --ingest-only\n\n# Avviare la chat CLI (alternativa alla web)\npython rag_preventivi/main.py\n\n# Indicizzare il catalogo articoli per ricerca semantica\ncd mcp_preventivi && python index_articoli.py\n\n# Visualizzare la mappa UMAP degli embedding articoli\ncd mcp_preventivi && python visualizza_embeddings.py\n\n# Eseguire i test automatici\ncd rag_preventivi && python -m pytest tests/ -v\ncd mcp_preventivi && python -m pytest tests/ -v\n\n# Verificare il modello caricato in LM Studio\ncurl http://localhost:1234/v1/models\n```\n\n---\n\n## Roadmap\n\n| Stato | Feature | Note |\n|-------|---------|------|\n| ✅ | **Interfaccia web Streamlit** | Chat streaming + tab Gestione documenti |\n| ✅ | **Ricerca web su richiesta** | DuckDuckGo via `ddgs`, solo su richiesta esplicita |\n| ✅ | **Vision provider configurabile** | LM Studio (locale) / Gemini / OpenAI — si cambia una riga in `config.py` |\n| ✅ | **Chunking separator-aware** | Rispetta paragrafi, righe di tabella e parole — nessun taglio a metà riga |\n| ✅ | **Vision prompt parametrizzabile** | Personalizzabile per dominio in `config.py` |\n| ✅ | **MCP SQL — confronto catalogo fornitori** | FastMCP server con 7 tool read-only su DB ordini/fornitori. `CatalogoTools` integra la ricerca nell'agente Agno (sync). Ricerca semantica degli articoli via ChromaDB + GeminiEmbedder. Visualizzazione UMAP 2D degli embedding. |\n| 🔜 | **Supporto Excel (.xlsx)** | Aggiungere `excel_extractor.py` con `openpyxl` per indicizzare anche i fogli di calcolo. |\n| 💡 | **Supporto Word (.docx)** | Estendere la pipeline con `python-docx`. |\n| 💡 | **Embedding locale** | Sostituire Gemini embedding con FastEmbed `bge-m3` per privacy totale e zero costi. |\n\n---\n\n*Stack: Python · Agno · DeepSeek · Gemini Embedding · ChromaDB · pymupdf · Streamlit · DuckDuckGo · FastMCP · pyodbc · UMAP · LM Studio (Qwen3.5)*\n"}, "7deb88358312c9fe1f79ec6efdeacc2c6230907b": {"t": 1785766194.9928265, "n": 27742, "h": "505933cd165e400d", "c": "# Agente RAG per i Preventivi — Guida Completa\n\n> Questa guida è pensata per chi non ha mai sentito parlare di RAG o di AI applicata ai documenti.\n> La leggi dall'inizio alla fine e alla fine sai usare il sistema.\n\n---\n\n## Indice\n\n1. [Cos'è un RAG? (spiegazione per neofiti)](#1-cosè-un-rag-spiegazione-per-neofiti)\n2. [Il problema che risolve questo progetto](#2-il-problema-che-risolve-questo-progetto)\n3. [Come funziona — architettura semplificata](#3-come-funziona--architettura-semplificata)\n4. [I componenti del sistema](#4-i-componenti-del-sistema)\n5. [Come è strutturato il progetto](#5-come-è-strutturato-il-progetto)\n6. [Prima installazione — passo passo](#6-prima-installazione--passo-passo)\n7. [Configurazione](#7-configurazione)\n8. [Indicizzare i PDF](#8-indicizzare-i-pdf)\n9. [Fare domande all'agente](#9-fare-domande-allagente)\n10. [Aggiungere nuovi documenti](#10-aggiungere-nuovi-documenti)\n11. [Adattare il sistema a un nuovo progetto](#11-adattare-il-sistema-a-un-nuovo-progetto)\n12. [Domande frequenti](#12-domande-frequenti)\n\n---\n\n## 1. Cos'è un RAG? (spiegazione per neofiti)\n\n**RAG** sta per **Retrieval-Augmented Generation**, che in italiano possiamo chiamare\n\"generazione aumentata dal recupero\". È una tecnica per far rispondere un'AI a domande\nsu documenti reali, senza \"inventare\" risposte.\n\n### Il problema delle AI classiche\n\nUn modello di linguaggio come ChatGPT è addestrato su miliardi di testi trovati su internet.\nSa moltissime cose di carattere generale. Ma **non conosce i tuoi documenti** —\ni tuoi preventivi, le tue offerte, i tuoi schemi tecnici.\n\nSe gli chiedi \"Qual è il prezzo del prodotto X nel preventivo del fornitore Y?\",\nnon sa rispondere. O peggio: inventa una risposta plausibile ma sbagliata.\nQuesto si chiama **allucinazione**.\n\n### Come funziona il RAG\n\nIl RAG risolve il problema in due passaggi:\n\n```\n[FASE 1 — INDICIZZAZIONE]\n\nI tuoi PDF ──► Estrai il testo ──► Dividi in piccoli pezzi (chunk)\n ──► Converti ogni pezzo in un numero (embedding)\n ──► Salva in un database vettoriale\n\n[FASE 2 — RISPOSTA]\n\nLa tua domanda ──► Converti anche lei in un numero (embedding)\n ──► Trova i pezzi più \"vicini\" per significato\n ──► Passa quei pezzi + la domanda all'AI\n ──► L'AI risponde basandosi SOLO su quei pezzi\n```\n\n### Il trucco degli \"embedding\"\n\nUn **embedding** è la traduzione di un testo in un array di numeri (un vettore).\nTesti con significato simile producono numeri simili. Quindi:\n\n- \"Prezzo fioriera grande\" e \"costo vaso esterno Ø1200\" producono numeri vicini\n- \"Preventivo facciata nord\" e \"offerta insegne fronte edificio\" producono numeri vicini\n\nQuesto permette di trovare documenti pertinenti anche se non usano le stesse parole\nesatte della domanda.\n\n---\n\n## 2. Il problema che risolve questo progetto\n\nUn progetto edilizio tipico accumula diversi preventivi da fornitori diversi, in formati diversi:\n\n| Documento | Contenuto |\n|-----------|-----------|\n| `scheda_prodotto.pdf` | Scheda prodotto con prezzi e dimensioni |\n| `preventivo_fornitore_a.pdf` | Preventivo Fornitore A per forniture |\n| `offerta_insegne.pdf` | Offerta insegne e segnaletica |\n| `offerta_facciata.pdf` | Offerta facciata continua |\n| `preventivo_edile.pdf` | Preventivo lavori edili |\n| `disegni_tecnici.pdf` | Disegni tecnici di facciata |\n| `offerta_verde.pdf` | Offerta verde/giardino |\n\nSenza il sistema, per rispondere a domande come \"Qual è il totale delle insegne?\"\nbisognava aprire ogni PDF, cercare manualmente, fare calcoli.\n\n**Con il sistema**, si scrive la domanda in italiano e si ottiene la risposta in pochi secondi,\ncon indicato il documento sorgente.\n\n---\n\n## 3. Come funziona — architettura semplificata\n\n```\n ┌─────────────────────────────────────────┐\n │ FASE DI INDICIZZAZIONE │\n │ (si fa una volta sola) │\n └─────────────────────────────────────────┘\n\nPreventivi/\n├── scheda_prodotto.pdf ──────────────────────────────────────────────────┐\n├── offerta_facciata.pdf ─────────────────────────────────────────────── │\n├── preventivo_fornitore.pdf ─────────────────────────────────────────── │\n└── ... altri PDF ... │\n ▼\n ┌────────────────────────────────┐\n │ PIPELINE INGESTION │\n │ │\n │ Passaggio 1 — TESTO │\n │ pymupdf estrae il testo │\n │ grezzo da ogni pagina │\n │ → chunk separator-aware │\n │ (paragrafo › riga › parola) │\n │ │\n │ Passaggio 2 — VISIONE │\n │ Ogni pagina viene │\n │ \"fotografata\" e mandata al │\n │ vision provider configurato │\n │ che la descrive in Markdown │\n │ → chunk separator-aware │\n └─────────────┬──────────────────┘\n │\n ▼\n ┌────────────────────────────────┐\n │ gemini-embedding-2-preview │\n │ │\n │ Ogni pezzo di testo diventa │\n │ un vettore di 3072 numeri │\n └─────────────┬──────────────────┘\n │\n ▼\n ┌────────────────────────────────┐\n │ ChromaDB │\n │ (database vettoriale) │\n │ salvato in chroma_db/ │\n └────────────────────────────────┘\n\n\n ┌─────────────────────────────────────────┐\n │ FASE DI RISPOSTA │\n │ (ogni volta che fai domande) │\n └─────────────────────────────────────────┘\n\nTu: \"Qual è il prezzo del prodotto X nel preventivo?\"\n │\n ▼\n┌─────────────────────┐ ┌──────────────────────────────────────────┐\n│ La domanda diventa │ │ ChromaDB cerca i 6 chunk più simili │\n│ anche lei un vettore│──────► │ per significato alla domanda │\n│ di 3072 numeri │ │ (\"prodotto X\", \"prezzo\", \"preventivo\") │\n└─────────────────────┘ └───────────────────┬──────────────────────┘\n │\n │ Restituisce i chunk più rilevanti:\n │ \"Codice prodotto X, dimensioni...\"\n │ \"Prezzo: € XXX,XX IVA esclusa...\"\n │ ...\n ▼\n ┌───────────────────────────────┐\n │ DeepSeek Chat │\n │ │\n │ Riceve: │\n │ - I chunk trovati │\n │ - La tua domanda │\n │ - Le istruzioni del progetto │\n │ │\n │ Risponde in italiano, │\n │ citando la fonte │\n └───────────────────────────────┘\n │\n ▼\n \"Il prodotto X costa € XXX,XX IVA esclusa.\n Fonte: Fornitore A (preventivo_fornitore_a.pdf)\"\n```\n\n---\n\n## 4. I componenti del sistema\n\n| Componente | Ruolo | Tecnologia |\n|------------|-------|------------|\n| **Estrazione testo** | Legge il testo dai PDF | `pymupdf` |\n| **Chunking** | Divide il testo rispettando paragrafi, righe e parole | separator-aware (`\\n\\n` › `\\n` › ` `) |\n| **Vision provider** | \"Guarda\" le pagine e le descrive in Markdown strutturato | configurabile: LM Studio, Gemini, OpenAI (vedi §7) |\n| **Embedding** | Trasforma testi in numeri | `gemini-embedding-2-preview` (3072 dim.) |\n| **Database vettoriale** | Archivia e cerca per similarità | `ChromaDB` (locale, nessun server) |\n| **Knowledge base** | Collega embedding e ricerca | `Agno` framework |\n| **Modello di chat** | Genera la risposta finale | `DeepSeek Chat` |\n| **Interfaccia web** | Chat + gestione documenti nel browser | `Streamlit` |\n| **Ricerca web** | Trova fornitori e alternative online | `DuckDuckGo` via `ddgs` (no API key) |\n| **CLI** | Interfaccia alternativa a riga di comando | Python `argparse` + `input()` |\n| **Deduplicazione** | Evita di re-indicizzare PDF già visti | SHA-256 hash + `indexed.json` |\n\n---\n\n## 5. Come è strutturato il progetto\n\n```\nC:\\Progetti Pilota\\EsploraPreventivi\\\n│\n├── Preventivi\\ ← I tuoi PDF (metti qui i nuovi)\n│ ├── scheda_prodotto.pdf\n│ ├── preventivo_fornitore.pdf\n│ └── ...\n│\n├── rag_preventivi\\ ← Il codice del sistema\n│ ├── config.py ← Impostazioni (modelli, soglie, path, vision provider, prompt)\n│ ├── knowledge.py ← Configura ChromaDB + GeminiEmbedder\n│ ├── agent.py ← Crea l'agente DeepSeek + WebSearchTools\n│ ├── chat_app.py ← Interfaccia web Streamlit (avvia con streamlit run)\n│ ├── admin_tab.py ← Tab \"Gestione\": lista, upload, indicizza, elimina\n│ ├── main.py ← CLI alternativa (argparse + input())\n│ │\n│ ├── ingestion\\ ← Pipeline di indicizzazione\n│ │ ├── text_extractor.py ← Estrae testo con pymupdf + chunking separator-aware\n│ │ ├── image_extractor.py ← Visione pagine con provider configurabile (LM Studio / Gemini / OpenAI)\n│ │ └── pipeline.py ← Orchestrazione + deduplicazione + streaming log\n│ │\n│ └── tests\\ ← Test automatici (23 test)\n│ ├── conftest.py\n│ ├── test_text_extractor.py\n│ ├── test_image_extractor.py\n│ ├── test_pipeline.py\n│ ├── test_agent.py\n│ └── test_admin_tab.py\n│\n├── chroma_db\\ ← Database vettoriale (auto-creato)\n├── indexed.json ← Registro dei PDF già indicizzati\n├── .env ← Le tue chiavi API (non condividere!)\n├── .gitignore ← Protegge .env e chroma_db da git\n└── README.md ← Questo file\n```\n\n---\n\n## 6. Prima installazione — passo passo\n\n### Prerequisiti\n\n- Python 3.11 o superiore installato\n- Connessione internet (per le API di embedding e chat)\n- Le chiavi API necessarie in base al vision provider scelto (vedi §7)\n\n### Passo 1 — Installa le dipendenze\n\n```bash\npip install -r rag_preventivi/requirements.txt\n```\n\nInstalla: agno, chromadb, pymupdf, google-generativeai, openai, python-dotenv, Pillow, pytest, streamlit, ddgs.\n\n### Passo 2 — Ottieni le chiavi API\n\nHai sempre bisogno di queste due:\n\n**Google API Key** (per l'embedding):\n1. Vai su [aistudio.google.com](https://aistudio.google.com)\n2. Clicca su \"Get API Key\" e copia la chiave (inizia con `AIza...`)\n\n**DeepSeek API Key** (per il modello di chat):\n1. Vai su [platform.deepseek.com](https://platform.deepseek.com/api_keys)\n2. Crea un account e genera una chiave API\n\nIn base al **vision provider** scelto (vedi §7), potrebbero servire chiavi aggiuntive:\n\n| Provider | Chiave aggiuntiva |\n|----------|-------------------|\n| `lmstudio` | Nessuna — gira in locale |\n| `gemini` | `GOOGLE_API_KEY` (già usata per l'embedding) |\n| `openai` | `OPENAI_API_KEY` |\n\n### Passo 3 — Crea il file .env\n\nNella cartella `C:\\Progetti Pilota\\EsploraPreventivi\\` crea un file `.env`:\n\n```\nGOOGLE_API_KEY=AIzaSy...la_tua_chiave...\nDEEPSEEK_API_KEY=sk-...la_tua_chiave...\n\n# Solo se usi VISION_PROVIDER = \"openai\"\n# OPENAI_API_KEY=sk-...la_tua_chiave...\n```\n\n> **Importante**: non condividere mai questo file. È già protetto da `.gitignore`.\n\n### Passo 4 — (Solo per `lmstudio`) Configura LM Studio\n\n1. Scarica e installa [LM Studio](https://lmstudio.ai)\n2. Scarica il modello `qwen/qwen3.5-9b` dalla scheda \"Discover\"\n3. Avvia il server locale: vai in \"Developer\" → \"Start Server\"\n4. Il server gira su `http://localhost:1234`\n\n### Passo 5 — Verifica l'installazione\n\n```bash\ncd rag_preventivi\npython -c \"from knowledge import build_knowledge; kb, vdb = build_knowledge(); print('OK!')\"\n```\n\nSe stampa `OK!` senza errori, sei pronto.\n\n---\n\n## 7. Configurazione\n\nTutto si configura nel file `rag_preventivi/config.py`. Non è mai necessario toccare altri file.\n\n### Parametri principali\n\n```python\n# ── Progetto ───────────────────────────────────────────────────────────────\nPROJECT_CONTEXT = \"preventivi edilizi del Centro Commerciale Leonardo di Imola\"\nCHROMA_COLLECTION = \"preventivi_leonardo_v2\" # nome univoco per questo progetto\n\n# ── Vision provider ────────────────────────────────────────────────────────\n# \"lmstudio\" — locale, gratis, privacy totale (richiede LM Studio in esecuzione)\n# \"gemini\" — API Google, qualità alta, richiede GOOGLE_API_KEY\n# \"openai\" — API OpenAI, richiede OPENAI_API_KEY\nVISION_PROVIDER = \"lmstudio\"\n\n# ── Vision prompt ──────────────────────────────────────────────────────────\n# Il testo inviato al modello vision per descrivere ogni pagina.\n# Personalizzalo per il tipo di documenti che indicizzi.\nVISION_PROMPT = \"\"\"Analizza questa pagina di documento commerciale.\nRestituisci SOLO il contenuto in Markdown strutturato:\n...\"\"\"\n\n# ── Chunking ───────────────────────────────────────────────────────────────\nCHUNK_SIZE = 1000 # caratteri massimi per chunk\nCHUNK_OVERLAP = 200 # caratteri di sovrapposizione tra chunk adiacenti\n```\n\n### Vision provider — dettaglio\n\n| Provider | Costo | Privacy | Qualità | Requisiti |\n|----------|-------|---------|---------|-----------|\n| `lmstudio` | Gratis | Locale | Alta (dipende dal modello) | LM Studio + GPU/RAM |\n| `gemini` | Basso | Dati a Google | Ottima | `GOOGLE_API_KEY` |\n| `openai` | Medio | Dati a OpenAI | Ottima | `OPENAI_API_KEY` |\n\nModelli consigliati per LM Studio:\n\n| Modello | VRAM | Note |\n|---------|------|------|\n| `qwen/qwen3.5-9b` | ~8 GB | Ottimo per testi tecnici e tabelle, multilingue |\n| `llava-v1.6` | ~6 GB | Buono per pagine con grafica |\n| `minicpm-v` | ~4 GB | Leggero, per GPU con poca VRAM |\n\n### Chunking — come funziona\n\nIl sistema divide il testo in chunk cercando il confine più naturale disponibile:\n\n```\n1° tentativo: taglia su \\n\\n (fine paragrafo) ← preferito\n2° tentativo: taglia su \\n (fine riga)\n3° tentativo: taglia su spazio (fine parola)\n4° tentativo: taglia sui caratteri ← ultimo resort\n```\n\nQuesto garantisce che una riga di tabella tipo `| P001 | Porta legno | 3 | €400 |`\nnon venga mai spezzata a metà.\n\n---\n\n## 8. Indicizzare i PDF\n\nL'indicizzazione è il processo che legge i PDF, li analizza e li salva nel database\nvettoriale. Si fa **una volta sola** per ogni set di documenti. I documenti già\nindicizzati vengono automaticamente saltati alle esecuzioni successive.\n\n### Comando base\n\n```bash\ncd \"C:\\Progetti Pilota\\EsploraPreventivi\\rag_preventivi\"\npython main.py --ingest-only\n```\n\n**Cosa succede:**\n\n```\n[INGEST] scheda_prodotto.pdf\n → 8 chunk testo\n → 4 chunk visione\n ✓ scheda_prodotto.pdf indicizzato\n\n[INGEST] preventivo_fornitore.pdf\n → 20 chunk testo\n → 8 chunk visione\n ✓ preventivo_fornitore.pdf indicizzato\n\n[SKIP] offerta_insegne.pdf già indicizzato ← PDF non modificato, viene saltato\n...\nIngestion completata.\n```\n\n**Quanto ci vuole?**\n- Con `lmstudio`: ~2-5 sec/pagina (dipende dalla GPU), nessun rate limit\n- Con `gemini`: ~1 pagina/secondo (rate limit integrato)\n- Con `openai`: veloce, dipende dalla rete\n\n### Re-indicizzare tutto da zero\n\nSe vuoi rielaborare tutti i PDF (es. dopo aver cambiato il vision provider o il prompt):\n\n```bash\npython main.py --reindex --ingest-only\n```\n\n> **Attenzione**: `--reindex` cancella e ricostruisce tutti gli embedding.\n> I PDF non vengono toccati, solo il database vettoriale.\n\n### Cosa analizza il sistema per ogni pagina\n\nPer ogni pagina di ogni PDF, il sistema fa **due passaggi**:\n\n1. **Testo**: estrae il testo grezzo con `pymupdf`. Veloce, gratuito, funziona bene\n per PDF con testo selezionabile. Il testo viene diviso in chunk rispettando\n i confini naturali (paragrafi, righe).\n\n2. **Visione**: rasterizza la pagina come immagine (150 DPI) e la invia al\n vision provider configurato, che la \"guarda\" e produce una descrizione Markdown\n strutturata. Questo recupera informazioni da tabelle, schemi, loghi, elementi\n grafici che il testo grezzo non cattura. Anche la descrizione viene chunckata\n rispettando i confini del Markdown.\n\n> **Se un PDF è solo immagine** (scansionato, senza testo selezionabile),\n> il sistema salta il passaggio testo e usa solo la visione.\n\n---\n\n## 9. Fare domande all'agente\n\nHai due modalità: **interfaccia web** (raccomandato) e **CLI** (terminale).\n\n---\n\n### Interfaccia web — Streamlit (raccomandato)\n\n```bash\ncd \"C:\\Progetti Pilota\\EsploraPreventivi\\rag_preventivi\"\nstreamlit run chat_app.py\n```\n\nSi apre automaticamente il browser su `http://localhost:8501`. Trovi due tab:\n\n**Tab \"💬 Chat\"** — scrivi le domande nella casella in basso e ottieni risposte in streaming:\n- Le fonti RAG appaiono nell'expander **📄 Fonti documenti**\n- Se hai chiesto una ricerca web, gli URL appaiono nell'expander **🌐 Fonti web**\n- La conversazione ha memoria multi-turno (puoi fare domande di seguito)\n\n**Tab \"🗂 Gestione\"** — gestisci i PDF senza toccare il filesystem:\n- Lista documenti con stato (✅ Indicizzato / ⏳ Non indicizzato / ⚠️ Corrotto)\n- Upload di nuovi PDF tramite drag & drop\n- Bottone \"▶️ Indicizza nuovi\" con log in tempo reale\n- Bottone \"🔄 Re-indicizza tutto\"\n- Pulsante 🗑️ per eliminare un documento\n\n---\n\n### Ricerca web su richiesta\n\nL'agente può cercare sul web quando glielo chiedi esplicitamente. Usa parole come:\n- \"Cercami sul web fornitori di serramenti in PVC\"\n- \"Trova alternative online per questo prodotto\"\n- \"Quali aziende vendono lo stesso articolo?\"\n\nPer domande sui preventivi, usa sempre il knowledge base (non va su internet da solo).\n\n---\n\n### CLI — riga di comando (alternativa)\n\n```bash\ncd \"C:\\Progetti Pilota\\EsploraPreventivi\\rag_preventivi\"\npython main.py\n```\n\n```\n=== Agente RAG Preventivi ===\nDigita 'exit' per uscire.\n\nTu: _\n```\n\nDigita la tua domanda in italiano e premi Invio. Digita `exit` o `quit` per uscire.\n\n---\n\n### Esempi di domande\n\n**Prezzi e quantità:**\n```\nQual è il prezzo del prodotto X?\nQual è il totale del preventivo del fornitore Y?\nElenca tutti i prodotti con il loro prezzo\n```\n\n**Fornitori:**\n```\nChi sono i fornitori del progetto?\nChi fornisce le insegne luminose?\nQuali sono i contatti del fornitore X?\n```\n\n**Elementi tecnici:**\n```\nChe sistema di facciata continua è previsto?\nDimensioni del prodotto X dalla scheda tecnica\n```\n\n**Ricerca web:**\n```\nTrovami fornitori alternativi per questo tipo di infissi\nCerca sul web il prezzo medio del calcestruzzo C25/30\n```\n\n### Come leggere la risposta\n\nL'agente cita sempre la fonte:\n\n```\nIl prodotto X ha un prezzo di **€ XXX,XX IVA esclusa**.\n\nFonte: Fornitore A, preventivo_fornitore_a.pdf\n\nSono disponibili anche le taglie:\n- Taglia S: dimensioni, peso → € [prezzo]\n(da analisi immagine)\n```\n\n> La dicitura **\"(da analisi immagine)\"** indica che l'informazione viene\n> da un chunk visione — il modello ha visto la pagina come immagine, non come testo.\n\n---\n\n## 10. Aggiungere nuovi documenti\n\n### Metodo 1 — Interfaccia web (più semplice)\n\n1. Apri l'app Streamlit (`streamlit run chat_app.py`)\n2. Vai nella tab \"🗂 Gestione\"\n3. Trascina il PDF nel campo upload — viene copiato automaticamente in `Preventivi\\`\n4. Clicca \"▶️ Indicizza nuovi\" — il log mostra il progresso in tempo reale\n\n### Metodo 2 — CLI\n\n1. Copia il PDF nella cartella `Preventivi\\`\n2. Esegui l'indicizzazione:\n\n```bash\ncd rag_preventivi\npython main.py --ingest-only\n```\n\nIl sistema rileva automaticamente il nuovo file (hash SHA-256 non presente\nin `indexed.json`) e lo indicizza. I file già indicizzati vengono saltati.\n\n### Aggiornare un PDF esistente\n\nSe modifichi un PDF già indicizzato:\n\n1. Sovrascrivi il file nella cartella `Preventivi\\`\n2. Esegui `python main.py --ingest-only`\n\nIl sistema rileva che l'hash è cambiato e re-indicizza automaticamente solo\nquel file.\n\n### Formati supportati\n\n| Formato | Supportato | Note |\n|---------|-----------|------|\n| `.pdf` con testo selezionabile | ✅ | Testo + visione |\n| `.pdf` scansionato (solo immagine) | ✅ | Solo visione |\n| `.pdf` con encoding corrotto | ✅ | Salta testo, usa solo visione |\n| `.xlsx` / `.xls` (Excel) | ❌ | Non ancora supportato |\n| `.docx` (Word) | ❌ | Non ancora supportato |\n\n---\n\n## 11. Adattare il sistema a un nuovo progetto\n\nIl sistema è progettato per funzionare con qualsiasi tipo di preventivo o documento\ncommerciale. Per adattarlo, modifica **solo `config.py`**:\n\n```python\n# 1. Dai un nome al progetto e alla sua collection ChromaDB\nPROJECT_CONTEXT = \"preventivi forniture hardware e software\"\nCHROMA_COLLECTION = \"preventivi_informatica\"\n\n# 2. Scegli il vision provider più adatto\nVISION_PROVIDER = \"lmstudio\" # o \"gemini\" o \"openai\"\n\n# 3. Personalizza il prompt per il tipo di documento\nVISION_PROMPT = \"\"\"Analizza questa pagina di offerta commerciale IT.\nRestituisci SOLO il contenuto in Markdown strutturato:\n- Usa tabelle Markdown (| Part Number | Descrizione | Qt | Prezzo unitario | Totale |)\n- Usa ## per sezioni (hardware, software, servizi, licenze)\n- Riporta: part number, SKU, versioni software, canoni annui, sconti\nNon aggiungere testo non presente. Solo estrarre.\"\"\"\n```\n\nPoi cancella `chroma_db/` e `indexed.json` (appartengono al progetto precedente)\ne ri-indicizza i nuovi PDF.\n\n**Esempi di prompt per altri domini:**\n\n```python\n# Forniture medicali\nVISION_PROMPT = \"\"\"Analizza questa pagina di offerta per forniture sanitarie.\nRestituisci SOLO il contenuto in Markdown strutturato:\n- Tabelle con (| Codice CND | Descrizione | Fabbricante | Qt | Prezzo |)\n- ## per sezioni (dispositivi, farmaci, consumabili, servizi)\n- Riporta: codici CND/RDM, fabbricante, numero di riferimento\nNon aggiungere testo non presente. Solo estrarre.\"\"\"\n\n# Lavori edili generici\nVISION_PROMPT = \"\"\"Analizza questa pagina di computo metrico o preventivo edile.\nRestituisci SOLO il contenuto in Markdown strutturato:\n- Tabelle con (| Voce | Unità | Quantità | Prezzo unit. | Importo |)\n- ## per categorie di lavoro (opere murarie, impianti, finiture...)\n- Riporta: codici voce, unità di misura, prezzi unitari, totali parziali\nNon aggiungere testo non presente. Solo estrarre.\"\"\"\n```\n\n---\n\n## 12. Domande frequenti\n\n### \"L'agente ha risposto una cosa sbagliata\"\n\nPuò succedere se l'informazione non è nei documenti indicizzati, o se è in una\nparte del documento non estratta correttamente. Prova a:\n- Riformulare la domanda con termini più vicini a quelli nel documento\n- Verificare manualmente nel PDF di origine\n- Cambiare il vision provider o migliorare il `VISION_PROMPT` in `config.py`\n\n### \"Ho ricevuto un errore 429\"\n\nSignifica che hai superato il limite di chiamate all'API Gemini.\nIl sistema ha già un rate limiting interno per il provider Gemini.\nSe persiste, attendi qualche minuto e riprova. Considera di passare a `lmstudio`\nper eliminare il problema alla radice.\n\n### \"LM Studio non risponde\"\n\nVerifica che LM Studio sia in esecuzione e che il server locale sia avviato\n(\"Developer\" → \"Start Server\"). Il modello deve essere caricato prima di avviare\nl'indicizzazione. Verifica che `LMSTUDIO_BASE_URL` in `config.py` corrisponda\nalla porta mostrata da LM Studio (default: `http://localhost:1234/v1`).\n\n### \"Il modello in LM Studio ha un nome diverso\"\n\nApri un terminale e lancia:\n```bash\ncurl http://localhost:1234/v1/models\n```\nCopia il campo `id` del modello caricato e incollalo in `config.py` come `LMSTUDIO_VISION_MODEL`.\n\n### \"Il database vettoriale sembra corrotto\"\n\nElimina la cartella `chroma_db\\` e il file `indexed.json`, poi esegui:\n```bash\npython main.py --reindex --ingest-only\n```\n\n### \"Voglio cambiare il modello di chat\"\n\nModifica `agent.py`, riga con `DeepSeek(id=\"deepseek-chat\")`.\nAgno supporta anche: OpenAI GPT-4, Anthropic Claude, Google Gemini, ecc.\n\n### \"Come aggiungo Excel o Word?\"\n\nBisogna aggiungere un nuovo estrattore in `ingestion/`. Per Excel con `openpyxl`:\n- Crea `ingestion/excel_extractor.py`\n- Estendi il glob in `pipeline.py` da `*.pdf` a `*.pdf` + `*.xlsx`\n\n---\n\n## Riepilogo dei comandi\n\n```bash\n# Prima installazione\npip install -r rag_preventivi/requirements.txt\n\n# Avviare l'interfaccia web (raccomandato)\nstreamlit run rag_preventivi/chat_app.py\n\n# Indicizzare i PDF via CLI (prima volta o nuovi PDF)\npython rag_preventivi/main.py --ingest-only\n\n# Re-indicizzare tutto da zero via CLI\npython rag_preventivi/main.py --reindex --ingest-only\n\n# Avviare la chat CLI (alternativa alla web)\npython rag_preventivi/main.py\n\n# Eseguire i test automatici\ncd rag_preventivi && python -m pytest tests/ -v\n\n# Verificare il modello caricato in LM Studio\ncurl http://localhost:1234/v1/models\n```\n\n---\n\n## Roadmap\n\n| Stato | Feature | Note |\n|-------|---------|------|\n| ✅ | **Interfaccia web Streamlit** | Chat streaming + tab Gestione documenti |\n| ✅ | **Ricerca web su richiesta** | DuckDuckGo via `ddgs`, solo su richiesta esplicita |\n| ✅ | **Vision provider configurabile** | LM Studio (locale) / Gemini / OpenAI — si cambia una riga in `config.py` |\n| ✅ | **Chunking separator-aware** | Rispetta paragrafi, righe di tabella e parole — nessun taglio a metà riga |\n| ✅ | **Vision prompt parametrizzabile** | Personalizzabile per dominio in `config.py` |\n| 🔜 | **MCP SQL — confronto Ordini Fornitori** | Collegare l'agente a un DB relazionale tramite MCP server. L'agente potrà confrontare i prezzi dei preventivi PDF con gli ordini fornitori già emessi, rilevare scostamenti e rispondere a domande che combinano documenti e dati strutturati. |\n| 🔜 | **Supporto Excel (.xlsx)** | Aggiungere `excel_extractor.py` con `openpyxl` per indicizzare anche i fogli di calcolo. |\n| 💡 | **Supporto Word (.docx)** | Estendere la pipeline con `python-docx`. |\n| 💡 | **Embedding locale** | Sostituire Gemini embedding con FastEmbed `bge-m3` per privacy totale e zero costi. |\n\n---\n\n*Stack: Python · Agno · DeepSeek · Gemini Embedding · ChromaDB · pymupdf · Streamlit · DuckDuckGo · LM Studio (Qwen3.5)*\n"}, "98467942fcd9bad7f7848818d7be4fba239ae0c7": {"t": 1785766196.6703408, "n": 21082, "h": "98c9d28159c5a85b", "c": "# Agente RAG per i Preventivi — Guida Completa\n\n> Questa guida è pensata per chi non ha mai sentito parlare di RAG o di AI applicata ai documenti.\n> La leggi dall'inizio alla fine e alla fine sai usare il sistema.\n\n---\n\n## Indice\n\n1. [Cos'è un RAG? (spiegazione per neofiti)](#1-cosè-un-rag-spiegazione-per-neofiti)\n2. [Il problema che risolve questo progetto](#2-il-problema-che-risolve-questo-progetto)\n3. [Come funziona — architettura semplificata](#3-come-funziona--architettura-semplificata)\n4. [I componenti del sistema](#4-i-componenti-del-sistema)\n5. [Come è strutturato il progetto](#5-come-è-strutturato-il-progetto)\n6. [Prima installazione — passo passo](#6-prima-installazione--passo-passo)\n7. [Indicizzare i PDF](#7-indicizzare-i-pdf)\n8. [Fare domande all'agente](#8-fare-domande-allagente)\n9. [Aggiungere nuovi documenti](#9-aggiungere-nuovi-documenti)\n10. [Domande frequenti](#10-domande-frequenti)\n\n---\n\n## 1. Cos'è un RAG? (spiegazione per neofiti)\n\n**RAG** sta per **Retrieval-Augmented Generation**, che in italiano possiamo chiamare\n\"generazione aumentata dal recupero\". È una tecnica per far rispondere un'AI a domande\nsu documenti reali, senza \"inventare\" risposte.\n\n### Il problema delle AI classiche\n\nUn modello di linguaggio come ChatGPT è addestrato su miliardi di testi trovati su internet.\nSa moltissime cose di carattere generale. Ma **non conosce i tuoi documenti** —\ni tuoi preventivi, le tue offerte, i tuoi schemi tecnici.\n\nSe gli chiedi \"Qual è il prezzo del prodotto X nel preventivo del fornitore Y?\",\nnon sa rispondere. O peggio: inventa una risposta plausibile ma sbagliata.\nQuesto si chiama **allucinazione**.\n\n### Come funziona il RAG\n\nIl RAG risolve il problema in due passaggi:\n\n```\n[FASE 1 — INDICIZZAZIONE]\n\nI tuoi PDF ──► Estrai il testo ──► Dividi in piccoli pezzi (chunk)\n ──► Converti ogni pezzo in un numero (embedding)\n ──► Salva in un database vettoriale\n\n[FASE 2 — RISPOSTA]\n\nLa tua domanda ──► Converti anche lei in un numero (embedding)\n ──► Trova i pezzi più \"vicini\" per significato\n ──► Passa quei pezzi + la domanda all'AI\n ──► L'AI risponde basandosi SOLO su quei pezzi\n```\n\n### Il trucco degli \"embedding\"\n\nUn **embedding** è la traduzione di un testo in un array di numeri (un vettore).\nTesti con significato simile producono numeri simili. Quindi:\n\n- \"Prezzo fioriera grande\" e \"costo vaso esterno Ø1200\" producono numeri vicini\n- \"Preventivo facciata nord\" e \"offerta insegne fronte edificio\" producono numeri vicini\n\nQuesto permette di trovare documenti pertinenti anche se non usano le stesse parole\nesatte della domanda.\n\n---\n\n## 2. Il problema che risolve questo progetto\n\nUn progetto edilizio tipico accumula diversi preventivi da fornitori diversi, in formati diversi:\n\n| Documento | Contenuto |\n|-----------|-----------|\n| `scheda_prodotto.pdf` | Scheda prodotto con prezzi e dimensioni |\n| `preventivo_fornitore_a.pdf` | Preventivo Fornitore A per forniture |\n| `offerta_insegne.pdf` | Offerta insegne e segnaletica |\n| `offerta_facciata.pdf` | Offerta facciata continua |\n| `preventivo_edile.pdf` | Preventivo lavori edili |\n| `disegni_tecnici.pdf` | Disegni tecnici di facciata |\n| `offerta_verde.pdf` | Offerta verde/giardino |\n\nSenza il sistema, per rispondere a domande come \"Qual è il totale delle insegne?\"\nbisognava aprire ogni PDF, cercare manualmente, fare calcoli.\n\n**Con il sistema**, si scrive la domanda in italiano e si ottiene la risposta in pochi secondi,\ncon indicato il documento sorgente.\n\n---\n\n## 3. Come funziona — architettura semplificata\n\n```\n ┌─────────────────────────────────────────┐\n │ FASE DI INDICIZZAZIONE │\n │ (si fa una volta sola) │\n └─────────────────────────────────────────┘\n\nPreventivi/\n├── scheda_prodotto.pdf ────────────────────────────────────────────────┐\n├── offerta_facciata.pdf ───────────────────────────────────────────── │\n├── preventivo_fornitore.pdf ────────────────────────────────────────── │\n└── ... altri PDF ... │\n ▼\n ┌────────────────────────────────┐\n │ PIPELINE INGESTION │\n │ │\n │ Passaggio 1 — TESTO │\n │ pymupdf estrae il testo │\n │ grezzo da ogni pagina │\n │ │\n │ Passaggio 2 — VISIONE │\n │ Ogni pagina viene │\n │ \"fotografata\" e mandata a │\n │ Gemini 2.5 Flash che la │\n │ descrive in italiano │\n └─────────────┬──────────────────┘\n │\n ▼\n ┌────────────────────────────────┐\n │ gemini-embedding-2-preview │\n │ │\n │ Ogni pezzo di testo diventa │\n │ un vettore di 3072 numeri │\n └─────────────┬──────────────────┘\n │\n ▼\n ┌────────────────────────────────┐\n │ ChromaDB │\n │ (database vettoriale) │\n │ salvato in chroma_db/ │\n └────────────────────────────────┘\n\n\n ┌─────────────────────────────────────────┐\n │ FASE DI RISPOSTA │\n │ (ogni volta che fai domande) │\n └─────────────────────────────────────────┘\n\nTu: \"Qual è il prezzo del prodotto X nel preventivo?\"\n │\n ▼\n┌─────────────────────┐ ┌──────────────────────────────────────────┐\n│ La domanda diventa │ │ ChromaDB cerca i 6 chunk più simili │\n│ anche lei un vettore│──────► │ per significato alla domanda │\n│ di 3072 numeri │ │ (\"prodotto X\", \"prezzo\", \"preventivo\") │\n└─────────────────────┘ └───────────────────┬──────────────────────┘\n │\n │ Restituisce i chunk più rilevanti:\n │ \"Codice prodotto X, dimensioni...\"\n │ \"Prezzo: € XXX,XX IVA esclusa...\"\n │ ...\n ▼\n ┌───────────────────────────────┐\n │ DeepSeek Chat │\n │ │\n │ Riceve: │\n │ - I chunk trovati │\n │ - La tua domanda │\n │ - Le istruzioni del progetto │\n │ │\n │ Risponde in italiano, │\n │ citando la fonte │\n └───────────────────────────────┘\n │\n ▼\n \"Il prodotto X costa € XXX,XX IVA esclusa.\n Fonte: Fornitore A (preventivo_fornitore_a.pdf)\"\n```\n\n---\n\n## 4. I componenti del sistema\n\n| Componente | Ruolo | Tecnologia |\n|------------|-------|------------|\n| **Estrazione testo** | Legge il testo dai PDF | `pymupdf` |\n| **Visione pagine** | \"Guarda\" le immagini e le descrive | `Gemini 2.5 Flash` via `google.genai` |\n| **Embedding** | Trasforma testi in numeri | `gemini-embedding-2-preview` (3072 dim.) |\n| **Database vettoriale** | Archivia e cerca per similarità | `ChromaDB` (locale, nessun server) |\n| **Knowledge base** | Collega embedding e ricerca | `Agno` framework |\n| **Modello di chat** | Genera la risposta finale | `DeepSeek Chat` |\n| **Interfaccia web** | Chat + gestione documenti nel browser | `Streamlit` |\n| **Ricerca web** | Trova fornitori e alternative online | `DuckDuckGo` via `ddgs` (no API key) |\n| **CLI** | Interfaccia alternativa a riga di comando | Python `argparse` + `input()` |\n| **Deduplicazione** | Evita di re-indicizzare PDF già visti | SHA-256 hash + `indexed.json` |\n\n---\n\n## 5. Come è strutturato il progetto\n\n```\nC:\\Progetti Pilota\\EsploraPreventivi\\\n│\n├── Preventivi\\ ← I tuoi PDF (metti qui i nuovi)\n│ ├── scheda_prodotto.pdf\n│ ├── preventivo_fornitore.pdf\n│ └── ...\n│\n├── rag_preventivi\\ ← Il codice del sistema\n│ ├── config.py ← Impostazioni (modelli, soglie, path)\n│ ├── knowledge.py ← Configura ChromaDB + GeminiEmbedder\n│ ├── agent.py ← Crea l'agente DeepSeek + WebSearchTools\n│ ├── chat_app.py ← Interfaccia web Streamlit (avvia con streamlit run)\n│ ├── admin_tab.py ← Tab \"Gestione\": lista, upload, indicizza, elimina\n│ ├── main.py ← CLI alternativa (argparse + input())\n│ │\n│ ├── ingestion\\ ← Pipeline di indicizzazione\n│ │ ├── text_extractor.py ← Estrae testo con pymupdf\n│ │ ├── image_extractor.py ← Visione pagine con Gemini\n│ │ └── pipeline.py ← Orchestrazione + deduplicazione + streaming log\n│ │\n│ └── tests\\ ← Test automatici (23 test)\n│ ├── conftest.py\n│ ├── test_text_extractor.py\n│ ├── test_image_extractor.py\n│ ├── test_pipeline.py\n│ ├── test_agent.py\n│ └── test_admin_tab.py\n│\n├── chroma_db\\ ← Database vettoriale (auto-creato)\n├── indexed.json ← Registro dei PDF già indicizzati\n├── .env ← Le tue chiavi API (non condividere!)\n├── .gitignore ← Protegge .env e chroma_db da git\n└── README.md ← Questo file\n```\n\n---\n\n## 6. Prima installazione — passo passo\n\n### Prerequisiti\n\n- Python 3.11 o superiore installato\n- Connessione internet (per le API)\n- Le chiavi API (vedi sotto)\n\n### Passo 1 — Ottieni le chiavi API\n\nHai bisogno di due chiavi:\n\n**Google API Key** (per Gemini Vision + Embedding):\n1. Vai su [aistudio.google.com](https://aistudio.google.com)\n2. Clicca su \"Get API Key\"\n3. Copia la chiave (inizia con `AIza...`)\n\n**DeepSeek API Key** (per il modello di chat):\n1. Vai su [platform.deepseek.com](https://platform.deepseek.com/api_keys)\n2. Crea un account e genera una chiave API\n3. Copia la chiave\n\n### Passo 2 — Crea il file .env\n\nNella cartella `C:\\Progetti Pilota\\EsploraPreventivi\\` crea un file chiamato `.env`\n(senza nome, solo estensione) con questo contenuto:\n\n```\nGOOGLE_API_KEY=AIzaSy...la_tua_chiave...\nDEEPSEEK_API_KEY=sk-...la_tua_chiave...\n```\n\n> **Importante**: non condividere mai questo file. È già protetto da `.gitignore`\n> quindi non viene mai caricato su git per errore.\n\n### Passo 3 — Installa le dipendenze\n\nApri un terminale nella cartella del progetto e digita:\n\n```bash\npip install -r rag_preventivi/requirements.txt\n```\n\nQuesto installa: agno, chromadb, pymupdf, google-generativeai, python-dotenv, Pillow, pytest, streamlit, ddgs.\n\n### Passo 4 — Verifica l'installazione\n\n```bash\ncd rag_preventivi\npython -c \"from knowledge import build_knowledge; kb, vdb = build_knowledge(); print('OK!')\"\n```\n\nSe stampa `OK!` senza errori, sei pronto.\n\n---\n\n## 7. Indicizzare i PDF\n\nL'indicizzazione è il processo che legge i PDF, li analizza e li salva nel database\nvettoriale. Si fa **una volta sola** per ogni set di documenti. I documenti già\nindicizzati vengono automaticamente saltati alle esecuzioni successive.\n\n### Comando base\n\n```bash\ncd \"C:\\Progetti Pilota\\EsploraPreventivi\\rag_preventivi\"\npython main.py --ingest-only\n```\n\n**Cosa succede:**\n\n```\n[INGEST] scheda_prodotto.pdf\n → 8 chunk testo\n → 2 chunk vision\n ✓ scheda_prodotto.pdf indicizzato\n\n[INGEST] preventivo_fornitore.pdf\n → 20 chunk testo\n → 5 chunk vision\n ✓ preventivo_fornitore.pdf indicizzato\n\n[SKIP] offerta_insegne.pdf già indicizzato ← PDF non modificato, viene saltato\n...\nIngestion completata.\n```\n\n**Quanto ci vuole?**\nPer una decina di PDF: circa 3-5 minuti.\nIl sistema processa ~1 pagina al secondo (limite di sicurezza dell'API Gemini).\n\n### Re-indicizzare tutto da zero\n\nSe vuoi rielaborare tutti i PDF (ad esempio dopo aver cambiato il modello di embedding):\n\n```bash\npython main.py --reindex --ingest-only\n```\n\n> **Attenzione**: `--reindex` cancella e ricostruisce tutti gli embedding.\n> I PDF non vengono toccati, solo il database vettoriale.\n\n### Cosa analizza il sistema per ogni pagina\n\nPer ogni pagina di ogni PDF, il sistema fa **due passaggi**:\n\n1. **Testo**: estrae il testo grezzo con `pymupdf`. Veloce, gratuito, funziona bene\n per PDF con testo selezionabile.\n\n2. **Visione**: rasterizza la pagina come immagine (150 DPI) e la invia a\n `Gemini 2.5 Flash` che la \"guarda\" e produce una descrizione in italiano.\n Questo recupera informazioni da tabelle, schemi, loghi, elementi grafici\n che il testo grezzo non cattura.\n\n> **Se un PDF è solo immagine** (scansionato, senza testo selezionabile),\n> il sistema salta il passaggio testo e usa solo la visione.\n\n---\n\n## 8. Fare domande all'agente\n\nHai due modalità: **interfaccia web** (raccomandato) e **CLI** (terminale).\n\n---\n\n### Interfaccia web — Streamlit (raccomandato)\n\n```bash\ncd \"C:\\Progetti Pilota\\EsploraPreventivi\\rag_preventivi\"\nstreamlit run chat_app.py\n```\n\nSi apre automaticamente il browser su `http://localhost:8501`. Trovi due tab:\n\n**Tab \"💬 Chat\"** — scrivi le domande nella casella in basso e ottieni risposte in streaming:\n- Le fonti RAG appaiono nell'expander **📄 Fonti documenti**\n- Se hai chiesto una ricerca web, gli URL appaiono nell'expander **🌐 Fonti web**\n- La conversazione ha memoria multi-turno (puoi fare domande di seguito)\n\n**Tab \"🗂 Gestione\"** — gestisci i PDF senza toccare il filesystem:\n- Lista documenti con stato (✅ Indicizzato / ⏳ Non indicizzato / ⚠️ Corrotto)\n- Upload di nuovi PDF tramite drag & drop\n- Bottone \"▶️ Indicizza nuovi\" con log in tempo reale\n- Bottone \"🔄 Re-indicizza tutto\"\n- Pulsante 🗑️ per eliminare un documento\n\n---\n\n### Ricerca web su richiesta\n\nL'agente può cercare sul web quando glielo chiedi esplicitamente. Usa parole come:\n- \"Cercami sul web fornitori di serramenti in PVC\"\n- \"Trova alternative online per questo prodotto\"\n- \"Quali aziende vendono lo stesso articolo?\"\n\nPer domande sui preventivi, usa sempre il knowledge base (non va su internet da solo).\n\n---\n\n### CLI — riga di comando (alternativa)\n\n```bash\ncd \"C:\\Progetti Pilota\\EsploraPreventivi\\rag_preventivi\"\npython main.py\n```\n\n```\n=== Agente RAG Preventivi ===\nDigita 'exit' per uscire.\n\nTu: _\n```\n\nDigita la tua domanda in italiano e premi Invio. Digita `exit` o `quit` per uscire.\n\n---\n\n### Esempi di domande\n\n**Prezzi e quantità:**\n```\nQual è il prezzo del prodotto X?\nQual è il totale del preventivo del fornitore Y?\nElenca tutti i prodotti con il loro prezzo\n```\n\n**Fornitori:**\n```\nChi sono i fornitori del progetto?\nChi fornisce le insegne luminose?\nQuali sono i contatti del fornitore X?\n```\n\n**Elementi tecnici:**\n```\nChe sistema di facciata continua è previsto?\nDimensioni del prodotto X dalla scheda tecnica\n```\n\n**Ricerca web:**\n```\nTrovami fornitori alternativi per questo tipo di infissi\nCerca sul web il prezzo medio del calcestruzzo C25/30\n```\n\n### Come leggere la risposta\n\nL'agente cita sempre la fonte:\n\n```\nIl prodotto X ha un prezzo di **€ XXX,XX IVA esclusa**.\n\nFonte: Fornitore A, preventivo_fornitore_a.pdf\n\nSono disponibili anche le taglie:\n- Taglia S: dimensioni, peso → € [prezzo]\n(da analisi immagine)\n```\n\n> La dicitura **\"(da analisi immagine)\"** indica che l'informazione viene\n> da un chunk visione — l'AI ha visto la pagina come immagine, non come testo.\n\n---\n\n## 9. Aggiungere nuovi documenti\n\n### Metodo 1 — Interfaccia web (più semplice)\n\n1. Apri l'app Streamlit (`streamlit run chat_app.py`)\n2. Vai nella tab \"🗂 Gestione\"\n3. Trascina il PDF nel campo upload — viene copiato automaticamente in `Preventivi\\`\n4. Clicca \"▶️ Indicizza nuovi\" — il log mostra il progresso in tempo reale\n\n### Metodo 2 — CLI\n\n1. Copia il PDF nella cartella `Preventivi\\`\n2. Esegui l'indicizzazione:\n\n```bash\ncd rag_preventivi\npython main.py --ingest-only\n```\n\nIl sistema rileva automaticamente il nuovo file (hash SHA-256 non presente\nin `indexed.json`) e lo indicizza. I file già indicizzati vengono saltati.\n\n### Aggiornare un PDF esistente\n\nSe modifichi un PDF già indicizzato:\n\n1. Sovrascrivi il file nella cartella `Preventivi\\`\n2. Esegui `python main.py --ingest-only`\n\nIl sistema rileva che l'hash è cambiato e re-indicizza automaticamente solo\nquel file.\n\n### Formati supportati\n\n| Formato | Supportato | Note |\n|---------|-----------|------|\n| `.pdf` con testo selezionabile | ✅ | Testo + visione |\n| `.pdf` scansionato (solo immagine) | ✅ | Solo visione (Gemini) |\n| `.pdf` con encoding corrotto | ✅ | Salta testo, usa solo visione |\n| `.xlsx` / `.xls` (Excel) | ❌ | Non ancora supportato |\n| `.docx` (Word) | ❌ | Non ancora supportato |\n\n---\n\n## 10. Domande frequenti\n\n### \"L'agente ha risposto una cosa sbagliata\"\n\nPuò succedere se l'informazione non è nei documenti indicizzati, o se è in una\nparte del documento non estratta correttamente. Prova a:\n- Riformulare la domanda con termini più vicini a quelli nel documento\n- Verificare manualmente nel PDF di origine\n- Se l'informazione è in una tabella o grafico complesso, potrebbe non essere\n stata estratta correttamente dalla visione\n\n### \"Ho ricevuto un errore 429\"\n\nSignifica che hai superato il limite di chiamate all'API Gemini.\nIl sistema ha già un rate limiting interno (1 richiesta/secondo).\nSe persiste, attendi qualche minuto e riprova.\n\n### \"Il database vettoriale sembra corrotto\"\n\nElimina la cartella `chroma_db\\` e il file `indexed.json`, poi esegui:\n```bash\npython main.py --reindex --ingest-only\n```\n\n### \"Voglio cambiare il modello di chat\"\n\nModifica `agent.py`, riga con `DeepSeek(id=\"deepseek-chat\")`.\nAgno supporta anche: OpenAI GPT-4, Anthropic Claude, Google Gemini, ecc.\n\n### \"Come aggiungo Excel o Word?\"\n\nBisogna aggiungere un nuovo estrattore in `ingestion/`. Per Excel con `openpyxl`:\n- Crea `ingestion/excel_extractor.py`\n- Estendi il glob in `pipeline.py` da `*.pdf` a `*.pdf` + `*.xlsx`\n\n---\n\n## Riepilogo dei comandi\n\n```bash\n# Prima installazione\npip install -r rag_preventivi/requirements.txt\n\n# Avviare l'interfaccia web (raccomandato)\nstreamlit run rag_preventivi/chat_app.py\n\n# Indicizzare i PDF via CLI (prima volta o nuovi PDF)\npython rag_preventivi/main.py --ingest-only\n\n# Re-indicizzare tutto da zero via CLI\npython rag_preventivi/main.py --reindex --ingest-only\n\n# Avviare la chat CLI (alternativa alla web)\npython rag_preventivi/main.py\n\n# Eseguire i test automatici\ncd rag_preventivi && python -m pytest tests/ -v\n```\n\n---\n\n---\n\n## Roadmap\n\n| Stato | Feature | Note |\n|-------|---------|------|\n| ✅ | **Interfaccia web Streamlit** | Chat streaming + tab Gestione documenti |\n| ✅ | **Ricerca web su richiesta** | DuckDuckGo via `ddgs`, solo su richiesta esplicita |\n| 🔜 | **Integrazione database SQL** | Collegare l'agente a un DB relazionale (es. storico ordini, anagrafica fornitori). L'agente potrà rispondere a domande che combinano i PDF con dati strutturati. |\n| 🔜 | **Supporto Excel (.xlsx)** | Aggiungere `excel_extractor.py` con `openpyxl` per indicizzare anche i fogli di calcolo. |\n| 💡 | **Supporto Word (.docx)** | Estendere la pipeline con `python-docx`. |\n| 💡 | **Multimodal embedding diretto** | Embeddare le immagini delle pagine direttamente con `gemini-embedding-2-preview` senza passare per la descrizione testuale, per retrieval ancora più accurato. |\n\n---\n\n*Stack: Python · Agno · DeepSeek · Gemini · ChromaDB · pymupdf · Streamlit · DuckDuckGo*\n"}, "03e3387cd6b6c7260e16a08e8ba66074e8875cf3": {"t": 1785766198.3697724, "n": 18973, "h": "d1fb085e95f0b427", "c": "# Agente RAG per i Preventivi — Guida Completa\n\n> Questa guida è pensata per chi non ha mai sentito parlare di RAG o di AI applicata ai documenti.\n> La leggi dall'inizio alla fine e alla fine sai usare il sistema.\n\n---\n\n## Indice\n\n1. [Cos'è un RAG? (spiegazione per neofiti)](#1-cosè-un-rag-spiegazione-per-neofiti)\n2. [Il problema che risolve questo progetto](#2-il-problema-che-risolve-questo-progetto)\n3. [Come funziona — architettura semplificata](#3-come-funziona--architettura-semplificata)\n4. [I componenti del sistema](#4-i-componenti-del-sistema)\n5. [Come è strutturato il progetto](#5-come-è-strutturato-il-progetto)\n6. [Prima installazione — passo passo](#6-prima-installazione--passo-passo)\n7. [Indicizzare i PDF](#7-indicizzare-i-pdf)\n8. [Fare domande all'agente](#8-fare-domande-allagente)\n9. [Aggiungere nuovi documenti](#9-aggiungere-nuovi-documenti)\n10. [Domande frequenti](#10-domande-frequenti)\n\n---\n\n## 1. Cos'è un RAG? (spiegazione per neofiti)\n\n**RAG** sta per **Retrieval-Augmented Generation**, che in italiano possiamo chiamare\n\"generazione aumentata dal recupero\". È una tecnica per far rispondere un'AI a domande\nsu documenti reali, senza \"inventare\" risposte.\n\n### Il problema delle AI classiche\n\nUn modello di linguaggio come ChatGPT è addestrato su miliardi di testi trovati su internet.\nSa moltissime cose di carattere generale. Ma **non conosce i tuoi documenti** —\ni tuoi preventivi, le tue offerte, i tuoi schemi tecnici.\n\nSe gli chiedi \"Qual è il prezzo del prodotto X nel preventivo del fornitore Y?\",\nnon sa rispondere. O peggio: inventa una risposta plausibile ma sbagliata.\nQuesto si chiama **allucinazione**.\n\n### Come funziona il RAG\n\nIl RAG risolve il problema in due passaggi:\n\n```\n[FASE 1 — INDICIZZAZIONE]\n\nI tuoi PDF ──► Estrai il testo ──► Dividi in piccoli pezzi (chunk)\n ──► Converti ogni pezzo in un numero (embedding)\n ──► Salva in un database vettoriale\n\n[FASE 2 — RISPOSTA]\n\nLa tua domanda ──► Converti anche lei in un numero (embedding)\n ──► Trova i pezzi più \"vicini\" per significato\n ──► Passa quei pezzi + la domanda all'AI\n ──► L'AI risponde basandosi SOLO su quei pezzi\n```\n\n### Il trucco degli \"embedding\"\n\nUn **embedding** è la traduzione di un testo in un array di numeri (un vettore).\nTesti con significato simile producono numeri simili. Quindi:\n\n- \"Prezzo fioriera grande\" e \"costo vaso esterno Ø1200\" producono numeri vicini\n- \"Preventivo facciata nord\" e \"offerta insegne fronte edificio\" producono numeri vicini\n\nQuesto permette di trovare documenti pertinenti anche se non usano le stesse parole\nesatte della domanda.\n\n---\n\n## 2. Il problema che risolve questo progetto\n\nUn progetto edilizio tipico accumula diversi preventivi da fornitori diversi, in formati diversi:\n\n| Documento | Contenuto |\n|-----------|-----------|\n| `scheda_prodotto.pdf` | Scheda prodotto con prezzi e dimensioni |\n| `preventivo_fornitore_a.pdf` | Preventivo Fornitore A per forniture |\n| `offerta_insegne.pdf` | Offerta insegne e segnaletica |\n| `offerta_facciata.pdf` | Offerta facciata continua |\n| `preventivo_edile.pdf` | Preventivo lavori edili |\n| `disegni_tecnici.pdf` | Disegni tecnici di facciata |\n| `offerta_verde.pdf` | Offerta verde/giardino |\n\nSenza il sistema, per rispondere a domande come \"Qual è il totale delle insegne?\"\nbisognava aprire ogni PDF, cercare manualmente, fare calcoli.\n\n**Con il sistema**, si scrive la domanda in italiano e si ottiene la risposta in pochi secondi,\ncon indicato il documento sorgente.\n\n---\n\n## 3. Come funziona — architettura semplificata\n\n```\n ┌─────────────────────────────────────────┐\n │ FASE DI INDICIZZAZIONE │\n │ (si fa una volta sola) │\n └─────────────────────────────────────────┘\n\nPreventivi/\n├── scheda_prodotto.pdf ────────────────────────────────────────────────┐\n├── offerta_facciata.pdf ───────────────────────────────────────────── │\n├── preventivo_fornitore.pdf ────────────────────────────────────────── │\n└── ... altri PDF ... │\n ▼\n ┌────────────────────────────────┐\n │ PIPELINE INGESTION │\n │ │\n │ Passaggio 1 — TESTO │\n │ pymupdf estrae il testo │\n │ grezzo da ogni pagina │\n │ │\n │ Passaggio 2 — VISIONE │\n │ Ogni pagina viene │\n │ \"fotografata\" e mandata a │\n │ Gemini 2.5 Flash che la │\n │ descrive in italiano │\n └─────────────┬──────────────────┘\n │\n ▼\n ┌────────────────────────────────┐\n │ gemini-embedding-2-preview │\n │ │\n │ Ogni pezzo di testo diventa │\n │ un vettore di 3072 numeri │\n └─────────────┬──────────────────┘\n │\n ▼\n ┌────────────────────────────────┐\n │ ChromaDB │\n │ (database vettoriale) │\n │ salvato in chroma_db/ │\n └────────────────────────────────┘\n\n\n ┌─────────────────────────────────────────┐\n │ FASE DI RISPOSTA │\n │ (ogni volta che fai domande) │\n └─────────────────────────────────────────┘\n\nTu: \"Qual è il prezzo del prodotto X nel preventivo?\"\n │\n ▼\n┌─────────────────────┐ ┌──────────────────────────────────────────┐\n│ La domanda diventa │ │ ChromaDB cerca i 6 chunk più simili │\n│ anche lei un vettore│──────► │ per significato alla domanda │\n│ di 3072 numeri │ │ (\"prodotto X\", \"prezzo\", \"preventivo\") │\n└─────────────────────┘ └───────────────────┬──────────────────────┘\n │\n │ Restituisce i chunk più rilevanti:\n │ \"Codice prodotto X, dimensioni...\"\n │ \"Prezzo: € XXX,XX IVA esclusa...\"\n │ ...\n ▼\n ┌───────────────────────────────┐\n │ DeepSeek Chat │\n │ │\n │ Riceve: │\n │ - I chunk trovati │\n │ - La tua domanda │\n │ - Le istruzioni del progetto │\n │ │\n │ Risponde in italiano, │\n │ citando la fonte │\n └───────────────────────────────┘\n │\n ▼\n \"Il prodotto X costa € XXX,XX IVA esclusa.\n Fonte: Fornitore A (preventivo_fornitore_a.pdf)\"\n```\n\n---\n\n## 4. I componenti del sistema\n\n| Componente | Ruolo | Tecnologia |\n|------------|-------|------------|\n| **Estrazione testo** | Legge il testo dai PDF | `pymupdf` |\n| **Visione pagine** | \"Guarda\" le immagini e le descrive | `Gemini 2.5 Flash` via `google.genai` |\n| **Embedding** | Trasforma testi in numeri | `gemini-embedding-2-preview` (3072 dim.) |\n| **Database vettoriale** | Archivia e cerca per similarità | `ChromaDB` (locale, nessun server) |\n| **Knowledge base** | Collega embedding e ricerca | `Agno` framework |\n| **Modello di chat** | Genera la risposta finale | `DeepSeek Chat` |\n| **CLI** | Interfaccia utente a riga di comando | Python `argparse` + `input()` |\n| **Deduplicazione** | Evita di re-indicizzare PDF già visti | SHA-256 hash + `indexed.json` |\n\n---\n\n## 5. Come è strutturato il progetto\n\n```\nC:\\Progetti Pilota\\EsploraPreventivi\\\n│\n├── Preventivi\\ ← I tuoi PDF (metti qui i nuovi)\n│ ├── scheda_prodotto.pdf\n│ ├── preventivo_fornitore.pdf\n│ └── ...\n│\n├── rag_preventivi\\ ← Il codice del sistema\n│ ├── config.py ← Impostazioni (modelli, soglie, path)\n│ ├── knowledge.py ← Configura ChromaDB + GeminiEmbedder\n│ ├── agent.py ← Crea l'agente DeepSeek\n│ ├── main.py ← Punto di ingresso (lo esegui tu)\n│ │\n│ ├── ingestion\\ ← Pipeline di indicizzazione\n│ │ ├── text_extractor.py ← Estrae testo con pymupdf\n│ │ ├── image_extractor.py ← Visione pagine con Gemini\n│ │ └── pipeline.py ← Orchestrazione + deduplicazione\n│ │\n│ └── tests\\ ← Test automatici (14 test)\n│ ├── conftest.py\n│ ├── test_text_extractor.py\n│ ├── test_image_extractor.py\n│ └── test_pipeline.py\n│\n├── chroma_db\\ ← Database vettoriale (auto-creato)\n├── indexed.json ← Registro dei PDF già indicizzati\n├── .env ← Le tue chiavi API (non condividere!)\n├── .gitignore ← Protegge .env e chroma_db da git\n└── README.md ← Questo file\n```\n\n---\n\n## 6. Prima installazione — passo passo\n\n### Prerequisiti\n\n- Python 3.11 o superiore installato\n- Connessione internet (per le API)\n- Le chiavi API (vedi sotto)\n\n### Passo 1 — Ottieni le chiavi API\n\nHai bisogno di due chiavi:\n\n**Google API Key** (per Gemini Vision + Embedding):\n1. Vai su [aistudio.google.com](https://aistudio.google.com)\n2. Clicca su \"Get API Key\"\n3. Copia la chiave (inizia con `AIza...`)\n\n**DeepSeek API Key** (per il modello di chat):\n1. Vai su [platform.deepseek.com](https://platform.deepseek.com/api_keys)\n2. Crea un account e genera una chiave API\n3. Copia la chiave\n\n### Passo 2 — Crea il file .env\n\nNella cartella `C:\\Progetti Pilota\\EsploraPreventivi\\` crea un file chiamato `.env`\n(senza nome, solo estensione) con questo contenuto:\n\n```\nGOOGLE_API_KEY=AIzaSy...la_tua_chiave...\nDEEPSEEK_API_KEY=sk-...la_tua_chiave...\n```\n\n> **Importante**: non condividere mai questo file. È già protetto da `.gitignore`\n> quindi non viene mai caricato su git per errore.\n\n### Passo 3 — Installa le dipendenze\n\nApri un terminale nella cartella del progetto e digita:\n\n```bash\npip install -r rag_preventivi/requirements.txt\n```\n\nQuesto installa: agno, chromadb, pymupdf, google-generativeai, python-dotenv, Pillow, pytest.\n\n### Passo 4 — Verifica l'installazione\n\n```bash\ncd rag_preventivi\npython -c \"from knowledge import build_knowledge; kb, vdb = build_knowledge(); print('OK!')\"\n```\n\nSe stampa `OK!` senza errori, sei pronto.\n\n---\n\n## 7. Indicizzare i PDF\n\nL'indicizzazione è il processo che legge i PDF, li analizza e li salva nel database\nvettoriale. Si fa **una volta sola** per ogni set di documenti. I documenti già\nindicizzati vengono automaticamente saltati alle esecuzioni successive.\n\n### Comando base\n\n```bash\ncd \"C:\\Progetti Pilota\\EsploraPreventivi\\rag_preventivi\"\npython main.py --ingest-only\n```\n\n**Cosa succede:**\n\n```\n[INGEST] scheda_prodotto.pdf\n → 8 chunk testo\n → 2 chunk vision\n ✓ scheda_prodotto.pdf indicizzato\n\n[INGEST] preventivo_fornitore.pdf\n → 20 chunk testo\n → 5 chunk vision\n ✓ preventivo_fornitore.pdf indicizzato\n\n[SKIP] offerta_insegne.pdf già indicizzato ← PDF non modificato, viene saltato\n...\nIngestion completata.\n```\n\n**Quanto ci vuole?**\nPer una decina di PDF: circa 3-5 minuti.\nIl sistema processa ~1 pagina al secondo (limite di sicurezza dell'API Gemini).\n\n### Re-indicizzare tutto da zero\n\nSe vuoi rielaborare tutti i PDF (ad esempio dopo aver cambiato il modello di embedding):\n\n```bash\npython main.py --reindex --ingest-only\n```\n\n> **Attenzione**: `--reindex` cancella e ricostruisce tutti gli embedding.\n> I PDF non vengono toccati, solo il database vettoriale.\n\n### Cosa analizza il sistema per ogni pagina\n\nPer ogni pagina di ogni PDF, il sistema fa **due passaggi**:\n\n1. **Testo**: estrae il testo grezzo con `pymupdf`. Veloce, gratuito, funziona bene\n per PDF con testo selezionabile.\n\n2. **Visione**: rasterizza la pagina come immagine (150 DPI) e la invia a\n `Gemini 2.5 Flash` che la \"guarda\" e produce una descrizione in italiano.\n Questo recupera informazioni da tabelle, schemi, loghi, elementi grafici\n che il testo grezzo non cattura.\n\n> **Se un PDF è solo immagine** (scansionato, senza testo selezionabile),\n> il sistema salta il passaggio testo e usa solo la visione.\n\n---\n\n## 8. Fare domande all'agente\n\n### Avviare la chat\n\n```bash\ncd \"C:\\Progetti Pilota\\EsploraPreventivi\\rag_preventivi\"\npython main.py\n```\n\nVedrai:\n\n```\n=== Agente RAG Preventivi ===\nDigita 'exit' per uscire.\n\nTu: _\n```\n\nDigita la tua domanda in italiano e premi Invio.\n\n### Esempi di domande\n\n**Prezzi e quantità:**\n```\nTu: Qual è il prezzo del prodotto X?\nTu: Qual è il totale del preventivo del fornitore Y?\nTu: Elenca tutti i prodotti con il loro prezzo\n```\n\n**Fornitori:**\n```\nTu: Chi sono i fornitori del progetto?\nTu: Chi fornisce le insegne luminose?\nTu: Quali sono i contatti del fornitore X?\n```\n\n**Elementi tecnici:**\n```\nTu: Che sistema di facciata continua è previsto?\nTu: Dimensioni del prodotto X dalla scheda tecnica\nTu: Com'è descritto il prospetto principale?\n```\n\n**Confronti e analisi:**\n```\nTu: Quale preventivo ha il totale più alto?\nTu: Ci sono informazioni sulle tempistiche di consegna?\n```\n\n### Come leggere la risposta\n\nL'agente cita sempre la fonte:\n\n```\nIl prodotto X ha un prezzo di **€ XXX,XX IVA esclusa**.\n\nFonte: Fornitore A, preventivo_fornitore_a.pdf\n\n---\n\nSono disponibili anche le taglie:\n- Taglia S: dimensioni, peso → € [prezzo]\n- Taglia L: dimensioni, peso → € [prezzo]\n(da analisi immagine)\n```\n\n> La dicitura **\"(da analisi immagine)\"** indica che l'informazione viene\n> da un chunk visione — l'AI ha visto la pagina come immagine, non come testo.\n\n### Uscire dalla chat\n\nDigita `exit`, `quit` oppure premi `Ctrl+C`.\n\n---\n\n## 9. Aggiungere nuovi documenti\n\n### Aggiungere un PDF\n\n1. Copia il PDF nella cartella `Preventivi\\`\n2. Esegui l'indicizzazione:\n\n```bash\ncd rag_preventivi\npython main.py --ingest-only\n```\n\nIl sistema rileva automaticamente il nuovo file (hash SHA-256 non presente\nin `indexed.json`) e lo indicizza. I file già indicizzati vengono saltati.\n\n### Aggiornare un PDF esistente\n\nSe modifichi un PDF già indicizzato:\n\n1. Sovrascrivi il file nella cartella `Preventivi\\`\n2. Esegui `python main.py --ingest-only`\n\nIl sistema rileva che l'hash è cambiato e re-indicizza automaticamente solo\nquel file.\n\n### Formati supportati\n\n| Formato | Supportato | Note |\n|---------|-----------|------|\n| `.pdf` con testo selezionabile | ✅ | Testo + visione |\n| `.pdf` scansionato (solo immagine) | ✅ | Solo visione (Gemini) |\n| `.pdf` con encoding corrotto | ✅ | Salta testo, usa solo visione |\n| `.xlsx` / `.xls` (Excel) | ❌ | Non ancora supportato |\n| `.docx` (Word) | ❌ | Non ancora supportato |\n\n---\n\n## 10. Domande frequenti\n\n### \"L'agente ha risposto una cosa sbagliata\"\n\nPuò succedere se l'informazione non è nei documenti indicizzati, o se è in una\nparte del documento non estratta correttamente. Prova a:\n- Riformulare la domanda con termini più vicini a quelli nel documento\n- Verificare manualmente nel PDF di origine\n- Se l'informazione è in una tabella o grafico complesso, potrebbe non essere\n stata estratta correttamente dalla visione\n\n### \"Ho ricevuto un errore 429\"\n\nSignifica che hai superato il limite di chiamate all'API Gemini.\nIl sistema ha già un rate limiting interno (1 richiesta/secondo).\nSe persiste, attendi qualche minuto e riprova.\n\n### \"Il database vettoriale sembra corrotto\"\n\nElimina la cartella `chroma_db\\` e il file `indexed.json`, poi esegui:\n```bash\npython main.py --reindex --ingest-only\n```\n\n### \"Voglio cambiare il modello di chat\"\n\nModifica `agent.py`, riga con `DeepSeek(id=\"deepseek-chat\")`.\nAgno supporta anche: OpenAI GPT-4, Anthropic Claude, Google Gemini, ecc.\n\n### \"Come aggiungo Excel o Word?\"\n\nBisogna aggiungere un nuovo estrattore in `ingestion/`. Per Excel con `openpyxl`:\n- Crea `ingestion/excel_extractor.py`\n- Estendi il glob in `pipeline.py` da `*.pdf` a `*.pdf` + `*.xlsx`\n\n---\n\n## Riepilogo dei comandi\n\n```bash\n# Prima installazione\npip install -r rag_preventivi/requirements.txt\n\n# Indicizzare i PDF (prima volta o nuovi PDF)\npython rag_preventivi/main.py --ingest-only\n\n# Re-indicizzare tutto da zero\npython rag_preventivi/main.py --reindex --ingest-only\n\n# Avviare la chat\npython rag_preventivi/main.py\n\n# Eseguire i test automatici\ncd rag_preventivi && python -m pytest tests/ -v\n```\n\n---\n\n---\n\n## Roadmap\n\n| Priorità | Feature | Note |\n|----------|---------|------|\n| 🔜 | **Integrazione database SQL** | Collegare l'agente a un DB relazionale (es. storico ordini, anagrafica fornitori). L'agente potrà rispondere a domande che combinano i PDF con dati strutturati. |\n| 🔜 | **Supporto Excel (.xlsx)** | Aggiungere `excel_extractor.py` con `openpyxl` per indicizzare anche i fogli di calcolo. |\n| 💡 | **Supporto Word (.docx)** | Estendere la pipeline con `python-docx`. |\n| 💡 | **Interfaccia web** | Sostituire la CLI con una UI (es. Gradio o Streamlit) per utenti non tecnici. |\n| 💡 | **Multimodal embedding diretto** | Embeddare le immagini delle pagine direttamente con `gemini-embedding-2-preview` senza passare per la descrizione testuale, per retrieval ancora più accurato. |\n\n---\n\n*Stack: Python · Agno · DeepSeek · Gemini · ChromaDB · pymupdf*\n"}, "c0f96ed84bcaf34aaf315cb106349d35a2df8ccb": {"t": 1785766200.0697722, "n": 19177, "h": "46baa42e9f0627ca", "c": "# Agente RAG per i Preventivi — Guida Completa\n\n> Questa guida è pensata per chi non ha mai sentito parlare di RAG o di AI applicata ai documenti.\n> La leggi dall'inizio alla fine e alla fine sai usare il sistema.\n\n---\n\n## Indice\n\n1. [Cos'è un RAG? (spiegazione per neofiti)](#1-cosè-un-rag-spiegazione-per-neofiti)\n2. [Il problema che risolve questo progetto](#2-il-problema-che-risolve-questo-progetto)\n3. [Come funziona — architettura semplificata](#3-come-funziona--architettura-semplificata)\n4. [I componenti del sistema](#4-i-componenti-del-sistema)\n5. [Come è strutturato il progetto](#5-come-è-strutturato-il-progetto)\n6. [Prima installazione — passo passo](#6-prima-installazione--passo-passo)\n7. [Indicizzare i PDF](#7-indicizzare-i-pdf)\n8. [Fare domande all'agente](#8-fare-domande-allagente)\n9. [Aggiungere nuovi documenti](#9-aggiungere-nuovi-documenti)\n10. [Domande frequenti](#10-domande-frequenti)\n\n---\n\n## 1. Cos'è un RAG? (spiegazione per neofiti)\n\n**RAG** sta per **Retrieval-Augmented Generation**, che in italiano possiamo chiamare\n\"generazione aumentata dal recupero\". È una tecnica per far rispondere un'AI a domande\nsu documenti reali, senza \"inventare\" risposte.\n\n### Il problema delle AI classiche\n\nUn modello di linguaggio come ChatGPT è addestrato su miliardi di testi trovati su internet.\nSa moltissime cose di carattere generale. Ma **non conosce i tuoi documenti** —\ni tuoi preventivi, le tue offerte, i tuoi schemi tecnici.\n\nSe gli chiedi \"Qual è il prezzo della fioriera ONICE nel preventivo Metalco?\",\nnon sa rispondere. O peggio: inventa una risposta plausibile ma sbagliata.\nQuesto si chiama **allucinazione**.\n\n### Come funziona il RAG\n\nIl RAG risolve il problema in due passaggi:\n\n```\n[FASE 1 — INDICIZZAZIONE]\n\nI tuoi PDF ──► Estrai il testo ──► Dividi in piccoli pezzi (chunk)\n ──► Converti ogni pezzo in un numero (embedding)\n ──► Salva in un database vettoriale\n\n[FASE 2 — RISPOSTA]\n\nLa tua domanda ──► Converti anche lei in un numero (embedding)\n ──► Trova i pezzi più \"vicini\" per significato\n ──► Passa quei pezzi + la domanda all'AI\n ──► L'AI risponde basandosi SOLO su quei pezzi\n```\n\n### Il trucco degli \"embedding\"\n\nUn **embedding** è la traduzione di un testo in un array di numeri (un vettore).\nTesti con significato simile producono numeri simili. Quindi:\n\n- \"Prezzo fioriera grande\" e \"costo vaso esterno Ø1200\" producono numeri vicini\n- \"Preventivo facciata nord\" e \"offerta insegne fronte edificio\" producono numeri vicini\n\nQuesto permette di trovare documenti pertinenti anche se non usano le stesse parole\nesatte della domanda.\n\n---\n\n## 2. Il problema che risolve questo progetto\n\nIl progetto **Centro Commerciale Leonardo di Imola** (cliente IABGroup) ha accumulato\ndiversi preventivi da fornitori diversi, in formati diversi:\n\n| Documento | Contenuto |\n|-----------|-----------|\n| `ONICE.pdf` | Scheda prodotto fioriere Metalco con prezzi e dimensioni |\n| `PROT.31 IAB SOC.COOP._130125.pdf` | Preventivo Metalco per forniture |\n| `prev (2).pdf` | Preventivo Adriatica Neon — insegne (NORD, OVEST, SUD) |\n| `24-0546.pdf` | Offerta Metro Infissi — facciata continua Schüco |\n| `0475.pdf` | Adria System — contropareti e lavori edili |\n| `LEONARDO_IMOLA Prospetto NORD/OVEST/SUD.pdf` | Disegni tecnici facciata |\n| `prev.pdf` | Offerta verde/giardino |\n\nSenza il sistema, per rispondere a \"Qual è il totale delle insegne per il prospetto Nord?\"\nbisognava aprire ogni PDF, cercare manualmente, fare calcoli.\n\n**Con il sistema**, si scrive la domanda in italiano e si ottiene la risposta in pochi secondi,\ncon indicato il documento sorgente.\n\n---\n\n## 3. Come funziona — architettura semplificata\n\n```\n ┌─────────────────────────────────────────┐\n │ FASE DI INDICIZZAZIONE │\n │ (si fa una volta sola) │\n └─────────────────────────────────────────┘\n\nPreventivi/\n├── ONICE.pdf ──────────────────────────────────────────────────────────┐\n├── 24-0546.pdf ─────────────────────────────────────────────────────── │\n├── prev (2).pdf ──────────────────────────────────────────────────── ──│\n└── ... altri PDF ... │\n ▼\n ┌────────────────────────────────┐\n │ PIPELINE INGESTION │\n │ │\n │ Passaggio 1 — TESTO │\n │ pymupdf estrae il testo │\n │ grezzo da ogni pagina │\n │ │\n │ Passaggio 2 — VISIONE │\n │ Ogni pagina viene │\n │ \"fotografata\" e mandata a │\n │ Gemini 2.5 Flash che la │\n │ descrive in italiano │\n └─────────────┬──────────────────┘\n │\n ▼\n ┌────────────────────────────────┐\n │ gemini-embedding-2-preview │\n │ │\n │ Ogni pezzo di testo diventa │\n │ un vettore di 3072 numeri │\n └─────────────┬──────────────────┘\n │\n ▼\n ┌────────────────────────────────┐\n │ ChromaDB │\n │ (database vettoriale) │\n │ salvato in chroma_db/ │\n └────────────────────────────────┘\n\n\n ┌─────────────────────────────────────────┐\n │ FASE DI RISPOSTA │\n │ (ogni volta che fai domande) │\n └─────────────────────────────────────────┘\n\nTu: \"Qual è il prezzo della fioriera ONICE Ø1200?\"\n │\n ▼\n┌─────────────────────┐ ┌──────────────────────────────────────────┐\n│ La domanda diventa │ │ ChromaDB cerca i 6 chunk più simili │\n│ anche lei un vettore│──────► │ per significato alla domanda │\n│ di 3072 numeri │ │ (\"ONICE\", \"Ø1200\", \"prezzo\", \"fioriera\")│\n└─────────────────────┘ └───────────────────┬──────────────────────┘\n │\n │ Restituisce i chunk più rilevanti:\n │ \"0300720 ONICE fioriera D=1200...\"\n │ \"Prezzo: € 746,20 IVA esclusa...\"\n │ ...\n ▼\n ┌───────────────────────────────┐\n │ DeepSeek Chat │\n │ │\n │ Riceve: │\n │ - I chunk trovati │\n │ - La tua domanda │\n │ - Le istruzioni del progetto │\n │ │\n │ Risponde in italiano, │\n │ citando la fonte │\n └───────────────────────────────┘\n │\n ▼\n \"La fioriera ONICE Ø1200 costa\n € 746,20 IVA esclusa.\n Fonte: Metalco SRL (PROT.31...)\"\n```\n\n---\n\n## 4. I componenti del sistema\n\n| Componente | Ruolo | Tecnologia |\n|------------|-------|------------|\n| **Estrazione testo** | Legge il testo dai PDF | `pymupdf` |\n| **Visione pagine** | \"Guarda\" le immagini e le descrive | `Gemini 2.5 Flash` via `google.genai` |\n| **Embedding** | Trasforma testi in numeri | `gemini-embedding-2-preview` (3072 dim.) |\n| **Database vettoriale** | Archivia e cerca per similarità | `ChromaDB` (locale, nessun server) |\n| **Knowledge base** | Collega embedding e ricerca | `Agno` framework |\n| **Modello di chat** | Genera la risposta finale | `DeepSeek Chat` |\n| **CLI** | Interfaccia utente a riga di comando | Python `argparse` + `input()` |\n| **Deduplicazione** | Evita di re-indicizzare PDF già visti | SHA-256 hash + `indexed.json` |\n\n---\n\n## 5. Come è strutturato il progetto\n\n```\nC:\\Progetti Pilota\\EsploraPreventivi\\\n│\n├── Preventivi\\ ← I tuoi PDF (metti qui i nuovi)\n│ ├── ONICE.pdf\n│ ├── 24-0546.pdf\n│ └── ...\n│\n├── rag_preventivi\\ ← Il codice del sistema\n│ ├── config.py ← Impostazioni (modelli, soglie, path)\n│ ├── knowledge.py ← Configura ChromaDB + GeminiEmbedder\n│ ├── agent.py ← Crea l'agente DeepSeek\n│ ├── main.py ← Punto di ingresso (lo esegui tu)\n│ │\n│ ├── ingestion\\ ← Pipeline di indicizzazione\n│ │ ├── text_extractor.py ← Estrae testo con pymupdf\n│ │ ├── image_extractor.py ← Visione pagine con Gemini\n│ │ └── pipeline.py ← Orchestrazione + deduplicazione\n│ │\n│ └── tests\\ ← Test automatici (14 test)\n│ ├── conftest.py\n│ ├── test_text_extractor.py\n│ ├── test_image_extractor.py\n│ └── test_pipeline.py\n│\n├── chroma_db\\ ← Database vettoriale (auto-creato)\n├── indexed.json ← Registro dei PDF già indicizzati\n├── .env ← Le tue chiavi API (non condividere!)\n├── .gitignore ← Protegge .env e chroma_db da git\n└── README.md ← Questo file\n```\n\n---\n\n## 6. Prima installazione — passo passo\n\n### Prerequisiti\n\n- Python 3.11 o superiore installato\n- Connessione internet (per le API)\n- Le chiavi API (vedi sotto)\n\n### Passo 1 — Ottieni le chiavi API\n\nHai bisogno di due chiavi:\n\n**Google API Key** (per Gemini Vision + Embedding):\n1. Vai su [aistudio.google.com](https://aistudio.google.com)\n2. Clicca su \"Get API Key\"\n3. Copia la chiave (inizia con `AIza...`)\n\n**DeepSeek API Key** (per il modello di chat):\n1. Vai su [platform.deepseek.com](https://platform.deepseek.com/api_keys)\n2. Crea un account e genera una chiave API\n3. Copia la chiave\n\n### Passo 2 — Crea il file .env\n\nNella cartella `C:\\Progetti Pilota\\EsploraPreventivi\\` crea un file chiamato `.env`\n(senza nome, solo estensione) con questo contenuto:\n\n```\nGOOGLE_API_KEY=AIzaSy...la_tua_chiave...\nDEEPSEEK_API_KEY=sk-...la_tua_chiave...\n```\n\n> **Importante**: non condividere mai questo file. È già protetto da `.gitignore`\n> quindi non viene mai caricato su git per errore.\n\n### Passo 3 — Installa le dipendenze\n\nApri un terminale nella cartella del progetto e digita:\n\n```bash\npip install -r rag_preventivi/requirements.txt\n```\n\nQuesto installa: agno, chromadb, pymupdf, google-generativeai, python-dotenv, Pillow, pytest.\n\n### Passo 4 — Verifica l'installazione\n\n```bash\ncd rag_preventivi\npython -c \"from knowledge import build_knowledge; kb, vdb = build_knowledge(); print('OK!')\"\n```\n\nSe stampa `OK!` senza errori, sei pronto.\n\n---\n\n## 7. Indicizzare i PDF\n\nL'indicizzazione è il processo che legge i PDF, li analizza e li salva nel database\nvettoriale. Si fa **una volta sola** per ogni set di documenti. I documenti già\nindicizzati vengono automaticamente saltati alle esecuzioni successive.\n\n### Comando base\n\n```bash\ncd \"C:\\Progetti Pilota\\EsploraPreventivi\\rag_preventivi\"\npython main.py --ingest-only\n```\n\n**Cosa succede:**\n\n```\n[INGEST] ONICE.pdf\n → 8 chunk testo\n → 2 chunk vision\n ✓ ONICE.pdf indicizzato\n\n[INGEST] 24-0546.pdf\n → 20 chunk testo\n → 5 chunk vision\n ✓ 24-0546.pdf indicizzato\n\n[SKIP] prev (2).pdf già indicizzato ← PDF non modificato, viene saltato\n...\nIngestion completata.\n```\n\n**Quanto ci vuole?**\nPer 9 PDF (come nel progetto attuale): circa 3-5 minuti.\nIl sistema processa ~1 pagina al secondo (limite di sicurezza dell'API Gemini).\n\n### Re-indicizzare tutto da zero\n\nSe vuoi rielaborare tutti i PDF (ad esempio dopo aver cambiato il modello di embedding):\n\n```bash\npython main.py --reindex --ingest-only\n```\n\n> **Attenzione**: `--reindex` cancella e ricostruisce tutti gli embedding.\n> I PDF non vengono toccati, solo il database vettoriale.\n\n### Cosa analizza il sistema per ogni pagina\n\nPer ogni pagina di ogni PDF, il sistema fa **due passaggi**:\n\n1. **Testo**: estrae il testo grezzo con `pymupdf`. Veloce, gratuito, funziona bene\n per PDF con testo selezionabile.\n\n2. **Visione**: rasterizza la pagina come immagine (150 DPI) e la invia a\n `Gemini 2.5 Flash` che la \"guarda\" e produce una descrizione in italiano.\n Questo recupera informazioni da tabelle, schemi, loghi, elementi grafici\n che il testo grezzo non cattura.\n\n> **Se un PDF è solo immagine** (scansionato, senza testo selezionabile),\n> il sistema salta il passaggio testo e usa solo la visione.\n\n---\n\n## 8. Fare domande all'agente\n\n### Avviare la chat\n\n```bash\ncd \"C:\\Progetti Pilota\\EsploraPreventivi\\rag_preventivi\"\npython main.py\n```\n\nVedrai:\n\n```\n=== Agente RAG Preventivi Leonardo ===\nDigita 'exit' per uscire.\n\nTu: _\n```\n\nDigita la tua domanda in italiano e premi Invio.\n\n### Esempi di domande\n\n**Prezzi e quantità:**\n```\nTu: Qual è il prezzo della fioriera ONICE Ø1200?\nTu: Qual è il totale del preventivo Metro Infissi?\nTu: Elenca tutti i prodotti con il loro prezzo\n```\n\n**Fornitori:**\n```\nTu: Chi sono i fornitori del progetto?\nTu: Chi fornisce le insegne luminose?\nTu: Quali sono i contatti di Adria System?\n```\n\n**Elementi tecnici:**\n```\nTu: Che sistema di facciata continua è previsto?\nTu: Dimensioni della fioriera ONICE dalla scheda tecnica\nTu: Com'è il prospetto Nord del Centro Commerciale?\n```\n\n**Confronti e analisi:**\n```\nTu: Quale preventivo ha il totale più alto?\nTu: Ci sono informazioni sulle tempistiche di consegna?\n```\n\n### Come leggere la risposta\n\nL'agente cita sempre la fonte:\n\n```\nLa fioriera ONICE Ø1200 ha un prezzo di **€ 746,20 IVA esclusa**.\n\nFonte: Metalco SRL, PROT.31 IAB SOC.COOP._130125.pdf\n\n---\n\nSono disponibili anche le taglie:\n- Ø800 mm, H 480 mm, 278 kg → € [prezzo]\n- Ø1500 mm, H 760 mm, 1361 kg → € [prezzo]\n(da analisi immagine)\n```\n\n> La dicitura **\"(da analisi immagine)\"** indica che l'informazione viene\n> da un chunk visione — l'AI ha visto la pagina come immagine, non come testo.\n\n### Uscire dalla chat\n\nDigita `exit`, `quit` oppure premi `Ctrl+C`.\n\n---\n\n## 9. Aggiungere nuovi documenti\n\n### Aggiungere un PDF\n\n1. Copia il PDF nella cartella `Preventivi\\`\n2. Esegui l'indicizzazione:\n\n```bash\ncd rag_preventivi\npython main.py --ingest-only\n```\n\nIl sistema rileva automaticamente il nuovo file (hash SHA-256 non presente\nin `indexed.json`) e lo indicizza. I file già indicizzati vengono saltati.\n\n### Aggiornare un PDF esistente\n\nSe modifichi un PDF già indicizzato:\n\n1. Sovrascrivi il file nella cartella `Preventivi\\`\n2. Esegui `python main.py --ingest-only`\n\nIl sistema rileva che l'hash è cambiato e re-indicizza automaticamente solo\nquel file.\n\n### Formati supportati\n\n| Formato | Supportato | Note |\n|---------|-----------|------|\n| `.pdf` con testo selezionabile | ✅ | Testo + visione |\n| `.pdf` scansionato (solo immagine) | ✅ | Solo visione (Gemini) |\n| `.pdf` con encoding corrotto | ✅ | Salta testo, usa solo visione |\n| `.xlsx` / `.xls` (Excel) | ❌ | Non ancora supportato |\n| `.docx` (Word) | ❌ | Non ancora supportato |\n\n---\n\n## 10. Domande frequenti\n\n### \"L'agente ha risposto una cosa sbagliata\"\n\nPuò succedere se l'informazione non è nei documenti indicizzati, o se è in una\nparte del documento non estratta correttamente. Prova a:\n- Riformulare la domanda con termini più vicini a quelli nel documento\n- Verificare manualmente nel PDF di origine\n- Se l'informazione è in una tabella o grafico complesso, potrebbe non essere\n stata estratta correttamente dalla visione\n\n### \"Ho ricevuto un errore 429\"\n\nSignifica che hai superato il limite di chiamate all'API Gemini.\nIl sistema ha già un rate limiting interno (1 richiesta/secondo).\nSe persiste, attendi qualche minuto e riprova.\n\n### \"Il database vettoriale sembra corrotto\"\n\nElimina la cartella `chroma_db\\` e il file `indexed.json`, poi esegui:\n```bash\npython main.py --reindex --ingest-only\n```\n\n### \"Voglio cambiare il modello di chat\"\n\nModifica `agent.py`, riga con `DeepSeek(id=\"deepseek-chat\")`.\nAgno supporta anche: OpenAI GPT-4, Anthropic Claude, Google Gemini, ecc.\n\n### \"Come aggiungo Excel o Word?\"\n\nBisogna aggiungere un nuovo estrattore in `ingestion/`. Per Excel con `openpyxl`:\n- Crea `ingestion/excel_extractor.py`\n- Estendi il glob in `pipeline.py` da `*.pdf` a `*.pdf` + `*.xlsx`\n\n---\n\n## Riepilogo dei comandi\n\n```bash\n# Prima installazione\npip install -r rag_preventivi/requirements.txt\n\n# Indicizzare i PDF (prima volta o nuovi PDF)\npython rag_preventivi/main.py --ingest-only\n\n# Re-indicizzare tutto da zero\npython rag_preventivi/main.py --reindex --ingest-only\n\n# Avviare la chat\npython rag_preventivi/main.py\n\n# Eseguire i test automatici\ncd rag_preventivi && python -m pytest tests/ -v\n```\n\n---\n\n---\n\n## Roadmap\n\n| Priorità | Feature | Note |\n|----------|---------|------|\n| 🔜 | **Integrazione database SQL** | Collegare l'agente a un DB relazionale (es. storico ordini, anagrafica fornitori). L'agente potrà rispondere a domande che combinano i PDF con dati strutturati. |\n| 🔜 | **Supporto Excel (.xlsx)** | Aggiungere `excel_extractor.py` con `openpyxl` per indicizzare anche i fogli di calcolo. |\n| 💡 | **Supporto Word (.docx)** | Estendere la pipeline con `python-docx`. |\n| 💡 | **Interfaccia web** | Sostituire la CLI con una UI (es. Gradio o Streamlit) per utenti non tecnici. |\n| 💡 | **Multimodal embedding diretto** | Embeddare le immagini delle pagine direttamente con `gemini-embedding-2-preview` senza passare per la descrizione testuale, per retrieval ancora più accurato. |\n\n---\n\n*Progetto: Centro Commerciale Leonardo, Imola (BO) — Cliente IABGroup*\n*Stack: Python · Agno · DeepSeek · Gemini · ChromaDB · pymupdf*\n"}, "d60e377652611e6b1e36f80bfae7dd6575302c38": {"t": 1785766201.8206303, "n": 18380, "h": "72a117ba85c380da", "c": "# Agente RAG per i Preventivi — Guida Completa\n\n> Questa guida è pensata per chi non ha mai sentito parlare di RAG o di AI applicata ai documenti.\n> La leggi dall'inizio alla fine e alla fine sai usare il sistema.\n\n---\n\n## Indice\n\n1. [Cos'è un RAG? (spiegazione per neofiti)](#1-cosè-un-rag-spiegazione-per-neofiti)\n2. [Il problema che risolve questo progetto](#2-il-problema-che-risolve-questo-progetto)\n3. [Come funziona — architettura semplificata](#3-come-funziona--architettura-semplificata)\n4. [I componenti del sistema](#4-i-componenti-del-sistema)\n5. [Come è strutturato il progetto](#5-come-è-strutturato-il-progetto)\n6. [Prima installazione — passo passo](#6-prima-installazione--passo-passo)\n7. [Indicizzare i PDF](#7-indicizzare-i-pdf)\n8. [Fare domande all'agente](#8-fare-domande-allagente)\n9. [Aggiungere nuovi documenti](#9-aggiungere-nuovi-documenti)\n10. [Domande frequenti](#10-domande-frequenti)\n\n---\n\n## 1. Cos'è un RAG? (spiegazione per neofiti)\n\n**RAG** sta per **Retrieval-Augmented Generation**, che in italiano possiamo chiamare\n\"generazione aumentata dal recupero\". È una tecnica per far rispondere un'AI a domande\nsu documenti reali, senza \"inventare\" risposte.\n\n### Il problema delle AI classiche\n\nUn modello di linguaggio come ChatGPT è addestrato su miliardi di testi trovati su internet.\nSa moltissime cose di carattere generale. Ma **non conosce i tuoi documenti** —\ni tuoi preventivi, le tue offerte, i tuoi schemi tecnici.\n\nSe gli chiedi \"Qual è il prezzo della fioriera ONICE nel preventivo Metalco?\",\nnon sa rispondere. O peggio: inventa una risposta plausibile ma sbagliata.\nQuesto si chiama **allucinazione**.\n\n### Come funziona il RAG\n\nIl RAG risolve il problema in due passaggi:\n\n```\n[FASE 1 — INDICIZZAZIONE]\n\nI tuoi PDF ──► Estrai il testo ──► Dividi in piccoli pezzi (chunk)\n ──► Converti ogni pezzo in un numero (embedding)\n ──► Salva in un database vettoriale\n\n[FASE 2 — RISPOSTA]\n\nLa tua domanda ──► Converti anche lei in un numero (embedding)\n ──► Trova i pezzi più \"vicini\" per significato\n ──► Passa quei pezzi + la domanda all'AI\n ──► L'AI risponde basandosi SOLO su quei pezzi\n```\n\n### Il trucco degli \"embedding\"\n\nUn **embedding** è la traduzione di un testo in un array di numeri (un vettore).\nTesti con significato simile producono numeri simili. Quindi:\n\n- \"Prezzo fioriera grande\" e \"costo vaso esterno Ø1200\" producono numeri vicini\n- \"Preventivo facciata nord\" e \"offerta insegne fronte edificio\" producono numeri vicini\n\nQuesto permette di trovare documenti pertinenti anche se non usano le stesse parole\nesatte della domanda.\n\n---\n\n## 2. Il problema che risolve questo progetto\n\nIl progetto **Centro Commerciale Leonardo di Imola** (cliente IABGroup) ha accumulato\ndiversi preventivi da fornitori diversi, in formati diversi:\n\n| Documento | Contenuto |\n|-----------|-----------|\n| `ONICE.pdf` | Scheda prodotto fioriere Metalco con prezzi e dimensioni |\n| `PROT.31 IAB SOC.COOP._130125.pdf` | Preventivo Metalco per forniture |\n| `prev (2).pdf` | Preventivo Adriatica Neon — insegne (NORD, OVEST, SUD) |\n| `24-0546.pdf` | Offerta Metro Infissi — facciata continua Schüco |\n| `0475.pdf` | Adria System — contropareti e lavori edili |\n| `LEONARDO_IMOLA Prospetto NORD/OVEST/SUD.pdf` | Disegni tecnici facciata |\n| `prev.pdf` | Offerta verde/giardino |\n\nSenza il sistema, per rispondere a \"Qual è il totale delle insegne per il prospetto Nord?\"\nbisognava aprire ogni PDF, cercare manualmente, fare calcoli.\n\n**Con il sistema**, si scrive la domanda in italiano e si ottiene la risposta in pochi secondi,\ncon indicato il documento sorgente.\n\n---\n\n## 3. Come funziona — architettura semplificata\n\n```\n ┌─────────────────────────────────────────┐\n │ FASE DI INDICIZZAZIONE │\n │ (si fa una volta sola) │\n └─────────────────────────────────────────┘\n\nPreventivi/\n├── ONICE.pdf ──────────────────────────────────────────────────────────┐\n├── 24-0546.pdf ─────────────────────────────────────────────────────── │\n├── prev (2).pdf ──────────────────────────────────────────────────── ──│\n└── ... altri PDF ... │\n ▼\n ┌────────────────────────────────┐\n │ PIPELINE INGESTION │\n │ │\n │ Passaggio 1 — TESTO │\n │ pymupdf estrae il testo │\n │ grezzo da ogni pagina │\n │ │\n │ Passaggio 2 — VISIONE │\n │ Ogni pagina viene │\n │ \"fotografata\" e mandata a │\n │ Gemini 2.5 Flash che la │\n │ descrive in italiano │\n └─────────────┬──────────────────┘\n │\n ▼\n ┌────────────────────────────────┐\n │ gemini-embedding-2-preview │\n │ │\n │ Ogni pezzo di testo diventa │\n │ un vettore di 3072 numeri │\n └─────────────┬──────────────────┘\n │\n ▼\n ┌────────────────────────────────┐\n │ ChromaDB │\n │ (database vettoriale) │\n │ salvato in chroma_db/ │\n └────────────────────────────────┘\n\n\n ┌─────────────────────────────────────────┐\n │ FASE DI RISPOSTA │\n │ (ogni volta che fai domande) │\n └─────────────────────────────────────────┘\n\nTu: \"Qual è il prezzo della fioriera ONICE Ø1200?\"\n │\n ▼\n┌─────────────────────┐ ┌──────────────────────────────────────────┐\n│ La domanda diventa │ │ ChromaDB cerca i 6 chunk più simili │\n│ anche lei un vettore│──────► │ per significato alla domanda │\n│ di 3072 numeri │ │ (\"ONICE\", \"Ø1200\", \"prezzo\", \"fioriera\")│\n└─────────────────────┘ └───────────────────┬──────────────────────┘\n │\n │ Restituisce i chunk più rilevanti:\n │ \"0300720 ONICE fioriera D=1200...\"\n │ \"Prezzo: € 746,20 IVA esclusa...\"\n │ ...\n ▼\n ┌───────────────────────────────┐\n │ DeepSeek Chat │\n │ │\n │ Riceve: │\n │ - I chunk trovati │\n │ - La tua domanda │\n │ - Le istruzioni del progetto │\n │ │\n │ Risponde in italiano, │\n │ citando la fonte │\n └───────────────────────────────┘\n │\n ▼\n \"La fioriera ONICE Ø1200 costa\n € 746,20 IVA esclusa.\n Fonte: Metalco SRL (PROT.31...)\"\n```\n\n---\n\n## 4. I componenti del sistema\n\n| Componente | Ruolo | Tecnologia |\n|------------|-------|------------|\n| **Estrazione testo** | Legge il testo dai PDF | `pymupdf` |\n| **Visione pagine** | \"Guarda\" le immagini e le descrive | `Gemini 2.5 Flash` via `google.genai` |\n| **Embedding** | Trasforma testi in numeri | `gemini-embedding-2-preview` (3072 dim.) |\n| **Database vettoriale** | Archivia e cerca per similarità | `ChromaDB` (locale, nessun server) |\n| **Knowledge base** | Collega embedding e ricerca | `Agno` framework |\n| **Modello di chat** | Genera la risposta finale | `DeepSeek Chat` |\n| **CLI** | Interfaccia utente a riga di comando | Python `argparse` + `input()` |\n| **Deduplicazione** | Evita di re-indicizzare PDF già visti | SHA-256 hash + `indexed.json` |\n\n---\n\n## 5. Come è strutturato il progetto\n\n```\nC:\\Progetti Pilota\\EsploraPreventivi\\\n│\n├── Preventivi\\ ← I tuoi PDF (metti qui i nuovi)\n│ ├── ONICE.pdf\n│ ├── 24-0546.pdf\n│ └── ...\n│\n├── rag_preventivi\\ ← Il codice del sistema\n│ ├── config.py ← Impostazioni (modelli, soglie, path)\n│ ├── knowledge.py ← Configura ChromaDB + GeminiEmbedder\n│ ├── agent.py ← Crea l'agente DeepSeek\n│ ├── main.py ← Punto di ingresso (lo esegui tu)\n│ │\n│ ├── ingestion\\ ← Pipeline di indicizzazione\n│ │ ├── text_extractor.py ← Estrae testo con pymupdf\n│ │ ├── image_extractor.py ← Visione pagine con Gemini\n│ │ └── pipeline.py ← Orchestrazione + deduplicazione\n│ │\n│ └── tests\\ ← Test automatici (14 test)\n│ ├── conftest.py\n│ ├── test_text_extractor.py\n│ ├── test_image_extractor.py\n│ └── test_pipeline.py\n│\n├── chroma_db\\ ← Database vettoriale (auto-creato)\n├── indexed.json ← Registro dei PDF già indicizzati\n├── .env ← Le tue chiavi API (non condividere!)\n├── .gitignore ← Protegge .env e chroma_db da git\n└── README.md ← Questo file\n```\n\n---\n\n## 6. Prima installazione — passo passo\n\n### Prerequisiti\n\n- Python 3.11 o superiore installato\n- Connessione internet (per le API)\n- Le chiavi API (vedi sotto)\n\n### Passo 1 — Ottieni le chiavi API\n\nHai bisogno di due chiavi:\n\n**Google API Key** (per Gemini Vision + Embedding):\n1. Vai su [aistudio.google.com](https://aistudio.google.com)\n2. Clicca su \"Get API Key\"\n3. Copia la chiave (inizia con `AIza...`)\n\n**DeepSeek API Key** (per il modello di chat):\n1. Vai su [platform.deepseek.com](https://platform.deepseek.com/api_keys)\n2. Crea un account e genera una chiave API\n3. Copia la chiave\n\n### Passo 2 — Crea il file .env\n\nNella cartella `C:\\Progetti Pilota\\EsploraPreventivi\\` crea un file chiamato `.env`\n(senza nome, solo estensione) con questo contenuto:\n\n```\nGOOGLE_API_KEY=AIzaSy...la_tua_chiave...\nDEEPSEEK_API_KEY=sk-...la_tua_chiave...\n```\n\n> **Importante**: non condividere mai questo file. È già protetto da `.gitignore`\n> quindi non viene mai caricato su git per errore.\n\n### Passo 3 — Installa le dipendenze\n\nApri un terminale nella cartella del progetto e digita:\n\n```bash\npip install -r rag_preventivi/requirements.txt\n```\n\nQuesto installa: agno, chromadb, pymupdf, google-generativeai, python-dotenv, Pillow, pytest.\n\n### Passo 4 — Verifica l'installazione\n\n```bash\ncd rag_preventivi\npython -c \"from knowledge import build_knowledge; kb, vdb = build_knowledge(); print('OK!')\"\n```\n\nSe stampa `OK!` senza errori, sei pronto.\n\n---\n\n## 7. Indicizzare i PDF\n\nL'indicizzazione è il processo che legge i PDF, li analizza e li salva nel database\nvettoriale. Si fa **una volta sola** per ogni set di documenti. I documenti già\nindicizzati vengono automaticamente saltati alle esecuzioni successive.\n\n### Comando base\n\n```bash\ncd \"C:\\Progetti Pilota\\EsploraPreventivi\\rag_preventivi\"\npython main.py --ingest-only\n```\n\n**Cosa succede:**\n\n```\n[INGEST] ONICE.pdf\n → 8 chunk testo\n → 2 chunk vision\n ✓ ONICE.pdf indicizzato\n\n[INGEST] 24-0546.pdf\n → 20 chunk testo\n → 5 chunk vision\n ✓ 24-0546.pdf indicizzato\n\n[SKIP] prev (2).pdf già indicizzato ← PDF non modificato, viene saltato\n...\nIngestion completata.\n```\n\n**Quanto ci vuole?**\nPer 9 PDF (come nel progetto attuale): circa 3-5 minuti.\nIl sistema processa ~1 pagina al secondo (limite di sicurezza dell'API Gemini).\n\n### Re-indicizzare tutto da zero\n\nSe vuoi rielaborare tutti i PDF (ad esempio dopo aver cambiato il modello di embedding):\n\n```bash\npython main.py --reindex --ingest-only\n```\n\n> **Attenzione**: `--reindex` cancella e ricostruisce tutti gli embedding.\n> I PDF non vengono toccati, solo il database vettoriale.\n\n### Cosa analizza il sistema per ogni pagina\n\nPer ogni pagina di ogni PDF, il sistema fa **due passaggi**:\n\n1. **Testo**: estrae il testo grezzo con `pymupdf`. Veloce, gratuito, funziona bene\n per PDF con testo selezionabile.\n\n2. **Visione**: rasterizza la pagina come immagine (150 DPI) e la invia a\n `Gemini 2.5 Flash` che la \"guarda\" e produce una descrizione in italiano.\n Questo recupera informazioni da tabelle, schemi, loghi, elementi grafici\n che il testo grezzo non cattura.\n\n> **Se un PDF è solo immagine** (scansionato, senza testo selezionabile),\n> il sistema salta il passaggio testo e usa solo la visione.\n\n---\n\n## 8. Fare domande all'agente\n\n### Avviare la chat\n\n```bash\ncd \"C:\\Progetti Pilota\\EsploraPreventivi\\rag_preventivi\"\npython main.py\n```\n\nVedrai:\n\n```\n=== Agente RAG Preventivi Leonardo ===\nDigita 'exit' per uscire.\n\nTu: _\n```\n\nDigita la tua domanda in italiano e premi Invio.\n\n### Esempi di domande\n\n**Prezzi e quantità:**\n```\nTu: Qual è il prezzo della fioriera ONICE Ø1200?\nTu: Qual è il totale del preventivo Metro Infissi?\nTu: Elenca tutti i prodotti con il loro prezzo\n```\n\n**Fornitori:**\n```\nTu: Chi sono i fornitori del progetto?\nTu: Chi fornisce le insegne luminose?\nTu: Quali sono i contatti di Adria System?\n```\n\n**Elementi tecnici:**\n```\nTu: Che sistema di facciata continua è previsto?\nTu: Dimensioni della fioriera ONICE dalla scheda tecnica\nTu: Com'è il prospetto Nord del Centro Commerciale?\n```\n\n**Confronti e analisi:**\n```\nTu: Quale preventivo ha il totale più alto?\nTu: Ci sono informazioni sulle tempistiche di consegna?\n```\n\n### Come leggere la risposta\n\nL'agente cita sempre la fonte:\n\n```\nLa fioriera ONICE Ø1200 ha un prezzo di **€ 746,20 IVA esclusa**.\n\nFonte: Metalco SRL, PROT.31 IAB SOC.COOP._130125.pdf\n\n---\n\nSono disponibili anche le taglie:\n- Ø800 mm, H 480 mm, 278 kg → € [prezzo]\n- Ø1500 mm, H 760 mm, 1361 kg → € [prezzo]\n(da analisi immagine)\n```\n\n> La dicitura **\"(da analisi immagine)\"** indica che l'informazione viene\n> da un chunk visione — l'AI ha visto la pagina come immagine, non come testo.\n\n### Uscire dalla chat\n\nDigita `exit`, `quit` oppure premi `Ctrl+C`.\n\n---\n\n## 9. Aggiungere nuovi documenti\n\n### Aggiungere un PDF\n\n1. Copia il PDF nella cartella `Preventivi\\`\n2. Esegui l'indicizzazione:\n\n```bash\ncd rag_preventivi\npython main.py --ingest-only\n```\n\nIl sistema rileva automaticamente il nuovo file (hash SHA-256 non presente\nin `indexed.json`) e lo indicizza. I file già indicizzati vengono saltati.\n\n### Aggiornare un PDF esistente\n\nSe modifichi un PDF già indicizzato:\n\n1. Sovrascrivi il file nella cartella `Preventivi\\`\n2. Esegui `python main.py --ingest-only`\n\nIl sistema rileva che l'hash è cambiato e re-indicizza automaticamente solo\nquel file.\n\n### Formati supportati\n\n| Formato | Supportato | Note |\n|---------|-----------|------|\n| `.pdf` con testo selezionabile | ✅ | Testo + visione |\n| `.pdf` scansionato (solo immagine) | ✅ | Solo visione (Gemini) |\n| `.pdf` con encoding corrotto | ✅ | Salta testo, usa solo visione |\n| `.xlsx` / `.xls` (Excel) | ❌ | Non ancora supportato |\n| `.docx` (Word) | ❌ | Non ancora supportato |\n\n---\n\n## 10. Domande frequenti\n\n### \"L'agente ha risposto una cosa sbagliata\"\n\nPuò succedere se l'informazione non è nei documenti indicizzati, o se è in una\nparte del documento non estratta correttamente. Prova a:\n- Riformulare la domanda con termini più vicini a quelli nel documento\n- Verificare manualmente nel PDF di origine\n- Se l'informazione è in una tabella o grafico complesso, potrebbe non essere\n stata estratta correttamente dalla visione\n\n### \"Ho ricevuto un errore 429\"\n\nSignifica che hai superato il limite di chiamate all'API Gemini.\nIl sistema ha già un rate limiting interno (1 richiesta/secondo).\nSe persiste, attendi qualche minuto e riprova.\n\n### \"Il database vettoriale sembra corrotto\"\n\nElimina la cartella `chroma_db\\` e il file `indexed.json`, poi esegui:\n```bash\npython main.py --reindex --ingest-only\n```\n\n### \"Voglio cambiare il modello di chat\"\n\nModifica `agent.py`, riga con `DeepSeek(id=\"deepseek-chat\")`.\nAgno supporta anche: OpenAI GPT-4, Anthropic Claude, Google Gemini, ecc.\n\n### \"Come aggiungo Excel o Word?\"\n\nBisogna aggiungere un nuovo estrattore in `ingestion/`. Per Excel con `openpyxl`:\n- Crea `ingestion/excel_extractor.py`\n- Estendi il glob in `pipeline.py` da `*.pdf` a `*.pdf` + `*.xlsx`\n\n---\n\n## Riepilogo dei comandi\n\n```bash\n# Prima installazione\npip install -r rag_preventivi/requirements.txt\n\n# Indicizzare i PDF (prima volta o nuovi PDF)\npython rag_preventivi/main.py --ingest-only\n\n# Re-indicizzare tutto da zero\npython rag_preventivi/main.py --reindex --ingest-only\n\n# Avviare la chat\npython rag_preventivi/main.py\n\n# Eseguire i test automatici\ncd rag_preventivi && python -m pytest tests/ -v\n```\n\n---\n\n*Progetto: Centro Commerciale Leonardo, Imola (BO) — Cliente IABGroup*\n*Stack: Python · Agno · DeepSeek · Gemini · ChromaDB · pymupdf*\n"}}}}