跳到主要内容

LangChain:可换零件的管道

这一章讲一件事: 为什么同一套 RAG 代码,换向量库、换检索策略、换 LLM 都只需要改一两行—— 以及换的时候,每个位置上有哪些选项、各自适合什么场景。 原书对这套东西的定位很准:把新更好的零件换进管道,是这个行业的常态,框架要为此而生1

1. 统一接口:框架存在的理由

LangChain 给三大件各定义了一个抽象类:vector store(向量库)、retriever(检索器)、LLM。 你的检索逻辑只对这些接口编程,底层实现随便换2

这个抽象的规模值得一观:LangChain 目前集成了 49 种向量库3。第 02 章用的 Chroma 只是其中之一。原书演示了换库的实际改动量:

  • 换 FAISS: Chroma.from_documents(...) 改成 FAISS.from_documents(...), 删掉 Chroma 特有的 collection 参数,完事。FAISS 是 Facebook 开源的检索库, 支持 GPU 加速(faiss-gpu,需要 NVIDIA 显卡,Apple Silicon 不行),能扛十亿级数据4;
  • 换 Weaviate: 稍麻烦——它要求先定义 schema(数据结构的正式定义), 还有一堆专属参数;批量(即一次一批地)写入用 client.batch

metadata 里不能用 id 这个名字(它是内部保留字段——字段即一条数据里的属性格子——要用 doc_id), 重建前要先删掉旧 schema。schema 带来更严格的约束和控制,代价是更多样板代码5

