跳到主要内容

LangChain — 把提示工程变成管道工程

这一章讲三件事: 什么时候「直接调 API」就不够用了; LangChain 把前面几章的手工活(拼提示、解输出、切文档)做成了哪些标准零件; 以及一条完整的多步管线长什么样——风格进去,角色、情节、场景、台词出来。 对应原书第 4 章全章。

1. 这一章讲什么

第 01 章到第 06 章的技术,全部都是「一次调用」内的事情。书里划出了三类徒手干不动的活1:

  • 上下文长度:把一整本书摘要成一篇梗概——塞不进窗口(第 05 章的预算问题);
  • 顺序组合:造一个故事,要先生成角色、再生成情节、再生成场景——一步的输出是下一步的输入;
  • 复杂推理:让模型当 agent(智能体:自己决定下一步做什么的程序,第 10 章专讲),比如帮你完成健身计划。

这三类活需要的不只是提示,还有数据从哪来、步骤怎么串、中间结果怎么存。这就是框架的位置。LangChain 是一个开源(源代码公开、人人可改可用)的框架,Python 和 TypeScript 两个版本,书里选它的理由:它相信最有价值的应用不会只「调一下 API」,还会做两件事——接上外部数据(书里叫 data awareness,数据感知)和让模型能动手影响环境(agency,能动性)2

主走查:一家电影公司的故事管线。 输入只有一个词「Fantasy」(奇幻),管线自动走完:生成角色 → 生成情节 → 拆出场景 → 逐场景写台词脚本,后一场还记得前一场讲了什么。这条管线把本章的零件几乎全用上了。

2. 顶层全景:六个模块,一根管道

徒手调 API: 你的代码 → 拼字符串当提示 → 模型 → 一段文本 → 自己写正则解析

上框架之后:
数据侧: document loaders → text splitters → 嵌入模型 → 向量库 → retriever
(把外部资料读进来、切开、转成向量、存好、按意思取)——第 09 章专讲取的部分

调用侧: prompt 模板 → model → output parser → 你的数据结构
└────────── LCEL 管道(竖线 | 串起来)──────────┘

图说:LangChain 把「拼提示、调模型、解输出」三件手工活做成标准零件,
再用管道符串成链。顺序敏感:数据从左往右流。

书里把框架整理成六个模块3,先混个脸熟,本章和第 09、10 章逐个用到:

模块管什么
Model I/O调模型本身:消息、提示模板、输出解析
Retrieval检索(按意思把相关资料找出来):加载、切分、向量库(存向量、按距离查的库)、取回
Chains链:把多步调用串成一条
Agents智能体:让模型自己决定下一步
Memory记忆(模型本身不记得上一轮对话,历史得外挂):存取对话状态
Callbacks回调(事件发生时自动执行的钩子函数):观测每一步在干什么

3. 核心原理

3.1 三种消息与三种调法

先立地基。聊天模型的输入不是一句话,而是一组消息,LangChain 里分三种4:系统消息(SystemMessage,定规矩的那条,比如「你是只回 JSON 的助手」)、人类消息(HumanMessage,用户说的)、AI 消息(AIMessage,模型回的——聊天模型的输出永远是这一种)。第 05 章数标记时见过的 system/user/assistant 角色,在这里变成了三个类。

调法也有三种,各治一个需求5:

调法干什么什么时候用
.invoke()一问一答,等全部写完默认
.stream()流式输出:写一块吐一块,不用等整篇给用户看进度,降低等待感
.batch()一次发多条,并行(同时进行多条请求)跑完一批多条的任务;max_concurrency=5 控制同时最多几条

还有第四类:所有方法加 a 前缀(.ainvoke().abatch())变成异步(async:发请求后不阻塞等待,可以同时跑别的)版本——书里一句话讲清了它的价值:让用户感知的延迟(等回答的时间)降下来6

3.2 模板与 LCEL:从拼字符串到拼管道

