spinny:~/writing $ less rag-langchain-deep-dive.md
12Duże modele językowe (LLM), takie jak GPT-4 i Claude, są niezwykle potężne, ale cierpią na fundamentalne ograniczenie: ich wiedza jest zamrożona w momencie treningu. Nie mogą uzyskać dostępu do Twoich wewnętrznych dokumentów, bazy danych ani informacji w czasie rzeczywistym. **Generowanie wspomagane wyszukiwaniem (RAG)** rozwiązuje dokładnie ten problem, łącząc generatywną moc LLM ze zdolnością do pobierania informacji ze źródeł zewnętrznych.34## Problem: ograniczenia LLM56Zanim porozmawiamy o RAG, ważne jest zrozumienie, dlaczego go potrzebujemy.781. **Statyczna wiedza**: LLM zna tylko to, co widział podczas treningu. Jeśli zapytasz o wydarzenie, które miało miejsce po jego odcięciu, nie będzie w stanie odpowiedzieć.92. **Halucynacje**: Gdy LLM nie zna odpowiedzi, ma tendencję do jej wymyślania, generując wiarygodne, ale całkowicie fałszywe informacje.103. **Brak dostępu do prywatnych danych**: Ogólny LLM nie ma dostępu do wewnętrznej dokumentacji Twojej firmy, zgłoszeń ani bazy kodu.1112RAG rozwiązuje wszystkie trzy te problemy, dostarczając modelowi **odpowiedni kontekst** pobrany ze źródeł zewnętrznych w momencie zapytania.1314## Czym jest RAG?1516Generowanie wspomagane wyszukiwaniem to architektura, która wzbogaca prompt wysyłany do LLM informacjami pobranymi z zewnętrznej bazy wiedzy. Zamiast polegać wyłącznie na parametrycznej wiedzy modelu, RAG najpierw **wyszukuje** odpowiednie informacje, a następnie **wstrzykuje** je do promptu, umożliwiając modelowi generowanie dokładnych, ugruntowanych odpowiedzi.1718```mermaid19graph LR20 User["User"] -- "Question" --> Retriever21 Retriever -- "Search relevant\ndocuments" --> VectorStore["Vector Store"]22 VectorStore -- "Relevant\ndocuments" --> Retriever23 Retriever -- "Context + Question" --> LLM24 LLM -- "Grounded\nresponse" --> User25```2627## Jak RAG działa w szczegółach2829Architektura RAG składa się z dwóch głównych faz: **Indeksowanie** (offline) i **Wyszukiwanie + Generowanie** (online).3031### Faza 1: Indeksowanie (przetwarzanie dokumentów)3233Faza indeksowania przygotowuje Twoje dokumenty do wyszukiwania semantycznego. Składa się z czterech kroków.3435```mermaid36graph TD37 A["Documents\n(PDF, HTML, MD, DB)"] --> B["Document Loader"]38 B --> C["Text Splitter"]39 C --> D["Text Chunks"]40 D --> E["Embedding Model"]41 E --> F["Numerical Vectors"]42 F --> G["Vector Store\n(ChromaDB, Pinecone, FAISS)"]43```4445#### 1. Ładowanie dokumentów4647Dokumenty mogą pochodzić z dowolnego źródła: pliki PDF, strony internetowe, bazy danych, pliki Markdown, API. **Document Loader** odczytuje te dokumenty i konwertuje je na ustrukturyzowany tekst.4849#### 2. Dzielenie tekstu (Chunking)5051LLM mają ograniczone okno kontekstowe, a dokumenty mogą być bardzo długie. **Text Splitter** dzieli dokumenty na mniejsze fragmenty zwane *chunks*. Jakość chunkingu jest kluczowa: zbyt małe chunks tracą kontekst, podczas gdy zbyt duże rozmywają trafność.5253Najczęstsze strategie to:54- **Rekurencyjne dzielenie znaków**: Rekurencyjnie dzieli tekst za pomocą separatorów takich jak `\n\n`, `\n`, `. `, respektując strukturę dokumentu.55- **Dzielenie semantyczne**: Wykorzystuje embeddings do znajdowania naturalnych punktów podziału w tekście.56- **Nakładanie się chunks**: Zawiera nakładanie się między kolejnymi chunks, aby zachować kontekst na granicach.5758#### 3. Embedding5960Każdy chunk jest przekształcany w **wektor numeryczny** (embedding) za pomocą modelu embedding (takiego jak `text-embedding-3-small` od OpenAI). Te wektory oddają semantyczne znaczenie tekstu: zdania o podobnym znaczeniu będą miały bliskie wektory w przestrzeni wielowymiarowej.6162#### 4. Vector Store6364Wektory są zapisywane w **Vector Store** (lub bazie danych wektorowej), takiej jak ChromaDB, Pinecone, Weaviate lub FAISS. Ta baza danych jest zoptymalizowana pod kątem **wyszukiwania podobieństwa**: dla danego zapytania znajduje najbardziej podobne wektory (a tym samym najbardziej trafne fragmenty tekstu).6566### Faza 2: Wyszukiwanie + Generowanie6768Gdy użytkownik zadaje pytanie:69701. Pytanie jest przekształcane w embedding za pomocą tego samego modelu embedding.712. Vector Store znajduje najbardziej podobne chunks poprzez **wyszukiwanie podobieństwa** (zwykle cosine similarity lub odległość euklidesowa).723. Pobrane chunks są wstawiane do promptu jako kontekst.734. LLM generuje odpowiedź na podstawie dostarczonego kontekstu.7475## Budowanie pipeline RAG z LangChain7677**LangChain** to najpopularniejszy framework Python (i JavaScript) do budowania aplikacji opartych na LLM. Zapewnia abstrakcje wysokiego poziomu dla każdego komponentu pipeline RAG.7879### Instalacja8081```bash82pip install langchain langchain-openai langchain-community chromadb83```8485### Krok 1: Załaduj dokumenty8687LangChain udostępnia dziesiątki Document Loaderów dla różnych źródeł danych.8889```python90from langchain_community.document_loaders import (91 PyPDFLoader,92 WebBaseLoader,93 DirectoryLoader,94 TextLoader,95)9697# Load a PDF98pdf_loader = PyPDFLoader("docs/manual.pdf")99pdf_docs = pdf_loader.load()100101# Load a web page102web_loader = WebBaseLoader("https://docs.example.com/guide")103web_docs = web_loader.load()104105# Load all .md files from a directory106dir_loader = DirectoryLoader("./knowledge_base", glob="**/*.md", loader_cls=TextLoader)107md_docs = dir_loader.load()108109all_docs = pdf_docs + web_docs + md_docs110```111112### Krok 2: Podziel dokumenty na chunks113114```python115from langchain.text_splitter import RecursiveCharacterTextSplitter116117text_splitter = RecursiveCharacterTextSplitter(118 chunk_size=1000,119 chunk_overlap=200,120 separators=["\n\n", "\n", ". ", " ", ""],121)122123chunks = text_splitter.split_documents(all_docs)124print(f"Original documents: {len(all_docs)}, Chunks: {len(chunks)}")125```126127Parametr `chunk_overlap` jest kluczowy: tworzy nakładanie się między kolejnymi chunks, aby kontekst nie był tracony na granicach.128129### Krok 3: Utwórz Embeddings i Vector Store130131```python132from langchain_openai import OpenAIEmbeddings133from langchain_community.vectorstores import Chroma134135embedding_model = OpenAIEmbeddings(model="text-embedding-3-small")136137vectorstore = Chroma.from_documents(138 documents=chunks,139 embedding=embedding_model,140 persist_directory="./chroma_db",141)142```143144### Krok 4: Utwórz Retriever145146Retriever to komponent, który dla danego zapytania pobiera najbardziej trafne chunks z vector store.147148```python149retriever = vectorstore.as_retriever(150 search_type="similarity",151 search_kwargs={"k": 4},152)153154relevant_docs = retriever.invoke("How does authentication work?")155for doc in relevant_docs:156 print(doc.page_content[:200])157 print("---")158```159160### Krok 5: Zbuduj łańcuch RAG161162Teraz połączmy wszystko razem z LLM i szablonem promptu.163164```python165from langchain_openai import ChatOpenAI166from langchain_core.prompts import ChatPromptTemplate167from langchain_core.runnables import RunnablePassthrough168from langchain_core.output_parsers import StrOutputParser169170llm = ChatOpenAI(model="gpt-4o", temperature=0)171172prompt = ChatPromptTemplate.from_template("""173Answer the question based only on the provided context.174If the context does not contain enough information, say you don't know.175176Context:177{context}178179Question: {question}180181Answer:182""")183184def format_docs(docs):185 return "\n\n".join(doc.page_content for doc in docs)186187rag_chain = (188 {"context": retriever | format_docs, "question": RunnablePassthrough()}189 | prompt190 | llm191 | StrOutputParser()192)193194response = rag_chain.invoke("How does authentication work in the system?")195print(response)196```197198## Zaawansowane techniki RAG199200Podstawowy pipeline działa dobrze, ale istnieje kilka technik znacząco poprawiających jakość odpowiedzi.201202### Wyszukiwanie wielozapytaniowe203204Czasami zapytanie użytkownika jest niejednoznaczne lub nie jest dopasowane do języka użytego w dokumentach. **Multi-Query Retriever** automatycznie generuje warianty oryginalnego pytania, aby uchwycić różne perspektywy.205206```python207from langchain.retrievers import MultiQueryRetriever208209multi_retriever = MultiQueryRetriever.from_llm(210 retriever=vectorstore.as_retriever(),211 llm=llm,212)213214docs = multi_retriever.invoke("What are the security best practices?")215```216217### Kompresja kontekstowa218219Nie cała zawartość chunka jest trafna dla zapytania. **Contextual Compression Retriever** wykorzystuje LLM do wyodrębnienia tylko istotnych części z każdego pobranego chunka.220221```python222from langchain.retrievers import ContextualCompressionRetriever223from langchain.retrievers.document_compressors import LLMChainExtractor224225compressor = LLMChainExtractor.from_llm(llm)226compression_retriever = ContextualCompressionRetriever(227 base_compressor=compressor,228 base_retriever=retriever,229)230```231232### Wyszukiwanie hybrydowe233234Czysto semantyczne wyszukiwanie nie zawsze jest optymalne. **Wyszukiwanie hybrydowe** łączy wyszukiwanie semantyczne (embeddings) z wyszukiwaniem leksykalnym (BM25, dopasowanie słów kluczowych) w celu osiągnięcia lepszych wyników.235236```python237from langchain.retrievers import EnsembleRetriever238from langchain_community.retrievers import BM25Retriever239240bm25_retriever = BM25Retriever.from_documents(chunks)241bm25_retriever.k = 4242243semantic_retriever = vectorstore.as_retriever(search_kwargs={"k": 4})244245hybrid_retriever = EnsembleRetriever(246 retrievers=[bm25_retriever, semantic_retriever],247 weights=[0.4, 0.6],248)249```250251### Konwersacyjny RAG (z pamięcią)252253Aby zbudować chatbot RAG zapamiętujący kontekst rozmowy, należy dodać pamięć, która przeformułowuje pytania użytkownika z uwzględnieniem historii konwersacji.254255```python256from langchain.chains import create_history_aware_retriever257from langchain_core.prompts import MessagesPlaceholder258259contextualize_prompt = ChatPromptTemplate.from_messages([260 ("system", "Given the chat history and the user's latest question, "261 "reformulate the question so it is understandable without the history."),262 MessagesPlaceholder("chat_history"),263 ("human", "{input}"),264])265266history_aware_retriever = create_history_aware_retriever(267 llm, retriever, contextualize_prompt268)269```270271## Najlepsze praktyki2722731. **Wybierz odpowiedni rozmiar chunka**: Eksperymentuj z różnymi rozmiarami (500-1500 tokenów). Mniejsze chunks dla precyzyjnych odpowiedzi, większe dla szerszego kontekstu.2742. **Używaj metadanych dokumentów**: Dodawaj źródło, datę i kategorię jako metadane do chunks. Umożliwia to filtrowanie wyników podczas wyszukiwania.2753. **Oceniaj jakość**: Używaj frameworków takich jak [RAGAS](https://docs.ragas.io/) do mierzenia metryk takich jak *faithfulness*, *relevancy* i *context precision*.2764. **Zarządzaj aktualizacjami dokumentów**: Zaimplementuj pipeline ponownego przetwarzania, aby utrzymać synchronizację vector store ze źródłami danych.2775. **Dodaj re-ranker**: Po początkowym wyszukiwaniu użyj modelu re-rankingu (takiego jak Cohere Rerank), aby uporządkować wyniki na podstawie rzeczywistej trafności.278279## Podsumowanie280281RAG stał się standardową architekturą do budowania aplikacji AI wymagających dostępu do konkretnej, aktualnej wiedzy. LangChain znacznie upraszcza implementację, dostarczając abstrakcje dla każdego komponentu pipeline.282283**Kolejne kroki:**284- **Eksperymentuj lokalnie**: Zacznij od ChromaDB i kilku dokumentów, aby zapoznać się z pipeline.285- **Odkryj LangSmith**: Użyj [LangSmith](https://smith.langchain.com/) do monitorowania i debugowania łańcuchów w produkcji.286- **Wypróbuj różne modele embedding**: Porównaj modele takie jak `text-embedding-3-small`, `text-embedding-3-large` i modele open-source z Sentence Transformers.287- **Sprawdź dokumentację**: [Dokumentacja LangChain](https://python.langchain.com/docs/) to doskonałe i stale aktualizowane źródło.288
:RAG i LangChain: Kompletny przewodnik po generowaniu wspomaganym wyszukiwaniemlines 1-288 (END) — press q to close