ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

LLM Cookbook学习——使用 LangChain 开发应用程序>基于文档的问答 Question Answering over Documents

LLM Cookbook学习——使用 LangChain 开发应用程序>基于文档的问答 Question Answering over Documents 一、为什么需要“基于文档的问答”普通的大语言模型虽然拥有很强的语言理解和生成能力但是它本身并不知道我们的私有数据。例如公司内部文档产品说明书PDF 论文CSV 商品数据库项目设计文档技术手册自己整理的学习笔记如果直接问 LLM请告诉我公司内部文档中 XXX 模块是怎么设计的LLM 并没有看到这份文档因此无法可靠回答。一种最直接的办法是文档内容 用户问题 → LLM但实际文档可能有几十页、几百页甚至数 GB不可能把所有内容一次性塞进 Prompt。因此需要一种新的思路用户问题 ↓ Embedding ↓ 向量相似度搜索 ↓ 找出最相关的几个文档片段 ↓ 相关文档片段 用户问题 ↓ LLM ↓ Answer这实际上就是后来非常常见的RAG —— Retrieval-Augmented Generation即检索增强生成。核心思想非常简单不是让 LLM 记住所有知识而是在回答之前先把相关知识检索出来再交给 LLM。二、本节整体流程LangChain 中基于文档问答的基本流程可以概括为原始文档 ↓ Document Loader ↓ Documents ↓ Text Splitter ↓ Chunks ↓ Embedding Model ↓ Vectors ↓ Vector Store ↓ Retriever ↓ 相关 Documents ↓ RetrievalQA ↓ LLM ↓ 答案可以进一步分成两个阶段。阶段一建立知识库Document ↓ Load ↓ Split ↓ Embedding ↓ Vector Store阶段二执行问答Question ↓ Embedding ↓ Similarity Search ↓ Relevant Documents ↓ Prompt ↓ LLM ↓ Answer所以整个文档问答系统最重要的四个概念是Loader Splitter Embedding Retriever最终再由RetrievalQA把它们连接起来。三、加载 CSV 文档课程使用的是一个户外服装商品数据集OutdoorClothingCatalog_1000.csv里面包含商品名称、商品描述等信息。首先导入 CSVLoaderfrom langchain.document_loaders import CSVLoader然后加载文件file OutdoorClothingCatalog_1000.csv loader CSVLoader( file_pathfile, encodingutf-8 )注意loader本身只是一个Loader 对象。真正加载数据需要docs loader.load()此时docs通常是List[Document]也就是说它不是普通字符串列表而是多个 LangChainDocument对象。四、理解 LangChain 的 DocumentLangChain 中非常重要的数据结构就是Document一个 Document 通常包含两个核心部分Document( page_content..., metadata{...} )其中1. page_content真正的文本内容。例如doc.page_content可能得到name: Womens Campside Oxfords description: This ultracomfortable lace-to-toe Oxford...2. metadata描述这段文本从哪里来的。例如doc.metadata可能得到{ source: OutdoorClothingCatalog_1000.csv, row: 0 }所以可以理解成Document ├── page_content │ └── metadata其中 metadata 非常重要。因为最终系统不仅可以回答答案是什么还可以回答答案来自哪一个文件 哪一页 哪一行这也是 RAG 系统能够进行来源追踪的重要基础。五、Embedding 是什么这一部分是整个文档问答系统最核心的概念之一。1. 计算机不能直接理解文本语义例如What clothing can keep me warm?和Which jacket is suitable for cold weather?两个句子的单词并不完全相同。如果使用传统关键词搜索warm未必能匹配cold weather但实际上二者语义高度相关。因此需要把文本转换成向量即Text ↓ Embedding Model ↓ Vector例如I like dogs可能被转换为[ 0.012, -0.034, 0.128, ... ]真实 Embedding 往往有几百甚至几千维。六、Embedding 的核心意义Embedding 最大的特点是语义相近的文本在向量空间中的距离通常也比较接近。例如dog puppy animal对应向量dog → Vector A puppy → Vector B animal → Vector C其中distance(A, B)往往会比较小。而dog airplane对应向量距离通常更大。因此可以通过向量距离实现语义搜索而不是简单关键词匹配。七、问题是怎么找到对应文档的假设数据库中有三个文档Document A 这是一件防水冲锋衣。 Document B 这是一双夏季凉鞋。 Document C 这是一件适合冬季穿着的羽绒服。Embedding 后A → Vector A B → Vector B C → Vector C用户问What clothes are suitable for cold weather?问题也进行 EmbeddingQuestion ↓ Embedding ↓ Vector Q然后计算similarity(Q, A) similarity(Q, B) similarity(Q, C)假设结果A 0.71 B 0.15 C 0.92那么系统就会优先返回Document C这就是Similarity Search即相似度搜索。八、Vector Store 是什么如果只有几个文档我们直接比较向量即可。但真实项目可能有10 万个文档 100 万个 chunk 1000 万个向量所以需要专门保存和搜索向量的数据结构。这就是Vector Store也可以理解为向量数据库 / 向量索引。课程示例使用DocArrayInMemorySearch导入from langchain.vectorstores import DocArrayInMemorySearch它属于一种内存向量数据库特点是简单 速度快 适合教学 适合小型数据 无需额外数据库服务但不适合真正的大规模生产环境。实际工程常见的向量数据库还包括Chroma FAISS Milvus Pinecone Qdrant Weaviate九、使用 VectorstoreIndexCreator课程首先演示了一种非常简洁的写法from langchain.indexes import VectorstoreIndexCreator然后index VectorstoreIndexCreator( vectorstore_clsDocArrayInMemorySearch ).from_loaders([loader])这一行虽然很短实际上内部帮我们完成了很多事情。可以近似理解为Loader ↓ Load Documents ↓ Split Documents ↓ Embedding ↓ Create Vector Store ↓ Store Vectors也就是说VectorstoreIndexCreator实际上是一个高度封装的工具。十、直接进行文档问答创建好 index 后可以直接query Please list all your shirts with sun protection in a table in markdown and summarize each one. response index.query(query)整个内部流程实际上是query ↓ query embedding ↓ vector search ↓ relevant documents ↓ prompt ↓ LLM ↓ answer所以index.query()虽然只有一行代码但背后实际上已经完成了一套简单 RAG。十一、VectorstoreIndexCreator 到底帮我们干了什么理解这部分非常重要。高层 APIindex VectorstoreIndexCreator(...).from_loaders([loader])实际上近似等价于docs loader.load() splits text_splitter.split_documents(docs) embeddings OpenAIEmbeddings() vectorstore DocArrayInMemorySearch.from_documents( splits, embeddings )因此必须建立一个认知VectorstoreIndexCreator不是某种新的 AI 技术。它只是帮我们把Load Split Embed Store封装到了一起。十二、为什么必须 Split 文档假设我们有一本500 页 PDF如果把整个 PDF 作为一个 DocumentPDF ↓ 一个巨大的 Vector问题就出现了。假设用户问第 378 页介绍的 AXI outstanding 是什么意思整本 PDF 只有一个 Embedding。那么这个向量表达的是整本 PDF 的综合语义而不是第 378 页具体内容。因此需要Document ↓ Text Splitter ↓ Chunk1 Chunk2 Chunk3 ... ChunkN每个 Chunk 单独生成向量Chunk1 → Vector1 Chunk2 → Vector2 Chunk3 → Vector3 ...这样用户提问后就可以精准找到Chunk137 Chunk284 Chunk301十三、Chunk Size 和 Chunk Overlap文档分割中两个非常重要的参数是chunk_size chunk_overlap例如RecursiveCharacterTextSplitter( chunk_size1000, chunk_overlap150 )含义chunk_size 1000表示每个文本块大约 1000 个字符或 token 单位附近。而chunk_overlap 150意味着相邻 Chunk 会保留一定重复内容。例如Chunk 1: AAAAAAAA BBBBBBBB CCCCCCCC Chunk 2: CCCCCCCC DDDDDDDD EEEEEEEE其中CCCCCCCC就是 overlap。十四、为什么需要 Chunk Overlap假设一句关键内容刚好处于两个 Chunk 的边界Chunk1: ......AXI允许多个事务同时处于 ------------------分割点------------------ Chunk2: 未完成状态这种机制叫Outstanding......如果完全没有 overlapchunk_overlap 0那么两个 Chunk 都可能失去完整语义。加入 overlap 后Chunk1: AXI允许多个事务同时处于未完成状态 Chunk2: 多个事务同时处于未完成状态这种机制叫Outstanding这样 Embedding 的语义完整性会明显更好。所以Overlap 本质上是在牺牲少量存储空间换取上下文连续性。十五、手动实现完整流程相比VectorstoreIndexCreator更值得掌握的是底层过程。核心步骤docs loader.load()然后拆分from langchain.text_splitter import RecursiveCharacterTextSplitter text_splitter RecursiveCharacterTextSplitter( chunk_size1000, chunk_overlap150 ) splits text_splitter.split_documents(docs)生成 Embeddingfrom langchain.embeddings.openai import OpenAIEmbeddings embeddings OpenAIEmbeddings()创建向量数据库db DocArrayInMemorySearch.from_documents( splits, embeddings )至此文档知识库就已经创建完成。十六、Retriever 是什么有了 Vector Store 后并不是直接交给 LLM。通常先转换成Retriever例如retriever db.as_retriever()Retriever 的职责非常明确Question ↓ Retriever ↓ Relevant Documents即Retriever 负责找资料LLM 负责读资料并回答。这是 RAG 系统最重要的职责划分之一。十七、Retriever 和 Vector Store 的区别这两个概念很容易混淆。Vector Store负责保存向量 执行向量搜索例如dbRetriever负责根据 Query 获取相关 Document例如retriever db.as_retriever()可以理解为Vector Store 数据库 Retriever 数据库查询接口因此LLM ↑ Retriever ↑ Vector Store十八、Top-K 检索Retriever 通常会设置k例如retriever db.as_retriever( search_kwargs{k: 4} )表示每次返回最相关的 4 个文档块假设Question与数据库 Chunk 相似度Chunk1 0.94 Chunk2 0.87 Chunk3 0.81 Chunk4 0.79 Chunk5 0.52 Chunk6 0.31如果k 4那么Chunk1 Chunk2 Chunk3 Chunk4会送给 LLM。十九、k 是越大越好吗不是。如果k太小可能漏掉有用信息如果k太大大量无关文本进入 Prompt会导致Token 消耗增加 噪声增加 LLM注意力被稀释 回答质量下降因此k本质上也是一个需要调优的超参数。典型值可能是3 4 5 8具体取决于Chunk Size 文档类型 查询复杂度 模型 Context Window二十、RetrievalQA完成 Retriever 之后就可以构造真正的RetrievalQA导入from langchain.chains import RetrievalQA创建qa RetrievalQA.from_chain_type( llmllm, chain_typestuff, retrieverretriever )然后result qa.run(query)完整流程Question ↓ Retriever ↓ Top-K Documents ↓ RetrievalQA ↓ Prompt ↓ LLM ↓ Answer二十一、RetrievalQA 的本质RetrievalQA 其实就是把两个系统连接Retriever LLM所以RetrievalQA Retrieval Generation也就是RAG核心逻辑可以近似写成docs retriever.get_relevant_documents(question) context \n.join( doc.page_content for doc in docs ) prompt f 请根据下面资料回答问题。 资料 {context} 问题 {question} answer llm(prompt)LangChain 只是把这些逻辑封装了。二十二、chain_type 是什么意思课程中一个非常重要的参数chain_type常见方式包括stuff map_reduce refine map_rerank1. stuff最简单的一种Doc1 Doc2 Doc3 Doc4 ↓ 全部塞入 Prompt ↓ LLM也就是chain_typestuff优点简单 速度快 只调用一次 LLM缺点文档过多容易超过 Context Window适合Top-K较少 Chunk较短 问题简单二十三、map_reduce流程Doc1 → LLM → Answer1 Doc2 → LLM → Answer2 Doc3 → LLM → Answer3 ↓ Reduce ↓ Final Answer即Map ↓ Reduce优点可以处理大量文档缺点需要多次调用 LLM 成本更高 速度更慢二十四、refine流程Doc1 ↓ Answer V1 ↓ Doc2 ↓ Answer V2 ↓ Doc3 ↓ Answer V3即在已有答案基础上不断加入新资料进行修正。适合需要逐步补充信息 需要综合多个文档缺点LLM调用次数多 后面文档会依赖前面的结果二十五、map_rerank流程类似Doc1 → Answer1 Score1 Doc2 → Answer2 Score2 Doc3 → Answer3 Score3然后选择Score最高的答案适合答案通常存在于单一文档块中二十六、四种 Chain 对比Chain工作方式优点缺点stuff所有文档一次输入快、简单容易超 Tokenmap_reduce分别处理再汇总可处理大量文本LLM 调用多refine不断修正答案综合能力强延迟高map_rerank分别回答并评分易找到最佳答案计算量较大最常用的入门方式通常还是chain_typestuff二十七、返回 Source Documents做 RAG 时非常推荐return_source_documentsTrue例如qa RetrievalQA.from_chain_type( llmllm, chain_typestuff, retrieverretriever, return_source_documentsTrue )查询result qa({query: query})得到的结果可能类似{ result: ..., source_documents: [...] }这样就能分别获取result[result]最终答案。以及result[source_documents]LLM 回答所依据的资料。二十八、为什么 Source Documents 很重要普通 ChatGPT 的一个问题是你不知道答案从哪里来的。而 RAG 可以做到Answer Evidence例如答案 该产品具有 UPF 50 防晒能力。 来源 OutdoorClothingCatalog_1000.csv row 327这对于企业知识库 法律问答 金融问答 论文问答 技术文档问答尤其重要。因为实际系统通常不仅需要Answer还需要Evidence二十九、整个 RetrievalQA 的内部结构完整结构可以理解为User │ ▼ Question │ ▼ Query Embedding │ ▼ Vector Store │ Similarity Search │ ▼ Retriever │ Top-K Documents │ ▼ Prompt ┌──────────┴──────────┐ │ │ Documents Question │ │ └──────────┬──────────┘ ▼ LLM │ ▼ Answer这实际上就是最基础的RAG Pipeline三十、为什么不能直接让 LLM 搜数据库这是理解 RAG 架构的关键。LLM 本身擅长理解 推理 总结 生成但它并不擅长直接存储百万篇文档 搜索百万篇文档 做大规模向量最近邻搜索所以系统进行了职责划分Vector Database 负责 Search LLM 负责 Reason Generate形成Retriever LLM这也是现代 RAG 系统最基本的架构思想。三十一、RAG 和模型训练的区别很多初学者容易认为我要让 ChatGPT 学习自己的 PDF是不是要重新训练模型实际上大多数情况下不用。Fine-tuning相当于修改模型参数主要用于行为调整 输出格式 特定风格 任务能力RAG相当于模型不变 动态提供知识例如1000页公司文档 ↓ Vector Database ↓ Retriever ↓ LLM所以知识库问答通常优先考虑RAG而不是 Fine-tuning。三十二、Embedding 模型和 LLM 不是一个东西RAG 系统通常实际上包含两个模型。模型 1Embedding Model负责Text ↓ Vector主要用于检索模型 2LLM负责Context Question ↓ Answer主要用于理解 推理 生成所以Embedding Model ≠ LLM两者作用完全不同。三十三、RAG 最关键的不是“回答”而是“检索”很多人刚开始做 RAG 会把注意力全部集中在换更强的 LLM实际上一个非常重要的工程规律是如果检索阶段没有找到正确资料再强的 LLM 也很难产生可靠答案。即Wrong Retrieval ↓ Wrong Context ↓ LLM ↓ Wrong Answer相反Good Retrieval ↓ Correct Context ↓ LLM ↓ Good Answer因此 RAG 项目中的核心问题往往不是模型够不够大而是Chunk 怎么切 Embedding 怎么选 Top-K 多少 怎么做召回 怎么做 Rerank三十四、Chunk Size 对 RAG 的影响例如查询AXI4为什么取消写数据交织如果 Chunk 太小Chunk A AXI4取消了 Chunk B 写数据交织功能 Chunk C 主要目的是简化互联逻辑每个 Chunk 都缺少完整语义。检索效果可能下降。但如果 Chunk 太大Chunk: 包含AXI ID、Burst、Outstanding、Interleaving、 Exclusive、QoS、Cache、Region……那么真正相关内容只占很小比例。Embedding 语义又会变得模糊。所以Chunk 太小 → 上下文破碎 Chunk 太大 → 语义稀释合理 Chunk Size 是 RAG 的重要调优内容。三十五、最简单的 RAG 伪代码如果把 LangChain 所有封装去掉整个 RAG 可以写成# 1. 加载文档 documents load_documents() # 2. 文档切片 chunks split_documents(documents) # 3. 每个 chunk 生成 embedding vectors [] for chunk in chunks: vector embedding_model.embed(chunk) vectors.append(vector) # 4. 保存向量 vector_db.add(vectors) # # 用户开始提问 # question input() # 5. 问题 embedding query_vector embedding_model.embed(question) # 6. 相似度搜索 documents vector_db.search( query_vector, top_k4 ) # 7. 构造上下文 context \n.join(documents) # 8. 构造 Prompt prompt f 请只根据以下资料回答 {context} 问题 {question} # 9. LLM回答 answer llm(prompt)理解这段伪代码之后再看RetrievalQA VectorstoreIndexCreator Retriever VectorStore就会非常容易。三十六、本节知识结构总结这一章表面是在学习RetrievalQA实际上学习的是一套完整的文档问答 / RAG 思维整个逻辑┌─────────────┐ │ Document │ └──────┬──────┘ ↓ Document Loader ↓ Documents ↓ Text Split ↓ Chunks ↓ Embedding ↓ Vectors ↓ Vector Store ↓ ────────────────────────────────── ↑ Question ↓ Embedding ↓ Similarity Search ↓ Retriever ↓ Relevant Chunks ↓ Prompt ↓ LLM ↓ Answer三十七、几个必须记住的概念Document Loader负责把外部数据读取成 LangChain Document。Text Splitter负责把大型 Document 拆成多个 Chunk。Embedding把文本转换成能够表达语义的向量。Vector Store保存向量并支持相似度搜索。Retriever根据用户问题找到最相关的 Documents。RetrievalQARetriever LLM负责完成检索 回答三十八、一句话理解 RAG如果只记一句话RAG 先查资料再让大模型根据查到的资料回答。如果写成工程流程User Question ↓ Retrieval ↓ Relevant Context ↓ Augmented Prompt ↓ Generation因此Retrieval Augmented Generation RAG三十九、从本节进一步学习什么学完这一节后可以继续深入下面几个方向。1. Document Loading解决PDF怎么加载 Word怎么加载 网页怎么加载 Markdown怎么加载2. Document Splitting重点研究chunk_size chunk_overlap RecursiveCharacterTextSplitter TokenTextSplitter3. Embedding理解文本如何映射到高维语义空间以及Cosine Similarity Euclidean Distance Dot Product4. Vector Database进一步学习FAISS Chroma Milvus Qdrant Pinecone5. Retrieval进一步学习Similarity Search MMR Metadata Filter Hybrid Search Multi-Query Retrieval Parent Document Retrieval6. Rerank基础流程Vector Search ↓ Top 20 ↓ Reranker ↓ Top 5 ↓ LLM可以提高最终进入 LLM 的文档质量。四十、学习心得真正值得掌握的是它背后的架构文档 ↓ Chunk ↓ Embedding ↓ Vector Store ↓ Retriever ↓ Context ↓ LLM以后即使不使用 LangChain而换成LlamaIndex Haystack Milvus FAISS Elasticsearch 自研 RAG核心思想仍然基本相同。VectorstoreIndexCreator本质封装的是Load Split Embed Store而RetrievalQA本质封装的是Retrieve Build Context Prompt LLM Generation只要把这两个结构彻底理解后面学习更复杂的 RAG 系统就会容易很多。四十一、本节最终思维导图LangChain 文档问答 │ ├── 1. 文档加载 │ └── CSVLoader │ ├── 2. Document │ ├── page_content │ └── metadata │ ├── 3. 文档切分 │ ├── chunk_size │ └── chunk_overlap │ ├── 4. Embedding │ └── Text → Vector │ ├── 5. Vector Store │ └── DocArrayInMemorySearch │ ├── 6. Similarity Search │ ├── 7. Retriever │ └── Top-K Documents │ ├── 8. RetrievalQA │ ├── stuff │ ├── map_reduce │ ├── refine │ └── map_rerank │ └── 9. LLM ↓ Answer
返回列表