spinny:~/writing $ vim rag-langchain-deep-dive.md
1~2大型语言模型(LLMs)如 GPT-4 和 Claude 功能强大,但存在一个根本性的局限:它们的知识在训练时就被冻结了。它们无法访问你的内部文档、数据库或实时信息。**检索增强生成(RAG)** 通过将 LLM 的生成能力与从外部来源检索信息的能力相结合,完美地解决了这个问题。3~4## 问题:LLM 的局限性5~6在谈论 RAG 之前,重要的是要理解为什么我们需要它。7~81. **静态知识**:LLM 只知道训练期间看到的内容。如果你询问其截止日期之后发生的事件,它无法回答。92. **幻觉**:当 LLM 不知道答案时,它倾向于编造一个,生成看似合理但完全虚假的信息。103. **无法访问私有数据**:通用 LLM 无法访问你公司的内部文档、工单或代码库。11~12RAG 通过在查询时从外部来源检索**相关上下文**来解决这三个问题。13~14## 什么是 RAG?15~16检索增强生成是一种架构,它使用从外部知识库检索的信息来丰富发送给 LLM 的提示。RAG 不是仅依赖模型的参数化知识,而是先**搜索**相关信息,然后将其**注入**到提示中,使模型能够生成准确、有据可依的回答。17~18```mermaid19graph LR20 User["用户"] -- "问题" --> Retriever21 Retriever -- "搜索相关\n文档" --> VectorStore["向量数据库"]22 VectorStore -- "相关\n文档" --> Retriever23 Retriever -- "上下文 + 问题" --> LLM24 LLM -- "有据可依的\n回答" --> User25```26~27## RAG 的详细工作原理28~29RAG 架构由两个主要阶段组成:**索引**(离线)和 **检索 + 生成**(在线)。30~31### 阶段 1:索引(文档摄取)32~33索引阶段为语义搜索准备文档,由四个步骤组成。34~35```mermaid36graph TD37 A["文档\n(PDF, HTML, MD, DB)"] --> B["文档加载器"]38 B --> C["文本分割器"]39 C --> D["文本块"]40 D --> E["嵌入模型"]41 E --> F["数值向量"]42 F --> G["向量数据库\n(ChromaDB, Pinecone, FAISS)"]43```44~45#### 1. 文档加载46~47文档可以来自任何来源:PDF 文件、网页、数据库、Markdown 文件、API。**文档加载器**读取这些文档并将其转换为结构化文本。48~49#### 2. 文本分割(Chunking)50~51LLM 的上下文窗口有限,而文档可能很长。**文本分割器**将文档分成更小的片段,称为 *chunks*。分块的质量至关重要:太小的块会丢失上下文,太大的块会稀释相关性。52~53最常见的策略包括:54- **递归字符分割**:使用 `\n\n`、`\n`、`. ` 等分隔符递归分割文本,尊重文档结构。55- **语义分割**:使用嵌入来找到文本中的自然断点。56- **块重叠**:在连续块之间包含重叠部分,以保留边界处的上下文。57~58#### 3. 嵌入59~60每个块通过嵌入模型(如 OpenAI 的 `text-embedding-3-small`)转换为**数值向量**(嵌入)。这些向量捕获文本的语义含义:含义相似的句子在多维空间中的向量会很接近。61~62#### 4. 向量数据库63~64向量存储在**向量数据库**中,如 ChromaDB、Pinecone、Weaviate 或 FAISS。该数据库针对**相似性搜索**进行了优化:给定查询,找到最相似的向量(因此也是最相关的文本块)。65~66### 阶段 2:检索 + 生成67~68当用户提出问题时:69~701. 使用相同的嵌入模型将问题转换为嵌入。712. 向量数据库通过**相似性搜索**(通常是余弦相似度或欧几里得距离)找到最相似的块。723. 检索到的块作为上下文插入到提示中。734. LLM 根据提供的上下文生成回答。74~75## 使用 LangChain 构建 RAG 管道76~77**LangChain** 是最流行的用于构建基于 LLM 应用的 Python(和 JavaScript)框架。它为 RAG 管道的每个组件提供高级抽象。78~79### 安装80~81```bash82pip install langchain langchain-openai langchain-community chromadb83```84~85### 步骤 1:加载文档86~87LangChain 为不同的数据源提供了数十个文档加载器。88~89```python90from langchain_community.document_loaders import (91 PyPDFLoader,92 WebBaseLoader,93 DirectoryLoader,94 TextLoader,95)96~97# 加载 PDF98pdf_loader = PyPDFLoader("docs/manual.pdf")99pdf_docs = pdf_loader.load()100~101# 加载网页102web_loader = WebBaseLoader("https://docs.example.com/guide")103web_docs = web_loader.load()104~105# 从目录加载所有 .md 文件106dir_loader = DirectoryLoader("./knowledge_base", glob="**/*.md", loader_cls=TextLoader)107md_docs = dir_loader.load()108~109all_docs = pdf_docs + web_docs + md_docs110```111~112### 步骤 2:将文档分割成块113~114```python115from langchain.text_splitter import RecursiveCharacterTextSplitter116~117text_splitter = RecursiveCharacterTextSplitter(118 chunk_size=1000,119 chunk_overlap=200,120 separators=["\n\n", "\n", ". ", " ", ""],121)122~123chunks = text_splitter.split_documents(all_docs)124print(f"原始文档: {len(all_docs)}, 块: {len(chunks)}")125```126~127`chunk_overlap` 参数至关重要:它在连续块之间创建重叠,使上下文不会在边界处丢失。128~129### 步骤 3:创建嵌入和向量数据库130~131```python132from langchain_openai import OpenAIEmbeddings133from langchain_community.vectorstores import Chroma134~135embedding_model = OpenAIEmbeddings(model="text-embedding-3-small")136~137vectorstore = Chroma.from_documents(138 documents=chunks,139 embedding=embedding_model,140 persist_directory="./chroma_db",141)142```143~144### 步骤 4:创建检索器145~146检索器是给定查询后从向量数据库中获取最相关块的组件。147~148```python149retriever = vectorstore.as_retriever(150 search_type="similarity",151 search_kwargs={"k": 4},152)153~154relevant_docs = retriever.invoke("身份验证如何工作?")155for doc in relevant_docs:156 print(doc.page_content[:200])157 print("---")158```159~160### 步骤 5:构建 RAG 链161~162现在让我们将所有内容与 LLM 和提示模板组合在一起。163~164```python165from langchain_openai import ChatOpenAI166from langchain_core.prompts import ChatPromptTemplate167from langchain_core.runnables import RunnablePassthrough168from langchain_core.output_parsers import StrOutputParser169~170llm = ChatOpenAI(model="gpt-4o", temperature=0)171~172prompt = ChatPromptTemplate.from_template("""173仅根据提供的上下文回答问题。174如果上下文中没有足够的信息,请说你不知道。175~176上下文:177{context}178~179问题:{question}180~181回答:182""")183~184def format_docs(docs):185 return "\n\n".join(doc.page_content for doc in docs)186~187rag_chain = (188 {"context": retriever | format_docs, "question": RunnablePassthrough()}189 | prompt190 | llm191 | StrOutputParser()192)193~194response = rag_chain.invoke("系统中的身份验证如何工作?")195print(response)196```197~198## 高级 RAG 技术199~200基本管道运行良好,但有几种技术可以显著提高回答质量。201~202### 多查询检索203~204有时用户的查询含糊不清,或与文档中使用的语言不一致。**多查询检索器**自动生成原始问题的变体,以捕获多个视角。205~206```python207from langchain.retrievers import MultiQueryRetriever208~209multi_retriever = MultiQueryRetriever.from_llm(210 retriever=vectorstore.as_retriever(),211 llm=llm,212)213~214docs = multi_retriever.invoke("安全最佳实践是什么?")215```216~217### 上下文压缩218~219并非块中的所有内容都与查询相关。**上下文压缩检索器**使用 LLM 从每个检索到的块中仅提取相关部分。220~221```python222from langchain.retrievers import ContextualCompressionRetriever223from langchain.retrievers.document_compressors import LLMChainExtractor224~225compressor = LLMChainExtractor.from_llm(llm)226compression_retriever = ContextualCompressionRetriever(227 base_compressor=compressor,228 base_retriever=retriever,229)230```231~232### 混合搜索233~234纯语义搜索并不总是最优的。**混合搜索**将语义搜索(嵌入)与词汇搜索(BM25、关键词匹配)相结合,以获得更好的结果。235~236```python237from langchain.retrievers import EnsembleRetriever238from langchain_community.retrievers import BM25Retriever239~240bm25_retriever = BM25Retriever.from_documents(chunks)241bm25_retriever.k = 4242~243semantic_retriever = vectorstore.as_retriever(search_kwargs={"k": 4})244~245hybrid_retriever = EnsembleRetriever(246 retrievers=[bm25_retriever, semantic_retriever],247 weights=[0.4, 0.6],248)249```250~251### 对话式 RAG(带记忆)252~253要构建一个能记住对话上下文的 RAG 聊天机器人,需要添加一个记忆组件,根据对话历史重新表述用户的问题。254~255```python256from langchain.chains import create_history_aware_retriever257from langchain_core.prompts import MessagesPlaceholder258~259contextualize_prompt = ChatPromptTemplate.from_messages([260 ("system", "根据聊天历史和用户的最新问题,"261 "重新表述问题使其在没有历史记录的情况下也能被理解。"),262 MessagesPlaceholder("chat_history"),263 ("human", "{input}"),264])265~266history_aware_retriever = create_history_aware_retriever(267 llm, retriever, contextualize_prompt268)269```270~271## 最佳实践272~2731. **选择正确的块大小**:尝试不同的大小(500-1500 个标记)。较小的块用于精确回答,较大的块用于更广泛的上下文。2742. **使用文档元数据**:将来源、日期和类别作为元数据添加到块中。这允许在检索期间过滤结果。2753. **评估质量**:使用 [RAGAS](https://docs.ragas.io/) 等框架来衡量 *faithfulness*、*relevancy* 和 *context precision* 等指标。2764. **处理文档更新**:实施重新摄取管道,使向量数据库与数据源保持同步。2775. **添加重新排序器**:在初始检索后,使用重新排序模型(如 Cohere Rerank)根据实际相关性重新排序结果。278~279## 结论280~281RAG 已成为构建需要访问特定且最新知识的 AI 应用的标准架构。LangChain 通过为管道的每个组件提供抽象,极大地简化了实现过程。282~283**下一步:**284- **本地实验**:从 ChromaDB 和少量文档开始,熟悉管道流程。285- **探索 LangSmith**:使用 [LangSmith](https://smith.langchain.com/) 在生产中监控和调试你的链。286- **尝试不同的嵌入模型**:比较 `text-embedding-3-small`、`text-embedding-3-large` 等模型以及 Sentence Transformers 的开源模型。287- **查阅文档**:[LangChain 文档](https://python.langchain.com/docs/) 是一个优秀且持续更新的资源。288~
NORMAL · rag-langchain-deep-dive.md [readonly]288 lines · :q to close