提示模板(prompt template)就是一个带占位符的提示:「给 {genre} 类型想三到五个角色」。它比 Python 的 f-string(字符串内插)贵,贵的理由是四样配套:校验输入、组合复用、接选择器(§3.5)、存成文件版本化7

真正的主角是 LCEL(LangChain Expression Language,LangChain 表达式语言):它借来 Unix 的管道符 |——竖线左边零件的输出,就是右边零件的输入8。于是一条调用链写成一行:

chain = prompt | model | parser

图说:字典塞进 prompt → 填好占位符的提示进 model → 模型的文本输出进 parser
→ 出来的是解析好的数据结构。顺序敏感:写反了就报错或出垃圾。

有一个坑书里专门立了一节:LCEL 链的第一节必须是 runnable(可运行单元:遵守统一调用接口的零件,模板、模型、解析器都是)——直接塞一个 Python 字典进去会报错,要先用 RunnablePassthrough()(原样透传输入的零件)包一下9。这个坑在主走查里马上会踩。

3.3 输出解析器:把第 04 章的军规自动化

第 04 章为了拿到 JSON,我们手动立了三条军规(只回合法 JSON、禁反引号、会被 json.loads 解析),还自己写正则兜底。LangChain 把这件事做成了一类零件:输出解析器(output parser)10。书里列了八种,最常用的叫 Pydantic 解析器——Pydantic 是 Python 的数据校验库,你用类型注解声明「我要的数据长什么样」,它负责校验和解析11

它的巧处在 .get_format_instructions():这个方法是根据你声明的数据结构,自动生成一段格式说明文字,注入提示12。也就是说,「只回合法 JSON、字段(数据的每一列)名是这些」这条提示不再是你手写的,是从代码里的结构声明推导出来的——改结构就自动改提示,两处永远不会脱节。解析失败还有 retry 解析器(拿错误信息再调一次模型修格式)和 auto-fixing 解析器兜底10

3.4 函数调用:模型挑函数,程序执行

到目前为止,模型只会「说」。函数调用(function calling)让它能「伸手」:你用 JSON schema(JSON 格式的结构声明:函数名、一句话描述、参数清单)告诉模型「外面有这几个函数可调用」,模型在回答里给出「我想调 schedule_meeting,参数是 date=..., time=..., attendees=...」——真正执行函数的是你的程序,模型只负责挑和填参数13

书里的演示函数 schedule_meeting 是个模拟件:收到参数就回 {"event_id": "1234", "status": "Meeting scheduled successfully!"}13。两个工程细节:tool_choice 参数控制自由度——"auto" 模型自己决定调不调(默认)、"none" 禁止调、指名则强制调某一个;模型还可以一次请求调多个函数(并行函数调用)14。第 10 章会把它和 ReAct 摆在一张表里选型。

3.5 few-shot 选择器:例子按长度动态增减

第 01 章的「给例子」有个遗留问题:例子该塞几个?书里的场景:一个讲故事应用,用户给的角色名单时长时短——名单长,再塞一堆例子就撑爆窗口。解法是一个会根据输入长度动态增减例子数量的零件:LengthBasedExampleSelector(按长度选例子的选择器)15

机制一句话:给例子池设一个总预算(max_length=1000,用第 05 章的 tiktoken 按标记数算),输入长就少塞几个例子,输入短就多塞15。模板侧对应 FewShotChatMessagePromptTemplate(把例子渲染成一组对话消息的模板)16。书里也如实写了代价:模型可能过拟合(overfitting,第 03 章讲过:对例子学得太死)到例子上,把例子看得比真正的指令还重17

3.6 数据管道五件套

「接上外部数据」在 LangChain 里是五节管道18:

document loaders → document transformers → 嵌入模型 → 向量库 → retrievers
(读文件成文档) (切分/清洗) (转成数串) (存) (按意思取)

图说:一本 497 页的营销教材走一遍这条管道,变成 776 个文档对象,
等着被检索。切分用的就是第 05 章讲的按标记递归切分器。