Chroma 与 Weaviate 的对比就是 spectrum 的两端:前者灵活省事,后者结构化、功能全6。 LangGraph 等框架的更深入拆解不在本书范围,我们的书架里有现成的 (补充(不在书里,依据我们的 frontier 书架):LangChain 的 Runnable/LCEL 接口设计、 自动获得 stream/batch/async 能力的机制,在我们对 LangChain 本体的拆解里有逐行分析。 依据: shelf=ai-frontier-reference/langchain#03-runnable-lcel.md 事实=所有 LLM 实现 Runnable 接口,默认具备 ainvoke/batch/stream 等方法,与原书第 7 节所述一致)。

2. 检索器:真正影响结果的旋钮都在这层

向量库上面的检索器层藏着一组直接决定检索质量的选项,原书一个个演示7:

检索器一句话适用
dense(默认)as_retriever(search_kwargs={"k": 10}),取最相似的 10 条大多数场景的起点
score thresholdsimilarity_score_threshold: 0.5——低于阈值(即划定的分数及格线)的结果直接丢宁缺毋滥的场景
MMRsearch_type="mmr"——兼顾相关与多样,主动避免返回十个差不多的结果结果同质化严重时
BM25 sparse关键词检索(第 05 章)专名、代码、精确匹配
Ensemble多路检索加权融合(如 dense 0.5 + sparse 0.5),内置 RRF 排序混合检索
KNNRetriever暴力精确检索小数据(见下)

还有两个挂在美国数据源上的特殊检索器,展示了「检索器」概念的弹性: WikipediaRetriever 直接把维基百科当检索库(作者用它查「加勒比海盗黄金时代」, 返回词条、摘要、来源链接);同类还有 PubMedRetriever(生物医学)、ArxivRetriever(200 万+论文)、 KayAiRetriever(SEC 财报)8

kNN 的翻案文章

原书在这里写了一段反主流的判断,值得整段记住9: k-NN 的精度至今仍优于一切后继者,包括所有向量厂商主推的 ANN——这不是印刷错误。 那为什么大家都用 ANN?因为 k-NN 不扩展,而厂商的目标客户是企业级规模。 但「企业级」是相对的:一百万条 1536 维向量,听上去很多,在全球企业尺度上其实很小,k-NN 毫无压力。 作者的判据:先用 k-NN,直到等待时间真的不可忍受,再换 ANN。很多中小项目用 ANN,纯属白丢精度。

最后是两个容易被忽略的特殊检索器:time-weighted(给新内容加权,治「老答案霸榜」) 和 Long-Context Reorder(把最相关的材料挪到上下文两头,治第 01 章提过的 lost in the middle)10

3. LLM:最不该忠诚的位置

LLM 是三大件里最该「货比三家」的位置。原书的经济账11:

  • gpt-4o-mini 是 GPT-4 系列里最便宜的,但仍是 gpt-3.5-turbo 的 10 倍价;
  • 贵的模型不一定合适:gpt-4-32k 又贵又不如 4o-mini 快和强,只是上下文大 4 倍;
  • 结论:别假设最新=最好=最贵,每次新模型发布都值得用评测(第 06 章)重新比一遍

开源路子:Together AI 一个 API 提供 200+ 开源模型。原书实测把管道里的 gpt-4o-mini 换成 Llama 3 70B(每百万 token $0.88)和 Mixtral MoE(MoE 即混合专家——多个小专家网络分工协作——的架构),同一问题(「Google 有哪些环保举措」) 的回答质量与 GPT-4o-mini 相当甚至更全面,而成本显著更低12

另外所有 LLM 都实现 Runnable 接口,自动获得 async/stream/batch 能力: 异步(即发出调用后不等结果、先干别的)默认起线程跑同步调用;batch 用多线程或 asyncio.gather 并发,max_concurrency 控制并发上限13

4. 配套件三兄弟

4.1 document loaders:什么格式都能吃

loaders 把各种来源的数据转成统一的 Document 对象。原书做了个聪明的演示: 把同一份 Google 环境报告 PDF 转存成 HTML、Word、JSON 三种格式, 分别用 BSHTMLLoaderPdfReaderDocx2txtLoaderJSONLoader 加载——产物完全互换, 下游管道一行不用改14。两个实战细节:不同 loader 会往 metadata 里塞自己的字段, 合并自己的元数据时要写 {**doc.metadata, "id": ...} 防覆盖;PDF 提取器有一排备选 (PyPDF2、PyMuPDF、Unstructured、Azure 文档智能等),复杂版式要挑着用15

4.2 text splitters:切块的学问

第 02 章用切块是「照抄参数」,这里讲清楚机理。为什么切,第 02 章说过:embedding 输入上限 + 块大语义稀释。怎么切才是学问16:

  • CharacterTextSplitter:按单一分隔符硬切。书里抓到一个经典坑: 它的默认分隔符是 \n\n(双换行),而目标文档里根本没有双换行——结果永不切分。 分隔符必须匹配你的内容17;
  • RecursiveCharacterTextSplitter(推荐默认):按分隔符列表 ["\n\n", "\n", ". ", " ", "" 递归降级——先试段落,段落太大改切句子,再不行切词。它保住的是「语义上属于一起的文字不被拆开」18;
  • overlap(重叠) 的直觉来自 CNN——一类擅长看图像的神经网络——的滑窗:相邻块共享一段文字,边界语义不丢19

卷积(即用小窗口逐块扫过输入、提取局部模式)是 CNN 名字的来历;切分器要保住的,正是每一段完整的局部内容。

原书推荐了 Greg Kamradt 的 ChunkViz 可视化工具,把切块结果涂色展示:同一份文本, recursive 切分器能整段整段地切,character 切分器任何参数下都会把句子拦腰斩断—— 半个句子的块,对 LLM 就是噪声20

4.3 output parsers:把回答变成结构

默认的 StrOutputParser 剥出纯文本;要结构化输出(即让模型按预定格式——如 JSON——返回)时用 JsonOutputParser + Pydantic 模型 定义字段(第 03 章守护 LLM 输出的 1-5 分数就是这条路)。原书补了一句时效性判断: 新模型大多内建了 JSON/XML 结构化输出,这个解析器是给不支持的模型准备的21。 书里还把此前两条并行链合并成了一条大链,展示了 LCEL 组合复杂逻辑的最终形态22

5. 边界与局限

  • 原书的选项清单是「与 LangChain 集成的」子集,各零件的真实生态都更大;
  • LCEL 的链调试体验一般——链越长,中间状态越难看(第 08 章的 agent 会改用图的可视化来缓解);
  • 开源模型的「质量相当」结论基于单一问题的对比,严肃选型仍要回到第 06 章的评测。

6. 可带走的

  1. 框架的价值 = 统一接口 + 可替换零件;换向量库一两行,前提是你只依赖抽象层。
  2. 检索质量的真实旋钮在检索器层:阈值、MMR、混合、kNN——先调这层,再动模型。
  3. k-NN 翻案:小数据(<百万级)先上暴力搜索,精度白赚。
  4. LLM 是最不该忠诚的位置——10 倍价差可能买到的是同等质量;用评测说话。
  5. CharacterTextSplitter 的「永不切分」坑:默认分隔符 \n\n 不是所有文本都有。
  6. 切块先可视化(ChunkViz)再上线——半个句子就是喂给 LLM 的噪声。
  7. 新模型多内建结构化输出,JSON 解析器是兼容层的角色。

7. 原文地图

主题原书章原文位置
统一接口与换库不改逻辑Key RAG Components in LangChaintext/72-fm-key-rag-components-in-langchain.txt:38(搜「unified interface」)
49 种向量库Key RAG Components in LangChaintext/72-fm-key-rag-components-in-langchain.txt:62(搜「49 vector store options」)
换 FAISS 的改动Key RAG Components in LangChaintext/72-fm-key-rag-components-in-langchain.txt:104(搜「FAISS.from_documents」) · text/72-fm-key-rag-components-in-langchain.txt:118(搜「faiss-gpu」)
Weaviate schema 与 doc_idKey RAG Components in LangChaintext/72-fm-key-rag-components-in-langchain.txt:196(搜「schema.create_class」) · text/72-fm-key-rag-components-in-langchain.txt:242(搜「doc_id」)
换组件是核心价值Key RAG Components in LangChaintext/72-fm-key-rag-components-in-langchain.txt:285(搜「swap components in and out」)
检索器各选项Key RAG Components in LangChaintext/72-fm-key-rag-components-in-langchain.txt:318(搜「dense_retriever」) · text/72-fm-key-rag-components-in-langchain.txt:333(搜「similarity_score_threshold」) · text/72-fm-key-rag-components-in-langchain.txt:337(搜「search_type=」)
Wikipedia 检索器与同类Key RAG Components in LangChaintext/72-fm-key-rag-components-in-langchain.txt:430(搜「WikipediaRetriever」) · text/72-fm-key-rag-components-in-langchain.txt:468(搜「PubMedRetriever」)
kNN 翻案Key RAG Components in LangChaintext/72-fm-key-rag-components-in-langchain.txt:477(搜「still better than anything」) · text/72-fm-key-rag-components-in-langchain.txt:480(搜「1 million data points」)
time-weighted 与 ReorderKey RAG Components in LangChaintext/72-fm-key-rag-components-in-langchain.txt:496(搜「time-weighted」) · text/72-fm-key-rag-components-in-langchain.txt:496(搜「Long-Context Reorder」)
10 倍价差与模型经济账Key RAG Components in LangChaintext/72-fm-key-rag-components-in-langchain.txt:572(搜「10X more than gpt-3.5-turbo」)
别假设最新最贵最好Key RAG Components in LangChaintext/72-fm-key-rag-components-in-langchain.txt:575(搜「shouldn」所在段,搜「most expensive」)
Together AI 实测Key RAG Components in LangChaintext/72-fm-key-rag-components-in-langchain.txt:584(搜「Together AI」) · text/72-fm-key-rag-components-in-langchain.txt:593(搜「0.88 per 1M tokens」) · text/72-fm-key-rag-components-in-langchain.txt:779(搜「considerably lower cost」)
Runnable 接口能力Key RAG Components in LangChaintext/72-fm-key-rag-components-in-langchain.txt:788(搜「ainvoke」) · text/72-fm-key-rag-components-in-langchain.txt:809(搜「max_concurrency」)
loaders 演示(一份数据四种格式)Using LangChain to Get More from RAGtext/73-fm-using-langchain-to-get-more-from-rag.txt:66(搜「HTML/web version」) · text/73-fm-using-langchain-to-get-more-from-rag.txt:122(搜「interchangeably」)
metadata 合并防覆盖Using LangChain to Get More from RAGtext/73-fm-using-langchain-to-get-more-from-rag.txt:181(搜「additional metadata」)
PDF 提取器清单Using LangChain to Get More from RAGtext/73-fm-using-langchain-to-get-more-from-rag.txt:146(搜「PyMuPDF」)
为什么切(上限+稀释)Using LangChain to Get More from RAGtext/73-fm-using-langchain-to-get-more-from-rag.txt:217(搜「8,191 tokens」)
CharacterTextSplitter 永不切分坑Using LangChain to Get More from RAGtext/73-fm-using-langchain-to-get-more-from-rag.txt:284(搜「never splits」)
Recursive 递归算法三步Using LangChain to Get More from RAGtext/73-fm-using-langchain-to-get-more-from-rag.txt:355(搜「recursively applies」)
overlap 与 CNN 滑窗类比Using LangChain to Get More from RAGtext/73-fm-using-langchain-to-get-more-from-rag.txt:297(搜「convolutional neural」)
ChunkViz 对比Using LangChain to Get More from RAGtext/73-fm-using-langchain-to-get-more-from-rag.txt:220(搜「ChunkViz」) · text/73-fm-using-langchain-to-get-more-from-rag.txt:233(搜「middle of a sentence」)
JSON parser 与内建结构化输出Using LangChain to Get More from RAGtext/73-fm-using-langchain-to-get-more-from-rag.txt:404(搜「built-in ways」)
两链合一Using LangChain to Get More from RAGtext/73-fm-using-langchain-to-get-more-from-rag.txt:466(搜「one larger chain」)

Footnotes

  1. 出处:「Key RAG Components in LangChain」第 285 段(text/72-fm-key-rag-components-in-langchain.txt:285,搜「swap components in and out」)。

  2. 出处:「Key RAG Components in LangChain」第 38 段(text/72-fm-key-rag-components-in-langchain.txt:38,搜「unified interface」)。

  3. 出处:「Key RAG Components in LangChain」第 62 段(text/72-fm-key-rag-components-in-langchain.txt:62,搜「49 vector store options」)。

  4. 出处:「Key RAG Components in LangChain」第 103-109 段(text/72-fm-key-rag-components-in-langchain.txt:104,搜「FAISS.from_documents」)与第 118-126 段(text/72-fm-key-rag-components-in-langchain.txt:118,搜「faiss-gpu」)。

  5. 出处:「Key RAG Components in LangChain」第 196 段(text/72-fm-key-rag-components-in-langchain.txt:196,搜「schema.create_class」)、第 242 段(text/72-fm-key-rag-components-in-langchain.txt:242,搜「doc_id」)、第 185-190 段(text/72-fm-key-rag-components-in-langchain.txt:186,搜「delete_class」)。

  6. 出处:「Key RAG Components in LangChain」第 282 段(text/72-fm-key-rag-components-in-langchain.txt:282,搜「simpler and more flexible」)。

  7. 出处:「Key RAG Components in LangChain」第 318-397 段(text/72-fm-key-rag-components-in-langchain.txt:318,搜「dense_retriever」;text/72-fm-key-rag-components-in-langchain.txt:333,搜「similarity_score_threshold」;text/72-fm-key-rag-components-in-langchain.txt:86,搜「mmr」;text/72-fm-key-rag-components-in-langchain.txt:384,搜「EnsembleRetriever」)。

  8. 出处:「Key RAG Components in LangChain」第 430-465 段(text/72-fm-key-rag-components-in-langchain.txt:430,搜「WikipediaRetriever」)与第 468 段(text/72-fm-key-rag-components-in-langchain.txt:468,搜「PubMedRetriever」)。

  9. 出处:「Key RAG Components in LangChain」第 477-480 段(text/72-fm-key-rag-components-in-langchain.txt:477,搜「still better than anything」;text/72-fm-key-rag-components-in-langchain.txt:480,搜「1 million data points」)。

  10. 出处:「Key RAG Components in LangChain」第 496 段(text/72-fm-key-rag-components-in-langchain.txt:496,搜「time-weighted」;同段搜「Long-Context Reorder」)。

  11. 出处:「Key RAG Components in LangChain」第 572 段(text/72-fm-key-rag-components-in-langchain.txt:572,搜「10X more than gpt-3.5-turbo」)与第 575 段(text/72-fm-key-rag-components-in-langchain.txt:575,搜「most expensive」)。

  12. 出处:「Key RAG Components in LangChain」第 584 段(text/72-fm-key-rag-components-in-langchain.txt:584,搜「Together AI」)、第 593 段(text/72-fm-key-rag-components-in-langchain.txt:593,搜「0.88 per 1M tokens」)、第 779 段(text/72-fm-key-rag-components-in-langchain.txt:779,搜「considerably lower cost」)。

  13. 出处:「Key RAG Components in LangChain」第 788 段(text/72-fm-key-rag-components-in-langchain.txt:788,搜「ainvoke」)与第 809 段(text/72-fm-key-rag-components-in-langchain.txt:809,搜「max_concurrency」)。

  14. 出处:「Using LangChain to Get More from RAG」第 66 段(text/73-fm-using-langchain-to-get-more-from-rag.txt:66,搜「HTML/web version」)与第 122 段(text/73-fm-using-langchain-to-get-more-from-rag.txt:122,搜「interchangeably」)。

  15. 出处:「Using LangChain to Get More from RAG」第 181 段(text/73-fm-using-langchain-to-get-more-from-rag.txt:181,搜「additional metadata」)与第 146 段(text/73-fm-using-langchain-to-get-more-from-rag.txt:146,搜「PyMuPDF」)。

  16. 出处:「Using LangChain to Get More from RAG」第 217 段(text/73-fm-using-langchain-to-get-more-from-rag.txt:217,搜「8,191 tokens」)。

  17. 出处:「Using LangChain to Get More from RAG」第 284 段(text/73-fm-using-langchain-to-get-more-from-rag.txt:284,搜「never splits」)。

  18. 出处:「Using LangChain to Get More from RAG」第 329 段(text/73-fm-using-langchain-to-get-more-from-rag.txt:329,搜「recursively splits text」)与第 355 段(text/73-fm-using-langchain-to-get-more-from-rag.txt:355,搜「recursively applies」)。

  19. 出处:「Using LangChain to Get More from RAG」第 297 段(text/73-fm-using-langchain-to-get-more-from-rag.txt:297,搜「convolutional neural」)。

  20. 出处:「Using LangChain to Get More from RAG」第 220-233 段(text/73-fm-using-langchain-to-get-more-from-rag.txt:220,搜「ChunkViz」;text/73-fm-using-langchain-to-get-more-from-rag.txt:233,搜「middle of a sentence」)。

  21. 出处:「Using LangChain to Get More from RAG」第 404 段(text/73-fm-using-langchain-to-get-more-from-rag.txt:404,搜「built-in ways」)。

  22. 出处:「Using LangChain to Get More from RAG」第 466 段(text/73-fm-using-langchain-to-get-more-from-rag.txt:466,搜「one larger chain」)。