spinny:~/writing $ less rag-langchain-deep-dive.md
12I Large Language Model (LLM) come GPT-4 e Claude sono straordinariamente potenti, ma soffrono di un limite fondamentale: la loro conoscenza è congelata al momento del training. Non possono accedere ai tuoi documenti interni, al tuo database o a informazioni aggiornate in tempo reale. La **Retrieval-Augmented Generation (RAG)** risolve esattamente questo problema, combinando la potenza generativa degli LLM con la capacità di recuperare informazioni da fonti esterne.34## Il Problema: I Limiti degli LLM56Prima di parlare di RAG, è importante capire perché ne abbiamo bisogno.781. **Conoscenza statica**: Un LLM sa solo quello che ha visto durante il training. Se gli chiedi informazioni su un evento avvenuto dopo il suo cutoff, non può rispondere.92. **Allucinazioni**: Quando un LLM non conosce la risposta, tende a inventarla, generando informazioni plausibili ma completamente false.103. **Nessun accesso a dati privati**: Un LLM generico non ha accesso alla documentazione interna della tua azienda, ai ticket, o al tuo codebase.1112La RAG affronta tutti e tre questi problemi fornendo al modello un **contesto rilevante** recuperato da fonti esterne al momento della query.1314## Cos'è la RAG?1516La Retrieval-Augmented Generation è un'architettura che arricchisce il prompt inviato a un LLM con informazioni recuperate da un knowledge base esterno. Invece di affidarsi esclusivamente alla conoscenza parametrica del modello, la RAG **cerca** prima le informazioni rilevanti e poi le **inietta** nel prompt, permettendo al modello di generare risposte accurate e fondate.1718```mermaid19graph LR20 User["Utente"] -- "Domanda" --> Retriever21 Retriever -- "Cerca documenti\nrilevanti" --> VectorStore["Vector Store"]22 VectorStore -- "Documenti\nrilevanti" --> Retriever23 Retriever -- "Contesto + Domanda" --> LLM24 LLM -- "Risposta\nfondata" --> User25```2627## Come Funziona la RAG in Dettaglio2829L'architettura RAG si compone di due fasi principali: **Indexing** (offline) e **Retrieval + Generation** (online).3031### Fase 1: Indexing (Ingestione dei Documenti)3233La fase di indexing prepara i tuoi documenti per la ricerca semantica. Si compone di quattro passi.3435```mermaid36graph TD37 A["Documenti\n(PDF, HTML, MD, DB)"] --> B["Document Loader"]38 B --> C["Text Splitter"]39 C --> D["Chunks di Testo"]40 D --> E["Embedding Model"]41 E --> F["Vettori Numerici"]42 F --> G["Vector Store\n(ChromaDB, Pinecone, FAISS)"]43```4445#### 1. Document Loading4647I documenti possono provenire da qualsiasi fonte: file PDF, pagine web, database, file Markdown, API. Il **Document Loader** si occupa di leggere questi documenti e convertirli in testo strutturato.4849#### 2. Text Splitting (Chunking)5051Gli LLM hanno una finestra di contesto limitata, e i documenti possono essere molto lunghi. Il **Text Splitter** divide i documenti in frammenti più piccoli chiamati _chunks_. La qualità del chunking è critica: chunk troppo piccoli perdono contesto, chunk troppo grandi diluiscono la rilevanza.5253Le strategie più comuni sono:5455- **Recursive Character Splitting**: Divide il testo ricorsivamente usando separatori come `\n\n`, `\n`, `. `, rispettando la struttura del documento.56- **Semantic Splitting**: Usa gli embedding per trovare i punti di rottura naturali nel testo.57- **Chunk Overlap**: Include una sovrapposizione tra chunk consecutivi per preservare il contesto ai confini.5859#### 3. Embedding6061Ogni chunk viene trasformato in un **vettore numerico** (embedding) tramite un modello di embedding (come `text-embedding-3-small` di OpenAI). Questi vettori catturano il significato semantico del testo: frasi con significati simili avranno vettori vicini nello spazio multidimensionale.6263#### 4. Vector Store6465I vettori vengono salvati in un **Vector Store** (o database vettoriale), come ChromaDB, Pinecone, Weaviate o FAISS. Questo database è ottimizzato per la **ricerca di similarità**: data una query, trova i vettori (e quindi i chunk di testo) più simili.6667### Fase 2: Retrieval + Generation6869Quando l'utente fa una domanda:70711. La domanda viene trasformata in un embedding usando lo stesso modello di embedding.722. Il Vector Store cerca i chunk più simili tramite **ricerca di similarità** (tipicamente cosine similarity o distanza euclidea).733. I chunk recuperati vengono inseriti nel prompt come contesto.744. L'LLM genera la risposta basandosi sul contesto fornito.7576## Costruire una Pipeline RAG con LangChain7778**LangChain** è il framework Python (e JavaScript) più popolare per costruire applicazioni basate su LLM. Offre astrazioni di alto livello per ogni componente della pipeline RAG.7980### Installazione8182```bash83pip install langchain langchain-openai langchain-community chromadb84```8586### Step 1: Caricare i Documenti8788LangChain fornisce decine di Document Loader per diverse fonti dati.8990```python91from langchain_community.document_loaders import (92 PyPDFLoader,93 WebBaseLoader,94 DirectoryLoader,95 TextLoader,96)9798# Caricare un PDF99pdf_loader = PyPDFLoader("docs/manuale.pdf")100pdf_docs = pdf_loader.load()101102# Caricare una pagina web103web_loader = WebBaseLoader("https://docs.example.com/guide")104web_docs = web_loader.load()105106# Caricare tutti i file .md da una directory107dir_loader = DirectoryLoader("./knowledge_base", glob="**/*.md", loader_cls=TextLoader)108md_docs = dir_loader.load()109110all_docs = pdf_docs + web_docs + md_docs111```112113### Step 2: Dividere i Documenti in Chunk114115```python116from langchain.text_splitter import RecursiveCharacterTextSplitter117118text_splitter = RecursiveCharacterTextSplitter(119 chunk_size=1000,120 chunk_overlap=200,121 separators=["\n\n", "\n", ". ", " ", ""],122)123124chunks = text_splitter.split_documents(all_docs)125print(f"Documenti originali: {len(all_docs)}, Chunks: {len(chunks)}")126```127128Il parametro `chunk_overlap` è fondamentale: crea una sovrapposizione tra chunk consecutivi in modo che il contesto non venga perso ai confini.129130### Step 3: Creare gli Embedding e il Vector Store131132```python133from langchain_openai import OpenAIEmbeddings134from langchain_community.vectorstores import Chroma135136embedding_model = OpenAIEmbeddings(model="text-embedding-3-small")137138vectorstore = Chroma.from_documents(139 documents=chunks,140 embedding=embedding_model,141 persist_directory="./chroma_db",142)143```144145### Step 4: Creare il Retriever146147Il retriever è il componente che, data una query, recupera i chunk più rilevanti dal vector store.148149```python150retriever = vectorstore.as_retriever(151 search_type="similarity",152 search_kwargs={"k": 4},153)154155relevant_docs = retriever.invoke("Come funziona l'autenticazione?")156for doc in relevant_docs:157 print(doc.page_content[:200])158 print("---")159```160161### Step 5: Costruire la Chain RAG162163Ora mettiamo tutto insieme con un LLM e un prompt template.164165```python166from langchain_openai import ChatOpenAI167from langchain_core.prompts import ChatPromptTemplate168from langchain_core.runnables import RunnablePassthrough169from langchain_core.output_parsers import StrOutputParser170171llm = ChatOpenAI(model="gpt-4o", temperature=0)172173prompt = ChatPromptTemplate.from_template("""174Rispondi alla domanda basandoti esclusivamente sul contesto fornito.175Se il contesto non contiene informazioni sufficienti, dì che non lo sai.176177Contesto:178{context}179180Domanda: {question}181182Risposta:183""")184185def format_docs(docs):186 return "\n\n".join(doc.page_content for doc in docs)187188rag_chain = (189 {"context": retriever | format_docs, "question": RunnablePassthrough()}190 | prompt191 | llm192 | StrOutputParser()193)194195response = rag_chain.invoke("Come funziona l'autenticazione nel sistema?")196print(response)197```198199## Tecniche RAG Avanzate200201La pipeline di base funziona bene, ma ci sono diverse tecniche per migliorare significativamente la qualità delle risposte.202203### Multi-Query Retrieval204205A volte la query dell'utente è ambigua o non allineata con il linguaggio usato nei documenti. Il **Multi-Query Retriever** genera automaticamente varianti della domanda originale per catturare più prospettive.206207```python208from langchain.retrievers import MultiQueryRetriever209210multi_retriever = MultiQueryRetriever.from_llm(211 retriever=vectorstore.as_retriever(),212 llm=llm,213)214215docs = multi_retriever.invoke("Quali sono le best practice di sicurezza?")216```217218### Contextual Compression219220Non tutto il contenuto di un chunk è rilevante per la query. Il **Contextual Compression Retriever** usa un LLM per estrarre solo le parti pertinenti da ogni chunk recuperato.221222```python223from langchain.retrievers import ContextualCompressionRetriever224from langchain.retrievers.document_compressors import LLMChainExtractor225226compressor = LLMChainExtractor.from_llm(llm)227compression_retriever = ContextualCompressionRetriever(228 base_compressor=compressor,229 base_retriever=retriever,230)231```232233### Hybrid Search234235La ricerca puramente semantica non è sempre ottimale. L'**Hybrid Search** combina la ricerca semantica (embedding) con la ricerca lessicale (BM25, keyword matching) per ottenere risultati migliori.236237```python238from langchain.retrievers import EnsembleRetriever239from langchain_community.retrievers import BM25Retriever240241bm25_retriever = BM25Retriever.from_documents(chunks)242bm25_retriever.k = 4243244semantic_retriever = vectorstore.as_retriever(search_kwargs={"k": 4})245246hybrid_retriever = EnsembleRetriever(247 retrievers=[bm25_retriever, semantic_retriever],248 weights=[0.4, 0.6],249)250```251252### Conversational RAG (con Memoria)253254Per costruire un chatbot RAG che ricordi il contesto della conversazione, è necessario aggiungere una memoria che riformuli le domande dell'utente tenendo conto della cronologia.255256```python257from langchain.chains import create_history_aware_retriever258from langchain_core.prompts import MessagesPlaceholder259260contextualize_prompt = ChatPromptTemplate.from_messages([261 ("system", "Data la cronologia della chat e l'ultima domanda dell'utente, "262 "riformula la domanda in modo che sia comprensibile senza la cronologia."),263 MessagesPlaceholder("chat_history"),264 ("human", "{input}"),265])266267history_aware_retriever = create_history_aware_retriever(268 llm, retriever, contextualize_prompt269)270```271272## Best Practice2732741. **Scegli la giusta dimensione dei chunk**: Sperimenta con diverse dimensioni (500-1500 token). Chunk più piccoli per risposte precise, più grandi per contesto ampio.2752. **Usa metadata nei documenti**: Aggiungi fonte, data, categoria come metadata ai chunk. Questo permette di filtrare i risultati in fase di retrieval.2763. **Valuta la qualità**: Usa framework come [RAGAS](https://docs.ragas.io/) per misurare metriche come _faithfulness_, _relevancy_ e _context precision_.2774. **Gestisci i documenti aggiornati**: Implementa una pipeline di re-ingestione per mantenere il vector store sincronizzato con le fonti dati.2785. **Aggiungi un re-ranker**: Dopo il retrieval iniziale, usa un modello di re-ranking (come Cohere Rerank) per riordinare i risultati in base alla rilevanza reale.279280## Conclusione281282La RAG è diventata l'architettura standard per costruire applicazioni AI che necessitano di accedere a conoscenze specifiche e aggiornate. LangChain semplifica enormemente l'implementazione, fornendo astrazioni per ogni componente della pipeline.283284**Prossimi passi:**285286- **Sperimenta in locale**: Inizia con ChromaDB e pochi documenti per prendere confidenza con la pipeline.287- **Esplora LangSmith**: Usa [LangSmith](https://smith.langchain.com/) per monitorare e debuggare le tue chain in produzione.288- **Prova diversi modelli di embedding**: Confronta modelli come `text-embedding-3-small`, `text-embedding-3-large` e modelli open-source come quelli di Sentence Transformers.289- **Consulta la documentazione**: La [documentazione di LangChain](https://python.langchain.com/docs/) è una risorsa eccellente e in costante aggiornamento.290
:RAG e LangChain: Guida Completa alla Retrieval-Augmented Generationlines 1-290 (END) — press q to close