加载器(loaders)负责把 PDF、Word、CSV 读成统一的文档对象;transformers 里最常用的就是第 05 章讲过的递归切分器;后面三节(嵌入、向量库、retriever,取回器)是第 09 章的全部内容,这里只要知道管道通向哪里19。书里还提了一句同类框架 LlamaIndex 和它的加载器库 LlamaHub,可以和 LangChain 混用20

3.7 主走查:电影公司管线

现在把零件装成一台机器。任务:输入「Fantasy」,产出一个短篇故事的骨架。书里的实现分两层21:

第一层:三步生成。

输入 {"genre": "Fantasy"}
→ character_chain:想 3-5 个角色(每个要名字+小传)
→ plot_chain: 拿着角色和类型,生成情节
→ scene_chain: 拿着情节,拆出场景清单
一次 invoke 的产出:5 个角色 + 9 个场景[^22]

三步串成一条 master_chain。这里踩了 §3.2 那个坑:第一节用 RunnablePassthrough() 把输入字典原样传下去,后面每一步用 itemgetter(Python 里「从字典取某个键」的工具)抽出自己要的那部分21。这三步就是第 01 章「分工」原则的代码形态,书里也点名了这层对应22

第二层:逐场景写台词,后一场记得前一场。 场景清单拆成列表,循环九次,每次调一条「写台词脚本」的链。关键在提示里的这个占位符:previous_scene_summary(前一场的摘要)——写完一场,立刻调另一条摘要链把这场压缩成一段摘要,塞进下一场的提示23:

第 1 场:提示里 previous_scene_summary = 空 → 写出脚本 → 摘要存起来
第 2 场:提示里带着第 1 场摘要 → 「不许重复前情」 → 写出脚本 → 更新摘要
……到第 9 场

这个「摘要喂下一场」的构造,就是记忆的最小形态:模型自己不记得写过什么,你把压缩过的历史塞回提示里。第 10 章会把这个思路展开成五种记忆方案。书里还点了一个省钱开关:这条链里实际用的是 gpt-3.5-turbo-16k 而非最贵的模型——用链条拆开任务之后,聪明模型出构思、便宜模型出文字,往往效果最好24

3.8 文档链:一组文档的四种吃法

最后一种零件回答的是「手里有一组文档(比如 776 块),怎么让模型一起处理」。书里给了四种文档链(document chain),一行 chain_type= 就能换25:

怎么转适用代价
Stuff全部塞进一个提示文档小、数量少大了直接撑爆窗口
Refine逐块循环:每来一块,迭代改写当前的答案要渐进提炼的活一圈圈调模型,慢,实时场景不合适
Map Reduce每块各自处理(映射:各算各的),再把结果归并一次很多份,可再递归压缩会丢掉文档顺序
Map Re-rank每块各自回答并给置信度,选分最高的答案只藏在某一块里打分不准就选错

(这张表是书里 Table 4-1 的压缩版26。)注意它们全是「分工」原则在文档集合上的变体:塞不下就拆开,拆开就要回答「散的结果怎么合」。

4. 作者的判断与证据

  • 本章所有零件都有可运行代码,包括完整的主走查管线(5 角色、9 场景是书里交代的真实产出数)27;
  • 「聪明的构思、便宜的生成」是作者的经验法则,书里一句话带过,没有量化对比24;
  • 顺序链的适用边界(慢、适合服务端任务、用户不等实时反馈)书里写得明确28;
  • 评测一节(LLM 当裁判、两两比较)已收进第 07 章,这里不重复。

判断(我们的,不是书里的): LCEL 真正的改变不是少写几行代码,而是把「提示」从字符串(一串字符)变成了数据流(数据沿零件流动的路径)的一个环节。手写时代,提示是埋在代码里的一坨字符;管道时代,提示、模型、解析器是三个可替换的零件——于是「换个模型试试」「换个格式约束」从改字符串变成换零件。这也解释了为什么框架对一次性脚本常常是过度设计:零件化的收益只在「需要反复替换」时才兑现。 如果错,会错在: 如果团队根本没有替换零件的需求(模型固定、格式固定),管道就是纯粹的开销——书里「简单任务徒手就够」那句话1此时权重更高。

