{"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"}}}}