Store sprogmodeller (LLMs) som GPT-4 og Claude er ekstraordinært kraftfulde, men lider af en fundamental begrænsning: deres viden er frosset på træningstidspunktet. Retrieval-Augmented Generation (RAG) løser præcis dette problem ved at kombinere den generative kraft i LLMs med evnen til at hente information fra eksterne kilder.
Problemet: LLM-begrænsninger
- Statisk viden: En LLM ved kun, hvad den så under træning.
- Hallucinationer: Når en LLM ikke kender svaret, har den tendens til at opfinde et.
- Ingen adgang til private data: En generisk LLM har ingen adgang til din virksomheds interne dokumentation.
Hvad er RAG?
Sådan fungerer RAG i detaljer
Fase 1: Indeksering (Dokumentindtagelse)
1. Dokumentindlæsning
Dokumenter kan komme fra enhver kilde: PDF-filer, websider, databaser, Markdown-filer, APIer.
2. Tekstopdeling (Chunking)
Text Splitter opdeler dokumenter i mindre fragmenter kaldet chunks.
3. Embedding
Hver chunk transformeres til en numerisk vektor (embedding) via en embedding-model.
4. Vector Store
Vektorerne gemmes i en Vector Store, optimeret til lighedssøgning.
Fase 2: Retrieval + Generation
- Spørgsmålet transformeres til et embedding.
- Vector Store finder de mest lignende chunks.
- De hentede chunks indsættes i prompten som kontekst.
- LLM genererer et svar baseret på den givne kontekst.
Opbygning af en RAG-pipeline med LangChain
Installation
pip install langchain langchain-openai langchain-community chromadb
Trin 1: Indlæs dokumenter
from langchain_community.document_loaders import ( PyPDFLoader, WebBaseLoader, DirectoryLoader, TextLoader, ) # Load a PDF pdf_loader = PyPDFLoader("docs/manual.pdf") pdf_docs = pdf_loader.load() # Load a web page web_loader = WebBaseLoader("https://docs.example.com/guide") web_docs = web_loader.load() # Load all .md files from a directory dir_loader = DirectoryLoader("./knowledge_base", glob="**/*.md", loader_cls=TextLoader) md_docs = dir_loader.load() all_docs = pdf_docs + web_docs + md_docs
Trin 2: Opdel dokumenter i chunks
from langchain.text_splitter import RecursiveCharacterTextSplitter text_splitter = RecursiveCharacterTextSplitter( chunk_size=1000, chunk_overlap=200, separators=["\n\n", "\n", ". ", " ", ""], ) chunks = text_splitter.split_documents(all_docs) print(f"Original documents: {len(all_docs)}, Chunks: {len(chunks)}")
Trin 3: Opret embeddings og Vector Store
from langchain_openai import OpenAIEmbeddings from langchain_community.vectorstores import Chroma embedding_model = OpenAIEmbeddings(model="text-embedding-3-small") vectorstore = Chroma.from_documents( documents=chunks, embedding=embedding_model, persist_directory="./chroma_db", )
Trin 4: Opret Retriever
retriever = vectorstore.as_retriever( search_type="similarity", search_kwargs={"k": 4}, ) relevant_docs = retriever.invoke("How does authentication work?") for doc in relevant_docs: print(doc.page_content[:200]) print("---")
Trin 5: Byg RAG-kæden
from langchain_openai import ChatOpenAI from langchain_core.prompts import ChatPromptTemplate from langchain_core.runnables import RunnablePassthrough from langchain_core.output_parsers import StrOutputParser llm = ChatOpenAI(model="gpt-4o", temperature=0) prompt = ChatPromptTemplate.from_template(""" Answer the question based only on the provided context. If the context does not contain enough information, say you don't know. Context: {context} Question: {question} Answer: """) def format_docs(docs): return "\n\n".join(doc.page_content for doc in docs) rag_chain = ( {"context": retriever | format_docs, "question": RunnablePassthrough()} | prompt | llm | StrOutputParser() ) response = rag_chain.invoke("How does authentication work in the system?") print(response)
Avancerede RAG-teknikker
Multi-Query Retrieval
from langchain.retrievers import MultiQueryRetriever multi_retriever = MultiQueryRetriever.from_llm( retriever=vectorstore.as_retriever(), llm=llm, ) docs = multi_retriever.invoke("What are the security best practices?")
Kontekstuel komprimering
from langchain.retrievers import ContextualCompressionRetriever from langchain.retrievers.document_compressors import LLMChainExtractor compressor = LLMChainExtractor.from_llm(llm) compression_retriever = ContextualCompressionRetriever( base_compressor=compressor, base_retriever=retriever, )
Hybrid søgning
from langchain.retrievers import EnsembleRetriever from langchain_community.retrievers import BM25Retriever bm25_retriever = BM25Retriever.from_documents(chunks) bm25_retriever.k = 4 semantic_retriever = vectorstore.as_retriever(search_kwargs={"k": 4}) hybrid_retriever = EnsembleRetriever( retrievers=[bm25_retriever, semantic_retriever], weights=[0.4, 0.6], )
Konversationel RAG (med hukommelse)
from langchain.chains import create_history_aware_retriever from langchain_core.prompts import MessagesPlaceholder contextualize_prompt = ChatPromptTemplate.from_messages([ ("system", "Given the chat history and the user's latest question, " "reformulate the question so it is understandable without the history."), MessagesPlaceholder("chat_history"), ("human", "{input}"), ]) history_aware_retriever = create_history_aware_retriever( llm, retriever, contextualize_prompt )
Bedste praksis
- Vælg den rigtige chunk-størrelse: Eksperimenter med forskellige størrelser (500-1500 tokens).
- Brug dokumentmetadata: Tilføj kilde, dato og kategori som metadata.
- Evaluer kvalitet: Brug frameworks som RAGAS.
- Håndter dokumentopdateringer: Implementer en re-ingestion pipeline.
- Tilføj en re-ranker: Brug en re-ranking model efter initial retrieval.
Konklusion
RAG er blevet standardarkitekturen for at bygge AI-applikationer, der har brug for adgang til specifik, opdateret viden. LangChain forenkler implementeringen enormt.
Næste trin:
- Eksperimenter lokalt: Start med ChromaDB og et par dokumenter.
- Udforsk LangSmith: Brug LangSmith til at overvåge og fejlsøge dine kæder.
- Prøv forskellige embedding-modeller.
- Tjek dokumentationen: LangChain-dokumentationen er en fremragende ressource.