5. 边界与局限

  • 书里写的是 2024 年的 LangChain。 这个框架以接口漂移出名:写作期间 OpenAI 已经把 Functions 改名 Tools,书里自己在对照表里标注了「已弃」;文档链在新版里也在往 LCEL 迁移(补充(不在书里,来自通用知识):load_summarize_chain 这类便捷入口后来进了 legacy 包)。读这章学的是「零件怎么想」,具体类名以当时的文档为准;
  • 框架遮不住底层的账。 标记照算钱、窗口照样有限——第 05 章的预算纪律一条不少;
  • 顺序链每多一步就多一次调用:延迟和成本线性(一步一份、成正比)叠加,书里明说它适合服务端28;
  • 选择器只按长度增减例子,不按内容选——按意思选例子要靠检索,那是第 09 章的事。

6. 可带走的

  1. 单步任务徒手调 API;出现「整本书」「多步组合」「自己动手」三种信号,再上框架;
  2. 消息只有三种:定规矩的、用户的、模型的——system 消息是放角色的标准位置;
  3. 要等待感消失用 .stream();要吞吐(单位时间完成的请求量)用 .batch(),并加上「同时最多几条」的限制;
  4. 链 = 模板 | 模型 | 解析器;第一节必须是 runnable,字典先过 RunnablePassthrough;
  5. 格式约束别手写:Pydantic 声明结构,格式说明自动注入提示,结构与提示永不脱节;
  6. 函数调用 = 模型挑函数填参数,执行永远在你的程序里;
  7. 例子池设总预算,按输入长度动态增减;
  8. 多步管线的标配:聪明模型出构思,便宜模型出文字;
  9. 后一步要「记得」前一步,就把前一步的摘要塞回提示——这是记忆的最小形态;
  10. 一组文档四种吃法:塞得下用 Stuff,要迭代用 Refine,一次很多份用 Map Reduce,找单一答案用 Map Re-rank。

7. 原文地图

主题原书章原文位置
何时需要框架(三类问题)Ch.4 LangChain 进阶text/24-fm-introduction-to-langchain.txt:7(搜「Context length」)
两支柱与六模块Ch.4 LangChain 进阶text/24-fm-introduction-to-langchain.txt:37(搜「six common modules」)
三种消息Ch.4 LangChain 进阶text/25-fm-environment-setup.txt:47(搜「AIMessage, HumanMessage, and SystemMessage」)
流式与批量Ch.4 LangChain 进阶text/25-fm-environment-setup.txt:103(搜「yields chunks」) · text/25-fm-environment-setup.txt:135(搜「max_concurrency」)
异步调用Ch.4 LangChain 进阶text/25-fm-environment-setup.txt:141(搜「asynchronous」)
LCEL 管道符Ch.4 LangChain 进阶text/25-fm-environment-setup.txt:173(搜「Unix pipe」)
第一节必须 runnableCh.4 LangChain 进阶text/31-fm-structuring-lcel-chains.txt:3(搜「must ensure that the first part」)
八种输出解析器Ch.4 LangChain 进阶text/25-fm-environment-setup.txt:267(搜「Output Parsers」) · text/25-fm-environment-setup.txt:289(搜「Pydantic (JSON) parser」)
格式说明注入Ch.4 LangChain 进阶text/25-fm-environment-setup.txt:370(搜「get_format_instructions」)
函数调用的 schemaCh.4 LangChain 进阶text/25-fm-environment-setup.txt:719(搜「JSON schema」) · text/25-fm-environment-setup.txt:842(搜「tool_choice」)
并行函数调用Ch.4 LangChain 进阶text/25-fm-environment-setup.txt:846(搜「parallel function calling」)
按长度选例子Ch.4 LangChain 进阶text/28-fm-selecting-few-shot-examples-by-length.txt:3(搜「LengthBasedExampleSelector」) · text/28-fm-selecting-few-shot-examples-by-length.txt:31(搜「max_length=1000」)
过拟合到例子Ch.4 LangChain 进阶text/28-fm-selecting-few-shot-examples-by-length.txt:83(搜「overfit」)
数据管道五件套Ch.4 LangChain 进阶text/28-fm-selecting-few-shot-examples-by-length.txt:155(搜「Document loaders」)
497 页到 776 块Ch.4 LangChain 进阶text/28-fm-selecting-few-shot-examples-by-length.txt:371(搜「776 smaller」)
电影公司管线Ch.4 LangChain 进阶text/29-fm-sequential-chain.txt:5(搜「character_generation_chain」) · text/30-fm-itemgetter-and-dictionary-key-extraction.txt:93(搜「master_chain」)
5 角色 9 场景Ch.4 LangChain 进阶text/30-fm-itemgetter-and-dictionary-key-extraction.txt:111(搜「five characters and nine scenes」)
前一场摘要喂下一场Ch.4 LangChain 进阶text/30-fm-itemgetter-and-dictionary-key-extraction.txt:129(搜「previous_scene_summary」)
聪明构思便宜生成Ch.4 LangChain 进阶text/30-fm-itemgetter-and-dictionary-key-extraction.txt:231(搜「smart model for the ideation」)
顺序链适合服务端Ch.4 LangChain 进阶text/31-fm-structuring-lcel-chains.txt:42(搜「server-side」)
四种文档链Ch.4 LangChain 进阶text/36-fm-map-re-rank.txt:5(搜「Table 4-1」) · text/32-fm-document-chains.txt:40(搜「chain_type」)

