给 RAG 装上循环:agent 与 LangGraph
这一章讲三件事: agent 到底是什么(答案朴素得让人失望);LangGraph 怎么用「图」 描述并控制这个循环;一次真实运行里,agent 的每一步「想法」长什么样。 这是全书从「管道」跨入「智能体」的枢纽章,也是 Part 3 的开篇。
1. agent 的祛魅:就是一个循环
作者说新手最容易被专家绕晕的就是 agent——大家用抽象话术谈它能干什么,却不说它是什么。 他的祛魅版本一句话:把你已经用熟的 LLM 调用,放进一个循环里,任务完成才退出。 「就这么简单,各位」1。
别小看这个循环。单次调用只能一问一答;循环让 LLM 能把大任务拆成小步、 每步之后看结果、再决定下一步——这更接近人类解决复杂问题的方式: 观察、推理、调整,而不是一次性吐出答案2。
循环里的 LLM 被叫作 agent 的「大脑」。这个比喻有一个真实世界没有的超能力: 大脑可以随时换——换个更聪明的 LLM,甚至配多个大脑互相校验3。
2. LangGraph:把循环画成图
裸写循环很容易失控——早期的 agent 框架常见「失控 agent」:循环退不出来,或者盯着错误的任务打转4。 LangChain 2024 年推出的 LangGraph 把控制流(即程序每一步该走哪里的决策逻辑)显式化成一张图,三个构件5:
| 构件 | 是什么 | 类比 |
|---|---|---|
| 节点(node) | 一个处理步骤(调用一次 LLM、跑一个工具) | 流水线工位 |
| 边(edge) | 固定的下一步走向 | 传送带 |
| 条件边(conditional edge) | 带判断的走向:按状态决定去哪个节点 | 分拣机 |
为什么值得专门为它造一个框架?LangGraph 补了两件裸循环难做的事: 轻松定义循环(图里画个圈就是循环)和内建持久化——状态的存续与恢复, 这让「多个会话并行」和「关键节点人工介入」成为可能。持久化的完整形态,第 12 章的记忆系统会展开 (补充(不在书里,依据我们的 frontier 书架):LangGraph 的持久化基于 checkpoint 机制, 每个超步末尾批量提交,中断后可从 checkpoint 恢复重放。 依据: shelf=ai-frontier-reference/langgraph#03-persistence-hil.md 事实=LangGraph 通过 checkpoint 数据结构实现持久化、中断与恢复,与原书「内建记忆」的说法对应)。
图还顺手统一了历史:早期的 ReAct 范式(先推理该做什么→执行动作→观察结果→再推理, 循环直到目标达成)今天作为框架已经过时,但它的循环图骨架原样活在 LangGraph 里。 原书给出了 ReAct 的论文坐标:arXiv:2210.036296。
3. 工具:给 LLM 的说明书
工具(tool)= agent 可以调用的能力。本章给 agent 配了两个7:
- web_search:Tavily 搜索(第三方服务),最多返回 4 条结果;
- retriever_tool:把第 05 章建好的 ensemble 检索器包装成工具,名字起得极长:
retrieve_google_environmental_question_answers,描述是「Extensive information about Google environmental efforts from 2023」。
名字为什么要啰嗦?因为工具名和描述是写给 LLM 看的——agent 靠它们推理「现在该用哪个工具」。 写得越清楚,agent 选得越准8。原书还提醒:chat 系模型普遍为工具调用(即模型输出「请帮我调用某工具」的结构化请求)微调过, 非 chat 模型可能根本不会用工具,尤其是复杂工具9。
同类工具的打包叫 toolkit(toolkit,3-5 个相关工具的组合,如 GitHub toolkit 含搜 issue、读文件、评论等)10。
4. 状态:循环的记事本
AgentState 是 agent 在一次执行中的共享草稿板:用户的提问、工具的输出、LLM 的中间结果, 全在这里。所有节点共享同一个状态对象,且约定只追加、不覆盖——后面的节点能看到前面发生过的一切11。
代码上是一个 TypedDict:
class AgentState(TypedDict):
messages: Annotated[Sequence[BaseMessage], add_messages]
add_messages 这个注解就是在告诉 LangGraph「往列表后面接,别覆盖」12。
注意边界:这个状态只活在本轮执行里。跨会话的长期记忆是另一个话题——第 12 章。
5. 主走查:一次完整的 agent 运行
输入: 用户问题「Google 的环保举措有哪些?」(语料库就是那份 Google 环境报告 PDF)。
图的拓扑(组装完成后)13:
┌──────────────────────────────────┐
▼ │
开始 → agent ──┬─(要用工具)→ retrieve ──(相关)→ generate → 结束
│ │
│ └─(不相关)→ improve ──┘
└─(不需要工具)→ 结束
图说:agent 是调度中枢;retrieve 是工具节点;improve 把问题改写后送回 agent 重来。
第一跳: agent 收到问题,输出一条「工具调用」消息——它选了
retrieve_google_environmental_question_answers,参数是
{"query": "Google's environmental initiatives"}。
原始输出里能看到 tool_calls 字段带着调用 id 和 JSON 参数,finish_reason 是 tool_calls
——模型没有直接回答,而是说了「我要用哪个工具」。作者评价:选得对14。
第二跳(条件边): 工具返回一批文档。走哪条路?由 score_documents 这个条件边决定15:
- 拿一个 Pydantic 模型
scoring(只有一个字段:binary_score,取值 yes/no); - 用
llm.with_structured_output(scoring)让 LLM 的输出严格落在这个结构里; - 提示词问的是:「这份检索文档与问题相关吗?给 yes 或 no」;
- yes → 去 generate(生成回答);no → 去 improve(改写问题)16。
这一步就是第 03 章「守护 LLM」思想的变体: again 用第二个 LLM 判断「问题 vs 材料」的相关性, 只是裁决结果从「拒答/放行」变成了「生成/重来」。
不相关分支: improve 节点让 LLM 重新审视原问题——「推断背后的语义意图, 重新表述一个更好的问题」——然后把改写后的问题送回 agent,重新走一遍检索17。 这就是循环的意义: 一次检索不满意,agent 会自己换一种问法再试,而传统管道一次定生死。
终点: generate 节点用我们熟悉的提示词(「照材料答,答不出就说不知道」)生成最终回答,图走到结束。
观察手段: graph.stream() 把每个节点的输出流式打印出来——你能在终端里看着 agent
「想」:调了哪个工具、打了几分、走了哪条边。作者把这个打印称作 agent 思考过程的可视化18。
6. 边界与局限
- 本章的 agent 只有一个检索工具加一个搜索工具,真实系统的工具数量、嵌套深度都会放大失控风险;
- score_documents 的「相关/不相关」是二值的,材料「部分相关」时它只能猜一头;
- 改写问题再检索的循环没有次数上限——生产上要加最大重试数,否则一个永远检索不到的问题会让 agent 转到天荒地老(书里没加,这是留白);
- 状态只活在一轮之内——轮即一次完整的提问与回答——「记得上周聊过什么」要靠第 12 章起的记忆系统。
7. 可带走的
- agent = LLM 调用 + 完成才退出的循环;一切玄学表述都可以还原成这个结构。
- 图 = 节点(步骤)+ 边(走向)+ 条件边(决策); 把控制流画出来,失控 agent 就现形了。
- 工具名与描述是 prompt 的一部分——写给 LLM 的,认真写。
- 状态只追加不覆盖,这是多节点协作的记事本纪律。
- **「检索结果不相关就改写问题重来」**是循环带给 RAG 的最直接红利。
- 结构化输出(with_structured_output)+ 二值判断是给 LLM 当裁判的标准姿势。
- graph.stream 是调试 agent 的眼睛——让每一步决策可见。
8. 原文地图
| 主题 | 原书章 | 原文位置 |
|---|---|---|
| agent=循环的祛魅定义 | Combining RAG with the Power of AI Agents and LangGraph | text/75-fm-combining-rag-with-the-power-of-ai-agents-and-la.txt:46(搜「just a loop, folks」) |
| 循环带来的推理与拆解能力 | Combining RAG with the Power of AI Agents and LangGraph | text/75-fm-combining-rag-with-the-power-of-ai-agents-and-la.txt:59(搜「break tasks down」) |
| 大脑比喻与可换脑 | Combining RAG with the Power of AI Agents and LangGraph | text/75-fm-combining-rag-with-the-power-of-ai-agents-and-la.txt:80(搜「brain」) · text/75-fm-combining-rag-with-the-power-of-ai-agents-and-la.txt:83(搜「swap out their LLM brain」) |
| LangGraph 2024 推出、AgentExecutor 旧路 | Combining RAG with the Power of AI Agents and LangGraph | text/75-fm-combining-rag-with-the-power-of-ai-agents-and-la.txt:92(搜「AgentExecutor」) |
| 两大新能力(循环+记忆) | Combining RAG with the Power of AI Agents and LangGraph | text/75-fm-combining-rag-with-the-power-of-ai-agents-and-la.txt:99(搜「cyclical graphs」) |
| 失控 agent 与控制流的必要性 | Combining RAG with the Power of AI Agents and LangGraph | text/75-fm-combining-rag-with-the-power-of-ai-agents-and-la.txt:122(搜「rogue agents」) |
| ReAct 范式与论文 | Combining RAG with the Power of AI Agents and LangGraph | text/75-fm-combining-rag-with-the-power-of-ai-agents-and-la.txt:115(搜「reason + act」) · text/75-fm-combining-rag-with-the-power-of-ai-agents-and-la.txt:128(搜「2210.03629」) |
| 持久化与 HITL | Combining RAG with the Power of AI Agents and LangGraph | text/75-fm-combining-rag-with-the-power-of-ai-agents-and-la.txt:125(搜「Persistence」) |
| 工具定义(Tavily+检索器工具) | Combining RAG with the Power of AI Agents and LangGraph | text/75-fm-combining-rag-with-the-power-of-ai-agents-and-la.txt:176(搜「TavilySearch」) · text/75-fm-combining-rag-with-the-power-of-ai-agents-and-la.txt:207(搜「retrieve_google_environmental_question_answers」) |
| 工具名是给 LLM 的说明书 | Combining RAG with the Power of AI Agents and LangGraph | text/75-fm-combining-rag-with-the-power-of-ai-agents-and-la.txt:216(搜「more verbose names」) |
| chat 模型才会用工具 | Combining RAG with the Power of AI Agents and LangGraph | text/75-fm-combining-rag-with-the-power-of-ai-agents-and-la.txt:227(搜「fine-tuned for tool calling」) |
| toolkits 定义 | Combining RAG with the Power of AI Agents and LangGraph | text/75-fm-combining-rag-with-the-power-of-ai-agents-and-la.txt:236(搜「toolkits」) |
| AgentState 与 add_messages | Combining RAG with the Power of AI Agents and LangGraph | text/75-fm-combining-rag-with-the-power-of-ai-agents-and-la.txt:260(搜「AgentState」) · text/75-fm-combining-rag-with-the-power-of-ai-agents-and-la.txt:263(搜「add_messages」) |
| 状态只活在本轮 | Combining RAG with the Power of AI Agents and LangGraph | text/75-fm-combining-rag-with-the-power-of-ai-agents-and-la.txt:267(搜「current agent execution」) |
| score_documents 条件边 | Combining RAG with the Power of AI Agents and LangGraph | text/75-fm-combining-rag-with-the-power-of-ai-agents-and-la.txt:339(搜「score_documents」) · text/75-fm-combining-rag-with-the-power-of-ai-agents-and-la.txt:359(搜「with_structured_output」) |
| yes/no 分流 | Combining RAG with the Power of AI Agents and LangGraph | text/75-fm-combining-rag-with-the-power-of-ai-agents-and-la.txt:435(搜「DOCS RELEVANT」) |
| improve 改写问题 | Combining RAG with the Power of AI Agents and LangGraph | text/75-fm-combining-rag-with-the-power-of-ai-agents-and-la.txt:489(搜「Formulate an improved question」) |
| 图组装与编译 | Combining RAG with the Power of AI Agents and LangGraph | text/75-fm-combining-rag-with-the-power-of-ai-agents-and-la.txt:569(搜「add_node」) · text/75-fm-combining-rag-with-the-power-of-ai-agents-and-la.txt:596(搜「set_entry_point」) |
| 第一跳的工具调用输出 | Combining RAG with the Power of AI Agents and LangGraph | text/75-fm-combining-rag-with-the-power-of-ai-agents-and-la.txt:711(搜「finish_reason」) |
| stream 打印 agent 想法 | Combining RAG with the Power of AI Agents and LangGraph | text/75-fm-combining-rag-with-the-power-of-ai-agents-and-la.txt:678(搜「graph.stream」) |