Footnotes

  1. 出处:「Ch.4 LangChain 进阶」第 7 段(text/24-fm-introduction-to-langchain.txt:7,搜「Context length」)。原文:简单的提示工程技术对多数任务够用,但三类问题需要更强的工具箱。 2

  2. 出处:「Ch.4 LangChain 进阶」第 25 段(text/24-fm-introduction-to-langchain.txt:25,搜「data awareness」)。原文的两个支柱:Enhance data awareness 与 Enhance agency。

  3. 出处:「Ch.4 LangChain 进阶」第 37 段(text/24-fm-introduction-to-langchain.txt:37,搜「six common modules」)。

  4. 出处:「Ch.4 LangChain 进阶」第 47 段(text/25-fm-environment-setup.txt:47,搜「AIMessage, HumanMessage, and SystemMessage」)。

  5. 出处:「Ch.4 LangChain 进阶」第 103 段(text/25-fm-environment-setup.txt:103,搜「yields chunks」)与第 128-135 段(text/25-fm-environment-setup.txt:128,搜「parallelize」;text/25-fm-environment-setup.txt:135,搜「max_concurrency」)。

  6. 出处:「Ch.4 LangChain 进阶」第 141 段(text/25-fm-environment-setup.txt:141,搜「asynchronous」)与第 143 段(text/25-fm-environment-setup.txt:143,搜「ainvoke」)。

  7. 出处:「Ch.4 LangChain 进阶」第 95 段(text/28-fm-selecting-few-shot-examples-by-length.txt:95,搜「store prompts as files」)。原文还强调模板便于分享、存储与版本化。

  8. 出处:「Ch.4 LangChain 进阶」第 173 段(text/25-fm-environment-setup.txt:173,搜「Unix pipe」)。

  9. 出处:「Ch.4 LangChain 进阶」第 3 段(text/31-fm-structuring-lcel-chains.txt:3,搜「must ensure that the first part」)与第 20 段(text/31-fm-structuring-lcel-chains.txt:20,搜「will not create a runnable」)。

  10. 出处:「Ch.4 LangChain 进阶」第 267 段(text/25-fm-environment-setup.txt:267,搜「Output Parsers」)。八种:列表、日期时间、枚举、auto-fixing、Pydantic、retry、结构化、XML。 2

  11. 出处:「Ch.4 LangChain 进阶」第 317 段(text/25-fm-environment-setup.txt:317,搜「data validation library」)。

  12. 出处:「Ch.4 LangChain 进阶」第 307 段(text/25-fm-environment-setup.txt:307,搜「get_format_instructions」)与第 370 段(text/25-fm-environment-setup.txt:370,搜「format_instructions」)。

  13. 出处:「Ch.4 LangChain 进阶」第 719 段(text/25-fm-environment-setup.txt:719,搜「JSON schema」)与第 736 段(text/25-fm-environment-setup.txt:736,搜「guiding the LLM」)。 2

  14. 出处:「Ch.4 LangChain 进阶」第 838 段(text/25-fm-environment-setup.txt:838,搜「tool_choice」)与第 846 段(text/25-fm-environment-setup.txt:846,搜「parallel function calling」)。

  15. 出处:「Ch.4 LangChain 进阶」第 3 段(text/28-fm-selecting-few-shot-examples-by-length.txt:3,搜「LengthBasedExampleSelector」)与第 31 段(text/28-fm-selecting-few-shot-examples-by-length.txt:31,搜「max_length=1000」)。用 tiktoken 数标记的定制见第 40 段。 2

  16. 出处:「Ch.4 LangChain 进阶」第 123 段(text/28-fm-selecting-few-shot-examples-by-length.txt:123,搜「FewShotChatMessagePromptTemplate」)。

  17. 出处:「Ch.4 LangChain 进阶」第 83 段(text/28-fm-selecting-few-shot-examples-by-length.txt:83,搜「overfit」)。

  18. 出处:「Ch.4 LangChain 进阶」第 155-171 段(text/28-fm-selecting-few-shot-examples-by-length.txt:155,搜「Document loaders」;text/28-fm-selecting-few-shot-examples-by-length.txt:171,搜「Retrievers」)。

  19. 出处:「Ch.4 LangChain 进阶」第 371 段(text/28-fm-selecting-few-shot-examples-by-length.txt:371,搜「776 smaller」)。497 页切出 776 个文档对象,用的是每块 500 标记、重叠 50。

  20. 出处:「Ch.4 LangChain 进阶」第 175 段(text/28-fm-selecting-few-shot-examples-by-length.txt:175,搜「LlamaIndex」)。

  21. 出处:「Ch.4 LangChain 进阶」第 5 段(text/29-fm-sequential-chain.txt:5,搜「character_generation_chain」)与第 35 段(text/30-fm-itemgetter-and-dictionary-key-extraction.txt:35,搜「master_chain」)。 2

  22. 出处:「Ch.4 LangChain 进阶」第 229 段(text/30-fm-itemgetter-and-dictionary-key-extraction.txt:229,搜「Divide Labor principle」)。

  23. 出处:「Ch.4 LangChain 进阶」第 129 段(text/30-fm-itemgetter-and-dictionary-key-extraction.txt:129,搜「previous_scene_summary」)。提示里写明:用前一场摘要保持连贯、避免重复。

  24. 出处:「Ch.4 LangChain 进阶」第 231 段(text/30-fm-itemgetter-and-dictionary-key-extraction.txt:231,搜「smart model for the ideation」)。链里用 gpt-3.5-turbo-16k 见第 171 段(text/30-fm-itemgetter-and-dictionary-key-extraction.txt:171,搜「gpt-3.5-turbo-16k」)。 2

  25. 出处:「Ch.4 LangChain 进阶」第 40 段(text/32-fm-document-chains.txt:40,搜「chain_type」)。

  26. 出处:「Ch.4 LangChain 进阶」第 5-37 段(text/36-fm-map-re-rank.txt:5,搜「Table 4-1」)。

  27. 出处:「Ch.4 LangChain 进阶」第 111 段(text/30-fm-itemgetter-and-dictionary-key-extraction.txt:111,搜「five characters and nine scenes」)。

  28. 出处:「Ch.4 LangChain 进阶」第 42 段(text/31-fm-structuring-lcel-chains.txt:42,搜「server-side」)。 2