<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>RAG 与 LangChain on 扎塔-Zata</title><link>https://www.zata.cc/categories/rag-%E4%B8%8E-langchain/</link><description>Recent content in RAG 与 LangChain on 扎塔-Zata</description><generator>Hugo -- gohugo.io</generator><language>zh-cn</language><copyright>Example Person</copyright><lastBuildDate>Thu, 24 Sep 2026 17:09:06 +0800</lastBuildDate><atom:link href="https://www.zata.cc/categories/rag-%E4%B8%8E-langchain/index.xml" rel="self" type="application/rss+xml"/><item><title>LangChain 常见报错与排查指南</title><link>https://www.zata.cc/p/langchain-%E5%B8%B8%E8%A7%81%E6%8A%A5%E9%94%99%E4%B8%8E%E6%8E%92%E6%9F%A5%E6%8C%87%E5%8D%97/</link><pubDate>Tue, 08 Sep 2026 13:30:00 +0800</pubDate><guid>https://www.zata.cc/p/langchain-%E5%B8%B8%E8%A7%81%E6%8A%A5%E9%94%99%E4%B8%8E%E6%8E%92%E6%9F%A5%E6%8C%87%E5%8D%97/</guid><description>&lt;img src="https://www.zata.cc/p/langchain-%E5%B8%B8%E8%A7%81%E6%8A%A5%E9%94%99%E4%B8%8E%E6%8E%92%E6%9F%A5%E6%8C%87%E5%8D%97/images/index/index.png" alt="Featured image of post LangChain 常见报错与排查指南" />&lt;h1 id="langchain-常见报错与排查指南">LangChain 常见报错与排查指南
&lt;/h1>&lt;p>LangChain 应用报错时，最浪费时间的不是修，而是不知道问题出在哪一层。密钥、模型、提示、解析、编排——每一层的症状可能长得一样（都是「输出不对」），但修法完全不同。&lt;/p>
&lt;p>最有效的第一步：&lt;strong>先看模型的原始输出&lt;/strong>。把解析器临时换成 &lt;code>StrOutputParser&lt;/code>（或直接 print 响应对象），原始输出正常，问题在解析层；原始输出本身就不对，问题在配置、提示或模型。这一步能省掉大半瞎猜时间。&lt;/p>
&lt;h2 id="先定位问题出在哪一层">先定位：问题出在哪一层
&lt;/h2>&lt;table>
&lt;thead>
&lt;tr>
&lt;th>层&lt;/th>
&lt;th>典型症状&lt;/th>
&lt;th>排查手段&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>配置层&lt;/td>
&lt;td>AuthenticationError、模型不存在&lt;/td>
&lt;td>检查环境变量；用 curl 直接调平台 API 验证密钥&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>模型层&lt;/td>
&lt;td>输出质量差、不按指令来&lt;/td>
&lt;td>换 StrOutputParser 看原始输出；换模型对比&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>解析层&lt;/td>
&lt;td>OutputParserException&lt;/td>
&lt;td>确认原始输出没问题后，再调格式指令&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>编排层&lt;/td>
&lt;td>Agent 死循环、链路数据对不上&lt;/td>
&lt;td>&lt;code>verbose=True&lt;/code>、LangSmith 追踪、astream_events&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;h2 id="七类高频报错">七类高频报错
&lt;/h2>&lt;h3 id="1-api-密钥错误authenticationerror">1. API 密钥错误（AuthenticationError）
&lt;/h3>&lt;p>&lt;strong>症状&lt;/strong>：提示 API 密钥无效、缺失或未设置。&lt;/p>
&lt;p>&lt;strong>原因&lt;/strong>：&lt;/p>
&lt;ul>
&lt;li>环境变量没设置或没导出（&lt;code>OPENAI_API_KEY&lt;/code>、&lt;code>DASHSCOPE_API_KEY&lt;/code> 等）&lt;/li>
&lt;li>密钥过期、被删或无效&lt;/li>
&lt;li>账户余额不足（百炼这类预付费平台尤其常见）&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>解决&lt;/strong>：&lt;/p>
&lt;ul>
&lt;li>&lt;code>print(os.getenv(&amp;quot;OPENAI_API_KEY&amp;quot;))&lt;/code> 确认进程内真的读到了&lt;/li>
&lt;li>检查密钥有效性，必要时重新生成&lt;/li>
&lt;li>确认账户状态和余额&lt;/li>
&lt;/ul>
&lt;h3 id="2-模型未找到notfounderror">2. 模型未找到（NotFoundError）
&lt;/h3>&lt;p>&lt;strong>症状&lt;/strong>：提示指定的模型不存在或无法访问。&lt;/p>
&lt;p>&lt;strong>原因&lt;/strong>：&lt;/p>
&lt;ul>
&lt;li>模型名拼写错误（比如把 &lt;code>qwen-plus&lt;/code> 写成 &lt;code>qwen_plus&lt;/code>）&lt;/li>
&lt;li>模型对当前 API 密钥或账户不可用（部分模型需要专门开通权限）&lt;/li>
&lt;li>API 服务区域问题&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>解决&lt;/strong>：&lt;/p>
&lt;ul>
&lt;li>核对模型名拼写；模型名是精确匹配，多一个横线少一个点都不行&lt;/li>
&lt;li>查提供商文档，确认模型可用性和访问权限&lt;/li>
&lt;/ul>
&lt;h3 id="3-提示模板错误templateformaterror--missinginputvariables">3. 提示模板错误（TemplateFormatError / MissingInputVariables）
&lt;/h3>&lt;p>&lt;strong>症状&lt;/strong>：渲染提示时报错，或最终提示里出现了没被替换的 &lt;code>{variable}&lt;/code>。&lt;/p>
&lt;p>&lt;strong>原因&lt;/strong>：&lt;/p>
&lt;ul>
&lt;li>模板占位符与调用时传入的输入键不匹配&lt;/li>
&lt;li>模板语法错误——f-string 风格里想输出字面量 &lt;code>{&lt;/code> 却没写成 &lt;code>{{&lt;/code>&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>解决&lt;/strong>：&lt;/p>
&lt;ul>
&lt;li>确保调用链时提供了模板里所有占位符变量&lt;/li>
&lt;li>单独测试渲染：&lt;code>prompt.invoke({...})&lt;/code> 看渲染结果是否符合预期&lt;/li>
&lt;/ul>
&lt;h3 id="4-agent-输出解析错误outputparserexception">4. Agent 输出解析错误（OutputParserException）
&lt;/h3>&lt;p>&lt;strong>症状&lt;/strong>：LLM 输出不符合 Agent 期望的格式（0.x 时代的 ReAct Agent 未能正确输出 &amp;ldquo;Thought:&amp;rdquo; / &amp;ldquo;Action:&amp;rdquo; 等）。&lt;/p>
&lt;p>&lt;strong>原因&lt;/strong>：&lt;/p>
&lt;ul>
&lt;li>模型难以理解提示中的输出格式要求&lt;/li>
&lt;li>提示过于复杂，或工具描述含糊导致模型困惑&lt;/li>
&lt;li>模型能力不足&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>解决&lt;/strong>：&lt;/p>
&lt;ul>
&lt;li>简化输出格式要求；在提示里加几个符合格式的 few-shot 示例&lt;/li>
&lt;li>改进工具描述，写清楚功能和预期输入&lt;/li>
&lt;li>&lt;code>handle_parsing_errors=True&lt;/code>（0.x 的 AgentExecutor 参数）：把解析错误反馈给模型，让它自我纠正&lt;/li>
&lt;li>换更强的模型&lt;/li>
&lt;/ul>
&lt;blockquote>
&lt;p>⚠️ 版本注记：这套症状主要属于 0.x 时代的文本协议 Agent（ReAct）。1.x 的 &lt;code>create_agent&lt;/code> 走原生 tool calling，模型输出结构化 JSON，这类格式解析错误已大幅减少。自己写 Pydantic 解析时仍会遇到 OutputParserException，处理思路见本站《langchain_core 组件详解》。&lt;/p>
&lt;/blockquote>
&lt;h3 id="5-工具执行错误toolexception">5. 工具执行错误（ToolException）
&lt;/h3>&lt;p>&lt;strong>症状&lt;/strong>：Agent 正确选中了工具，但工具执行失败。&lt;/p>
&lt;p>&lt;strong>原因&lt;/strong>：&lt;/p>
&lt;ul>
&lt;li>传给工具的参数无效&lt;/li>
&lt;li>工具依赖的外部服务不可用（限流、网络问题）&lt;/li>
&lt;li>工具内部逻辑错误&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>解决&lt;/strong>：&lt;/p>
&lt;ul>
&lt;li>工具内部用 try-except 捕获异常，返回有意义的错误信息给 Agent，而不是让它崩掉&lt;/li>
&lt;li>在工具入口验证参数；把工具拿出来独立测试&lt;/li>
&lt;li>在提示里引导 Agent：工具失败时换一种方法，或如实报告&lt;/li>
&lt;/ul>
&lt;h3 id="6-上下文长度超出contextwindowexceeded--invalidrequesterror">6. 上下文长度超出（ContextWindowExceeded / InvalidRequestError）
&lt;/h3>&lt;p>&lt;strong>症状&lt;/strong>：提示 + 对话历史 + 输出预留的总 token 超过模型上限。&lt;/p>
&lt;p>&lt;strong>原因&lt;/strong>：&lt;/p>
&lt;ul>
&lt;li>对话历史无限累积&lt;/li>
&lt;li>RAG 检索了过多的文档块&lt;/li>
&lt;li>提示本身冗长&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>解决&lt;/strong>：&lt;/p>
&lt;ul>
&lt;li>⚠️ 0.x 的 &lt;code>ConversationBufferWindowMemory&lt;/code> / &lt;code>ConversationTokenBufferMemory&lt;/code> / &lt;code>ConversationSummaryBufferMemory&lt;/code> 已随 legacy memory 体系移除；现代等价做法是 LangGraph checkpointer + 消息裁剪（&lt;code>trim_messages&lt;/code>），或对长输入做分块摘要（本站《订阅摘要 Agent 实战》有完整案例）&lt;/li>
&lt;li>控制检索文档的数量和长度&lt;/li>
&lt;li>精简提示；或换长上下文模型&lt;/li>
&lt;/ul>
&lt;h3 id="7-agent-死循环maxiterationsexceeded">7. Agent 死循环（MaxIterationsExceeded）
&lt;/h3>&lt;p>&lt;strong>症状&lt;/strong>：Agent 反复执行同样的无效操作，直到达到迭代上限。&lt;/p>
&lt;p>&lt;strong>原因&lt;/strong>：&lt;/p>
&lt;ul>
&lt;li>提示不清晰，无法引导 Agent 走向正确方向&lt;/li>
&lt;li>工具集不完备，缺少解决任务所需的关键能力&lt;/li>
&lt;li>LLM 在复杂推理中迷失&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>解决&lt;/strong>：&lt;/p>
&lt;ul>
&lt;li>改进提示，给出明确的目标和终止条件&lt;/li>
&lt;li>审查工具集是否齐备&lt;/li>
&lt;li>谨慎加大迭代上限——0.x 是 AgentExecutor 的 &lt;code>max_iterations&lt;/code>，LangGraph 时代对应 config 里的 &lt;code>recursion_limit&lt;/code>；但先排查其他原因，调上限只是兜底&lt;/li>
&lt;li>引入人工介入：Agent 卡住时让用户兜底&lt;/li>
&lt;/ul>
&lt;h2 id="1x-时代最常见的报错import-一个已被移除的类">1.x 时代最常见的报错：import 一个已被移除的类
&lt;/h2>&lt;p>LangChain 1.0（2025 年 10 月发布）移除了全部 legacy 抽象。老教程代码今天最常见的死法不是逻辑错误，而是 import 直接报错：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">ImportError: cannot import name &amp;#39;LLMChain&amp;#39; from &amp;#39;langchain.chains&amp;#39;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>遇到这类报错，对照表查一下，基本都有明确的现代等价物：&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>被移除的老写法&lt;/th>
&lt;th>现代等价物&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;code>LLMChain&lt;/code>&lt;/td>
&lt;td>LCEL 管道：&lt;code>prompt | llm | parser&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>RetrievalQA&lt;/code>&lt;/td>
&lt;td>LCEL 检索链，或 &lt;code>create_retrieval_tool&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>ConversationChain&lt;/code>&lt;/td>
&lt;td>LangGraph &lt;code>StateGraph&lt;/code> + checkpointer&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>initialize_agent&lt;/code> / &lt;code>AgentExecutor&lt;/code>&lt;/td>
&lt;td>&lt;code>langchain.agents.create_agent&lt;/code>（1.x）/ &lt;code>langgraph.prebuilt.create_react_agent&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>create_openai_functions_agent&lt;/code> 等旧 Agent 构造器&lt;/td>
&lt;td>同上，统一到 &lt;code>create_agent&lt;/code>，协议统一走 tool calling&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>langchain.memory.*&lt;/code>（ConversationBufferMemory 等）&lt;/td>
&lt;td>LangGraph checkpointer + 消息裁剪（&lt;code>trim_messages&lt;/code>）&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>load_summarize_chain&lt;/code>&lt;/td>
&lt;td>LangGraph map-reduce，或 LCEL 手写&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>langchain.text_splitter&lt;/code>&lt;/td>
&lt;td>&lt;code>langchain_text_splitters&lt;/code> 独立包&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>langchain_community&lt;/code> 里的 ChatOllama / HuggingFaceEmbeddings&lt;/td>
&lt;td>&lt;code>langchain-ollama&lt;/code> / &lt;code>langchain-huggingface&lt;/code> 独立包&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>langchain_core.pydantic_v1&lt;/code>&lt;/td>
&lt;td>直接用 pydantic v2&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>需要读存量老代码时，这份表反过来用就是「翻译词典」。&lt;/p>
&lt;h2 id="排查顺序清单">排查顺序清单
&lt;/h2>&lt;ol>
&lt;li>报错栈最底层是平台 API 的 4xx？→ 配置层，查密钥、模型名、余额&lt;/li>
&lt;li>拿原始输出：StrOutputParser 或 print，判断问题在模型层还是解析层&lt;/li>
&lt;li>是解析错？改格式指令 + few-shot + 重试&lt;/li>
&lt;li>Agent 行为怪？开 &lt;code>verbose&lt;/code> / 用 LangSmith 追踪，看每一步的消息流&lt;/li>
&lt;li>还查不出来？把最小可复现片段剥出来单独跑&lt;/li>
&lt;/ol>
&lt;h2 id="总结">总结
&lt;/h2>&lt;ul>
&lt;li>报错先分层：配置 → 模型 → 解析 → 编排，症状会骗人，层不会&lt;/li>
&lt;li>看原始输出是最快的定位手段&lt;/li>
&lt;li>1.x 时代大半「奇怪的报错」其实是 import 了已移除的类，对照表一查便知&lt;/li>
&lt;li>过程观测交给工具：LangSmith（见本站《LangSmith使用教程》）+ astream_events&lt;/li>
&lt;/ul></description></item><item><title>LangChain 与 MCP 极简教程：让 Agent 接入外部工具的另一种方式</title><link>https://www.zata.cc/p/langchain-%E4%B8%8E-mcp-%E6%9E%81%E7%AE%80%E6%95%99%E7%A8%8B%E8%AE%A9-agent-%E6%8E%A5%E5%85%A5%E5%A4%96%E9%83%A8%E5%B7%A5%E5%85%B7%E7%9A%84%E5%8F%A6%E4%B8%80%E7%A7%8D%E6%96%B9%E5%BC%8F/</link><pubDate>Tue, 08 Sep 2026 13:00:00 +0800</pubDate><guid>https://www.zata.cc/p/langchain-%E4%B8%8E-mcp-%E6%9E%81%E7%AE%80%E6%95%99%E7%A8%8B%E8%AE%A9-agent-%E6%8E%A5%E5%85%A5%E5%A4%96%E9%83%A8%E5%B7%A5%E5%85%B7%E7%9A%84%E5%8F%A6%E4%B8%80%E7%A7%8D%E6%96%B9%E5%BC%8F/</guid><description>&lt;img src="https://www.zata.cc/p/langchain-%E4%B8%8E-mcp-%E6%9E%81%E7%AE%80%E6%95%99%E7%A8%8B%E8%AE%A9-agent-%E6%8E%A5%E5%85%A5%E5%A4%96%E9%83%A8%E5%B7%A5%E5%85%B7%E7%9A%84%E5%8F%A6%E4%B8%80%E7%A7%8D%E6%96%B9%E5%BC%8F/images/index/index.png" alt="Featured image of post LangChain 与 MCP 极简教程：让 Agent 接入外部工具的另一种方式" />&lt;h1 id="langchain-与-mcp-极简教程">LangChain 与 MCP 极简教程
&lt;/h1>&lt;p>给 Agent 接工具，最常见的做法是把所有工具函数都写在应用代码里，再逐个注册给模型。工具少的时候没问题；一旦工具多起来、或者想跨项目复用，这条路就会越来越累——同样的工具，每个应用都要重新写一遍。&lt;/p>
&lt;p>MCP（Model Context Protocol，模型上下文协议）是 Anthropic 在 2024 年底推出的开放协议，解决的就是这个问题：把「工具的定义和执行」从应用代码里拆出来，放到独立的服务器上，客户端按需发现和调用。本文用一个最小的双文件例子——FastMCP 起一个数学工具服务，&lt;code>langchain.mcp&lt;/code> 把它接进 Agent——把这条链路跑通。&lt;/p>
&lt;p>客户端这一侧，2026 年 9 月有个值得注意的变化：&lt;strong>MCP 支持已经进到 LangChain 主干里&lt;/strong>。LangChain &lt;code>v1.4.0&lt;/code> 新增了 &lt;code>langchain.mcp&lt;/code> 命名空间，基于 FastMCP 实现，把原先独立的 &lt;code>langchain-mcp-adapters&lt;/code> 包整个取代了——&lt;code>MultiServerMCPClient&lt;/code> 收敛成一个 &lt;code>MCPAdapter&lt;/code>。所以现在装依赖是 &lt;code>pip install &amp;quot;langchain[mcp]&amp;quot;&lt;/code>，不再是单独装一个适配器包。本文代码全部按新写法给。&lt;/p>
&lt;h2 id="mcp-和-function-call-是什么关系">MCP 和 Function Call 是什么关系
&lt;/h2>&lt;p>先说结论：两者不是竞争关系，而是工作在两层的东西。&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Function calling 是模型的能力&lt;/strong>：模型根据工具的名称、描述和参数 schema，决定「该调哪个工具、传什么参数」，并以结构化 JSON 输出。&lt;/li>
&lt;li>&lt;strong>MCP 是工具的供给协议&lt;/strong>：约定客户端如何发现服务器上有哪些工具（tool list）、如何调用（tool call）、如何取回结果。&lt;/li>
&lt;/ul>
&lt;p>也就是说，MCP 解决的不是「模型会不会调工具」，而是「工具从哪来、谁来维护、能不能复用」。写一次 MCP server，任何支持 MCP 的客户端（Claude Desktop、Cursor、你自己的 Agent）都能连。&lt;/p>
&lt;h2 id="最小实战两个文件跑通">最小实战：两个文件跑通
&lt;/h2>&lt;p>项目就两个文件：&lt;/p>
&lt;ul>
&lt;li>&lt;code>math_server.py&lt;/code>：MCP 服务端，提供数学工具&lt;/li>
&lt;li>&lt;code>client.py&lt;/code>：LangChain 客户端，把 MCP 工具接进 Agent&lt;/li>
&lt;/ul>
&lt;h3 id="服务端fastmcp-起服务">服务端：FastMCP 起服务
&lt;/h3>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># math_server.py&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">mcp.server.fastmcp&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">FastMCP&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">mcp&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">FastMCP&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;Math&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nd">@mcp.tool&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">add&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">a&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">int&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">b&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">int&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="nb">int&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;Add two numbers&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">a&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="n">b&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nd">@mcp.tool&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">multiply&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">a&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">int&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">b&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">int&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="nb">int&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;Multiply two numbers&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">a&lt;/span> &lt;span class="o">*&lt;/span> &lt;span class="n">b&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">if&lt;/span> &lt;span class="vm">__name__&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="s2">&amp;#34;__main__&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">mcp&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">run&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">transport&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;stdio&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>两个细节值得注意：&lt;/p>
&lt;ul>
&lt;li>工具用 &lt;code>@mcp.tool()&lt;/code> 装饰器定义，函数签名和 docstring 就是模型看到的工具说明——和 LangChain &lt;code>@tool&lt;/code> 的玩法一致，类型注解必须写清楚。&lt;/li>
&lt;li>&lt;code>transport=&amp;quot;stdio&amp;quot;&lt;/code> 表示通过标准输入/输出通信，客户端会以子进程方式拉起这个脚本，本地工具用它最省事；要跨机器就走 HTTP（streamable HTTP / SSE）。&lt;/li>
&lt;/ul>
&lt;h3 id="客户端把-mcp-工具接进-agent">客户端：把 MCP 工具接进 Agent
&lt;/h3>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># client.py&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">asyncio&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">os&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">pathlib&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">Path&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">langchain.agents&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">create_agent&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">langchain.mcp&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">MCPAdapter&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">langchain_openai&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">ChatOpenAI&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 模型走阿里云百炼的 OpenAI 兼容模式&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">model&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">ChatOpenAI&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">api_key&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">os&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">getenv&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;DASHSCOPE_API_KEY&amp;#34;&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">base_url&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;https://dashscope.aliyuncs.com/compatible-mode/v1&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">model&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;qwen-plus&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 目标类型决定传输方式：Path = stdio 子进程，字符串 = 必须是 http(s) URL&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">adapter&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">MCPAdapter&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">Path&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;math_server.py&amp;#34;&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">async&lt;/span> &lt;span class="k">def&lt;/span> &lt;span class="nf">run_agent&lt;/span>&lt;span class="p">():&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">async&lt;/span> &lt;span class="k">with&lt;/span> &lt;span class="n">adapter&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># 把 MCP 工具加载为 LangChain 工具&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">tools&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">await&lt;/span> &lt;span class="n">adapter&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">list_tools&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># 创建并运行 agent&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">agent&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">create_agent&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">model&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">tools&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="k">await&lt;/span> &lt;span class="n">agent&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">ainvoke&lt;/span>&lt;span class="p">({&lt;/span>&lt;span class="s2">&amp;#34;messages&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;what&amp;#39;s (3 + 5) x 12?&amp;#34;&lt;/span>&lt;span class="p">})&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">if&lt;/span> &lt;span class="vm">__name__&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="s2">&amp;#34;__main__&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">asyncio&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">run&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">run_agent&lt;/span>&lt;span class="p">()))&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;code>MCPAdapter&lt;/code> 最省事的地方是&lt;strong>它从你交给它的目标自己推断传输方式&lt;/strong>，不需要再写 &lt;code>transport&lt;/code> 字段：&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>传给 &lt;code>MCPAdapter&lt;/code> 的目标&lt;/th>
&lt;th>推断出的传输&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;code>&amp;quot;https://example.com/mcp&amp;quot;&lt;/code>&lt;/td>
&lt;td>Streamable HTTP&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>Path(&amp;quot;math_server.py&amp;quot;)&lt;/code>&lt;/td>
&lt;td>stdio 子进程（每个 adapter 一个）&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>一个 &lt;code>FastMCP&lt;/code> 实例&lt;/td>
&lt;td>进程内直连，不走子进程也不走 socket，跑测试最合适&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>{&amp;quot;mcpServers&amp;quot;: {...}}&lt;/code> 配置字典&lt;/td>
&lt;td>多服务器，工具名自动加 &lt;code>{server}_&lt;/code> 前缀&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>现成的 &lt;code>fastmcp.Client&lt;/code>&lt;/td>
&lt;td>完全自定义（transport、缓存、认证）&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>有个坑值得单独说：&lt;strong>脚本路径必须传 &lt;code>Path&lt;/code> 而不是 &lt;code>str&lt;/code>&lt;/strong>。FastMCP 解析字符串时会先当文件路径试，再当 URL 试，所以一个字符串形式的 &lt;code>&amp;quot;math_server.py&amp;quot;&lt;/code> 有被当成本地脚本拉起的风险；&lt;code>MCPAdapter&lt;/code> 的做法更保守——直接拒绝所有不符合 URL 形状的字符串。想跑本地脚本，明确写 &lt;code>Path(...)&lt;/code>。&lt;/p>
&lt;h3 id="运行">运行
&lt;/h3>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">pip install &lt;span class="s2">&amp;#34;langchain[mcp]&amp;#34;&lt;/span> langchain-openai mcp
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">export&lt;/span> &lt;span class="nv">DASHSCOPE_API_KEY&lt;/span>&lt;span class="o">=&lt;/span>your_api_key
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">python client.py
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>客户端会自己拉起 &lt;code>math_server.py&lt;/code>，提出问题 &amp;ldquo;what&amp;rsquo;s (3 + 5) x 12?&amp;quot;，并输出计算结果。&lt;/p>
&lt;h2 id="运行时发生了什么">运行时发生了什么
&lt;/h2>&lt;ol>
&lt;li>&lt;code>MCPAdapter(Path(&amp;quot;math_server.py&amp;quot;))&lt;/code> 从目标类型推断出 stdio 传输&lt;/li>
&lt;li>进入 &lt;code>async with adapter&lt;/code> 时以子进程方式启动 &lt;code>math_server.py&lt;/code>，完成 MCP 握手&lt;/li>
&lt;li>&lt;code>adapter.list_tools()&lt;/code> 把服务器上的工具转换成 LangChain 工具列表&lt;/li>
&lt;li>&lt;code>create_agent(model, tools)&lt;/code> 用这些工具构建 Agent&lt;/li>
&lt;li>Agent 收到问题，决定调用哪个工具；实际执行发生在 MCP server 侧，结果经客户端回传给模型&lt;/li>
&lt;/ol>
&lt;p>关键点在第 3 步：对 Agent 来说，MCP 工具和本地 &lt;code>@tool&lt;/code> 定义的工具没有任何区别，适配器把协议细节全部藏掉了。另外返回的每个工具自己持有连接、每次调用各开一个会话，所以**&lt;code>async with&lt;/code> 退出后 agent 依然可用**——不必把整轮推理都关在上下文里。&lt;/p>
&lt;h2 id="function-call-vs-mcp">Function Call vs MCP
&lt;/h2>&lt;p>&lt;img src="https://www.zata.cc/p/langchain-%E4%B8%8E-mcp-%E6%9E%81%E7%AE%80%E6%95%99%E7%A8%8B%E8%AE%A9-agent-%E6%8E%A5%E5%85%A5%E5%A4%96%E9%83%A8%E5%B7%A5%E5%85%B7%E7%9A%84%E5%8F%A6%E4%B8%80%E7%A7%8D%E6%96%B9%E5%BC%8F/images/index/image.png"
width="720"
height="773"
srcset="https://www.zata.cc/p/langchain-%E4%B8%8E-mcp-%E6%9E%81%E7%AE%80%E6%95%99%E7%A8%8B%E8%AE%A9-agent-%E6%8E%A5%E5%85%A5%E5%A4%96%E9%83%A8%E5%B7%A5%E5%85%B7%E7%9A%84%E5%8F%A6%E4%B8%80%E7%A7%8D%E6%96%B9%E5%BC%8F/images/index/image_hu17208915534754346694.png 480w, https://www.zata.cc/p/langchain-%E4%B8%8E-mcp-%E6%9E%81%E7%AE%80%E6%95%99%E7%A8%8B%E8%AE%A9-agent-%E6%8E%A5%E5%85%A5%E5%A4%96%E9%83%A8%E5%B7%A5%E5%85%B7%E7%9A%84%E5%8F%A6%E4%B8%80%E7%A7%8D%E6%96%B9%E5%BC%8F/images/index/image_hu16087748710215217863.png 1024w"
loading="lazy"
alt="Function Calling 与 MCP 的链路对比"
class="gallery-image"
data-flex-grow="93"
data-flex-basis="223px"
>&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>维度&lt;/th>
&lt;th>Function Calling&lt;/th>
&lt;th>MCP&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>解决的问题&lt;/td>
&lt;td>模型如何表达「调这个工具、参数是这些」&lt;/td>
&lt;td>工具如何被外部程序发现、提供和复用&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>所在层&lt;/td>
&lt;td>模型 API 的能力&lt;/td>
&lt;td>客户端与服务端之间的协议&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>工具定义位置&lt;/td>
&lt;td>应用代码里（&lt;code>@tool&lt;/code> / JSON Schema）&lt;/td>
&lt;td>独立的 MCP server&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>复用性&lt;/td>
&lt;td>换个应用基本要重写&lt;/td>
&lt;td>一个 server 服务所有支持 MCP 的客户端&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>传输方式&lt;/td>
&lt;td>随模型 API 请求一起发送&lt;/td>
&lt;td>stdio / HTTP，工具在进程外执行&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>一句话总结：function calling 决定模型「怎么说」，MCP 决定工具「从哪来」；MCP 工具最终还是要靠 function calling 让模型真正用起来。&lt;/p>
&lt;h2 id="版本注记从-langchain-mcp-adapters-迁移过来">版本注记：从 langchain-mcp-adapters 迁移过来
&lt;/h2>&lt;p>这篇教程最早写在 MCP 适配器还是独立包的时期，当时的客户端长这样：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">langchain_mcp_adapters.client&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">MultiServerMCPClient&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">langgraph.prebuilt&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">create_react_agent&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">client&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">MultiServerMCPClient&lt;/span>&lt;span class="p">({&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;math&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">{&lt;/span>&lt;span class="s2">&amp;#34;transport&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;stdio&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;command&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;python&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;args&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;math_server.py&amp;#34;&lt;/span>&lt;span class="p">]},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">})&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">tools&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">await&lt;/span> &lt;span class="n">client&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">get_tools&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">agent&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">create_react_agent&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">model&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">tools&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>LangChain &lt;code>v1.4.0&lt;/code>（2026-09-01）之后，这套写法整体退休。对应关系如下：&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>旧（&lt;code>langchain-mcp-adapters&lt;/code>）&lt;/th>
&lt;th>新（&lt;code>langchain.mcp&lt;/code>）&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;code>MultiServerMCPClient(...)&lt;/code>&lt;/td>
&lt;td>&lt;code>MCPAdapter(target)&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>await client.get_tools()&lt;/code>&lt;/td>
&lt;td>&lt;code>await adapter.list_tools()&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>load_mcp_tools(session)&lt;/code>&lt;/td>
&lt;td>直接用 &lt;code>adapter.list_tools()&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>convert_mcp_tool_to_langchain_tool&lt;/code>&lt;/td>
&lt;td>&lt;code>as_langchain_tool&lt;/code>（改名，且变成协程）&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>tool_name_prefix&lt;/code>&lt;/td>
&lt;td>多服务器时自动加 &lt;code>{server}_&lt;/code> 前缀&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>handle_tool_errors&lt;/code> 开关&lt;/td>
&lt;td>移除。行为固定：&lt;code>isError=True&lt;/code> 变成 &lt;code>ToolMessage(status=&amp;quot;error&amp;quot;)&lt;/code>，传输故障直接抛异常&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>Callbacks(on_elicitation=...)&lt;/code>&lt;/td>
&lt;td>取消回调，改为默认开启的 LangGraph &lt;code>interrupt()&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>tool_interceptors&lt;/code>&lt;/td>
&lt;td>用 LangChain &lt;code>@wrap_tool_call&lt;/code> 中间件（能拦所有工具，不止 MCP 的）&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>连接上的 &lt;code>auth&lt;/code> / &lt;code>headers&lt;/code>&lt;/td>
&lt;td>挪到 &lt;code>fastmcp.Client&lt;/code> 上&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>三个必须知道的边界：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>&lt;code>langchain.mcp&lt;/code> 目前是 beta&lt;/strong>（需要 &lt;code>langchain[mcp]&amp;gt;=1.4.0&lt;/code>），import 时会抛一次 &lt;code>LangChainBetaWarning&lt;/code>，API 可能变。&lt;/li>
&lt;li>&lt;strong>elicitation 变成 interrupt&lt;/strong>：服务端中途要输入时，不再是回调，而是暂停整个 run，用 &lt;code>Command(resume={&amp;quot;responses&amp;quot;: {key: 答案}})&lt;/code> 恢复。这是设计变化，不是 bug。&lt;/li>
&lt;li>&lt;strong>prompts / resources 还没有包装&lt;/strong>：&lt;code>load_mcp_prompt&lt;/code>、&lt;code>load_mcp_resources&lt;/code> 这些没有对应实现，需要的话直接用 FastMCP 客户端的 &lt;code>client.get_prompt(...)&lt;/code> / &lt;code>client.read_resource(...)&lt;/code>。sampling 和 roots 则是因为 MCP 协议本身在无会话时代移除了，会抛 &lt;code>NotImplementedError&lt;/code>。&lt;/li>
&lt;/ul>
&lt;p>依赖包名也顺便更新一下：现在装的是 &lt;code>langchain[mcp]&lt;/code>，不再需要 &lt;code>pip install langchain-mcp-adapters&lt;/code>。&lt;/p>
&lt;p>收藏的参考资料：&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>资源&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;a class="link" href="https://guangzhengli.com/blog/zh/model-context-protocol" target="_blank" rel="noopener"
>MCP 终极指南（很值得读）&lt;/a>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;a class="link" href="https://docs.langchain.com/oss/python/langchain/mcp" target="_blank" rel="noopener"
>LangChain 官方 MCP 文档&lt;/a>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;a class="link" href="https://docs.langchain.com/oss/python/migrate/langchain-mcp-adapters" target="_blank" rel="noopener"
>从 langchain-mcp-adapters 迁移&lt;/a>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;a class="link" href="https://cobusgreyling.medium.com/using-langchain-with-model-context-protocol-mcp-e89b87ee3c4c" target="_blank" rel="noopener"
>Using LangChain With Model Context Protocol (MCP)&lt;/a>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;a class="link" href="https://zhuanlan.zhihu.com/p/27327515233" target="_blank" rel="noopener"
>知乎：一文看懂 MCP（大模型上下文协议）&lt;/a>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;a class="link" href="https://www.dailydoseofds.com/p/function-calling-mcp-for-llms/" target="_blank" rel="noopener"
>Function Call vs MCP&lt;/a>（本文配图出处）&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;h2 id="下一步">下一步
&lt;/h2>&lt;ul>
&lt;li>把数学工具换成你真正需要的东西：查数据库、调内部 API、文件处理，思路完全一样&lt;/li>
&lt;li>工具链路稳定之后，如果要做长周期、多步骤的 Agent 工程（文件系统、子智能体、上下文管理），看本站的《DeepAgents完全指南》&lt;/li>
&lt;/ul></description></item><item><title>LangGraph 实战：StateGraph、手写 ReAct 循环与 Map-Reduce 摘要</title><link>https://www.zata.cc/p/langgraph-%E5%AE%9E%E6%88%98stategraph%E6%89%8B%E5%86%99-react-%E5%BE%AA%E7%8E%AF%E4%B8%8E-map-reduce-%E6%91%98%E8%A6%81/</link><pubDate>Tue, 08 Sep 2026 12:30:00 +0800</pubDate><guid>https://www.zata.cc/p/langgraph-%E5%AE%9E%E6%88%98stategraph%E6%89%8B%E5%86%99-react-%E5%BE%AA%E7%8E%AF%E4%B8%8E-map-reduce-%E6%91%98%E8%A6%81/</guid><description>&lt;img src="https://www.zata.cc/p/langgraph-%E5%AE%9E%E6%88%98stategraph%E6%89%8B%E5%86%99-react-%E5%BE%AA%E7%8E%AF%E4%B8%8E-map-reduce-%E6%91%98%E8%A6%81/images/index/index.png" alt="Featured image of post LangGraph 实战：StateGraph、手写 ReAct 循环与 Map-Reduce 摘要" />&lt;h1 id="langgraph-实战stategraph手写-react-循环与-map-reduce-摘要">LangGraph 实战：StateGraph、手写 ReAct 循环与 Map-Reduce 摘要
&lt;/h1>&lt;p>LangChain 进入 1.x 之后，LangGraph 不再是&amp;quot;进阶选读&amp;quot;，而是整个框架的编排核心：官方一行式 &lt;code>create_agent&lt;/code> 的底下就是一张 LangGraph 图，Agent 的决策、工具调用、记忆全在图上跑。看懂 LangGraph，基本等于看懂 Agent 本身。&lt;/p>
&lt;p>这篇文章解决三件事。一是把 StateGraph、State、条件边这几个地基概念一次讲透；二是不用任何 Agent 封装，手写一遍 tool-call 循环——写完你会发现 Agent 没有魔法，它就是消息列表上转的一圈代码；三是用 LangGraph 把这个循环工程化成完整的 ReAct 天气助手，再补一个 Map-Reduce 长文本摘要图和记忆持久化。&lt;/p>
&lt;p>文章由我 2025 年 5～6 月的三篇笔记（《Langchain-Graph实战教程》《LangChain-实战-Tools使用教程》《Langgraph使用教程》）整合而成。合并时修正了原笔记的几处真实 bug——条件边示例的构建顺序与路由映射错误、手动循环里对 &lt;code>tool_call[&amp;quot;args&amp;quot;]&lt;/code> 多加的一句 &lt;code>json.loads&lt;/code>——过时 API 统一在文末版本注记里交代。全文示例模型走阿里云 DashScope 的 qwen 系（qwen-plus / qwen-max）OpenAI 兼容端点，只需要一个 &lt;code>DASHSCOPE_API_KEY&lt;/code> 环境变量。&lt;/p>
&lt;p>前五节是地基，任何版本都用得上。第六节是 2026 年的新增内容：LangGraph 1.1 / 1.2 给节点加上了&lt;strong>超时&lt;/strong>、&lt;strong>错误处理器&lt;/strong>和&lt;strong>优雅停机&lt;/strong>，还给流式输出换了一套以内容块为中心的 API。这几样是&amp;quot;从能跑到敢上生产&amp;quot;之间的那段距离，值得单独看。&lt;/p>
&lt;h2 id="1-基础三件套stategraphstate-与条件边">1. 基础三件套：StateGraph、State 与条件边
&lt;/h2>&lt;h3 id="11-stategraph把应用建成一张状态机">1.1 StateGraph：把应用建成一张状态机
&lt;/h3>&lt;p>&lt;code>StateGraph&lt;/code> 把应用定义为状态机：节点（node）是执行单元，通常是普通 Python 函数；边（edge）决定流程如何在节点之间转换。构建一张图固定是四步：&lt;/p>
&lt;ol>
&lt;li>定义 State——图运行时传递的数据结构&lt;/li>
&lt;li>&lt;code>add_node&lt;/code> 添加节点&lt;/li>
&lt;li>连边：普通边写死流转，条件边动态路由，入口用 &lt;code>START&lt;/code>&lt;/li>
&lt;li>&lt;code>compile()&lt;/code> 编译成可执行应用，之后用 &lt;code>invoke&lt;/code> 跑&lt;/li>
&lt;/ol>
&lt;p>依赖安装一次说清：&lt;code>pip install langchain langgraph langchain_openai&lt;/code>。&lt;/p>
&lt;h3 id="12-state-与-annotated覆盖还是追加">1.2 State 与 Annotated：覆盖还是追加
&lt;/h3>&lt;p>State 是一个 &lt;code>TypedDict&lt;/code>，定义图的模式和状态更新方式。聊天场景下 State 通常只有一个键：&lt;code>messages&lt;/code>。关键在 &lt;code>add_messages&lt;/code> 这个 reducer 函数——它决定新消息是&lt;strong>追加&lt;/strong>进列表，而不是覆盖列表：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">typing&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">Annotated&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">typing_extensions&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">TypedDict&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">langgraph.graph.message&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">add_messages&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">State&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">TypedDict&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># Messages 的类型是 &amp;#34;list&amp;#34;。`add_messages` 函数&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># 在注解中定义了如何更新这个状态键&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># （在这种情况下，它会将消息追加到列表中，而不是覆盖它们）&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">messages&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">Annotated&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nb">list&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">add_messages&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;code>Annotated&lt;/code> 是 Python 类型提示系统的特殊类型，允许为类型附加元数据。在 LangGraph 里它承担两个职责：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>类型定义&lt;/strong>：第一个参数 &lt;code>list&lt;/code> 表示 &lt;code>messages&lt;/code> 字段是列表&lt;/li>
&lt;li>&lt;strong>行为定义&lt;/strong>：第二个参数 &lt;code>add_messages&lt;/code> 是 reducer 函数，定义这个键怎么更新——新消息到来时追加进现有列表，而不是整体替换&lt;/li>
&lt;/ul>
&lt;p>两种语义的区别直观感受一下：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 不使用 Annotated（默认行为：覆盖）&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">state&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span>&lt;span class="s2">&amp;#34;messages&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;消息1&amp;#34;&lt;/span>&lt;span class="p">]}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">state&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;messages&amp;#34;&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;消息2&amp;#34;&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 结果：[&amp;#34;消息2&amp;#34;]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 使用 Annotated + add_messages&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">state&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span>&lt;span class="s2">&amp;#34;messages&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;消息1&amp;#34;&lt;/span>&lt;span class="p">]}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 新消息到来时，add_messages 将其追加到列表&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 结果：[&amp;#34;消息1&amp;#34;, &amp;#34;消息2&amp;#34;]&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>这种设计在对话场景特别有用：保持对话历史、消息按顺序累积、不会意外覆盖。反过来，没有 reducer 注解的字段就是&amp;quot;最后一次写入覆盖&amp;quot;的语义——第 4 节 Map-Reduce 的流水线正好要靠这个默认行为。&lt;/p>
&lt;h3 id="13-messagesstate内置的消息状态">1.3 MessagesState：内置的消息状态
&lt;/h3>&lt;p>自己写 &lt;code>Annotated[list, add_messages]&lt;/code> 是标准做法，LangGraph 也把它做成了内置状态类 &lt;code>MessagesState&lt;/code>，位于 &lt;code>langgraph.graph&lt;/code> 模块，专为对话场景设计。它内部维护一个 &lt;code>messages&lt;/code> 键，等价于：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">MessagesState&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">TypedDict&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">messages&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">Annotated&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nb">list&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">add_messages&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>它的特点：&lt;/p>
&lt;ul>
&lt;li>与 LangChain 的消息类型（&lt;code>HumanMessage&lt;/code>、&lt;code>AIMessage&lt;/code>、&lt;code>SystemMessage&lt;/code>、&lt;code>ToolMessage&lt;/code>）直接兼容&lt;/li>
&lt;li>节点返回 &lt;code>{&amp;quot;messages&amp;quot;: [...]}&lt;/code> 时自动追加进消息列表&lt;/li>
&lt;li>可以通过继承扩展自定义字段&lt;/li>
&lt;/ul>
&lt;p>一个能跑通的最小示例：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">os&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">langgraph.graph&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">MessagesState&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">StateGraph&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">START&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">END&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">langchain_core.messages&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">HumanMessage&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">langchain_openai&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">ChatOpenAI&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">llm&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">ChatOpenAI&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">model&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;qwen-plus&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">base_url&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;https://dashscope.aliyuncs.com/compatible-mode/v1&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">api_key&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">os&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">getenv&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;DASHSCOPE_API_KEY&amp;#34;&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">chatbot_node&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">state&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">MessagesState&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># 取出当前消息，调用 LLM；返回值会自动追加进 messages&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">response&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">llm&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">invoke&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">state&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;messages&amp;#34;&lt;/span>&lt;span class="p">])&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="p">{&lt;/span>&lt;span class="s2">&amp;#34;messages&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="n">response&lt;/span>&lt;span class="p">]}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">graph&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">StateGraph&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">MessagesState&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">graph&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">add_node&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;chatbot&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">chatbot_node&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">graph&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">add_edge&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">START&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;chatbot&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">graph&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">add_edge&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;chatbot&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">END&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">app&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">graph&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">compile&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">result&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">app&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">invoke&lt;/span>&lt;span class="p">({&lt;/span>&lt;span class="s2">&amp;#34;messages&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="n">HumanMessage&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">content&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;你好！今天能帮我什么？&amp;#34;&lt;/span>&lt;span class="p">)]})&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">for&lt;/span> &lt;span class="n">message&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">result&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;messages&amp;#34;&lt;/span>&lt;span class="p">]:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">message&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="vm">__class__&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="vm">__name__&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">message&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">content&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>运行后会打印两条消息：一条 &lt;code>HumanMessage&lt;/code>，一条 LLM 追加的 &lt;code>AIMessage&lt;/code>。&lt;/p>
&lt;p>需要扩展状态时，继承加字段即可：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">CustomMessagesState&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">MessagesState&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">user_id&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">session_id&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">int&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">graph&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">StateGraph&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">CustomMessagesState&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>状态将包含 &lt;code>messages&lt;/code> 列表以及 &lt;code>user_id&lt;/code> 和 &lt;code>session_id&lt;/code>，节点可读写这些字段。两个使用注意：消息必须符合 &lt;code>BaseMessage&lt;/code> 或兼容格式，塞普通字符串会出问题；长对话历史要么确认模型的上下文窗口装得下，要么自己加截断策略。&lt;/p>
&lt;h3 id="14-条件边让图自己决定下一步">1.4 条件边：让图自己决定下一步
&lt;/h3>&lt;p>普通边是写死的流转，条件边（&lt;code>add_conditional_edges&lt;/code>）根据状态动态路由：条件函数的返回值经过映射字典决定下一个节点。这是 LangGraph 表达分支逻辑的方式，也是后面 ReAct 循环里&amp;quot;有 tool_calls 就去执行工具、没有就结束&amp;quot;的实现基础。&lt;/p>
&lt;p>下面这个示例修正过原笔记的两处问题：一是 &lt;code>add_edge(START, &amp;quot;router&amp;quot;)&lt;/code> 写在了 &lt;code>add_node(&amp;quot;router&amp;quot;, ...)&lt;/code> 之前——节点还没注册就连边，LangGraph 校验阶段直接报错；二是路由函数返回 &lt;code>&amp;quot;help_node&amp;quot;&lt;/code> / &lt;code>&amp;quot;chatbot_node&amp;quot;&lt;/code>，与图中实际节点名 &lt;code>&amp;quot;help&amp;quot;&lt;/code> / &lt;code>&amp;quot;chatbot&amp;quot;&lt;/code> 错位，而且同一个函数既被当节点又被当条件函数。修正思路：路由函数只作为条件函数，从 &lt;code>START&lt;/code> 直接条件路由，返回值与节点名严格对齐。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">typing&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">Literal&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">langgraph.graph&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">MessagesState&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">StateGraph&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">START&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">END&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">langchain_core.messages&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">HumanMessage&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">AIMessage&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">route_by_intent&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">state&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">MessagesState&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="n">Literal&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;help&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;chatbot&amp;#34;&lt;/span>&lt;span class="p">]:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">last_message&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">state&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;messages&amp;#34;&lt;/span>&lt;span class="p">][&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">content&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">lower&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="s2">&amp;#34;帮助&amp;#34;&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">last_message&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="s2">&amp;#34;help&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="s2">&amp;#34;chatbot&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">help_node&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">state&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">MessagesState&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="p">{&lt;/span>&lt;span class="s2">&amp;#34;messages&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="n">AIMessage&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">content&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;这里是帮助信息！&amp;#34;&lt;/span>&lt;span class="p">)]}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">chatbot_node&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">state&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">MessagesState&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="p">{&lt;/span>&lt;span class="s2">&amp;#34;messages&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="n">AIMessage&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">content&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;来聊聊吧！&amp;#34;&lt;/span>&lt;span class="p">)]}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">graph&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">StateGraph&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">MessagesState&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">graph&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">add_node&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;help&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">help_node&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">graph&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">add_node&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;chatbot&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">chatbot_node&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 条件边：起点是 START，路由函数的返回值映射到目标节点&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">graph&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">add_conditional_edges&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">START&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">route_by_intent&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>&lt;span class="s2">&amp;#34;help&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;help&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;chatbot&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;chatbot&amp;#34;&lt;/span>&lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">graph&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">add_edge&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;help&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">END&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">graph&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">add_edge&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;chatbot&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">END&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">app&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">graph&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">compile&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">result&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">app&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">invoke&lt;/span>&lt;span class="p">({&lt;/span>&lt;span class="s2">&amp;#34;messages&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="n">HumanMessage&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">content&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;我需要帮助&amp;#34;&lt;/span>&lt;span class="p">)]})&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">result&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;messages&amp;#34;&lt;/span>&lt;span class="p">][&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">content&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="c1"># 输出：这里是帮助信息！&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;code>add_conditional_edges&lt;/code> 收三个参数：起点节点（这里用特殊节点 &lt;code>START&lt;/code>）、条件函数、返回值到目标节点的映射字典。条件边也可以从普通节点出发——第 3 节的 &lt;code>should_continue&lt;/code> 就是从 &lt;code>agent&lt;/code> 节点出发的。&lt;/p>
&lt;h2 id="2-手写-tool-call-循环agent-到底在替你做什么">2. 手写 tool-call 循环：Agent 到底在替你做什么
&lt;/h2>&lt;p>在让 LangGraph 接管之前，值得把这个循环亲手写一遍。&lt;code>create_agent&lt;/code>、&lt;code>AgentExecutor&lt;/code>、&lt;code>ToolNode&lt;/code> 这些封装拆开看没有魔法，核心就是：&lt;strong>在消息列表上跑一圈循环&lt;/strong>。整个流程四步：&lt;/p>
&lt;ol>
&lt;li>把用户消息和工具定义一起发给 LLM&lt;/li>
&lt;li>检查响应里有没有 &lt;code>tool_calls&lt;/code>&lt;/li>
&lt;li>有：执行对应函数，结果包成 &lt;code>ToolMessage&lt;/code> 回填进消息列表&lt;/li>
&lt;li>带着完整消息再次调用 LLM，它基于工具结果生成最终回复&lt;/li>
&lt;/ol>
&lt;p>先定义模型和工具（沿用原笔记的三个 mock 工具）：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">os&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">typing&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">List&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">Dict&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">Any&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">Optional&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">langchain_openai&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">ChatOpenAI&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">langchain_core.tools&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">tool&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">langchain_core.messages&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">HumanMessage&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">ToolMessage&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">model&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">ChatOpenAI&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">model&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;qwen-plus&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">base_url&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;https://dashscope.aliyuncs.com/compatible-mode/v1&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">api_key&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">os&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">getenv&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;DASHSCOPE_API_KEY&amp;#34;&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nd">@tool&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">get_weather&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">location&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">unit&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;celsius&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;获取指定位置的当前天气。&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">location&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">当前天气晴朗，温度25&lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">unit&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nd">@tool&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">create_user&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">name&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">email&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">age&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">Optional&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nb">int&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">None&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="n">Dict&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nb">str&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">Any&lt;/span>&lt;span class="p">]:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;在系统中创建新用户。&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">user&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span>&lt;span class="s2">&amp;#34;id&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">hash&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">email&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">%&lt;/span> &lt;span class="mi">10000&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;name&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">name&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;email&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">email&lt;/span>&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">age&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">user&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;age&amp;#34;&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">age&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">user&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nd">@tool&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">search_database&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">query&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="n">List&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">Dict&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nb">str&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">Any&lt;/span>&lt;span class="p">]]:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;搜索数据库获取信息。&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="s2">&amp;#34;产品A&amp;#34;&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">query&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="p">[{&lt;/span>&lt;span class="s2">&amp;#34;id&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;name&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;产品A&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;price&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mf">99.99&lt;/span>&lt;span class="p">}]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="p">[]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">tools&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="n">get_weather&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">create_user&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">search_database&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># bind_tools：把工具定义绑定到 LLM，LLM 就&amp;#34;知道&amp;#34;这些工具的存在&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">llm_with_tools&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">model&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">bind_tools&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">tools&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 工具名 -&amp;gt; Tool 对象，供手动执行时查找&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">available_tools&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span>&lt;span class="n">t&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">name&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">t&lt;/span> &lt;span class="k">for&lt;/span> &lt;span class="n">t&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">tools&lt;/span>&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;code>@tool&lt;/code> 装饰器把普通 Python 函数变成 LangChain 工具：函数签名和 docstring 会被转成 LLM 可读的工具描述——LLM 何时调用、怎么调用全靠 docstring。&lt;code>bind_tools(tools)&lt;/code> 是关键一步：绑定之后，LLM 在认为合适时就会在响应里返回&amp;quot;工具调用请求&amp;quot;，而不是纯文本。&lt;/p>
&lt;p>接下来是循环本身，不用任何 Agent 框架：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 步骤 1：初始消息列表&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">messages&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">HumanMessage&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">content&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;创建一个名为李四的用户，邮箱为lisi@example.com，年龄28岁。然后告诉我北京的天气。&amp;#34;&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 步骤 2：第一次调用 LLM（消息 + 工具定义）&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">first_response&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">llm_with_tools&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">invoke&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">messages&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">messages&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">append&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">first_response&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="c1"># AIMessage，可能带 tool_calls&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 步骤 3：检查并处理 tool_calls&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">if&lt;/span> &lt;span class="n">first_response&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">tool_calls&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="n">tool_call&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">first_response&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">tool_calls&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># tool_call 是 {&amp;#34;name&amp;#34;: ..., &amp;#34;args&amp;#34;: ..., &amp;#34;id&amp;#34;: ...}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># 注意：args 在 LangChain 里已经是解析好的 dict&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">function_name&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">tool_call&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;name&amp;#34;&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">function_args&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">tool_call&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;args&amp;#34;&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">function_name&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">available_tools&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">try&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">result&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">available_tools&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">function_name&lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">invoke&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">function_args&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">except&lt;/span> &lt;span class="ne">Exception&lt;/span> &lt;span class="k">as&lt;/span> &lt;span class="n">e&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">result&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;Error executing function &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">function_name&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">e&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">content&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">result&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">else&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">content&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;Error: Unknown function &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">function_name&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># 结果包成 ToolMessage，tool_call_id 关联原始请求&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">messages&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">append&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">ToolMessage&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">tool_call_id&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">tool_call&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;id&amp;#34;&lt;/span>&lt;span class="p">],&lt;/span> &lt;span class="c1"># 必须提供原始调用的 ID&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">content&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">content&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">name&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">function_name&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># 步骤 4：带着工具结果二次调用 LLM&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">final_response&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">llm_with_tools&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">invoke&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">messages&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">final_response&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">content&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">else&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># 第一次响应就没有 tool_calls，说明 LLM 直接回答了&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">first_response&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">content&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>四个关键点：&lt;/p>
&lt;ul>
&lt;li>&lt;code>first_response.tool_calls&lt;/code> 是一个列表。这条消息同时涉及&amp;quot;创建用户&amp;quot;和&amp;quot;查天气&amp;quot;两件事，模型支持并行工具调用时，一次响应里会返回多个 tool_calls，循环逐个执行、逐个回填——并行工具调用的全部真相就这么多&lt;/li>
&lt;li>&lt;code>tool_call[&amp;quot;args&amp;quot;]&lt;/code> 拿到的&lt;strong>已经是解析好的 dict&lt;/strong>。原笔记在这里紧跟过一句 &lt;code>json.loads(...)&lt;/code>——dict 传进 &lt;code>loads&lt;/code> 会直接 TypeError，这句必须删掉。较老的单函数 &lt;code>function_call&lt;/code> 格式里参数才是 JSON 字符串，两种格式混写最容易在这里翻车&lt;/li>
&lt;li>&lt;code>ToolMessage&lt;/code> 必须带 &lt;code>tool_call_id&lt;/code>，LLM 靠它把结果和哪一次请求对应起来&lt;/li>
&lt;li>二次调用时，发给 LLM 的是完整三段消息：&lt;code>HumanMessage&lt;/code>（原始请求）+ &lt;code>AIMessage&lt;/code>（带 tool_calls 的调用决定）+ &lt;code>ToolMessage&lt;/code>（执行结果）。LLM 看到这三段，才能生成综合回复&lt;/li>
&lt;/ul>
&lt;p>把这段跑通之后再回头看 Agent 封装：&lt;code>create_agent&lt;/code> / &lt;code>AgentExecutor&lt;/code> / LangGraph 的 &lt;code>ToolNode&lt;/code>，做的就是&amp;quot;第 3 步 + 第 4 步包进一个带停止条件的循环&amp;quot;——没有 tool_calls 就退出循环返回文本。手写一遍的意义就在这里：以后封装出问题，你知道往哪一层查。&lt;/p>
&lt;h2 id="3-完整实战天气助手-react">3. 完整实战：天气助手 ReAct
&lt;/h2>&lt;p>现在把第 2 节手写的循环交给 LangGraph 工程化：用户用自然语言问某城市天气（示例支持上海和北京），应用自动判断是否调用天气工具，拿到结果后作答，并且记得住对话上下文。&lt;/p>
&lt;p>这一节整体迁自我当时的天气助手实战笔记，代码分六段，每段跟着当时的知识点注解。&lt;/p>
&lt;h3 id="31-环境与依赖">3.1 环境与依赖
&lt;/h3>&lt;p>安装（原笔记把 &lt;code>typing&lt;/code>、&lt;code>os&lt;/code> 也写进了 pip install——它们是标准库，不是 pip 包，这里已删掉）：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">pip install langchain langgraph langchain_openai langchain_core
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>设置环境变量 &lt;code>DASHSCOPE_API_KEY&lt;/code>，并做启动检查：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">os&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">if&lt;/span> &lt;span class="ow">not&lt;/span> &lt;span class="n">os&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">getenv&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;DASHSCOPE_API_KEY&amp;#34;&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">raise&lt;/span> &lt;span class="ne">ValueError&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;DASHSCOPE_API_KEY environment variable not set. Please set it before running.&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;code>os.getenv&lt;/code> 从环境变量读密钥，避免硬编码进代码。&lt;/p>
&lt;h3 id="32-工具定义tool">3.2 工具定义：@tool
&lt;/h3>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">langchain_core.tools&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">tool&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nd">@tool&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">get_weather_updates&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">query&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"> 查询城市当前天气 (Query current weather for a city)
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"> Use this tool to find out the current weather for a given city.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"> &amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;--- Tool &amp;#39;get_weather_updates&amp;#39; called with query: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">query&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2"> ---&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">query_lower&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">query&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">lower&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="s2">&amp;#34;上海&amp;#34;&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">query_lower&lt;/span> &lt;span class="ow">or&lt;/span> &lt;span class="s1">&amp;#39;shanghai&amp;#39;&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">query_lower&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="s2">&amp;#34;now is 30 celsius, foggy&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">elif&lt;/span> &lt;span class="s2">&amp;#34;北京&amp;#34;&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">query_lower&lt;/span> &lt;span class="ow">or&lt;/span> &lt;span class="s1">&amp;#39;beijing&amp;#39;&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">query_lower&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="s2">&amp;#34;now is 20 celsius, sunny&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">else&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;Weather information for &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">query&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2"> not available with this tool. Only Shanghai and Beijing are supported.&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">tools&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="n">get_weather_updates&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>知识点：&lt;/p>
&lt;ul>
&lt;li>&lt;code>@tool&lt;/code> 装饰器把普通函数转成 LangChain 工具，&lt;code>query: str&lt;/code> 的类型提示和返回值提示都会进入工具描述&lt;/li>
&lt;li>docstring 至关重要：LLM 根据工具名和 docstring 判断&lt;strong>何时调用&lt;/strong>（工具能干什么）和&lt;strong>怎么调用&lt;/strong>（&lt;code>query&lt;/code> 参数该传什么）。描述含糊，调用就跟着含糊&lt;/li>
&lt;li>工具内部目前是 mock 的假数据，实际应用中替换成真实天气 API 调用即可&lt;/li>
&lt;li>&lt;code>tools = [get_weather_updates]&lt;/code> 收进列表，后续绑定到 LLM&lt;/li>
&lt;/ul>
&lt;h3 id="33-模型初始化与工具绑定">3.3 模型初始化与工具绑定
&lt;/h3>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">langchain_openai&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">ChatOpenAI&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">model&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">ChatOpenAI&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">model&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;qwen-max&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">base_url&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;https://dashscope.aliyuncs.com/compatible-mode/v1&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">api_key&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">os&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">getenv&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;DASHSCOPE_API_KEY&amp;#34;&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">temperature&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="mi">0&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">model_with_tools&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">model&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">bind_tools&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">tools&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>知识点：&lt;/p>
&lt;ul>
&lt;li>&lt;code>ChatOpenAI&lt;/code> 虽然名字带 OpenAI，但通过 &lt;code>base_url&lt;/code> + &lt;code>api_key&lt;/code> 可以接任何 OpenAI 兼容服务，这里接 DashScope&lt;/li>
&lt;li>&lt;code>temperature=0&lt;/code> 让输出更确定，对需要精确调用工具的场景是稳妥选择&lt;/li>
&lt;li>&lt;code>bind_tools(tools)&lt;/code> 之后，LLM 在认为合适时返回的将不是纯文本，而是携带工具调用指令的 &lt;code>AIMessage&lt;/code>&lt;/li>
&lt;/ul>
&lt;h3 id="34-状态与节点">3.4 状态与节点
&lt;/h3>&lt;p>状态用第 1 节的 &lt;code>MessagesState&lt;/code>——本质就是 &lt;code>messages: Annotated[list, add_messages]&lt;/code>，每次节点返回 &lt;code>{&amp;quot;messages&amp;quot;: [...]}&lt;/code> 时自动追加。节点有两个，一个调模型，一个执行工具：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">langchain_core.messages&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">HumanMessage&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">AIMessage&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">ToolMessage&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">langgraph.graph&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">StateGraph&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">MessagesState&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">langgraph.prebuilt&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">ToolNode&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">tool_node&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">ToolNode&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">tools&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="c1"># 预构建的工具执行节点&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">call_model&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">state&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">MessagesState&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">messages&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">state&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;messages&amp;#34;&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;--- Calling model with &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="nb">len&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">messages&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2"> messages. Last message type: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="nb">type&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">messages&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">])&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2"> ---&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">response&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">model_with_tools&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">invoke&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">messages&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="p">{&lt;/span>&lt;span class="s2">&amp;#34;messages&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="n">response&lt;/span>&lt;span class="p">]}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>知识点：&lt;/p>
&lt;ul>
&lt;li>&lt;code>ToolNode(tools)&lt;/code> 是 LangGraph 的预构建节点：接收工具调用请求，执行对应工具，把输出包装成 &lt;code>ToolMessage&lt;/code> 返回——第 2 节手写的那段 for 循环，它内部替你做了&lt;/li>
&lt;li>&lt;code>call_model&lt;/code> 是自定义的 Agent 节点：把当前全部消息历史交给绑定了工具的 LLM。LLM 能直接回答时返回普通 &lt;code>AIMessage&lt;/code>；需要工具时返回带 &lt;code>tool_calls&lt;/code> 属性的 &lt;code>AIMessage&lt;/code>，其中包含工具名、参数和唯一的 &lt;code>tool_call_id&lt;/code>&lt;/li>
&lt;li>返回值封装成 &lt;code>{&amp;quot;messages&amp;quot;: [response]}&lt;/code>，由 &lt;code>MessagesState&lt;/code> 自动追加进消息列表&lt;/li>
&lt;/ul>
&lt;h3 id="35-条件边与图构建">3.5 条件边与图构建
&lt;/h3>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">typing&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">Literal&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">langgraph.graph&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">START&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">END&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">langgraph.checkpoint.memory&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">MemorySaver&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">should_continue&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">state&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">MessagesState&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="n">Literal&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;tools&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">END&lt;/span>&lt;span class="p">]:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">messages&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">state&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;messages&amp;#34;&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">last_message&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">messages&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># 最后一条消息是否是带工具调用请求的 AIMessage&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="nb">hasattr&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">last_message&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">&amp;#39;tool_calls&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="ow">and&lt;/span> &lt;span class="n">last_message&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">tool_calls&lt;/span> &lt;span class="ow">and&lt;/span> &lt;span class="nb">len&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">last_message&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">tool_calls&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">&amp;gt;&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;--- LLM decided to use tools: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">last_message&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">tool_calls&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2"> ---&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="s2">&amp;#34;tools&amp;#34;&lt;/span> &lt;span class="c1"># 有工具调用，去 &amp;#34;tools&amp;#34; 节点&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;--- LLM decided NOT to use tools. Ending. ---&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">END&lt;/span> &lt;span class="c1"># 没有，结束流程&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">workflow&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">StateGraph&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">MessagesState&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">workflow&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">add_node&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;agent&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">call_model&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="c1"># Agent 节点&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">workflow&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">add_node&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;tools&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">tool_node&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="c1"># 工具节点&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">workflow&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">add_edge&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">START&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;agent&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="c1"># 入口&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 条件边：从 &amp;#34;agent&amp;#34; 出发，由 should_continue 的返回值决定去向&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">workflow&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">add_conditional_edges&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;agent&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">should_continue&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;tools&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;tools&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="c1"># 返回 &amp;#34;tools&amp;#34; -&amp;gt; 去 &amp;#34;tools&amp;#34; 节点&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">END&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">END&lt;/span> &lt;span class="c1"># 返回 END -&amp;gt; 结束&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">workflow&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">add_edge&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;tools&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;agent&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="c1"># 工具执行完，回到 &amp;#34;agent&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">checkpointer&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">MemorySaver&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="c1"># 内存检查点，保存对话状态&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">app&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">workflow&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">compile&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">checkpointer&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">checkpointer&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>知识点：&lt;/p>
&lt;ul>
&lt;li>&lt;code>tools -&amp;gt; agent&lt;/code> 这条回边是整张图的灵魂：工具执行完回到 LLM，让它基于工具结果继续生成——这就是 ReAct（Reasoning and Acting）循环&lt;/li>
&lt;li>&lt;code>MemorySaver&lt;/code> 把每个线程（对话）的状态存在内存里，让对话有记忆；生产环境要换持久化存储（第 5 节展开）&lt;/li>
&lt;li>&lt;code>compile(checkpointer=...)&lt;/code> 把图定义转成可执行应用，同时接入状态持久化&lt;/li>
&lt;/ul>
&lt;h3 id="36-执行两轮对话">3.6 执行：两轮对话
&lt;/h3>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 用字符串区分不同的对话线程&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">thread_id&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;chat_thread_42&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">config&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span>&lt;span class="s2">&amp;#34;configurable&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">{&lt;/span>&lt;span class="s2">&amp;#34;thread_id&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">thread_id&lt;/span>&lt;span class="p">}}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="se">\n&lt;/span>&lt;span class="s2">Invoking for Shanghai...&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">shanghai_input&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span>&lt;span class="s2">&amp;#34;messages&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="n">HumanMessage&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">content&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;what&amp;#39;s the weather in Shanghai?&amp;#34;&lt;/span>&lt;span class="p">)]}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">final_state_shanghai&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">app&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">invoke&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">shanghai_input&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">config&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">config&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">if&lt;/span> &lt;span class="n">final_state_shanghai&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;messages&amp;#34;&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="ow">and&lt;/span> &lt;span class="nb">isinstance&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">final_state_shanghai&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;messages&amp;#34;&lt;/span>&lt;span class="p">][&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">],&lt;/span> &lt;span class="n">AIMessage&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;Final response for Shanghai: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">final_state_shanghai&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s1">&amp;#39;messages&amp;#39;&lt;/span>&lt;span class="p">][&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">content&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="se">\n&lt;/span>&lt;span class="s2">Invoking for Beijing (same thread)...&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">beijing_input_message&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">HumanMessage&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">content&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;what&amp;#39;s the weather in Beijing?&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 第二次调用：同一个 thread_id，新消息自动追加到已有历史之后&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">final_state_beijing&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">app&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">invoke&lt;/span>&lt;span class="p">({&lt;/span>&lt;span class="s2">&amp;#34;messages&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="n">beijing_input_message&lt;/span>&lt;span class="p">]},&lt;/span> &lt;span class="n">config&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">config&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">if&lt;/span> &lt;span class="n">final_state_beijing&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;messages&amp;#34;&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="ow">and&lt;/span> &lt;span class="nb">isinstance&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">final_state_beijing&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;messages&amp;#34;&lt;/span>&lt;span class="p">][&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">],&lt;/span> &lt;span class="n">AIMessage&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;Final response for Beijing: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">final_state_beijing&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s1">&amp;#39;messages&amp;#39;&lt;/span>&lt;span class="p">][&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">content&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>知识点：&lt;/p>
&lt;ul>
&lt;li>&lt;code>thread_id&lt;/code> 区分对话会话。配合 checkpointer，相同 &lt;code>thread_id&lt;/code> 的调用共享同一段对话历史&lt;/li>
&lt;li>第二次问北京天气时只传新的 &lt;code>HumanMessage&lt;/code>——&lt;code>MessagesState&lt;/code> 自动把它追加到上海那轮之后，LLM 处理北京问题时看得到之前的完整对话&lt;/li>
&lt;li>&lt;code>final_state[&amp;quot;messages&amp;quot;][-1].content&lt;/code> 取最后一条 AI 消息，即最终回复&lt;/li>
&lt;/ul>
&lt;h3 id="37-六步执行流程梳理">3.7 六步执行流程梳理
&lt;/h3>&lt;p>现在把 &amp;ldquo;what&amp;rsquo;s the weather in Shanghai?&amp;rdquo; 这句话输进去之后，图里到底发生了什么逐步拆开——这段是当初记这篇笔记时最值得留下的部分：&lt;/p>
&lt;ol>
&lt;li>
&lt;p>&lt;strong>入口（&lt;code>agent&lt;/code> 节点）&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>&lt;code>call_model&lt;/code> 被调用，&lt;code>model_with_tools.invoke&lt;/code> 收到 &lt;code>[HumanMessage(content=&amp;quot;what's the weather in Shanghai?&amp;quot;)]&lt;/code>&lt;/li>
&lt;li>LLM（qwen-max）分析输入和 &lt;code>get_weather_updates&lt;/code> 的 docstring，判断需要调用这个工具，参数 &lt;code>query&lt;/code> 应为 &amp;ldquo;Shanghai&amp;rdquo;&lt;/li>
&lt;li>LLM 返回带 &lt;code>tool_calls&lt;/code> 的 &lt;code>AIMessage&lt;/code>，例如 &lt;code>tool_calls=[ToolCall(name='get_weather_updates', args={'query': 'Shanghai'}, id='call_abc123')]&lt;/code>&lt;/li>
&lt;li>&lt;code>call_model&lt;/code> 返回 &lt;code>{&amp;quot;messages&amp;quot;: [AIMessage_with_tool_call]}&lt;/code>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>条件路由（&lt;code>should_continue&lt;/code>）&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>检查最后一条消息，发现有 &lt;code>tool_calls&lt;/code>&lt;/li>
&lt;li>返回 &lt;code>&amp;quot;tools&amp;quot;&lt;/code>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>工具执行（&lt;code>tools&lt;/code> 节点）&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>&lt;code>ToolNode&lt;/code> 接收 &lt;code>tool_calls&lt;/code>，找到 &lt;code>get_weather_updates&lt;/code>，用参数 &lt;code>{'query': 'Shanghai'}&lt;/code> 调用&lt;/li>
&lt;li>工具执行，返回字符串 &lt;code>&amp;quot;now is 30 celsius, foggy&amp;quot;&lt;/code>&lt;/li>
&lt;li>&lt;code>ToolNode&lt;/code> 把结果包装成 &lt;code>ToolMessage(content=&amp;quot;now is 30 celsius, foggy&amp;quot;, tool_call_id='call_abc123')&lt;/code>&lt;/li>
&lt;li>返回 &lt;code>{&amp;quot;messages&amp;quot;: [ToolMessage_with_result]}&lt;/code>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>返回 Agent（&lt;code>agent&lt;/code> 节点）&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>&lt;code>tools -&amp;gt; agent&lt;/code> 的回边把流程带回 &lt;code>call_model&lt;/code>，此时 &lt;code>state[&amp;quot;messages&amp;quot;]&lt;/code> 包含：
&lt;ol>
&lt;li>&lt;code>HumanMessage(content=&amp;quot;what's the weather in Shanghai?&amp;quot;)&lt;/code>&lt;/li>
&lt;li>&lt;code>AIMessage(..., tool_calls=[...])&lt;/code>&lt;/li>
&lt;li>&lt;code>ToolMessage(content=&amp;quot;now is 30 celsius, foggy&amp;quot;, tool_call_id='call_abc123')&lt;/code>&lt;/li>
&lt;/ol>
&lt;/li>
&lt;li>LLM 看到原始问题、自己调用工具的决定、工具的执行结果，基于这些生成自然回复，例如 &lt;code>AIMessage(content=&amp;quot;The current weather in Shanghai is 30 degrees Celsius and foggy.&amp;quot;)&lt;/code>&lt;/li>
&lt;li>&lt;code>call_model&lt;/code> 返回 &lt;code>{&amp;quot;messages&amp;quot;: [AIMessage_final_response]}&lt;/code>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>条件路由（&lt;code>should_continue&lt;/code>）&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>检查最后一条消息（最终回复），没有 &lt;code>tool_calls&lt;/code>&lt;/li>
&lt;li>返回 &lt;code>END&lt;/code>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>结束&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>图执行结束，&lt;code>app.invoke&lt;/code> 返回最终状态&lt;/li>
&lt;li>代码从 &lt;code>final_state[&amp;quot;messages&amp;quot;][-1].content&lt;/code> 提取并打印最终回复&lt;/li>
&lt;li>&lt;code>MemorySaver&lt;/code> 把包含这四条消息的完整状态存到 &lt;code>thread_id=&amp;quot;chat_thread_42&amp;quot;&lt;/code> 名下&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ol>
&lt;p>后续问北京天气时，由于 &lt;code>thread_id&lt;/code> 相同，&lt;code>call_model&lt;/code> 的初始状态里就包含上海那四条消息，再加上新的 &lt;code>HumanMessage(content=&amp;quot;what's the weather in Beijing?&amp;quot;)&lt;/code>——LLM 因此有上下文感知。&lt;/p>
&lt;p>回头看这张图，它就是第 2 节手写循环的图化：&lt;code>agent&lt;/code> 节点是&amp;quot;调 LLM&amp;quot;，&lt;code>tools&lt;/code> 节点是&amp;quot;执行 tool_calls 并回填 ToolMessage&amp;quot;，&lt;code>should_continue&lt;/code> 条件边是循环的停止条件。手写过一遍再看这里，每一层都能对上号。&lt;/p>
&lt;h3 id="38-收尾要点">3.8 收尾要点
&lt;/h3>&lt;ul>
&lt;li>工具定义（&lt;code>@tool&lt;/code>）：docstring 是 LLM 理解工具的唯一入口&lt;/li>
&lt;li>模型绑定（&lt;code>bind_tools&lt;/code>）：让 LLM 知道有哪些工具可用&lt;/li>
&lt;li>&lt;code>MessagesState&lt;/code>：消息历史的自动追加&lt;/li>
&lt;li>&lt;code>ToolNode&lt;/code>：预置的工具执行器&lt;/li>
&lt;li>条件边 + &lt;code>END&lt;/code>：循环的出口&lt;/li>
&lt;li>&lt;code>MemorySaver&lt;/code> + &lt;code>thread_id&lt;/code>：跨轮次的对话记忆&lt;/li>
&lt;/ul>
&lt;h2 id="4-map-reduce超长文本分段摘要">4. Map-Reduce：超长文本分段摘要
&lt;/h2>&lt;p>前两节都在聊 Agent 循环，换一个场景：文本长度超过模型单次上下文窗口，怎么做摘要？&lt;/p>
&lt;p>经典解法是 Map-Reduce——大数据领域的老朋友，用在长文本摘要上正合适：&lt;/p>
&lt;ol>
&lt;li>&lt;strong>切片（Chunking）&lt;/strong>：把长文本切成模型一次吃得下的小块&lt;/li>
&lt;li>&lt;strong>映射（Map）&lt;/strong>：对每个块独立摘要，这一步天然可并行&lt;/li>
&lt;li>&lt;strong>规约（Reduce）&lt;/strong>：把所有小摘要合并，让模型做最后一次整合，产出连贯的最终摘要&lt;/li>
&lt;/ol>
&lt;p>LangGraph 适合表达这种多步骤工作流：状态在节点间流转，每一步做什么清清楚楚。完整代码：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">os&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">typing&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">TypedDict&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">List&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">langchain_core.prompts&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">ChatPromptTemplate&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">langchain_openai&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">ChatOpenAI&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">langchain.text_splitter&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">RecursiveCharacterTextSplitter&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">langgraph.graph&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">StateGraph&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">START&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">END&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">llm&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">ChatOpenAI&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">model&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;qwen-plus&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">base_url&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;https://dashscope.aliyuncs.com/compatible-mode/v1&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">api_key&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">os&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">getenv&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;DASHSCOPE_API_KEY&amp;#34;&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">temperature&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">text_splitter&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">RecursiveCharacterTextSplitter&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">chunk_size&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="mi">2000&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">chunk_overlap&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="mi">200&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 1. 图的状态：整个流程要跟踪的全部数据&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">GraphState&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">TypedDict&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">text&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span> &lt;span class="c1"># 原始长文本&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">chunks&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">List&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nb">str&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="c1"># 切分后的文本块列表&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">summaries&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">List&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nb">str&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="c1"># 每个文本块的摘要列表&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">final_summary&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span> &lt;span class="c1"># 最终的摘要&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 2. 节点：切分&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">chunk_text_node&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">state&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">GraphState&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;--- 正在切分文本 ---&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">chunks&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">text_splitter&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">split_text&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">state&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s1">&amp;#39;text&amp;#39;&lt;/span>&lt;span class="p">])&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="p">{&lt;/span>&lt;span class="s2">&amp;#34;chunks&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">chunks&lt;/span>&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 3. 节点：Map，对每个块独立摘要&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">summarize_map_node&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">state&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">GraphState&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;--- 正在对每个块进行摘要 (Map) ---&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">map_prompt&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">ChatPromptTemplate&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">from_template&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;简要总结以下文本内容：&lt;/span>&lt;span class="se">\n\n&lt;/span>&lt;span class="si">{chunk}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">summarize_chain&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">map_prompt&lt;/span> &lt;span class="o">|&lt;/span> &lt;span class="n">llm&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># batch 并行处理所有块，max_concurrency 控制并发&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">summaries&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">summarize_chain&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">batch&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">[{&lt;/span>&lt;span class="s2">&amp;#34;chunk&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">chunk&lt;/span>&lt;span class="p">}&lt;/span> &lt;span class="k">for&lt;/span> &lt;span class="n">chunk&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">state&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s1">&amp;#39;chunks&amp;#39;&lt;/span>&lt;span class="p">]],&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">config&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="s2">&amp;#34;max_concurrency&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">5&lt;/span>&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># batch 返回 AIMessage 列表，提取 .content&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">cleaned_summaries&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="n">s&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">content&lt;/span> &lt;span class="k">for&lt;/span> &lt;span class="n">s&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">summaries&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="p">{&lt;/span>&lt;span class="s2">&amp;#34;summaries&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">cleaned_summaries&lt;/span>&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 4. 节点：Reduce，合并所有小摘要&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">summarize_reduce_node&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">state&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">GraphState&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;--- 正在合并所有摘要 (Reduce) ---&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">summaries_joined&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="se">\n\n&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">join&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">state&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s1">&amp;#39;summaries&amp;#39;&lt;/span>&lt;span class="p">])&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">reduce_prompt&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">ChatPromptTemplate&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">from_template&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;你收到了关于一个长文档的多个摘要。请将它们整合成一个连贯、流畅、全面的最终摘要。&lt;/span>&lt;span class="se">\n\n&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;以下是各个部分的摘要：&lt;/span>&lt;span class="se">\n&lt;/span>&lt;span class="si">{summaries_text}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">reduce_chain&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">reduce_prompt&lt;/span> &lt;span class="o">|&lt;/span> &lt;span class="n">llm&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">final_summary&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">reduce_chain&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">invoke&lt;/span>&lt;span class="p">({&lt;/span>&lt;span class="s2">&amp;#34;summaries_text&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">summaries_joined&lt;/span>&lt;span class="p">})&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="p">{&lt;/span>&lt;span class="s2">&amp;#34;final_summary&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">final_summary&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">content&lt;/span>&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 5. 连图&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">workflow&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">StateGraph&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">GraphState&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">workflow&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">add_node&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;chunker&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">chunk_text_node&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">workflow&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">add_node&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;mapper&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">summarize_map_node&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">workflow&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">add_node&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;reducer&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">summarize_reduce_node&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">workflow&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">add_edge&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">START&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;chunker&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="c1"># 入口&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">workflow&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">add_edge&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;chunker&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;mapper&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">workflow&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">add_edge&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;mapper&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;reducer&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">workflow&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">add_edge&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;reducer&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">END&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">app&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">workflow&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">compile&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 6. 跑一个示例：把一段文本重复 10 次模拟长文本&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">long_text&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2">人工智能（AI）正在以前所未有的速度改变世界。从自动驾驶汽车到医疗诊断，AI的应用无处不在。
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2">其核心技术包括机器学习、深度学习和自然语言处理。机器学习使计算机能够从数据中学习规律，而无需进行显式编程。
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2">深度学习是机器学习的一个分支，它利用深度神经网络模型，在图像识别、语音识别等领域取得了巨大成功。
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2">自然语言处理则致力于让计算机能够理解和生成人类语言，Siri和ChatGPT就是最好的例子。
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2">然而，AI的发展也带来了挑战，如数据隐私、算法偏见和就业冲击。解决这些问题需要技术、法律和伦理的共同努力。
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;&lt;/span> &lt;span class="o">*&lt;/span> &lt;span class="mi">10&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">final_state&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">app&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">invoke&lt;/span>&lt;span class="p">({&lt;/span>&lt;span class="s2">&amp;#34;text&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">long_text&lt;/span>&lt;span class="p">})&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">final_state&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s1">&amp;#39;final_summary&amp;#39;&lt;/span>&lt;span class="p">])&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>代码要点：&lt;/p>
&lt;ol>
&lt;li>&lt;code>GraphState&lt;/code> 是 TypedDict，像一份清单，规定流程要跟踪的全部数据（原文、块、各块摘要、最终摘要）。注意这里的字段&lt;strong>没有&lt;/strong> &lt;code>add_messages&lt;/code> 这类 reducer——默认覆盖语义，每个字段只由一个节点整体写入一次，正好匹配这条线性流水线（对照 1.2 节）&lt;/li>
&lt;li>&lt;code>chunk_text_node&lt;/code> 是入口节点：接收 &lt;code>text&lt;/code>，用 &lt;code>RecursiveCharacterTextSplitter&lt;/code> 切分，&lt;code>chunks&lt;/code> 写回状态&lt;/li>
&lt;li>&lt;code>summarize_map_node&lt;/code> 是 Map 步骤：&lt;code>batch()&lt;/code> 并行为每个块生成摘要，比手写 for 循环高效；返回的 &lt;code>AIMessage&lt;/code> 列表要取 &lt;code>.content&lt;/code>&lt;/li>
&lt;li>&lt;code>summarize_reduce_node&lt;/code> 是 Reduce 步骤：小摘要拼接后交给 LLM 做最后一次、也是最关键的整合&lt;/li>
&lt;li>边的连接定义流水线顺序：&lt;code>chunker -&amp;gt; mapper -&amp;gt; reducer -&amp;gt; END&lt;/code>&lt;/li>
&lt;li>&lt;code>invoke({&amp;quot;text&amp;quot;: long_text})&lt;/code> 启动整张图——初始状态只需要 &lt;code>text&lt;/code> 一个键，LangGraph 按定义好的流程执行完所有步骤，返回包含全部结果的最终状态&lt;/li>
&lt;/ol>
&lt;p>这套结构既绕开了上下文窗口限制，流程又清晰可维护：想加一步&amp;quot;摘要去重&amp;quot;或&amp;quot;质量过滤&amp;quot;，加个节点就行。&lt;/p>
&lt;h2 id="5-记忆与持久化memorysaver-与-thread_id">5. 记忆与持久化：MemorySaver 与 thread_id
&lt;/h2>&lt;p>第 3 节的天气助手已经用上了 &lt;code>MemorySaver&lt;/code>，这里把机制单独拆开讲，因为它值得。&lt;/p>
&lt;p>LangGraph 的记忆靠 checkpointer（检查点）机制：图每执行完一步，就把当前状态存一份快照。编译时传入 checkpointer，运行时指定 &lt;code>thread_id&lt;/code>：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">langgraph.checkpoint.memory&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">MemorySaver&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">checkpointer&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">MemorySaver&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">app&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">graph&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">compile&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">checkpointer&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">checkpointer&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">config&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span>&lt;span class="s2">&amp;#34;configurable&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">{&lt;/span>&lt;span class="s2">&amp;#34;thread_id&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;1&amp;#34;&lt;/span>&lt;span class="p">}}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">result&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">app&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">invoke&lt;/span>&lt;span class="p">({&lt;/span>&lt;span class="s2">&amp;#34;messages&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="n">HumanMessage&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">content&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;你好！&amp;#34;&lt;/span>&lt;span class="p">)]},&lt;/span> &lt;span class="n">config&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 后续调用恢复状态：只传新消息，历史自动接上&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">result&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">app&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">invoke&lt;/span>&lt;span class="p">({&lt;/span>&lt;span class="s2">&amp;#34;messages&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="n">HumanMessage&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">content&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;继续聊&amp;#34;&lt;/span>&lt;span class="p">)]},&lt;/span> &lt;span class="n">config&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;ul>
&lt;li>&lt;code>thread_id&lt;/code> 是记忆的单位：同一个 &lt;code>thread_id&lt;/code> 下的调用共享状态，换一个 &lt;code>thread_id&lt;/code> 就是全新对话&lt;/li>
&lt;li>&lt;code>MemorySaver&lt;/code> 存在内存里，进程结束记忆就没了——适合开发和测试&lt;/li>
&lt;li>生产环境要换持久化实现：官方提供了 SQLite、Postgres 等独立安装的 checkpointer 包，也可以自己接 Redis 这类存储&lt;/li>
&lt;/ul>
&lt;p>命名注记：这个类现在叫 &lt;code>InMemorySaver&lt;/code>，&lt;code>MemorySaver&lt;/code> 是保留的旧别名，两个名字指向同一个东西，新代码建议用前者。&lt;/p>
&lt;h2 id="6-langgraph-11--12从能跑到敢上生产">6. LangGraph 1.1 / 1.2：从&amp;quot;能跑&amp;quot;到&amp;quot;敢上生产&amp;quot;
&lt;/h2>&lt;p>前五节的图，跑通没问题，但真接到线上会遇到几类很实际的问题：某个节点卡住了整张图跟着挂、某一步失败之后没有退路只能整个 run 报废、服务收到 SIGTERM 时正在跑的活儿全丢、长对话的 checkpoint 越存越大。1.1 和 1.2 基本就是冲着这些来的。&lt;/p>
&lt;p>以下内容默认需要 &lt;code>langgraph&amp;gt;=1.2&lt;/code>；6.7 那节是 1.1 引入的。&lt;/p>
&lt;h3 id="61-节点超时timeoutpolicy">6.1 节点超时：&lt;code>TimeoutPolicy&lt;/code>
&lt;/h3>&lt;p>&lt;code>add_node&lt;/code> 现在接受 &lt;code>timeout=&lt;/code>，给单次节点尝试封顶：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">datetime&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">timedelta&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">langgraph.types&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">TimeoutPolicy&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 简单封顶：秒数或 timedelta&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">builder&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">add_node&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;call_model&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">call_model&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">timeout&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="mi">60&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">builder&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">add_node&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;call_model&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">call_model&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">timeout&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">timedelta&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">minutes&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="mi">2&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 分别限制总时长和空闲时长&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">builder&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">add_node&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;call_model&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">call_model&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">timeout&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">TimeoutPolicy&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">run_timeout&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="mi">120&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">idle_timeout&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="mi">30&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>两种限制的语义不一样，别混用：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>&lt;code>run_timeout&lt;/code>&lt;/strong> 是硬性挂钟上限，&lt;strong>永不刷新&lt;/strong>。不管节点多活跃，到点就砍——适合&amp;quot;这个调用最多花两分钟&amp;quot;这种业务约束。&lt;/li>
&lt;li>&lt;strong>&lt;code>idle_timeout&lt;/code>&lt;/strong> 是&lt;strong>随进度重置&lt;/strong>的上限。只在节点一段时间内没有任何可观测进展时才触发——适合&amp;quot;卡死检测&amp;quot;。还能配 &lt;code>refresh_on=&amp;quot;heartbeat&amp;quot;&lt;/code> 用心跳当进度信号。&lt;/li>
&lt;/ul>
&lt;p>两个值可以同时给。超时后 LangGraph 抛 &lt;code>NodeTimeoutError&lt;/code>，&lt;strong>清掉这次失败尝试写入的状态&lt;/strong>，然后交给重试策略决定要不要重试。&lt;code>NodeTimeoutError&lt;/code> 默认是可重试的，而且超时时钟每次重试重新计时，所以 &lt;code>timeout&lt;/code> 和 &lt;code>retry_policy&lt;/code> 放在一起是开箱可用的。&lt;/p>
&lt;p>有个容易踩的坑：&lt;strong>节点超时只对 async 节点生效&lt;/strong>。同步节点带 &lt;code>timeout&lt;/code> 会在编译期就被拒绝。要包阻塞式 I/O，得在 async 节点里用 &lt;code>asyncio.to_thread&lt;/code>。&lt;/p>
&lt;h3 id="62-节点级错误处理error_handler">6.2 节点级错误处理：&lt;code>error_handler&lt;/code>
&lt;/h3>&lt;p>重试解决的是&amp;quot;偶发失败&amp;quot;，但有些失败重试三次也还是失败，这时候你需要的是&lt;strong>退路&lt;/strong>而不是重试。&lt;code>add_node&lt;/code> 的 &lt;code>error_handler=&lt;/code> 就是干这个的：它在节点失败&lt;strong>且重试耗尽之后&lt;/strong>执行，拿到当前状态，可以改状态，也可以用 &lt;code>Command&lt;/code> 跳到别的节点。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">langgraph.errors&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">NodeError&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">langgraph.types&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">Command&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">RetryPolicy&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">charge_payment&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">state&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">State&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="n">State&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">raise&lt;/span> &lt;span class="ne">RuntimeError&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;payment gateway timeout&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">payment_error_handler&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">state&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">State&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">error&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">NodeError&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="n">Command&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">Command&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">update&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="s2">&amp;#34;status&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;compensated: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">error&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">error&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">goto&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;finalize&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">graph&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">StateGraph&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">State&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">.&lt;/span>&lt;span class="n">add_node&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;charge_payment&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">charge_payment&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">retry_policy&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">RetryPolicy&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">max_attempts&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="mi">3&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">retry_on&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="ne">ConnectionError&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">error_handler&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">payment_error_handler&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">.&lt;/span>&lt;span class="n">add_node&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;finalize&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">finalize&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">.&lt;/span>&lt;span class="n">add_edge&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">START&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;charge_payment&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">.&lt;/span>&lt;span class="n">compile&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>这就是 &lt;strong>Saga / 补偿模式&lt;/strong>的表达方式：失败不是把整张图炸掉，而是走一条补偿路径把状态收拾干净。&lt;/p>
&lt;p>几个细节：&lt;/p>
&lt;ul>
&lt;li>执行顺序是固定的——&lt;strong>先重试，重试耗尽（或没配重试策略）才轮到错误处理器&lt;/strong>。两者解耦，可以分别配。&lt;/li>
&lt;li>想拿到失败上下文，就把参数标注成 &lt;code>error: NodeError&lt;/code>（按类型注解注入，和 &lt;code>runtime: Runtime&lt;/code> 一个套路）。&lt;code>NodeError&lt;/code> 是 frozen dataclass，只有两个字段：&lt;code>node&lt;/code>（失败的节点名）和 &lt;code>error&lt;/code>（原始异常）。&lt;/li>
&lt;li>不需要上下文的话，签名写成 &lt;code>(state)&lt;/code> 或 &lt;code>(state, runtime)&lt;/code> 也行。&lt;/li>
&lt;li>注入是可选的，但&lt;strong>每个节点最多一个 &lt;code>error_handler&lt;/code>&lt;/strong>。&lt;/li>
&lt;/ul>
&lt;h3 id="63-图级默认值set_node_defaults">6.3 图级默认值：&lt;code>set_node_defaults&lt;/code>
&lt;/h3>&lt;p>如果每个节点都要重复写一遍 &lt;code>retry_policy=&lt;/code> / &lt;code>error_handler=&lt;/code> / &lt;code>timeout=&lt;/code>，代码会很难看。&lt;code>set_node_defaults&lt;/code> 把这些提成图级默认：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">langgraph.types&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">RetryPolicy&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">TimeoutPolicy&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">builder&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">StateGraph&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">State&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">.&lt;/span>&lt;span class="n">set_node_defaults&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">error_handler&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">default_error_handler&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">timeout&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">TimeoutPolicy&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">run_timeout&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="mi">30&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">.&lt;/span>&lt;span class="n">add_node&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;step_a&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">step_a&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="c1"># 用默认处理器&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">.&lt;/span>&lt;span class="n">add_node&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;step_b&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">step_b&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">error_handler&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">custom_error_handler&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="c1"># 用自己的&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>这个能力在&amp;quot;每次图运行都对应一个外部进程&amp;quot;的场景下特别值——比如后台任务表里的一行，任何未处理的节点失败都应该把那行标成 failed，不用在每个 &lt;code>add_node&lt;/code> 上重复声明。&lt;strong>单节点配置优先于图级默认值&lt;/strong>。&lt;/p>
&lt;p>有个安全设计要注意：错误处理器节点本身&lt;strong>不套用 &lt;code>error_handler&lt;/code> 默认值&lt;/strong>——处理器不能接住自己，否则就是无限递归。&lt;/p>
&lt;h3 id="64-优雅停机runcontrol-与-drain">6.4 优雅停机：&lt;code>RunControl&lt;/code> 与 drain
&lt;/h3>&lt;p>服务要重启、容器要缩容，收到 SIGTERM 时正在跑的图怎么办？1.2 给了一个协作式的停机机制：&lt;strong>跑完当前 superstep 再停&lt;/strong>，并保存一个可恢复的 checkpoint。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">signal&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">langgraph.runtime&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">RunControl&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">langgraph.errors&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">GraphDrained&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">control&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">RunControl&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">signal&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">signal&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">signal&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">SIGTERM&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="k">lambda&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="n">_&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">control&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">request_drain&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;sigterm&amp;#34;&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">try&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">result&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">graph&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">invoke&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">inputs&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">config&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">control&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">control&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">except&lt;/span> &lt;span class="n">GraphDrained&lt;/span> &lt;span class="k">as&lt;/span> &lt;span class="n">e&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">log&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">info&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;graph drained: &lt;/span>&lt;span class="si">%s&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">e&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">reason&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># 下次启动时用同一个 config 继续&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>drain 的语义是&amp;quot;&lt;strong>协作&lt;/strong>&amp;quot;，它只在 superstep 之间生效，绝不抢占已经在跑的工作：&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>场景&lt;/th>
&lt;th>行为&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>节点执行到一半&lt;/td>
&lt;td>跑完。drain 在下一个 superstep 生效&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>节点正在按重试策略重试&lt;/td>
&lt;td>重试循环跑到成功或耗尽，之后才生效&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>图正好在同一次 tick 自然结束&lt;/td>
&lt;td>正常返回。用 &lt;code>control.drain_requested&lt;/code> 区分&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>后面还有 superstep&lt;/td>
&lt;td>抛 &lt;code>GraphDrained(reason)&lt;/code>，checkpoint 已保存可续跑&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>子图请求 drain&lt;/td>
&lt;td>&lt;code>GraphDrained&lt;/code> 向上冒泡，让父图在它自己的下一个边界停&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>节点内也能感知 drain，用来跳过昂贵的工作：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">langgraph.runtime&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">Runtime&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">async&lt;/span> &lt;span class="k">def&lt;/span> &lt;span class="nf">my_node&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">state&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">State&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">runtime&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">Runtime&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="n">State&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">runtime&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">drain_requested&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="p">{&lt;/span>&lt;span class="s2">&amp;#34;status&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;skipped&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;reason&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">runtime&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">drain_reason&lt;/span>&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="p">{&lt;/span>&lt;span class="s2">&amp;#34;status&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="k">await&lt;/span> &lt;span class="n">do_work&lt;/span>&lt;span class="p">()}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>恢复就是同一 &lt;code>thread_id&lt;/code> 下 &lt;code>graph.invoke(None, config)&lt;/code>。&lt;/p>
&lt;p>&lt;strong>别把它当成强制中断&lt;/strong>：&lt;code>request_drain()&lt;/code> 不会取消正在跑的 asyncio task，也不会杀线程。想要硬上限，得把 drain 和一个 grace timeout + 任务取消配合使用。&lt;/p>
&lt;h3 id="65-deltachannel给长线程的-checkpoint-减重">6.5 DeltaChannel：给长线程的 checkpoint 减重
&lt;/h3>&lt;p>图跑得越久，checkpoint 越大。原因是消息列表这类 channel 每步都在增长，而过去的做法是&lt;strong>每一步都把累积后的全量值重新序列化进 checkpoint&lt;/strong>——于是存储和读取延迟随对话长度线性上升。&lt;/p>
&lt;p>1.2 引入的 &lt;code>DeltaChannel&lt;/code>（beta）只存每一步写入的&lt;strong>增量&lt;/strong>，不存全量。对消息列表这种只会越堆越长的 channel，效果最明显。代价是读历史要重放 delta，所以可以配 &lt;code>snapshot_frequency=K&lt;/code>，每 K 步写一次全量快照给读取延迟封顶。&lt;/p>
&lt;p>如果你在用 DeepAgents，注意它从 0.6.0 起也把消息历史和 agent 文件切到了 &lt;code>DeltaChannel&lt;/code>，而且&lt;strong>这个变更不可回滚&lt;/strong>——细节见《DeepAgents完全指南》3.5 节。&lt;/p>
&lt;h3 id="66-流式输出换代事件流v3">6.6 流式输出换代：事件流（v3）
&lt;/h3>&lt;p>&lt;code>stream_mode=&amp;quot;messages&amp;quot;&lt;/code> / &lt;code>&amp;quot;updates&amp;quot;&lt;/code> 那套是 LangGraph 的&lt;strong>原始&lt;/strong>流式接口，拿到的是 dict，得自己判断这块是什么。1.2 起多了一层更适合应用代码的东西——&lt;strong>事件流&lt;/strong>，传 &lt;code>version=&amp;quot;v3&amp;quot;&lt;/code> 开启：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="n">stream&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">graph&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">stream_events&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">input_data&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">version&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;v3&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">for&lt;/span> &lt;span class="n">message&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">stream&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">messages&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="c1"># token 级模型输出&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nb">str&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">message&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">text&lt;/span>&lt;span class="p">),&lt;/span> &lt;span class="n">end&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">flush&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="kc">True&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">final_state&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">stream&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">output&lt;/span> &lt;span class="c1"># 最终状态&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>它的关键设计是：&lt;strong>一次 run 的事件流被归一化成几个带类型的投影，多个消费者可以同时读&lt;/strong>——读 &lt;code>stream.messages&lt;/code> 不会消耗 &lt;code>stream.values&lt;/code> 需要的事件。可用的投影有 &lt;code>stream.messages&lt;/code>、&lt;code>stream.values&lt;/code>、&lt;code>stream.output&lt;/code>、&lt;code>stream.subgraphs&lt;/code>、&lt;code>stream.interrupts&lt;/code> / &lt;code>stream.interrupted&lt;/code>，以及给自定义 transformer 用的 &lt;code>stream.extensions&lt;/code>。&lt;/p>
&lt;p>异步并发读用 &lt;code>asyncio.gather&lt;/code>；同步代码想按严格到达顺序混读多个投影，用 &lt;code>stream.interleave(&amp;quot;values&amp;quot;, &amp;quot;messages&amp;quot;, &amp;quot;subgraphs&amp;quot;)&lt;/code>。&lt;/p>
&lt;p>底层 &lt;code>messages&lt;/code> 通道是按 &lt;strong>content block&lt;/strong> 建模的，事件序列固定为 &lt;code>message-start&lt;/code> → &lt;code>content-block-start&lt;/code> → &lt;code>content-block-delta&lt;/code> → &lt;code>content-block-finish&lt;/code> → &lt;code>message-finish&lt;/code>，文本、推理、工具调用、多模态内容都有显式边界，不再依赖各家 provider 的格式。&lt;/p>
&lt;p>&lt;code>v1&lt;/code> / &lt;code>v2&lt;/code> 保持兼容，没有破坏性变更——老代码不用动，新代码建议直接从 v3 起步。&lt;/p>
&lt;h3 id="67-类型安全的-invoke--stream11">6.7 类型安全的 invoke / stream（1.1）
&lt;/h3>&lt;p>1.1 就往这个方向走了一步：&lt;code>invoke()&lt;/code> / &lt;code>stream()&lt;/code> 传 &lt;code>version=&amp;quot;v2&amp;quot;&lt;/code> 会拿到带类型的东西。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">langgraph.types&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">GraphOutput&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">result&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">GraphOutput&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">graph&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">invoke&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">input_data&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">config&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">version&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;v2&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">result&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">value&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="c1"># 状态值&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">result&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">interrupts&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="c1"># 中断信息&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;code>stream(..., version=&amp;quot;v2&amp;quot;)&lt;/code> 则统一输出带 &lt;code>type&lt;/code> / &lt;code>ns&lt;/code> / &lt;code>data&lt;/code> 三个键的 &lt;code>StreamPart&lt;/code>，每种 mode 一个 &lt;code>TypedDict&lt;/code>，都能从 &lt;code>langgraph.types&lt;/code> 导入。另外 &lt;code>version=&amp;quot;v2&amp;quot;&lt;/code> 下，&lt;code>invoke()&lt;/code> 和 &lt;code>values&lt;/code> mode 的输出会&lt;strong>自动转换&lt;/strong>成你声明的 Pydantic 模型或 dataclass 类型。&lt;/p>
&lt;p>&lt;code>version=&amp;quot;v2&amp;quot;&lt;/code> 是 opt-in，&lt;code>GraphOutput&lt;/code> 也保留了废弃的 dict 风格访问，方便渐进迁移。&lt;/p>
&lt;h2 id="7-版本注记从-0x-写法到-langchain-1x">7. 版本注记：从 0.x 写法到 LangChain 1.x
&lt;/h2>&lt;p>这篇整合自 0.x 时代的笔记，几处新旧差异统一交代：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>&lt;code>set_entry_point&lt;/code> 已统一为 &lt;code>add_edge(START, ...)&lt;/code>&lt;/strong>。原笔记的天气助手和 Map-Reduce 图用的都是 &lt;code>set_entry_point(&amp;quot;agent&amp;quot;)&lt;/code> 这类写法，官方后来统一成 &lt;code>add_edge(START, &amp;quot;agent&amp;quot;)&lt;/code>（连同 &lt;code>set_finish_point&lt;/code> → &lt;code>add_edge(node, END)&lt;/code>），本文正文已全部改写。旧写法当前仍可用，但属废弃路径&lt;/li>
&lt;li>&lt;strong>&lt;code>ChatTongyi&lt;/code> 属 legacy&lt;/strong>。原笔记另一处用 &lt;code>langchain_community.chat_models&lt;/code> 的 &lt;code>ChatTongyi&lt;/code> 直连通义——这个包里的集成属于 legacy。本文统一走 DashScope 的 OpenAI 兼容端点（&lt;code>ChatOpenAI&lt;/code> + &lt;code>base_url&lt;/code>），这也是当时验证过可行的路线&lt;/li>
&lt;li>&lt;strong>模型&lt;/strong>。原笔记示例里有 &lt;code>gpt-3.5-turbo&lt;/code>（已下线）和 &lt;code>gpt-4o-mini&lt;/code>，整合时统一替换为 DashScope 的 &lt;code>qwen-plus&lt;/code> / &lt;code>qwen-max&lt;/code>&lt;/li>
&lt;li>&lt;strong>一行式封装&lt;/strong>。现在构建 ReAct Agent 不需要手搓图：&lt;code>langgraph.prebuilt.create_react_agent&lt;/code> 一行拉起；LangChain 1.x 的 &lt;code>langchain.agents.create_agent&lt;/code> 是官方标准入口，底层就是 LangGraph。但我的判断是：手写一遍第 2 节那个循环，仍然是理解这些封装的最佳方式——封装出问题时，你得知道问题出在哪一层&lt;/li>
&lt;li>&lt;strong>&lt;code>MemorySaver&lt;/code> → &lt;code>InMemorySaver&lt;/code>&lt;/strong>（见第 5 节）。旧名仍是可用别名，但新代码建议用 &lt;code>InMemorySaver&lt;/code>&lt;/li>
&lt;li>&lt;strong>1.1 / 1.2 是增量而非破坏性变更&lt;/strong>。第 6 节的超时、错误处理器、优雅停机、&lt;code>DeltaChannel&lt;/code>、事件流都是 opt-in 的新参数或新 API，老代码不改也能继续跑。注意 &lt;code>timeout&lt;/code> / &lt;code>error_handler&lt;/code> / 优雅停机需要 &lt;code>langgraph&amp;gt;=1.2&lt;/code>&lt;/li>
&lt;/ul>
&lt;h4 id="版本基线">版本基线
&lt;/h4>&lt;table>
&lt;thead>
&lt;tr>
&lt;th>组件&lt;/th>
&lt;th>本文参考版本&lt;/th>
&lt;th>备注&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;code>langgraph&lt;/code>&lt;/td>
&lt;td>1.2.11（2026-08-11）&lt;/td>
&lt;td>1.2.0 起有超时 / 错误处理 / 优雅停机 / 事件流&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>langgraph-checkpoint&lt;/code>&lt;/td>
&lt;td>4.2.0&lt;/td>
&lt;td>&lt;code>DeltaChannel&lt;/code> 相关修复在这个线上&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>langgraph-sdk&lt;/code>&lt;/td>
&lt;td>0.4.4&lt;/td>
&lt;td>走 Agent Server 时用&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>langchain&lt;/code>&lt;/td>
&lt;td>1.4.0&lt;/td>
&lt;td>&lt;code>create_agent&lt;/code> 入口；MCP 已内建为 &lt;code>langchain.mcp&lt;/code>&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>更深的 Agent 工程化路线——多智能体、子智能体、规划与上下文管理——同目录的《DeepAgents完全指南》是这篇的自然延伸。&lt;/p>
&lt;p>参考：&lt;a class="link" href="https://docs.langchain.com/oss/python/langgraph/overview" target="_blank" rel="noopener"
>LangGraph 官方文档&lt;/a>、&lt;a class="link" href="https://docs.langchain.com/oss/python/langgraph/fault-tolerance" target="_blank" rel="noopener"
>容错与优雅停机&lt;/a>、&lt;a class="link" href="https://docs.langchain.com/oss/python/langgraph/event-streaming" target="_blank" rel="noopener"
>事件流&lt;/a>&lt;/p>
&lt;h2 id="附题外话我试过的-langchain-知识图谱不是-langgraph">附：题外话——我试过的 LangChain 知识图谱（不是 LangGraph）
&lt;/h2>&lt;p>如实交代：写《Langchain-Graph实战教程》那篇笔记时，我把两个 &amp;ldquo;Graph&amp;rdquo; 搞混了——LangChain 的 Knowledge Graph 功能和 LangGraph 是完全不同的东西。前者（&lt;code>GraphQAChain&lt;/code>、&lt;code>NetworkXEntityGraph&lt;/code>、&lt;code>LLMGraphTransformer&lt;/code>）用图结构表示知识、做图谱问答；后者是工作流编排框架。一个是数据，一个是执行引擎。&lt;/p>
&lt;p>不过那次实验的踩坑记录值得留档。当时用 &lt;code>LLMGraphTransformer&lt;/code>（&lt;code>langchain_experimental&lt;/code>）从文本构建内存知识图谱，写法如下（&lt;code>Tongyi&lt;/code> 同样来自 &lt;code>langchain_community&lt;/code>，属当时的旧写法）：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">langchain_community.llms&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">Tongyi&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">langchain_community.graphs&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">NetworkXEntityGraph&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">langchain_experimental.graph_transformers.llm&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">LLMGraphTransformer&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">langchain_core.documents&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">Document&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">llm_for_graph&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">Tongyi&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">model_name&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;qwen-plus&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">document_content&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2">张三是北京大学的教授，他研究人工智能领域。
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2">李四是张三的学生，他就读于清华大学，学习计算机科学。
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2">王五是百度的工程师，百度是一家位于北京的科技公司。
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">docs&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="n">Document&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">page_content&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">document_content&lt;/span>&lt;span class="p">)]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">llm_transformer&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">LLMGraphTransformer&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">llm&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">llm_for_graph&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">graph_documents&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">llm_transformer&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">convert_to_graph_documents&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">docs&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">final_graph&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">NetworkXEntityGraph&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">final_graph&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">add_graph_documents&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">graph_documents&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">for&lt;/span> &lt;span class="n">triple&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">final_graph&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">get_triples&lt;/span>&lt;span class="p">():&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">triple&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>两个结论：&lt;/p>
&lt;ol>
&lt;li>&lt;strong>&lt;code>GraphQAChain&lt;/code> 对 &lt;code>NetworkXEntityGraph&lt;/code> 支持不佳&lt;/strong>。当时直接 &lt;code>graph_qa_chain.run(question)&lt;/code> 基本走不通——它内部按生成 Cypher 查询的思路工作，而内存图根本不支持 Cypher，一跑就抛错&lt;/li>
&lt;li>&lt;strong>手工三元组检索是可用的备选&lt;/strong>。绕开 Chain，直接从图里捞相关三元组当上下文，交给 LLM 回答：&lt;/li>
&lt;/ol>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">get_relevant_triples&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">graph&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">entity_name&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;检索与某个实体相关的三元组&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">lines&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="n">s&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">p&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">o&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">graph&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">get_triples&lt;/span>&lt;span class="p">():&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">entity_name&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">s&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="ow">or&lt;/span> &lt;span class="n">entity_name&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">o&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">lines&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">append&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;(&lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">s&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">, &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">p&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">, &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">o&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">)&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="se">\n&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">join&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">lines&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">context&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">get_relevant_triples&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">final_graph&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;张三&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 再把 context 和问题拼进 prompt，交给 LLM 生成答案&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>另外说明：&lt;code>LLMGraphTransformer&lt;/code> 至今还在 &lt;code>langchain_experimental&lt;/code> 里，仍属实验性，别在生产上依赖它。我的判断：真要做知识图谱问答，直接上 Neo4j 配 &lt;code>GraphCypherQAChain&lt;/code>；或者更轻的做法——三元组检索本质上就是 RAG 的一种上下文来源，没必要被&amp;quot;图谱&amp;quot;这个词吓住。&lt;/p>
&lt;h2 id="总结">总结
&lt;/h2>&lt;ul>
&lt;li>LangGraph 的地基是三件套：&lt;code>StateGraph&lt;/code>（状态机）、State（reducer 决定覆盖还是追加）、条件边（动态路由）。&lt;code>MessagesState&lt;/code> 只是 &lt;code>Annotated[list, add_messages]&lt;/code> 的官方打包&lt;/li>
&lt;li>Agent 没有魔法：&lt;code>tool_calls&lt;/code> → 执行 → &lt;code>ToolMessage&lt;/code> 回填 → 二次调用，一行式封装做的就是在消息列表上转这一圈&lt;/li>
&lt;li>ReAct 循环的图表达就三个部件：&lt;code>agent&lt;/code> / &lt;code>tools&lt;/code> 节点、&lt;code>should_continue&lt;/code> 条件边、&lt;code>tools -&amp;gt; agent&lt;/code> 回边&lt;/li>
&lt;li>超出上下文窗口的活交给 Map-Reduce，图让流水线每一步可插拔&lt;/li>
&lt;li>记忆的单位是 &lt;code>thread_id&lt;/code> + checkpointer，内存版叫 &lt;code>InMemorySaver&lt;/code>&lt;/li>
&lt;li>上线前补齐三件事：&lt;code>timeout=&lt;/code> 防卡死（注意只对 async 节点生效）、&lt;code>error_handler=&lt;/code> 给失败留退路（Saga 补偿）、&lt;code>RunControl&lt;/code> + drain 应对停机。重复的配置用 &lt;code>set_node_defaults&lt;/code> 提到图级&lt;/li>
&lt;li>长线程的 checkpoint 膨胀用 &lt;code>DeltaChannel&lt;/code> 解决；应用层读流式输出优先用 &lt;code>version=&amp;quot;v3&amp;quot;&lt;/code> 的事件流，投影带类型且可多消费者并发读&lt;/li>
&lt;/ul>
&lt;p>先手写一遍循环、看懂消息怎么流，再谈一行式封装——这是全文的路线，也是我判断的入门 LangGraph 最不绕的路。&lt;/p></description></item><item><title>一个订阅更新摘要 Agent 的完整实践：从提示词踩坑到长文本分块</title><link>https://www.zata.cc/p/%E4%B8%80%E4%B8%AA%E8%AE%A2%E9%98%85%E6%9B%B4%E6%96%B0%E6%91%98%E8%A6%81-agent-%E7%9A%84%E5%AE%8C%E6%95%B4%E5%AE%9E%E8%B7%B5%E4%BB%8E%E6%8F%90%E7%A4%BA%E8%AF%8D%E8%B8%A9%E5%9D%91%E5%88%B0%E9%95%BF%E6%96%87%E6%9C%AC%E5%88%86%E5%9D%97/</link><pubDate>Tue, 08 Sep 2026 12:00:00 +0800</pubDate><guid>https://www.zata.cc/p/%E4%B8%80%E4%B8%AA%E8%AE%A2%E9%98%85%E6%9B%B4%E6%96%B0%E6%91%98%E8%A6%81-agent-%E7%9A%84%E5%AE%8C%E6%95%B4%E5%AE%9E%E8%B7%B5%E4%BB%8E%E6%8F%90%E7%A4%BA%E8%AF%8D%E8%B8%A9%E5%9D%91%E5%88%B0%E9%95%BF%E6%96%87%E6%9C%AC%E5%88%86%E5%9D%97/</guid><description>&lt;img src="https://www.zata.cc/p/%E4%B8%80%E4%B8%AA%E8%AE%A2%E9%98%85%E6%9B%B4%E6%96%B0%E6%91%98%E8%A6%81-agent-%E7%9A%84%E5%AE%8C%E6%95%B4%E5%AE%9E%E8%B7%B5%E4%BB%8E%E6%8F%90%E7%A4%BA%E8%AF%8D%E8%B8%A9%E5%9D%91%E5%88%B0%E9%95%BF%E6%96%87%E6%9C%AC%E5%88%86%E5%9D%97/images/index/index.png" alt="Featured image of post 一个订阅更新摘要 Agent 的完整实践：从提示词踩坑到长文本分块" />&lt;h1 id="一个订阅更新摘要-agent-的完整实践从提示词踩坑到长文本分块">一个订阅更新摘要 Agent 的完整实践：从提示词踩坑到长文本分块
&lt;/h1>&lt;blockquote>
&lt;p>本文整合自同一个项目的三篇旧文（2025 年 3 月–4 月），代码、截图、结论都来自当时的真实记录，合并时只做整理和勘误，没有补写任何&amp;quot;当时应该是这样&amp;quot;的情节——包括没起作用的 memory。文中 &lt;code>ConversationSummaryBufferMemory&lt;/code> 等 legacy memory API 已在 LangChain 1.0 移除，相应位置有版本注记，核心思路不受版本影响。&lt;/p>
&lt;/blockquote>
&lt;p>2025 年 3 月，我给自己做了一个订阅系统的 agent：定期抓取订阅的网站，对比前后两次抓到的内容，把 diff 交给 LLM，让它总结&amp;quot;这段时间到底更新了什么&amp;quot;，输出一份固定格式的 JSON 摘要（概要、关键点、字数、生成时间）。项目不大，一个月里却把 agent 开发最常见的两类坑各踩了一遍——提示词的坑，和长文本的坑。&lt;/p>
&lt;p>先把两个贯穿全文的判断放在前面：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>对人类差不多的提示词，对 agent 是天壤之别。&lt;/strong> 把&amp;quot;仅仅是时间的变化&amp;quot;扩成&amp;quot;时间&lt;strong>或者数据&lt;/strong>的变化&amp;quot;，几个字的改动，让同一批模型、同一类数据上的输出从&amp;quot;全是噪音&amp;quot;变成&amp;quot;基本可用&amp;quot;。&lt;/li>
&lt;li>&lt;strong>超长文本靠 map-reduce 式分块，不靠记忆机制。&lt;/strong> diff 超出 token 上限时，按变更单元切块、逐块抽取、最后汇总——朴素，但真的解决问题。中间版本引入过的&amp;quot;记忆机制&amp;quot;，如后文坦白，在实际代码路径里基本是装饰。&lt;/li>
&lt;/ul>
&lt;p>全文按时间线组织：第一节是第一版管线的完整代码；第二节是提示词踩坑与迭代的全过程，含双模型前后对照截图；第三节是超长 diff 的分块处理；第四节把散在代码里的工程取舍集中成一份清单。&lt;/p>
&lt;hr>
&lt;h2 id="一第一版管线从-prompttemplate-到-json-解析">一、第一版管线：从 PromptTemplate 到 JSON 解析
&lt;/h2>&lt;h3 id="输入与输出">输入与输出
&lt;/h3>&lt;p>业务的输入只有一样东西：contentdiff——前后两次抓取的差异文本，形如：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">&amp;#34;Changed: &amp;#39;旧文本&amp;#39; -&amp;gt; &amp;#39;新文本&amp;#39;&amp;#34;, &amp;#34;Added: &amp;#39;新增的段落&amp;#39;&amp;#34;, &amp;#34;Deleted: &amp;#39;被删掉的段落&amp;#39;&amp;#34;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>由于文本长度的限制，历史全文不可能都给模型，只给当前这次更新的差异。输出是固定结构的 JSON，先用一个 dict 把形状定下来：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="n">SUMMARY_TEMPLATE&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;summary&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;content&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;key_points&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">[],&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;word_count&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;generated_at&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;status&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;success&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;error_message&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">None&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="subscriptionagent提示组链解析">SubscriptionAgent：提示、组链、解析
&lt;/h3>&lt;p>第一版的完整骨架。LLM 来自项目内部模块 &lt;code>src.agent.llm&lt;/code> 的 &lt;code>get_ali_llm&lt;/code>——它做的事就是返回一个 LangChain 兼容的模型实例，等价写法附在本节末尾：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">langchain.prompts&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">PromptTemplate&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">json&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">src.agent.llm&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">get_ali_llm&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">SubscriptionAgent&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="fm">__init__&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">llm_model&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="kc">None&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">llm&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">llm_model&lt;/span> &lt;span class="k">if&lt;/span> &lt;span class="n">llm_model&lt;/span> &lt;span class="k">else&lt;/span> &lt;span class="n">get_ali_llm&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;qwen-7b-chat&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># 冒烟测试：先 invoke 一句&amp;#34;你好&amp;#34;，确认 LLM 连得上&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">llm&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">invoke&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;你好&amp;#34;&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">prompt_template&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">PromptTemplate&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">input_variables&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;contentdiff&amp;#34;&lt;/span>&lt;span class="p">],&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">template&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"> 你是一个订阅号运营专家，可以根据差异内容总结出订阅内容的更新情况，请对以下内容差异进行总结：
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"> &lt;/span>&lt;span class="si">{contentdiff}&lt;/span>&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"> 要求：
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"> 1. 提供简洁的内容更新概要
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"> 2. 提取关键点
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"> 3. 计算总字数
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"> 返回结果使用中文
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"> 返回格式json（请严格按照以下格式返回）：
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"> {{
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"> &amp;#34;summary&amp;#34;: {{
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"> &amp;#34;content&amp;#34;: &amp;#34;&amp;#34;,
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"> &amp;#34;key_points&amp;#34;: [],
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"> &amp;#34;word_count&amp;#34;: 0,
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"> &amp;#34;generated_at&amp;#34;: &amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"> }},
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"> }}
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"> &amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">chain&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">prompt_template&lt;/span> &lt;span class="o">|&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">llm&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>三个点值得停下来看：&lt;/p>
&lt;ul>
&lt;li>&lt;code>PromptTemplate&lt;/code> 用 &lt;code>input_variables&lt;/code> 声明动态变量，模板里 &lt;code>{contentdiff}&lt;/code> 占位。要留心 JSON 花括号必须写成 &lt;code>{{&lt;/code> &lt;code>}}&lt;/code> 转义，否则会被当成变量占位符。&lt;/li>
&lt;li>&lt;code>self.prompt_template | self.llm&lt;/code> 就是 LCEL 的组链写法：&lt;code>|&lt;/code> 两侧只要是 Runnable，拼起来就是一条链。&lt;code>invoke&lt;/code> 时 PromptTemplate 先把变量填进模板，产出的字符串直接喂给 LLM，返回一个带 &lt;code>.content&lt;/code> 的消息对象。0.1 之前要写 &lt;code>LLMChain(prompt=..., llm=...)&lt;/code>，现在一行管道就替代了。&lt;/li>
&lt;li>&lt;code>invoke&lt;/code> 的输入是字典，key 必须和 &lt;code>input_variables&lt;/code> 匹配——传 &lt;code>{&amp;quot;contentdiff&amp;quot;: ...}&lt;/code>，不是裸字符串。&lt;/li>
&lt;/ul>
&lt;p>解析和兜底在 &lt;code>generate_summary&lt;/code> 里：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">generate_summary&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">contentdiff&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="nb">dict&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">try&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">result&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">chain&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">invoke&lt;/span>&lt;span class="p">({&lt;/span>&lt;span class="s2">&amp;#34;contentdiff&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">contentdiff&lt;/span>&lt;span class="p">})&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">content&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">parsed_result&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">json&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">loads&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">result&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">summary_content&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">parsed_result&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;summary&amp;#34;&lt;/span>&lt;span class="p">][&lt;/span>&lt;span class="s2">&amp;#34;content&amp;#34;&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">key_points&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">parsed_result&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;summary&amp;#34;&lt;/span>&lt;span class="p">][&lt;/span>&lt;span class="s2">&amp;#34;key_points&amp;#34;&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">word_count&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">parsed_result&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;summary&amp;#34;&lt;/span>&lt;span class="p">][&lt;/span>&lt;span class="s2">&amp;#34;word_count&amp;#34;&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">generated_at&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">parsed_result&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;summary&amp;#34;&lt;/span>&lt;span class="p">][&lt;/span>&lt;span class="s2">&amp;#34;generated_at&amp;#34;&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">response&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">SUMMARY_TEMPLATE&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">copy&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">response&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;summary&amp;#34;&lt;/span>&lt;span class="p">][&lt;/span>&lt;span class="s2">&amp;#34;content&amp;#34;&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">summary_content&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">response&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;summary&amp;#34;&lt;/span>&lt;span class="p">][&lt;/span>&lt;span class="s2">&amp;#34;key_points&amp;#34;&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">key_points&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">response&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;summary&amp;#34;&lt;/span>&lt;span class="p">][&lt;/span>&lt;span class="s2">&amp;#34;word_count&amp;#34;&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">word_count&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">response&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;summary&amp;#34;&lt;/span>&lt;span class="p">][&lt;/span>&lt;span class="s2">&amp;#34;generated_at&amp;#34;&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">generated_at&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">response&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">except&lt;/span> &lt;span class="ne">Exception&lt;/span> &lt;span class="k">as&lt;/span> &lt;span class="n">e&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">error_response&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">SUMMARY_TEMPLATE&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">copy&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">error_response&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;status&amp;#34;&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;error&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">error_response&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;error_message&amp;#34;&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;Error on line &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">e&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">__traceback__&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">tb_lineno&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="nb">str&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">e&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">error_response&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>流程很直白：跑链、&lt;code>json.loads&lt;/code> 直解、把字段填进模板；任何一步炸了就整体降级成 &lt;code>status=&amp;quot;error&amp;quot;&lt;/code> 的结构返回，不向上抛异常。这里有个隐患：&lt;code>SUMMARY_TEMPLATE.copy()&lt;/code> 是浅拷贝，嵌套的 &lt;code>summary&lt;/code> 字典是共享的——当时没暴露问题，是因为成功路径每次都把四个字段全覆盖了一遍。复用这段代码的话，用 &lt;code>copy.deepcopy&lt;/code> 或者每次构造新 dict 更稳。&lt;/p>
&lt;p>用一段玩具 diff（&amp;ldquo;The quick brown fox jumps over the lazy dog&amp;rdquo; 那种）喂进去，跑通的输出长这样（原文记录的示例）：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-json" data-lang="json">&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;summary&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;content&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;内容从&amp;#39;quick&amp;#39;改为&amp;#39;swift&amp;#39;，&amp;#39;jumps&amp;#39;改为&amp;#39;leaps&amp;#39;，新增&amp;#39;quickly&amp;#39;。&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;key_points&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;quick -&amp;gt; swift&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;jumps -&amp;gt; leaps&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;新增 quickly&amp;#34;&lt;/span>&lt;span class="p">],&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;word_count&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">18&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;generated_at&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;2025-03-13T10:00:00&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;status&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;success&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;error_message&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">null&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;code>get_ali_llm&lt;/code> 的等价写法——任何 OpenAI 兼容端点都适用（这也是项目后期脚本里的实际写法）：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">langchain_openai&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">ChatOpenAI&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">os&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">get_ali_llm&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">model&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;qwen-7b-chat&amp;#34;&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">ChatOpenAI&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">api_key&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">os&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">getenv&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;DASHSCOPE_API_KEY&amp;#34;&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">base_url&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;https://dashscope.aliyuncs.com/compatible-mode/v1&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">model&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">model&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="当时已知的软肋与对策">当时已知的软肋与对策
&lt;/h3>&lt;p>第一版最明显的软肋是 LLM 不一定严格按 JSON 格式返回。当时的对策有三条：&lt;/p>
&lt;p>&lt;strong>解析器上链。&lt;/strong> 把 &lt;code>SimpleJsonOutputParser&lt;/code> 挂在链尾，输出自动解成 dict，不用再手写 &lt;code>json.loads&lt;/code>：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">langchain_core.output_parsers.json&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">SimpleJsonOutputParser&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">chain&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">prompt_template&lt;/span> &lt;span class="o">|&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">llm&lt;/span> &lt;span class="o">|&lt;/span> &lt;span class="n">SimpleJsonOutputParser&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">result&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">chain&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">invoke&lt;/span>&lt;span class="p">({&lt;/span>&lt;span class="s2">&amp;#34;contentdiff&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">contentdiff&lt;/span>&lt;span class="p">})&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>重试。&lt;/strong> LCEL 的 Runnable 自带 &lt;code>.with_retry()&lt;/code>：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="n">result&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">chain&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">with_retry&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">stop_after_attempt&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="mi">3&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">invoke&lt;/span>&lt;span class="p">({&lt;/span>&lt;span class="s2">&amp;#34;contentdiff&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">contentdiff&lt;/span>&lt;span class="p">})&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>后来实际落地时用的是外层循环重试（第三节脚本里的写法），重试间隔和日志更好控制，两种都可行。&lt;/p>
&lt;p>&lt;strong>异步。&lt;/strong> 同步 &lt;code>invoke&lt;/code> 在高频场景是瓶颈，链本身支持 &lt;code>ainvoke&lt;/code>，接口不用改。&lt;/p>
&lt;p>管线能跑，只说明&amp;quot;能返回 JSON&amp;quot;；JSON 里装的是不是想要的内容，是另一回事——这就是下一节踩的坑。&lt;/p>
&lt;hr>
&lt;h2 id="二提示词踩坑一字之差的质变">二、提示词踩坑：一字之差的质变
&lt;/h2>&lt;blockquote>
&lt;p>我之前一直认为，只要模型好，提示词随便写一写，模型都能返回差不多的内容。实际上，对于人来说是差不多的，因为人的大脑可以进一步理解内容。但是，如果开发 agent 就不一样了！！！&lt;/p>
&lt;/blockquote>
&lt;p>这段话是当时记下的，现在仍然是我对提示词工程最重要的体会。这节把整个过程原样重放一遍。&lt;/p>
&lt;h3 id="误判现场">误判现场
&lt;/h3>&lt;p>当时我在开发这个订阅系统的 agent，任务是给它一些内容，让它总结这段时间更新了什么。由于文本长度的限制，不可能把历史文本都给它，只给当前更新的差异。&lt;/p>
&lt;p>然后遇到了一个问题，对于下面的差异文本：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">&amp;#34;[&amp;#34;&amp;#34;Changed: &amp;#39;53 分钟&amp;#39; -&amp;gt; &amp;#39;1 小时&amp;#39;&amp;#34;&amp;#34;, &amp;#34;&amp;#34;Changed: &amp;#39;1&amp;#39; -&amp;gt; &amp;#39;2&amp;#39;&amp;#34;&amp;#34;, &amp;#34;&amp;#34;Changed: &amp;#39;3&amp;#39; -&amp;gt; &amp;#39;4&amp;#39;&amp;#34;&amp;#34;, &amp;#34;&amp;#34;Changed: &amp;#39;3&amp;#39; -&amp;gt; &amp;#39;4&amp;#39;&amp;#34;&amp;#34;, &amp;#34;&amp;#34;Changed: &amp;#39;4&amp;#39; -&amp;gt; &amp;#39;5&amp;#39;&amp;#34;&amp;#34;, &amp;#34;&amp;#34;Changed: &amp;#39;4&amp;#39; -&amp;gt; &amp;#39;5&amp;#39;&amp;#34;&amp;#34;]&amp;#34;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>人一眼就能看出来，这个内容实际上是没有变化的，因为变化的只不过是网站上内容的间隔时间，但是 agent 却认为有变化。&lt;/strong> 我的提示词如下写道——这版已经带上了&amp;quot;注意&amp;quot;段，专门提醒模型忽略纯时间变化（第一版连这个都还没有）：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">你是一个订阅号运营专家，可以根据差异内容总结出订阅内容的更新情况，请对以下内容差异进行总结：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> {contentdiff}
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 注意，有些内容的更新可能仅仅是由于时间的变化，比如[&amp;#34;&amp;#34;Changed: &amp;#39;33&amp;#39; -&amp;gt; &amp;#39;40&amp;#39;&amp;#34;&amp;#34;, &amp;#34;&amp;#34;Changed: &amp;#39;58 分钟&amp;#39; -&amp;gt; &amp;#39;1 小时&amp;#39;&amp;#34;&amp;#34;]，这样的内容更新是不需要总结的，可以看作没有更新
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 要求：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 1. 提供简洁的内容更新概要
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 2. 提取关键点
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 3. 计算内容的列表长度
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 返回结果使用中文,如果内容更新或者没有关键点，请返回空数组。
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 请根据以上要求，总结出订阅内容的更新情况，并返回结果。
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 返回格式json（请严格按照以下格式返回）：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> {{
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &amp;#34;summary&amp;#34;: {{
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &amp;#34;content&amp;#34;: [],
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &amp;#34;key_points&amp;#34;: [],
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &amp;#34;word_count&amp;#34;: 0,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &amp;#34;generated_at&amp;#34;: &amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> }},
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> }}
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>然后看 qwq-32b 和 grok3 的输出：&lt;/p>
&lt;p>&lt;img src="https://www.zata.cc/p/%E4%B8%80%E4%B8%AA%E8%AE%A2%E9%98%85%E6%9B%B4%E6%96%B0%E6%91%98%E8%A6%81-agent-%E7%9A%84%E5%AE%8C%E6%95%B4%E5%AE%9E%E8%B7%B5%E4%BB%8E%E6%8F%90%E7%A4%BA%E8%AF%8D%E8%B8%A9%E5%9D%91%E5%88%B0%E9%95%BF%E6%96%87%E6%9C%AC%E5%88%86%E5%9D%97/images/index/grok3-before.png"
width="915"
height="2042"
srcset="https://www.zata.cc/p/%E4%B8%80%E4%B8%AA%E8%AE%A2%E9%98%85%E6%9B%B4%E6%96%B0%E6%91%98%E8%A6%81-agent-%E7%9A%84%E5%AE%8C%E6%95%B4%E5%AE%9E%E8%B7%B5%E4%BB%8E%E6%8F%90%E7%A4%BA%E8%AF%8D%E8%B8%A9%E5%9D%91%E5%88%B0%E9%95%BF%E6%96%87%E6%9C%AC%E5%88%86%E5%9D%97/images/index/grok3-before_hu29585498707138032.png 480w, https://www.zata.cc/p/%E4%B8%80%E4%B8%AA%E8%AE%A2%E9%98%85%E6%9B%B4%E6%96%B0%E6%91%98%E8%A6%81-agent-%E7%9A%84%E5%AE%8C%E6%95%B4%E5%AE%9E%E8%B7%B5%E4%BB%8E%E6%8F%90%E7%A4%BA%E8%AF%8D%E8%B8%A9%E5%9D%91%E5%88%B0%E9%95%BF%E6%96%87%E6%9C%AC%E5%88%86%E5%9D%97/images/index/grok3-before_hu17151703834038982400.png 1024w"
loading="lazy"
alt="改动前 grok3 的输出"
class="gallery-image"
data-flex-grow="44"
data-flex-basis="107px"
>&lt;/p>
&lt;p>&lt;img src="https://www.zata.cc/p/%E4%B8%80%E4%B8%AA%E8%AE%A2%E9%98%85%E6%9B%B4%E6%96%B0%E6%91%98%E8%A6%81-agent-%E7%9A%84%E5%AE%8C%E6%95%B4%E5%AE%9E%E8%B7%B5%E4%BB%8E%E6%8F%90%E7%A4%BA%E8%AF%8D%E8%B8%A9%E5%9D%91%E5%88%B0%E9%95%BF%E6%96%87%E6%9C%AC%E5%88%86%E5%9D%97/images/index/qwq-32b-before.png"
width="948"
height="4146"
srcset="https://www.zata.cc/p/%E4%B8%80%E4%B8%AA%E8%AE%A2%E9%98%85%E6%9B%B4%E6%96%B0%E6%91%98%E8%A6%81-agent-%E7%9A%84%E5%AE%8C%E6%95%B4%E5%AE%9E%E8%B7%B5%E4%BB%8E%E6%8F%90%E7%A4%BA%E8%AF%8D%E8%B8%A9%E5%9D%91%E5%88%B0%E9%95%BF%E6%96%87%E6%9C%AC%E5%88%86%E5%9D%97/images/index/qwq-32b-before_hu9815642844934612282.png 480w, https://www.zata.cc/p/%E4%B8%80%E4%B8%AA%E8%AE%A2%E9%98%85%E6%9B%B4%E6%96%B0%E6%91%98%E8%A6%81-agent-%E7%9A%84%E5%AE%8C%E6%95%B4%E5%AE%9E%E8%B7%B5%E4%BB%8E%E6%8F%90%E7%A4%BA%E8%AF%8D%E8%B8%A9%E5%9D%91%E5%88%B0%E9%95%BF%E6%96%87%E6%9C%AC%E5%88%86%E5%9D%97/images/index/qwq-32b-before_hu2321586101093334250.png 1024w"
loading="lazy"
alt="改动前 qwq-32b 的输出"
class="gallery-image"
data-flex-grow="22"
data-flex-basis="54px"
>&lt;/p>
&lt;p>可以看到他们都没给我想要的输出——我想要的就是一个空的数组，因为没有更新，但它们给我返回的是……&lt;/p>
&lt;h3 id="关键改动">关键改动
&lt;/h3>&lt;p>然后我把提示词改成这样：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">你是一个订阅号运营专家，可以根据差异内容总结出订阅内容的更新情况，请对以下内容差异进行总结：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> {contentdiff}
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 注意，有些内容的可能仅仅是时间或者数据的变化，这样的内容更新是不需要总结的，可以看作没有更新，返回空数组
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 要求：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 1. 提供简洁的内容更新概要
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 2. 提取关键点
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 3. 计算内容的列表长度
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 返回结果使用中文,如果内容更新或者没有关键点，请返回空数组。
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 请根据以上要求，总结出订阅内容的更新情况，并返回结果。
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 返回格式json（请严格按照以下格式返回）：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> {{
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &amp;#34;summary&amp;#34;: {{
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &amp;#34;content&amp;#34;: [],
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &amp;#34;key_points&amp;#34;: [],
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &amp;#34;word_count&amp;#34;: 0,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &amp;#34;generated_at&amp;#34;: &amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> }},
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> }}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>就这一点点的改动：&lt;/p>
&lt;p>&lt;strong>注意，有些内容的可能仅仅是时间或者数据的变化，这样的内容更新是不需要总结的，可以看作没有更新，返回空数组&lt;/strong>&lt;/p>
&lt;p>但是效果：&lt;/p>
&lt;p>&lt;img src="https://www.zata.cc/p/%E4%B8%80%E4%B8%AA%E8%AE%A2%E9%98%85%E6%9B%B4%E6%96%B0%E6%91%98%E8%A6%81-agent-%E7%9A%84%E5%AE%8C%E6%95%B4%E5%AE%9E%E8%B7%B5%E4%BB%8E%E6%8F%90%E7%A4%BA%E8%AF%8D%E8%B8%A9%E5%9D%91%E5%88%B0%E9%95%BF%E6%96%87%E6%9C%AC%E5%88%86%E5%9D%97/images/index/grok3-after.png"
width="911"
height="2165"
srcset="https://www.zata.cc/p/%E4%B8%80%E4%B8%AA%E8%AE%A2%E9%98%85%E6%9B%B4%E6%96%B0%E6%91%98%E8%A6%81-agent-%E7%9A%84%E5%AE%8C%E6%95%B4%E5%AE%9E%E8%B7%B5%E4%BB%8E%E6%8F%90%E7%A4%BA%E8%AF%8D%E8%B8%A9%E5%9D%91%E5%88%B0%E9%95%BF%E6%96%87%E6%9C%AC%E5%88%86%E5%9D%97/images/index/grok3-after_hu7359286291637723218.png 480w, https://www.zata.cc/p/%E4%B8%80%E4%B8%AA%E8%AE%A2%E9%98%85%E6%9B%B4%E6%96%B0%E6%91%98%E8%A6%81-agent-%E7%9A%84%E5%AE%8C%E6%95%B4%E5%AE%9E%E8%B7%B5%E4%BB%8E%E6%8F%90%E7%A4%BA%E8%AF%8D%E8%B8%A9%E5%9D%91%E5%88%B0%E9%95%BF%E6%96%87%E6%9C%AC%E5%88%86%E5%9D%97/images/index/grok3-after_hu14272511110810606847.png 1024w"
loading="lazy"
alt="改动后 grok3 的输出"
class="gallery-image"
data-flex-grow="42"
data-flex-basis="100px"
>&lt;/p>
&lt;p>&lt;img src="https://www.zata.cc/p/%E4%B8%80%E4%B8%AA%E8%AE%A2%E9%98%85%E6%9B%B4%E6%96%B0%E6%91%98%E8%A6%81-agent-%E7%9A%84%E5%AE%8C%E6%95%B4%E5%AE%9E%E8%B7%B5%E4%BB%8E%E6%8F%90%E7%A4%BA%E8%AF%8D%E8%B8%A9%E5%9D%91%E5%88%B0%E9%95%BF%E6%96%87%E6%9C%AC%E5%88%86%E5%9D%97/images/index/qwq-32b-after.png"
width="981"
height="1379"
srcset="https://www.zata.cc/p/%E4%B8%80%E4%B8%AA%E8%AE%A2%E9%98%85%E6%9B%B4%E6%96%B0%E6%91%98%E8%A6%81-agent-%E7%9A%84%E5%AE%8C%E6%95%B4%E5%AE%9E%E8%B7%B5%E4%BB%8E%E6%8F%90%E7%A4%BA%E8%AF%8D%E8%B8%A9%E5%9D%91%E5%88%B0%E9%95%BF%E6%96%87%E6%9C%AC%E5%88%86%E5%9D%97/images/index/qwq-32b-after_hu10837178500985986731.png 480w, https://www.zata.cc/p/%E4%B8%80%E4%B8%AA%E8%AE%A2%E9%98%85%E6%9B%B4%E6%96%B0%E6%91%98%E8%A6%81-agent-%E7%9A%84%E5%AE%8C%E6%95%B4%E5%AE%9E%E8%B7%B5%E4%BB%8E%E6%8F%90%E7%A4%BA%E8%AF%8D%E8%B8%A9%E5%9D%91%E5%88%B0%E9%95%BF%E6%96%87%E6%9C%AC%E5%88%86%E5%9D%97/images/index/qwq-32b-after_hu6428853674628378006.png 1024w"
loading="lazy"
alt="改动后 qwq-32b 的输出"
class="gallery-image"
data-flex-grow="71"
data-flex-basis="170px"
>&lt;/p>
&lt;p>我感觉没什么问题。&lt;/p>
&lt;h3 id="第二轮真实-diff-再验证">第二轮：真实 diff 再验证
&lt;/h3>&lt;p>再来试试其他的——这次直接上真实抓取的 diff，数据原样贴出（它也是第三节要处理的&amp;quot;超长输入&amp;quot;的真实模样）：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">你是一个订阅号运营专家，可以根据差异内容总结出订阅内容的更新情况，请对以下内容差异进行总结：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &amp;#34;[&amp;#34;&amp;#34;Changed: &amp;#39;2&amp;#39; -&amp;gt; &amp;#39;33&amp;#39;&amp;#34;&amp;#34;, &amp;#34;&amp;#34;Changed: &amp;#39;Fl&amp;#39; -&amp;gt; &amp;#39;Andr&amp;#39;&amp;#34;&amp;#34;, &amp;#34;&amp;#34;Changed: &amp;#39;we&amp;#39; -&amp;gt; &amp;#39;id Studio集成Gemini新增多模态功能，开发者可上传图像获取UI代码\n谷歌最新宣布，And&amp;#39;&amp;#34;&amp;#34;, &amp;#34;&amp;#34;Changed: &amp;#39; Labs 颠覆AI&amp;#39; -&amp;gt; &amp;#39;oid Studio中的Gemini助手已升级支持多模态输入功能，开发者现在可以直接将图像附加到提示中，以获取&amp;#39;&amp;#34;&amp;#34;, &amp;#34;&amp;#34;Added: &amp;#39;程序开发过程中的视觉辅助。这项多&amp;#39;&amp;#34;&amp;#34;, &amp;#34;&amp;#34;Added: &amp;#39;态功能最初在I/O2024大会上亮相，升级后的Gemini现能够\&amp;#34;&amp;#34;理解简单的线框，并将其转换为可用的Jetpack Compose代码\&amp;#34;&amp;#34;。在Android Studio Narwal的Canary版本中，Ask Gemini字段新增了\&amp;#34;&amp;#34;附加图像文件\&amp;#34;&amp;#34;（支持JPEG或PNG格&amp;#39;&amp;#34;&amp;#34;, &amp;#34;&amp;#34;Changed: &amp;#39;，2&amp;#39; -&amp;gt; &amp;#39;）选项。谷歌建议用户使用具有\&amp;#34;&amp;#34;强烈色彩对比\&amp;#34;&amp;#34;的图像并提供\&amp;#34;&amp;#34;清晰的提示\&amp;#34;&amp;#34;以获得最佳效果。开发者可以上传从简单线框到高保真模型\n58 分钟前\n.\nAIbase\n北京新增&amp;#39;&amp;#34;&amp;#34;, &amp;#34;&amp;#34;Changed: &amp;#39;60万美元打造首个全开放混合计算平台\n&amp;#39; -&amp;gt; &amp;#39;4款已完成登记的生成式AI服务，小米AI搜索、Monica在列\n网信北京发布了最新北京市生成式&amp;#39;&amp;#34;&amp;#34;, &amp;#34;&amp;#34;Changed: &amp;#39;正在以前所未&amp;#39; -&amp;gt; &amp;#39;服务已登记信息公告，公称，根据《生成式人工智能服务管理暂行办法》及&amp;#39;&amp;#34;&amp;#34;, &amp;#34;&amp;#34;Changed: &amp;#39;的速度融入我们的日常&amp;#39; -&amp;gt; &amp;#39;关规定，对于通过API接口或其他方式直接调用已备案大模型能力的生成式人工智能&amp;#39;&amp;#34;&amp;#34;, &amp;#34;&amp;#34;Changed: &amp;#39;，而一家名为Flower Labs的初创公司正以革命性的&amp;#39; -&amp;gt; &amp;#39;或功能，采用登记管理&amp;#39;&amp;#34;&amp;#34;, &amp;#34;&amp;#34;Changed: &amp;#39;改变AI模型的部署和运行方&amp;#39; -&amp;gt; &amp;#39;，允许上线提供服务。截至3月14日，我市新增34款已完成登记的生成&amp;#39;&amp;#34;&amp;#34;, &amp;#34;&amp;#34;Changed: &amp;#39;。这家获得Y C&amp;#39; -&amp;gt; &amp;#39;人工智能服务，累计已完成46款生成式人工智能服务登记。其中，小米AI搜索、小米AI写作、M&amp;#39;&amp;#34;&amp;#34;, &amp;#34;&amp;#34;Changed: &amp;#39;mb&amp;#39; -&amp;gt; &amp;#39;n&amp;#39;&amp;#34;&amp;#34;, &amp;#34;&amp;#34;Deleted: &amp;#39;nator支持的新锐企业近日推出了Flower Intelligen&amp;#39;&amp;#34;&amp;#34;, &amp;#34;&amp;#34;Changed: &amp;#39;e，一个创新的分布&amp;#39; -&amp;gt; &amp;#39;a等产品在列。已上线的生成&amp;#39;&amp;#34;&amp;#34;, &amp;#34;&amp;#34;Deleted: &amp;#39;云平台，专为在移动设备、个&amp;#39;&amp;#34;&amp;#34;, &amp;#34;&amp;#34;Changed: &amp;#39;电脑和网络&amp;#39; -&amp;gt; &amp;#39;工智能&amp;#39;&amp;#34;&amp;#34;, &amp;#34;&amp;#34;Added: &amp;#39;或功能，应在显著位置或产品详情页面，公示所取\n1 小时前\n.\nAIbase\n阿里通义实验室语音团队负责人鄢志杰离职\n据tech星球消息，阿里通义实验室语音团队负责人鄢志杰已于 2 月 15 日正式离职，其职级为阿里原P序列体系&amp;#39;&amp;#34;&amp;#34;, &amp;#34;&amp;#34;Changed: &amp;#39;提供AI模型服务而设计。Flower Intelligence的核心优势在于其独特的混合计算策略。该平台允许应用程序在本地设备上运行AI模型，既保证了速度，又增强了隐私保护。当需要更强大的计算&amp;#39; -&amp;gt; &amp;#39;的P10 级别。鄢志杰是智&amp;#39;&amp;#34;&amp;#34;, &amp;#34;&amp;#34;Changed: &amp;#39;力时，系统会在获得用户同意的情况下，无\n7 分钟前\n.\nAIbase\n调查：5&amp;#39; -&amp;gt; &amp;#39;语音领域专家， &amp;#39;&amp;#34;&amp;#34;, &amp;#34;&amp;#34;Changed: &amp;#39;%美国成&amp;#39; -&amp;gt; &amp;#39;003 &amp;#39;&amp;#34;&amp;#34;, &amp;#34;&amp;#34;Changed: &amp;#39;人使用过AI聊天机器人\n美国埃隆大学的一项调查显示，52%的美国成年人都曾使用过像ChatGPT、Gemini、Claude这样的AI&amp;#39; -&amp;gt; &amp;#39;进入中科&amp;#39;&amp;#34;&amp;#34;, &amp;#34;&amp;#34;Changed: &amp;#39;言模型。这项由北卡罗来纳州埃隆&amp;#39; -&amp;gt; &amp;#39;音实验室攻读博士，师从科&amp;#39;&amp;#34;&amp;#34;, &amp;#34;&amp;#34;Changed: &amp;#39;学“想象数字未来中心”在1月份开展的调查，选取了5&amp;#39; -&amp;gt; &amp;#39;讯飞创始人王仁华教授。 2&amp;#39;&amp;#34;&amp;#34;, &amp;#34;&amp;#34;Changed: &amp;#39;名受访者。结果&amp;#39; -&amp;gt; &amp;#39;8 年获博士学位后，他在微软亚洲研究院语音组担任主管研究员至 2015 年，主要研究语音识别、语音合成等领域。学术上，他&amp;#39;&amp;#34;&amp;#34;, &amp;#34;&amp;#34;Deleted: &amp;#39;现，在使用过AI的人群中，34%的人&amp;#39;&amp;#34;&amp;#34;, &amp;#34;&amp;#34;Changed: &amp;#39;示至少每天会使用一次大语言模型。其中，ChatGPT最受欢迎，72%的受访者都用过;谷歌的Gemini位居第二，使用率为50% &amp;#39; -&amp;gt; &amp;#39;多篇顶级论文，拥有多项专利&amp;#39;&amp;#34;&amp;#34;, &amp;#34;&amp;#34;Deleted: &amp;#39;越来越多的人开始和AI聊天机器人建立起特殊的关系。调查显示，38%的用户认为大语言模\n&amp;#39;&amp;#34;&amp;#34;, &amp;#34;&amp;#34;Changed: &amp;#39;7 分钟前\n.\nAIbase\n北京将在中小学打造&amp;#39; -&amp;gt; &amp;#39;0&amp;#39;&amp;#34;&amp;#34;, &amp;#34;&amp;#34;Changed: &amp;#39;1个人工&amp;#39; -&amp;gt; &amp;#39;5 年加入阿里巴巴后，鄢志杰曾担任IDST&amp;#39;&amp;#34;&amp;#34;, &amp;#34;&amp;#34;Changed: &amp;#39;应用场景，启动7个\&amp;#34;&amp;#34;京娃\&amp;#34;&amp;#34;智能体\n据央视新闻报道，北京市教委宣布，今年将在中小学重点打造首批&amp;#39; -&amp;gt; &amp;#39;语音交互\n&amp;#39;&amp;#34;&amp;#34;, &amp;#34;&amp;#34;Changed: &amp;#39;1个人工智能应用场景，并启动培育建设7个\&amp;#34;&amp;#34;京娃\&amp;#34;&amp;#34;智能体，旨在以人工智能技术赋能五育融合培养体系，助力学生个性化、多样化发展。这些应用场景涵盖了\&amp;#34;&amp;#34;AI助教\&amp;#34;&amp;#34;的智能备课、智能课堂质量监测、智慧作业/命题;\&amp;#34;&amp;#34;AI助学\&amp;#34;&amp;#34;的智能错题分析及资源推荐、自主写作批改、外语学习助手;\&amp;#34;&amp;#34;AI助育\&amp;#34;&amp;#34;的智慧体育、心理健康助手;\&amp;#34;&amp;#34;AI助评\&amp;#34;&amp;#34;的智慧综合素质评价;\&amp;#34;&amp;#34;AI助研\&amp;#34;&amp;#34;的智能教师专业发展平台;以及\&amp;#34;&amp;#34;AI助管\&amp;#34;&amp;#34;的智慧校园。随着场景落地，7个各具\n39 分钟前\n.\nAIbase\n百万成本揭秘LLM训练黄金法则，阶跃星辰推出全领域适用的超参数优化工具\n在人工智能的激烈竞争中，一场耗资百万美元的大规模实验正悄然改变着大语言模型的训练方式。阶跃星辰研究团队日前发布重磅研究成果，他们通过耗费近100万NVIDIA H800GPU&amp;#39; -&amp;gt; &amp;#39; &amp;#39;&amp;#34;&amp;#34;, &amp;#34;&amp;#34;Deleted: &amp;#39;的算力，从零开始训练了3，700个不同规模的模型，累计训练了惊人的100万亿个token，揭示出一条被称为\&amp;#34;&amp;#34;Step Law\&amp;#34;&amp;#34;的普适性缩放规律，为大语言模型的高效训练提供了全新指南。这项研究不仅仅是对超参数优化的探索，更是第一个全面考察模型最优超参在不同形状、稀疏度和数据分布下稳定性的工作。研究结果表明，无\n55 分钟&amp;#39;&amp;#34;&amp;#34;, &amp;#34;&amp;#34;Changed: &amp;#39;AI“天眼”再进化！YOLOE破壳而出：&amp;#39; -&amp;gt; &amp;#39;论文阅读噩梦&amp;#39;&amp;#34;&amp;#34;, &amp;#34;&amp;#34;Deleted: &amp;#39;物体检测“刻板印象”，万物皆可实时识别&amp;#39;&amp;#34;&amp;#34;, &amp;#34;&amp;#34;Changed: &amp;#39;\n曾几何时，AI的“眼睛”还带着厚重的“滤镜”，只能识别预设好的“剧本”。 但现在，游戏规则彻底改写! 一种名为YOLOE的全新AI模型破空问世，它像一位打破枷锁的“视觉艺术家”，挥别了传统物体检测的“僵化教条”，宣告了一个“万物皆可实时识别”的全新纪元! 想象一下，AI不再需要“死记硬背”类别标签，而是像人类一样，仅凭文本描述、模糊图像，甚至在毫无线索的情况下，就能“秒懂”眼前的一切。 这种颠覆性的突破，正是YOLOE带来的震撼变革!YOLOE的&amp;#39; -&amp;gt; &amp;#39;AI神器&amp;#39;&amp;#34;&amp;#34;, &amp;#34;&amp;#34;Changed: &amp;#39;，宛&amp;#39; -&amp;gt; &amp;#39;： arXiv论文一键变博客，科研效率火箭式飙升！\n你是否还在论文的海洋里苦苦挣扎?面对学术网站 arXiv 上堆积&amp;#39;&amp;#34;&amp;#34;, &amp;#34;&amp;#34;Changed: &amp;#39;给AI装上\n1&amp;#39; -&amp;gt; &amp;#39;山的论文，是不是也感到头皮发麻，无从下手? 那些晦涩难懂的术语，曲折冗长的段落，复杂烧脑的图表，简直像一道道 impenetrable 的高墙，将求知的心牢牢阻隔在外。 对于科研爱好者、莘莘学子，甚至是专业人士来说，啃下一篇论文，往往需要耗费数小时的精力，甚至要查阅海量资料才能勉强摸到门道，这效率，简直让人抓狂!但!是! 所有挣扎，都将成为过去式! 一款横空出世的AI神器—— alphaXiv，要来彻底拯救你于论文苦海!\n3&amp;#39;&amp;#34;&amp;#34;, &amp;#34;&amp;#34;Changed: &amp;#39;英国首相计划&amp;#39; -&amp;gt; &amp;#39;​一男子因&amp;#39;&amp;#34;&amp;#34;, &amp;#34;&amp;#34;Changed: &amp;#39;AI替代部分公务员工作\n英国&amp;#39; -&amp;gt; &amp;#39; AI 撰写色情小说被判刑十个月，非法获利超两万元\n近期，湖北省大冶市人民法院对一起&amp;#39;&amp;#34;&amp;#34;, &amp;#34;&amp;#34;Changed: &amp;#39;相基尔・斯塔默（Keir Starmer）近日提出了一项新的计划，旨在通过数字化和&amp;#39; -&amp;gt; &amp;#39;例利用&amp;#39;&amp;#34;&amp;#34;, &amp;#34;&amp;#34;Deleted: &amp;#39;来提高政府工作的效率。他将在周四的演讲中详细阐述这一构想，表示希望能够在公务员的工作中，尽可能地用数字化和 AI 替代那些可以以相同标准完成的任务。他强调，公务员的时间应该优先用于更需要人类判断和创造力的工作。斯塔默认为，英国政府通过更广泛地采用数字化方法，可以在未来节省超过 450 亿英镑的开支，并计划招募 2000 名新的&amp;#39;&amp;#34;&amp;#34;, &amp;#34;&amp;#34;Changed: &amp;#39;学&amp;#39; -&amp;gt; &amp;#39;撰写色情小说并进行牟利的案件作出判决。被告人柯某因制作、贩卖、传播淫秽物品牟利罪，被判处有期&amp;#39;&amp;#34;&amp;#34;, &amp;#34;&amp;#34;Changed: &amp;#39;来充实公务员队伍。他表示，这些措\n1 小时前\n.\nAIbase\n英矽智能完成1.1亿美&amp;#39; -&amp;gt; &amp;#39;刑十个月，并处罚金人民币五千&amp;#39;&amp;#34;&amp;#34;, &amp;#34;&amp;#34;Changed: &amp;#39;E轮融资 推动AI平台升级\n​今日，英矽智能，一家专注于生成式人工智能技术的生物医药科技公司，正式对外宣布，已成功完成1.1亿美元的E轮融资。本轮融资由惠理集团（HKG:0806）旗下的私募股权基金、浦东创投、浦发集团、锡创投以及宜兴国控联合领投。此外，&amp;#39; -&amp;gt; &amp;#39;，退&amp;#39;&amp;#34;&amp;#34;, &amp;#34;&amp;#34;Changed: &amp;#39;有多位专注于行业和科技领域的新晋投资者参与本轮融资，同时&amp;#39; -&amp;gt; &amp;#39;违法所&amp;#39;&amp;#34;&amp;#34;, &amp;#34;&amp;#34;Changed: &amp;#39;到了现有投资者的鼎力支持。\n1 小时前\n.\nAIbase\n报道称MiniMax 意向收购AI视频创业公司鹿影科技\n&amp;#39; -&amp;gt; &amp;#39;。&amp;#39;&amp;#34;&amp;#34;, &amp;#34;&amp;#34;Changed: &amp;#39;蓝鲸新闻独家消息&amp;#39; -&amp;gt; &amp;#39;公诉机关的&amp;#39;&amp;#34;&amp;#34;, &amp;#34;&amp;#34;Changed: &amp;#39;出，人工智能视频初创公司鹿影科技（Avolution.ai）或将被知名人工智能公司MiniMax收购。据多位知情人士透露，双方已就收购达成初步意向，相关流程正在进行中。截至发稿，MiniMax尚未对此消息做出回应。据悉，鹿影科技&amp;#39; -&amp;gt; &amp;#39;控，柯某&amp;#39;&amp;#34;&amp;#34;, &amp;#34;&amp;#34;Changed: &amp;#39;4&amp;#39; -&amp;gt; &amp;#39;2&amp;#39;&amp;#34;&amp;#34;, &amp;#34;&amp;#34;Deleted: &amp;#39;天使轮融资时的估值约为&amp;#39;&amp;#34;&amp;#34;, &amp;#34;&amp;#34;Changed: &amp;#39;亿人民币左右，低于2000万美元。知情人士表示，鹿影科技自去年起寻求第二轮融资并不顺利，而其在AI视频领域的技术积累最终促成了与MiniMax的合作，这被认为是双赢的选择。公开资料显示，鹿影科技成立于&amp;#39; -&amp;gt; &amp;#39;1月至&amp;#39;&amp;#34;&amp;#34;, &amp;#34;&amp;#34;Changed: &amp;#39;9&amp;#39; -&amp;gt; &amp;#39;3&amp;#39;&amp;#34;&amp;#34;, &amp;#34;&amp;#34;Changed: &amp;#39;\n2&amp;#39; -&amp;gt; &amp;#39;期间，作为一名大专文化的网络文学作者，利用 AI 程序撰写色情小说，并通过在境外黄色网站上发布，同时在其他网站进行销售。在短短五个月的时间内，柯某发布了数十篇色情小说，非法获利超过两万元。检方送检的\n3&amp;#39;&amp;#34;&amp;#34;, &amp;#34;&amp;#34;Added: &amp;#39;AI助力房地产市场&amp;#39;&amp;#34;&amp;#34;, &amp;#34;&amp;#34;Changed: &amp;#39;讯&amp;#39; -&amp;gt; &amp;#39;飞，预计2030年规模将达1803.45亿美&amp;#39;&amp;#34;&amp;#34;, &amp;#34;&amp;#34;Changed: &amp;#39;宝&amp;#39; -&amp;gt; &amp;#39;\n全球人工智能（AI）在房地产市场的应用正在迅速崛起，预计到2030年将达到1803.45亿美元，年均增长率高达35%。这一市场的快速发展得益于机器学习、预测分析等技术的进步，以及房地产管理对自动化的日益需求。在这一市场中，主要参&amp;#39;&amp;#34;&amp;#34;, &amp;#34;&amp;#34;Changed: &amp;#39;腾讯&amp;#39; -&amp;gt; &amp;#39;者包括 Zillow 集团、Compass、Redfin 公司和 Reonomy 等。它们正在利用 AI 驱动的工具，提升客户体验，并优化物业管理流程。图源备注：图片由AI生成，图片授权服务商Midjourney美国市场是该领域的佼佼者，因其 AI 技术的快速采纳和健全的房地产基础设施。近期，\n4 小时前\n.\nAIbase\nOpenAI Chat Playground升级为Prompts Playground 更好测试、迭代提示词\nOpenAI 宣布，其广受欢迎的 Chat Playground 正式升级并更名为 Prompts Playground。这一更新带来了全新的设计和功能，旨在为用户提供更强大的工具，以便更好地测试、比较和迭代提示（prompts）。根据 OpenAI 在 X 平台上的最新介绍，此次重新设计整合了包括 Web 搜索和&amp;#39;&amp;#34;&amp;#34;, &amp;#34;&amp;#34;Changed: &amp;#39;档打通&amp;#39; -&amp;gt; &amp;#39;件搜索在内的高级工具，进一步提升了用户体验和创作灵活性。据 OpenAI 开发团队透露，Prompts Playground 的核心目标是让用户能够更高效地探索和优化 AI 模型的交互方式。除了保留原有的对话功能外，新平台还允许用户保存和共享特定\n4 小时前\n.\nAIbase\nSesame发布CSM模型&amp;#39;&amp;#34;&amp;#34;, &amp;#34;&amp;#34;Changed: &amp;#39;支持一键上传和导出为腾讯文档&amp;#39; -&amp;gt; &amp;#39;实时情感定制 AI语音合成迈向新高度&amp;#39;&amp;#34;&amp;#34;, &amp;#34;&amp;#34;Changed: &amp;#39;腾讯&amp;#39; -&amp;gt; &amp;#39;Sesame公司推出其最新语音合成模型CSM，引发业界关注。据官方介绍，CSM采用端到端基于Transformer的多模态学习架构，能够理解上下文信息，生成自然且富有情感的语音，声音效果贴近真人，令人惊艳。该模型支持实时语音生成，可处理文本和音频输入，用户还能通过调整参数控制语气、语调、节奏及情感等特性，展现高度灵活性。CSM被认为是AI语音技术的重要突破。其语音自然度极高，甚至“无法分辨是人工合成还是真人”。有用户录制视频展示CSM几近无延迟的表现，称其为“体验\n4 小时前\n.\nAIbase\nAnthropic、IBM 和 Meta 的技术领导者警告称，人工智能将取代软件开发人员的工作\n在最近一次国际会议上，Anthropic 首席执行官达里奥・阿莫迪（Dario Amodei）发表了一个引人注目的预测，他认为人工智能将在未来三到六个月内承担90% 的代码编写工作。阿莫迪表示，如果这一趋势持续下去，到了12个月后，AI 可能将几乎完全取代人类程序员的工作。他指出，尽管程序员仍需为 AI 设定特定的条件和目标，但未来这一过程也可能被技术所取代。阿莫迪认为，尽管人工智能将逐渐取代人类的某些工作，但这也将促使我们重新审视人力资源的有效利用。他指出，当前的思维模式已\n5 小时前\n.\nAIbase\n巨人网络发布行业首个DeepSeek原生游戏玩法 太空杀推出内鬼挑战\n巨人网络&amp;#39;&amp;#34;&amp;#34;, &amp;#34;&amp;#34;Changed: &amp;#39;智&amp;#39; -&amp;gt; &amp;#39;社交推理游戏《太空杀》正式接入DeepSeek大模型，并推出基于该技术的原生游戏玩法“内鬼挑战”，目前该玩法已开启灰度测试，后续将面向全量用户开放。这标志着业内首次将DeepSeek大模型&amp;#39;&amp;#34;&amp;#34;, &amp;#34;&amp;#34;Changed: &amp;#39;助手腾讯元宝与腾讯文档实现&amp;#39; -&amp;gt; &amp;#39;力&amp;#39;&amp;#34;&amp;#34;, &amp;#34;&amp;#34;Changed: &amp;#39;打通，这一升级为&amp;#39; -&amp;gt; &amp;#39;应&amp;#39;&amp;#34;&amp;#34;, &amp;#34;&amp;#34;Changed: &amp;#39;户带来了更加便捷高效的办公&amp;#39; -&amp;gt; &amp;#39;于游戏玩法创新，以AI驱动游戏核心玩法，重塑游戏的竞技和交互&amp;#39;&amp;#34;&amp;#34;, &amp;#34;&amp;#34;Changed: &amp;#39;，有望改变现有的办公协作模式。在以往的工作场景中，用户需要在文档编辑与智能助手之间频繁进行复制粘贴操作，流程繁琐且易出错。而此次腾讯元宝的升级，成功解决了这一痛点。上传指引:移动端点击右下角“+”后选择腾讯文档;Web端点击“上传文档-上传腾讯文档”;现在，用户不仅可以从元宝一键上传腾讯文档，支持表格、文档、PPT、PDF、思维导图等多种格式，无需\n2 小时前\n.\nAIbase\nLuma开源图像模型预训练技术IMM 采样效率提高10倍\n人工智能初创公司Luma近日在X平台宣布，其开源了一项名为Inductive Moment Matching（IMM）的图像模型预训练技术。这一突破性技术以其高效和稳定的特性引发了广泛关注，被认为是生成式AI领域的一次重要进步。据X用户linqi_zhou透露，IMM是一种全新的生成范式，能够以单模型和单一目标从零开始稳定训练，同时在采样效率和样本质量上超越传统方法。他在帖子中兴奋地表示:“IMM在ImageNet256×256上仅用8步就达到了1.99FID（Fréchet Inception Distance），在CIFAR-10上仅用2步就达到了1.98FID。”这一性能不仅刷&amp;#39; -&amp;gt; &amp;#39;。&amp;#39;&amp;#34;&amp;#34;]&amp;#34;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 注意，有些内容的可能仅仅是时间或者数据的变化，这样的内容更新是不需要总结的，可以看作没有更新，返回空数组
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 要求：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 1. 提供简洁的内容更新概要
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 2. 提取关键点
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 3. 计算内容的列表长度
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 返回结果使用中文,如果内容更新或者没有关键点，请返回空数组。
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 请根据以上要求，总结出订阅内容的更新情况，并返回结果。
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 返回格式json（请严格按照以下格式返回）：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> {{
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &amp;#34;summary&amp;#34;: {{
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &amp;#34;content&amp;#34;: [],
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &amp;#34;key_points&amp;#34;: [],
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &amp;#34;word_count&amp;#34;: 0,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &amp;#34;generated_at&amp;#34;: &amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> }},
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> }}
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;img src="https://www.zata.cc/p/%E4%B8%80%E4%B8%AA%E8%AE%A2%E9%98%85%E6%9B%B4%E6%96%B0%E6%91%98%E8%A6%81-agent-%E7%9A%84%E5%AE%8C%E6%95%B4%E5%AE%9E%E8%B7%B5%E4%BB%8E%E6%8F%90%E7%A4%BA%E8%AF%8D%E8%B8%A9%E5%9D%91%E5%88%B0%E9%95%BF%E6%96%87%E6%9C%AC%E5%88%86%E5%9D%97/images/index/grok3-real-diff.png"
width="870"
height="7798"
srcset="https://www.zata.cc/p/%E4%B8%80%E4%B8%AA%E8%AE%A2%E9%98%85%E6%9B%B4%E6%96%B0%E6%91%98%E8%A6%81-agent-%E7%9A%84%E5%AE%8C%E6%95%B4%E5%AE%9E%E8%B7%B5%E4%BB%8E%E6%8F%90%E7%A4%BA%E8%AF%8D%E8%B8%A9%E5%9D%91%E5%88%B0%E9%95%BF%E6%96%87%E6%9C%AC%E5%88%86%E5%9D%97/images/index/grok3-real-diff_hu2912413749862056331.png 480w, https://www.zata.cc/p/%E4%B8%80%E4%B8%AA%E8%AE%A2%E9%98%85%E6%9B%B4%E6%96%B0%E6%91%98%E8%A6%81-agent-%E7%9A%84%E5%AE%8C%E6%95%B4%E5%AE%9E%E8%B7%B5%E4%BB%8E%E6%8F%90%E7%A4%BA%E8%AF%8D%E8%B8%A9%E5%9D%91%E5%88%B0%E9%95%BF%E6%96%87%E6%9C%AC%E5%88%86%E5%9D%97/images/index/grok3-real-diff_hu2326213629668681312.png 1024w"
loading="lazy"
alt="第二轮 grok3 的输出"
class="gallery-image"
data-flex-grow="11"
data-flex-basis="26px"
>&lt;/p>
&lt;p>&lt;img src="https://www.zata.cc/p/%E4%B8%80%E4%B8%AA%E8%AE%A2%E9%98%85%E6%9B%B4%E6%96%B0%E6%91%98%E8%A6%81-agent-%E7%9A%84%E5%AE%8C%E6%95%B4%E5%AE%9E%E8%B7%B5%E4%BB%8E%E6%8F%90%E7%A4%BA%E8%AF%8D%E8%B8%A9%E5%9D%91%E5%88%B0%E9%95%BF%E6%96%87%E6%9C%AC%E5%88%86%E5%9D%97/images/index/qwq-32b-real-diff.png"
width="914"
height="3624"
srcset="https://www.zata.cc/p/%E4%B8%80%E4%B8%AA%E8%AE%A2%E9%98%85%E6%9B%B4%E6%96%B0%E6%91%98%E8%A6%81-agent-%E7%9A%84%E5%AE%8C%E6%95%B4%E5%AE%9E%E8%B7%B5%E4%BB%8E%E6%8F%90%E7%A4%BA%E8%AF%8D%E8%B8%A9%E5%9D%91%E5%88%B0%E9%95%BF%E6%96%87%E6%9C%AC%E5%88%86%E5%9D%97/images/index/qwq-32b-real-diff_hu6131709592511742820.png 480w, https://www.zata.cc/p/%E4%B8%80%E4%B8%AA%E8%AE%A2%E9%98%85%E6%9B%B4%E6%96%B0%E6%91%98%E8%A6%81-agent-%E7%9A%84%E5%AE%8C%E6%95%B4%E5%AE%9E%E8%B7%B5%E4%BB%8E%E6%8F%90%E7%A4%BA%E8%AF%8D%E8%B8%A9%E5%9D%91%E5%88%B0%E9%95%BF%E6%96%87%E6%9C%AC%E5%88%86%E5%9D%97/images/index/qwq-32b-real-diff_hu4392458250136354161.png 1024w"
loading="lazy"
alt="第二轮 qwq-32b 的输出"
class="gallery-image"
data-flex-grow="25"
data-flex-basis="60px"
>&lt;/p>
&lt;p>这个感觉也没什么问题，qwq-32b 的效果更好。&lt;/p>
&lt;h3 id="复盘">复盘
&lt;/h3>&lt;p>对照两版提示词，实质是三处改动：&lt;/p>
&lt;ul>
&lt;li>范围从&amp;quot;时间的变化&amp;quot;扩成&amp;quot;时间&lt;strong>或者数据&lt;/strong>的变化&amp;quot;——&lt;code>Changed: '1' -&amp;gt; '2'&lt;/code> 这种裸数字变化终于被覆盖；&lt;/li>
&lt;li>删掉了举例——现在回看，那两个时间变化的例子反而把模型的理解锚死在字面上的时间字符串，裸数字就漏了出去；&lt;/li>
&lt;li>把&amp;quot;返回空数组&amp;quot;这个动作直接缀在&amp;quot;可以看作没有更新&amp;quot;后面——判断和行为写在同一句里，不靠模型自己把两段话联系起来。&lt;/li>
&lt;/ul>
&lt;p>验证也是从这次开始分两步走：先用构造的最小样例确认&amp;quot;该空的时候空不空&amp;quot;，再用真实数据回归&amp;quot;该有的时候有没有&amp;quot;。只测前者，会漏掉把真实更新也判成&amp;quot;没有更新&amp;quot;的过头修正。&lt;/p>
&lt;hr>
&lt;h2 id="三超长文本按变更单元分块map-reduce-汇总">三、超长文本：按变更单元分块，map-reduce 汇总
&lt;/h2>&lt;p>进入四月，新的问题来了：真实抓取的 diff 越来越长，一次全塞给模型开始顶不住 token 上限。这一节的完整可执行脚本放在本文同目录：&lt;a class="link" href="./%e8%a7%a3%e5%86%b3%e4%b8%8a%e4%b8%8b%e6%96%87%e8%bf%87%e9%95%bf.py" >解决上下文过长.py&lt;/a>，下面讲思路，代码有删节（去掉了日志行），完整版直接看文件。&lt;/p>
&lt;p>模型换成 Dashscope 上的 &lt;code>qwen-plus&lt;/code>，走 OpenAI 兼容端点：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">pip install langchain-core langchain-openai pydantic openai
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">export&lt;/span> &lt;span class="nv">DASHSCOPE_API_KEY&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;你的密钥&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="输出结构从手写-dict-到-pydantic">输出结构：从手写 dict 到 Pydantic
&lt;/h3>&lt;p>第一版的手写 &lt;code>SUMMARY_TEMPLATE&lt;/code> 升级成了 Pydantic 模型，多了 &lt;code>url_list&lt;/code>（每个关键点对应的 URL 二维数组）和 &lt;code>raw_response&lt;/code>（LLM 原始响应存档）两个字段：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">pydantic&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">BaseModel&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">Field&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">typing&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">List&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">Optional&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">SummaryResponse&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">BaseModel&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">content&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">List&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nb">str&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">Field&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">default_factory&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="nb">list&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">description&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;内容更新摘要&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">key_points&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">List&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nb">str&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">Field&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">default_factory&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="nb">list&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">description&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;关键点列表&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">url_list&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">List&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">List&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nb">str&lt;/span>&lt;span class="p">]]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">Field&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">default_factory&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="nb">list&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">description&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;每个关键点对应的URL列表&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">word_count&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">int&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">Field&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">default&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">description&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;内容的总字数&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">generated_at&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">Field&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">default&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">description&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;生成时间戳&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">status&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">Field&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">default&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;success&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">description&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;处理状态&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">error_message&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">Optional&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nb">str&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">Field&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">default&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="kc">None&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">description&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;错误信息（如有）&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">raw_response&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">Optional&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nb">str&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">Field&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">default&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="kc">None&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">description&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;LLM原始响应&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>解析器换成 &lt;code>PydanticOutputParser&lt;/code>：结构、类型、默认值都在一处定义，&lt;code>get_format_instructions()&lt;/code> 直接生成 JSON 格式说明，用 &lt;code>partial_variables&lt;/code> 注进提示词——不用再手抄一遍&amp;quot;请严格按照以下格式返回&amp;quot;的模板：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">langchain_core.prompts&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">PromptTemplate&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">langchain_core.output_parsers&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">PydanticOutputParser&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">langchain_openai&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">ChatOpenAI&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">llm&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">ChatOpenAI&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">api_key&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">os&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">getenv&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;DASHSCOPE_API_KEY&amp;#34;&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">base_url&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;https://dashscope.aliyuncs.com/compatible-mode/v1&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">model&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;qwen-plus&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">parser&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">PydanticOutputParser&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">pydantic_object&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">SummaryResponse&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">prompt_template&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">PromptTemplate&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">input_variables&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;contentdiff&amp;#34;&lt;/span>&lt;span class="p">],&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">partial_variables&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="s2">&amp;#34;format_instructions&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">parser&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">get_format_instructions&lt;/span>&lt;span class="p">()},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">template&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"> You are a subscription content expert. Summarize the following content differences:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"> &lt;/span>&lt;span class="si">{contentdiff}&lt;/span>&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"> ### Notes:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"> - Ignore updates that are only time or data changes (return empty arrays).
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"> - Ensure content and key_points lists have a one-to-one correspondence.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"> - url_list is a 2D array, each sublist contains URLs for the corresponding key point.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"> ### Requirements:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"> 1. Provide content update summaries (content) as an array.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"> 2. Extract key points (key_points) for each content item.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"> 3. Extract URLs for each key point (url_list); return empty arrays if none.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"> 4. Calculate word count for content (Chinese/English characters only, no punctuation/spaces).
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"> 5. Return results in Chinese if updates exist; otherwise, return empty arrays.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"> 6. Follow this JSON format:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"> &lt;/span>&lt;span class="si">{format_instructions}&lt;/span>&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"> &amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>提示词本身换成了英文，但 Notes 第一条就是第二节踩坑换来的那句——&lt;code>Ignore updates that are only time or data changes&lt;/code>。几轮迭代下来的结论，直接固化进了代码。&lt;/p>
&lt;h3 id="主流程token-估算与分块决策">主流程：token 估算与分块决策
&lt;/h3>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">generate_summary&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">contentdiff&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="n">SummaryResponse&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">avg_token_per_char&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mf">0.5&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">estimated_tokens&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nb">len&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">contentdiff&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">*&lt;/span> &lt;span class="n">avg_token_per_char&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">estimated_tokens&lt;/span> &lt;span class="o">&amp;gt;&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">max_token_limit&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">generate_summary_with_memory&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">contentdiff&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="c1"># 超限走分块路径&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># 未超限：直接一条链处理&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>token 数不引 tokenizer，拿字符数乘 0.5 粗估。这个数不需要准——它只服务&amp;quot;要不要分块&amp;quot;这一个二元决策。宁可把 &lt;code>max_token_limit&lt;/code> 设得比模型实际上限低一截，让估算误差只往&amp;quot;多分一块&amp;quot;的方向错，而不是该分没分、请求直接撞上下文上限。&lt;/p>
&lt;h3 id="分块策略优先变更单元字符数兜底">分块策略：优先变更单元，字符数兜底
&lt;/h3>&lt;p>contentdiff 的结构天然适合按语义边界切：每条变更都是一个 &lt;code>&amp;quot;&amp;quot;Changed: ...&amp;quot;&amp;quot;&lt;/code> / &lt;code>&amp;quot;&amp;quot;Added: ...&amp;quot;&amp;quot;&lt;/code> / &lt;code>&amp;quot;&amp;quot;Deleted: ...&amp;quot;&amp;quot;&lt;/code> 单元。固定字符切分会把一条变更从中间切断，半截单元对模型是纯噪音，还容易诱导出半句幻觉。所以正则优先按变更单元攒包，实在匹配不到单元（比如输入根本不是 diff 格式）才按字符数硬切兜底：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">chunking_content&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">contentdiff&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="n">List&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nb">str&lt;/span>&lt;span class="p">]:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">avg_token_per_char&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mf">0.5&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="nb">len&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">contentdiff&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">*&lt;/span> &lt;span class="n">avg_token_per_char&lt;/span> &lt;span class="o">&amp;lt;=&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">max_token_limit&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="n">contentdiff&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">chunks&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">change_units&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">re&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">findall&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">r&lt;/span>&lt;span class="s1">&amp;#39;&amp;#34;&amp;#34;(?:Changed|Added|Deleted):.*?&amp;#34;&amp;#34;,&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">contentdiff&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">re&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">DOTALL&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="ow">not&lt;/span> &lt;span class="n">change_units&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># 兜底：没有变更单元，按字符数硬切&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">chars_per_chunk&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nb">int&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">max_token_limit&lt;/span> &lt;span class="o">/&lt;/span> &lt;span class="n">avg_token_per_char&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="n">i&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="nb">range&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nb">len&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">contentdiff&lt;/span>&lt;span class="p">),&lt;/span> &lt;span class="n">chars_per_chunk&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">chunks&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">append&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">contentdiff&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">i&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="n">i&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="n">chars_per_chunk&lt;/span>&lt;span class="p">])&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">else&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># 优先：按变更单元攒包&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">chars_per_chunk&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nb">int&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">max_token_limit&lt;/span> &lt;span class="o">/&lt;/span> &lt;span class="n">avg_token_per_char&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">current_chunk&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="n">unit&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">change_units&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="nb">len&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">current_chunk&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="nb">len&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">unit&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">&amp;gt;&lt;/span> &lt;span class="n">chars_per_chunk&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">chunks&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">append&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">current_chunk&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">current_chunk&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">unit&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">else&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">current_chunk&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">current_chunk&lt;/span> &lt;span class="o">+=&lt;/span> &lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="se">\n&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">current_chunk&lt;/span> &lt;span class="o">+=&lt;/span> &lt;span class="n">unit&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">current_chunk&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">chunks&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">append&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">current_chunk&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">chunks&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="map逐块抽取">map：逐块抽取
&lt;/h3>&lt;p>每个块用一个简单的 &lt;code>chunk_prompt&lt;/code> 抽 content / key_points / urls，三个 collected 列表各自累积。单块失败只跳过该块，不炸全局：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="k">for&lt;/span> &lt;span class="n">i&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">chunk&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="nb">enumerate&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">content_chunks&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">chunk_prompt&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">PromptTemplate&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">input_variables&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;chunk_content&amp;#34;&lt;/span>&lt;span class="p">],&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">template&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"> Analyze the following content difference chunk:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"> &lt;/span>&lt;span class="si">{chunk_content}&lt;/span>&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"> Provide in JSON format:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"> 1. content: List of content update summaries
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"> 2. key_points: List of corresponding key points
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"> 3. urls: List of URLs (2D array)
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"> Return only JSON, no extra text.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"> &amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">chunk_chain&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">chunk_prompt&lt;/span> &lt;span class="o">|&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">llm&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">try&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">chunk_result&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">chunk_chain&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">invoke&lt;/span>&lt;span class="p">({&lt;/span>&lt;span class="s2">&amp;#34;chunk_content&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">chunk&lt;/span>&lt;span class="p">})&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">chunk_content&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">chunk_result&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">content&lt;/span> &lt;span class="k">if&lt;/span> &lt;span class="nb">hasattr&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">chunk_result&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">&amp;#39;content&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="k">else&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">chunk_result&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">json_match&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">re&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">search&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">r&lt;/span>&lt;span class="s1">&amp;#39;\{.*\}&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">chunk_content&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">re&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">DOTALL&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">json_match&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">chunk_json&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">json&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">loads&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">json_match&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">group&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="s1">&amp;#39;content&amp;#39;&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">chunk_json&lt;/span> &lt;span class="ow">and&lt;/span> &lt;span class="nb">isinstance&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">chunk_json&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s1">&amp;#39;content&amp;#39;&lt;/span>&lt;span class="p">],&lt;/span> &lt;span class="nb">list&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">collected_content&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">extend&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">chunk_json&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s1">&amp;#39;content&amp;#39;&lt;/span>&lt;span class="p">])&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="s1">&amp;#39;key_points&amp;#39;&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">chunk_json&lt;/span> &lt;span class="ow">and&lt;/span> &lt;span class="nb">isinstance&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">chunk_json&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s1">&amp;#39;key_points&amp;#39;&lt;/span>&lt;span class="p">],&lt;/span> &lt;span class="nb">list&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">collected_key_points&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">extend&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">chunk_json&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s1">&amp;#39;key_points&amp;#39;&lt;/span>&lt;span class="p">])&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="s1">&amp;#39;urls&amp;#39;&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">chunk_json&lt;/span> &lt;span class="ow">and&lt;/span> &lt;span class="nb">isinstance&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">chunk_json&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s1">&amp;#39;urls&amp;#39;&lt;/span>&lt;span class="p">],&lt;/span> &lt;span class="nb">list&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">collected_urls&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">extend&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">chunk_json&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s1">&amp;#39;urls&amp;#39;&lt;/span>&lt;span class="p">])&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">memory&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">save_context&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>&lt;span class="s2">&amp;#34;input&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;Chunk &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">i&lt;/span>&lt;span class="o">+&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">:&lt;/span>&lt;span class="se">\n&lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">chunk&lt;/span>&lt;span class="p">[:&lt;/span>&lt;span class="mi">200&lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">...&amp;#34;&lt;/span>&lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>&lt;span class="s2">&amp;#34;output&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;Analysis:&lt;/span>&lt;span class="se">\n&lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">chunk_content&lt;/span>&lt;span class="p">[:&lt;/span>&lt;span class="mi">200&lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">...&amp;#34;&lt;/span>&lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">except&lt;/span> &lt;span class="ne">Exception&lt;/span> &lt;span class="k">as&lt;/span> &lt;span class="n">e&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">continue&lt;/span> &lt;span class="c1"># 单块失败只跳过，不炸全局&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="reduce汇总成最终结果">reduce：汇总成最终结果
&lt;/h3>&lt;p>所有块处理完，把三个 collected 列表交给 final prompt，让模型合并、去重，生成最终的 &lt;code>SummaryResponse&lt;/code>：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="n">final_prompt_template&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">PromptTemplate&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">input_variables&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;collected_content&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;collected_key_points&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;collected_urls&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;format_instructions&amp;#34;&lt;/span>&lt;span class="p">],&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">template&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"> Generate a final summary from the collected information:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"> Content updates: &lt;/span>&lt;span class="si">{collected_content}&lt;/span>&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"> Key points: &lt;/span>&lt;span class="si">{collected_key_points}&lt;/span>&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"> URLs: &lt;/span>&lt;span class="si">{collected_urls}&lt;/span>&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"> Return JSON in this format:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"> &lt;/span>&lt;span class="si">{format_instructions}&lt;/span>&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"> Notes:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"> 1. Ensure content and key_points correspond one-to-one.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"> 2. url_list is a 2D array for each key point&amp;#39;s URLs.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"> 3. Calculate word_count (characters, no punctuation/spaces).
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"> 4. Merge or remove duplicate content.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"> 5. Return only JSON.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"> &amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">final_chain&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">final_prompt_template&lt;/span> &lt;span class="o">|&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">llm&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">raw_response&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">final_chain&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">invoke&lt;/span>&lt;span class="p">({&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;collected_content&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">collected_content&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;collected_key_points&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">collected_key_points&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;collected_urls&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">collected_urls&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;format_instructions&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">parser&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">get_format_instructions&lt;/span>&lt;span class="p">(),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">})&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>解析成功后还有一层本地兜底——能本地算的字段不依赖模型：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="n">response&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">parser&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">parse&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">extract_json&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">raw_content&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">if&lt;/span> &lt;span class="ow">not&lt;/span> &lt;span class="n">response&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">generated_at&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">response&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">generated_at&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">datetime&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">now&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">isoformat&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="c1"># 时间戳本地填，不信模型&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">if&lt;/span> &lt;span class="n">response&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">word_count&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="mi">0&lt;/span> &lt;span class="ow">and&lt;/span> &lt;span class="n">response&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">content&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">response&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">word_count&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nb">len&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;&amp;#34;&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">join&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">response&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">content&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">replace&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34; &amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">replace&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;,&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">replace&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;.&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;&amp;#34;&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">if&lt;/span> &lt;span class="nb">len&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">response&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">url_list&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">&amp;lt;&lt;/span> &lt;span class="nb">len&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">response&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">key_points&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="n">_&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="nb">range&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nb">len&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">response&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">key_points&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&lt;/span> &lt;span class="nb">len&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">response&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">url_list&lt;/span>&lt;span class="p">)):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">response&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">url_list&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">append&lt;/span>&lt;span class="p">([])&lt;/span> &lt;span class="c1"># 补齐一一对应&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">response&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">raw_response&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">raw_content&lt;/span> &lt;span class="c1"># 永远存原文，排查不用重跑&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="容错与重试">容错与重试
&lt;/h3>&lt;p>模型偶尔会在 JSON 前后裹话（&amp;ldquo;以下是总结：…&amp;ldquo;之类），裸 &lt;code>json.loads&lt;/code> 直接炸。&lt;code>extract_json&lt;/code> 用一个贪婪的 &lt;code>\{.*\}&lt;/code>（DOTALL）把最外层花括号之间的内容抠出来再交给解析器：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">extract_json&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">raw_content&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">json_match&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">re&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">search&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">r&lt;/span>&lt;span class="s1">&amp;#39;\{.*\}&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">raw_content&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">re&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">DOTALL&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">json_match&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">group&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="k">if&lt;/span> &lt;span class="n">json_match&lt;/span> &lt;span class="k">else&lt;/span> &lt;span class="n">raw_content&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>重试是外层 while 循环，直接路径和最终汇总两条路径各挂一个，间隔 &lt;code>retry_delay&lt;/code> 秒。所有重试耗尽也不抛异常——返回一个 &lt;code>status=&amp;quot;error&amp;quot;&lt;/code> 的 &lt;code>SummaryResponse&lt;/code>，下游拿到的永远是合法结构，错误本身也是数据：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="n">retries&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">0&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">while&lt;/span> &lt;span class="n">retries&lt;/span> &lt;span class="o">&amp;lt;&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">max_retries&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">try&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">raw_response&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">chain&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">invoke&lt;/span>&lt;span class="p">({&lt;/span>&lt;span class="s2">&amp;#34;contentdiff&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">contentdiff&lt;/span>&lt;span class="p">})&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">response&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">parser&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">parse&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">extract_json&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">raw_response&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">content&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">...&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">response&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">except&lt;/span> &lt;span class="ne">Exception&lt;/span> &lt;span class="k">as&lt;/span> &lt;span class="n">e&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">retries&lt;/span> &lt;span class="o">+=&lt;/span> &lt;span class="mi">1&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">retries&lt;/span> &lt;span class="o">&amp;lt;&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">max_retries&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">time&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">sleep&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">retry_delay&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 重试耗尽：返回带错误信息的合法结构&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">return&lt;/span> &lt;span class="n">SummaryResponse&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">status&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;error&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">error_message&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;Failed to parse SummaryResponse: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">e&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="o">...&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>演示的时候把 &lt;code>max_token_limit&lt;/code> 调到 300 就能强制走分块路径，不用真造一份 3 万 token 的输入。&lt;/p>
&lt;h3 id="如实交代memory-并没有真正生效">如实交代：memory 并没有真正生效
&lt;/h3>&lt;p>原文的标题把这部分叫&amp;quot;记忆机制处理超长文本&amp;rdquo;，但合并这篇的时候要如实交代两件事：&lt;/p>
&lt;ol>
&lt;li>&lt;code>__init__&lt;/code> 里初始化的 &lt;code>self.memory&lt;/code>，在当前实现里从未被 &lt;code>generate_summary&lt;/code> 真正使用——真正干活的是 &lt;code>generate_summary_with_memory&lt;/code> 里局部新建的另一个实例。&lt;/li>
&lt;li>局部 memory 也只做了 &lt;code>save_context&lt;/code>——把每块输入/输出的前 200 个字符存进去，理论上为后续块提供上下文。但实际每个块的处理是相对独立的：把结果串起来的是代码里的三个 collected 列表，最终汇总读的也是它们，不是 memory。&lt;/li>
&lt;/ol>
&lt;p>所以这套流程的本质就是 map-reduce：map 是逐块抽取，reduce 是最终汇总。要复现的话，把 memory 相关代码整个删掉，主流程不受影响。&lt;/p>
&lt;blockquote>
&lt;p>⚠️ 版本注记：&lt;code>ConversationSummaryBufferMemory&lt;/code> 来自 &lt;code>langchain.memory&lt;/code>，属于 legacy memory 体系，LangChain 1.0 已将其整体移除。现在的等价做法是 LangGraph 的 checkpointer（线程级持久化）或对消息列表做裁剪/摘要。这里保留原样，是为了如实记录当时的选择。&lt;/p>
&lt;/blockquote>
&lt;hr>
&lt;h2 id="四工程细节清单">四、工程细节清单
&lt;/h2>&lt;p>把散在代码里的取舍集中列一遍：&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>细节&lt;/th>
&lt;th>做法&lt;/th>
&lt;th>取舍&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>重试&lt;/td>
&lt;td>直接路径与汇总路径各挂一个外层 while 循环 + &lt;code>time.sleep(retry_delay)&lt;/code>&lt;/td>
&lt;td>直观、间隔和日志可控；LCEL 的 &lt;code>.with_retry(stop_after_attempt=3)&lt;/code> 一行可替换，脚本里实际落地的是循环写法&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>JSON 容错&lt;/td>
&lt;td>&lt;code>extract_json&lt;/code> 正则 &lt;code>\{.*\}&lt;/code>（DOTALL）先抠 → &lt;code>PydanticOutputParser.parse&lt;/code> 再校验&lt;/td>
&lt;td>模型在 JSON 前后裹话时不至于炸；&lt;code>raw_response&lt;/code> 永远存原文，排查不用重跑&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>token 估算&lt;/td>
&lt;td>&lt;code>len(contentdiff) * 0.5&lt;/code>&lt;/td>
&lt;td>不引 tokenizer、零依赖；只服务&amp;quot;分不分块&amp;quot;的二元决策，把 &lt;code>max_token_limit&lt;/code> 设保守点吸收误差&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>失败语义&lt;/td>
&lt;td>重试耗尽不抛异常，返回 &lt;code>status=&amp;quot;error&amp;quot;&lt;/code> 的 &lt;code>SummaryResponse&lt;/code>&lt;/td>
&lt;td>下游拿到的永远是合法结构，错误也是数据&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>几个不起眼的小处理，一并记在这里：&lt;/p>
&lt;ul>
&lt;li>&lt;code>generated_at&lt;/code> 不信模型，本地 &lt;code>datetime.now().isoformat()&lt;/code> 填；&lt;/li>
&lt;li>&lt;code>word_count&lt;/code> 为 0 且有 content 时本地重算（直接路径：拼接后去空格去逗号句号数长度）；&lt;/li>
&lt;li>&lt;code>url_list&lt;/code> 比 &lt;code>key_points&lt;/code> 短就补空列表，保住一一对应；&lt;/li>
&lt;li>每块抽取失败 &lt;code>continue&lt;/code> 跳过，一块的失败不拖垮整份摘要。&lt;/li>
&lt;/ul>
&lt;p>还有一处坑，合并本文时验证过：脚本里 memory 路径的字数兜底写的是 &lt;code>re.sub(r'[\s\p{P}]', '', content_text, flags=re.UNICODE)&lt;/code>——&lt;code>\p{P}&lt;/code> 是 PCRE / &lt;code>regex&lt;/code> 第三方库的语法，标准库 &lt;code>re&lt;/code> 不认，这行会直接抛 &lt;code>re.error: bad escape \p&lt;/code>（外层 try 会接住，触发无意义的重试）。要按原意去标点，得换 &lt;code>regex&lt;/code> 库或手写标点集。直接路径的同名逻辑用的是 &lt;code>replace(&amp;quot; &amp;quot;, &amp;quot;&amp;quot;).replace(&amp;quot;,&amp;quot;, &amp;quot;&amp;quot;).replace(&amp;quot;.&amp;quot;, &amp;quot;&amp;quot;)&lt;/code>，土，但不会炸。&lt;/p>
&lt;hr>
&lt;h2 id="总结">总结
&lt;/h2>&lt;p>这个项目从第一版管线到能吃下超长 diff，前后一个月，值得留下的结论就这几条：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>提示词是 agent 的行为规格，不是写给人的备忘。&lt;/strong> 对人类差不多的提示词，对 agent 是天壤之别——&amp;ldquo;时间或者数据的变化&amp;quot;几个字的改动就是质变。改完之后，最小样例和真实数据各验证一轮，缺一不可。&lt;/li>
&lt;li>&lt;strong>长文本的正解是 map-reduce 分块。&lt;/strong> 按语义边界（变更单元）切块优先、字符数兜底，逐块抽取再汇总，朴素但有效；引入的&amp;quot;记忆机制&amp;quot;实际没起作用，如实记录比包装成&amp;quot;记忆驱动&amp;quot;更有参考价值。&lt;/li>
&lt;li>&lt;strong>结构化输出的可靠性靠工程兜底，不靠模型自觉。&lt;/strong> 容错解析、重试、永不抛异常的失败语义、raw_response 存档——这一层做好了，模型偶尔不听话只是重试一次的事。&lt;/li>
&lt;li>&lt;strong>模型选型：这个任务上 qwq-32b 比 grok3 表现更好。&lt;/strong> 样本很小，是个人判断，但它是双模型、双轮验证之后得出的。&lt;/li>
&lt;/ul>
&lt;hr>
&lt;h2 id="溯源">溯源
&lt;/h2>&lt;p>本文整合自同一个项目的三篇旧文，按时间顺序：&lt;/p>
&lt;ol>
&lt;li>《使用 LangChain 构建订阅内容更新总结智能代理》（2025-03-13）——并入本文第一节：第一版管线、SubscriptionAgent 逐段讲解、LLM 输出不一致的对策。&lt;/li>
&lt;li>《提示词使用教程|工程应用实践》（2025-03-14）——并入本文第二节：误判问题、提示词改动前后对照、双模型验证截图与个人结论。&lt;/li>
&lt;li>《08-内容分块 (chunking) 和记忆机制 (memory) 处理超出 LLM Token 限制的长文本》（2025-04-12）——并入本文第三节：分块策略、容错重试、可执行脚本&lt;a class="link" href="./%e8%a7%a3%e5%86%b3%e4%b8%8a%e4%b8%8b%e6%96%87%e8%bf%87%e9%95%bf.py" >解决上下文过长.py&lt;/a>。&lt;/li>
&lt;/ol>
&lt;p>合并时做的处理，都列在这里：&lt;/p>
&lt;ul>
&lt;li>原文一的 &lt;code>from langchain_core.retry import retry&lt;/code> 是不存在的 API（当时的记录里混进了幻觉写法），合并版替换为 &lt;code>.with_retry()&lt;/code> 与外层重试循环两种真实写法；&lt;code>get_ali_llm&lt;/code> 是项目内模块，补了 ChatOpenAI + base_url 的等价写法。&lt;/li>
&lt;li>原文三标题里的&amp;quot;记忆机制&amp;quot;如第三节坦白并未真正生效，合并版按实际代码路径写成 map-reduce 分块，并补了 legacy memory 的版本注记。&lt;/li>
&lt;li>原文里被 Markdown 链接污染的 URL（&lt;code>base_url=&amp;quot;[https://...](...)&amp;quot;&lt;/code> 形式）已还原为纯 URL。&lt;/li>
&lt;li>原文二的通用技巧小节（少样本提示等）与烂尾的&amp;quot;PUA大模型&amp;quot;小节没有并入正文；后者保存的 Windsurf（Codeium）系统提示词泄漏截图仍保留在 &lt;a class="link" href="images/index/windsurf-system-prompt.png" >images/index/windsurf-system-prompt.png&lt;/a>。&lt;/li>
&lt;/ul></description></item><item><title>langchain_core 组件详解：Prompt 模板与 Output Parsers</title><link>https://www.zata.cc/p/langchain_core-%E7%BB%84%E4%BB%B6%E8%AF%A6%E8%A7%A3prompt-%E6%A8%A1%E6%9D%BF%E4%B8%8E-output-parsers/</link><pubDate>Tue, 08 Sep 2026 11:30:00 +0800</pubDate><guid>https://www.zata.cc/p/langchain_core-%E7%BB%84%E4%BB%B6%E8%AF%A6%E8%A7%A3prompt-%E6%A8%A1%E6%9D%BF%E4%B8%8E-output-parsers/</guid><description>&lt;img src="https://www.zata.cc/p/langchain_core-%E7%BB%84%E4%BB%B6%E8%AF%A6%E8%A7%A3prompt-%E6%A8%A1%E6%9D%BF%E4%B8%8E-output-parsers/images/index/index.png" alt="Featured image of post langchain_core 组件详解：Prompt 模板与 Output Parsers" />&lt;h1 id="langchain_core-组件详解prompt-模板与-output-parsers">langchain_core 组件详解：Prompt 模板与 Output Parsers
&lt;/h1>&lt;p>把一个 LangChain 应用拆到最小，真正绕不开的是一条链：&lt;code>prompt | llm | parser&lt;/code>。prompt 决定喂给模型什么，llm 吐出一段自由文本，parser 再把文本变成程序能用的数据。langchain_core 把这条链的两头做成了两个模块——&lt;code>prompts&lt;/code> 管输入侧，&lt;code>output_parsers&lt;/code> 管输出侧。&lt;/p>
&lt;p>这篇是这两个模块笔记的合并整理：输入侧讲 Prompt 模板家族怎么选、FewShot 怎么从固定示例进化到动态选例；输出侧讲五种常用解析器和解析失败后的 async 重试实战；解析报错的排查方法放在末尾。带运行输出和截图的部分来自我的实践记录，代码跑在 LangChain 0.3 时期的 langchain_core 上，个别老 API 的位置会标 ⚠️ 说明。&lt;/p>
&lt;p>两个核心判断先放在这里：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>输入侧默认 ChatPromptTemplate。&lt;/strong> 它吃一组消息、吐一组消息，和现代聊天模型的接口天然对齐；字符串版 &lt;code>PromptTemplate&lt;/code> 退居二线，主要做内部格式化片段。&lt;/li>
&lt;li>&lt;strong>要结构化输出，优先 &lt;code>with_structured_output&lt;/code>。&lt;/strong> LangChain 1.x 时代的主流做法是把 schema 绑在模型上，走 tool calling 或 JSON mode 通道，结构由模型原生保证。&lt;code>PydanticOutputParser&lt;/code> 这一族文本解析器是备选：模型不支持原生结构化输出，或需要把格式说明精确嵌进提示词时才用。但 format_instructions、解析、重试这套思路依然值得吃透——排查结构化输出问题时，最终都要回到这条最原始的链上。&lt;/li>
&lt;/ul>
&lt;hr>
&lt;h2 id="一prompt-模板输入侧">一、Prompt 模板（输入侧）
&lt;/h2>&lt;p>&lt;code>langchain_core.prompts&lt;/code> 解决的问题只有一个：把动态变量填进固定结构，生成模型输入。模板类有一族，日常主力只有一个。&lt;/p>
&lt;h3 id="chatprompttemplate默认选择">ChatPromptTemplate：默认选择
&lt;/h3>&lt;p>&lt;code>ChatPromptTemplate&lt;/code> 生成的不是一段字符串，而是一组结构化消息（&lt;code>List[BaseMessage]&lt;/code>），正好对齐聊天模型的输入格式：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">langchain_core.prompts&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">ChatPromptTemplate&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">template&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;system&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;You are a helpful AI assistant named &lt;/span>&lt;span class="si">{name}&lt;/span>&lt;span class="s2">.&amp;#34;&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;human&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;Tell me a joke about &lt;/span>&lt;span class="si">{topic}&lt;/span>&lt;span class="s2">.&amp;#34;&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">prompt_template&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">ChatPromptTemplate&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">from_messages&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">template&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">prompt&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">prompt_template&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">invoke&lt;/span>&lt;span class="p">({&lt;/span>&lt;span class="s2">&amp;#34;name&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;Grok&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;topic&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;dogs&amp;#34;&lt;/span>&lt;span class="p">})&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">prompt&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">messages&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># [SystemMessage(content=&amp;#39;You are a helpful AI assistant named Grok.&amp;#39;),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># HumanMessage(content=&amp;#39;Tell me a joke about dogs.&amp;#39;)]&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;code>from_messages&lt;/code> 接受 &lt;code>(&amp;quot;角色&amp;quot;, &amp;quot;模板&amp;quot;)&lt;/code> 元组，也接受消息模板对象和占位符，可以任意混搭。日常写角色字符串就够了；&lt;code>SystemMessagePromptTemplate&lt;/code>、&lt;code>HumanMessagePromptTemplate&lt;/code>、&lt;code>AIMessagePromptTemplate&lt;/code> 这些细粒度的类，只在需要程序化拼装单条消息时才用得上。&lt;/p>
&lt;p>真正解决独立问题的类是 &lt;code>MessagesPlaceholder&lt;/code>——往模板中间插一段动态消息列表，最典型的用途是对话历史：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">langchain_core.prompts&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">ChatPromptTemplate&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">MessagesPlaceholder&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">langchain_core.messages&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">HumanMessage&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">AIMessage&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">chat_prompt&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">ChatPromptTemplate&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">from_messages&lt;/span>&lt;span class="p">([&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;system&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;You are a helpful assistant.&amp;#34;&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">MessagesPlaceholder&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">variable_name&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;history&amp;#34;&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;human&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="si">{input}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">])&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">history&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">HumanMessage&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">content&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;What&amp;#39;s the weather like?&amp;#34;&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">AIMessage&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">content&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;It&amp;#39;s sunny!&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">prompt&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">chat_prompt&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">invoke&lt;/span>&lt;span class="p">({&lt;/span>&lt;span class="s2">&amp;#34;history&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">history&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;input&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;What&amp;#39;s the forecast tomorrow?&amp;#34;&lt;/span>&lt;span class="p">})&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">prompt&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">messages&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># [SystemMessage(...), HumanMessage(content=&amp;#34;What&amp;#39;s the weather like?&amp;#34;),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># AIMessage(...), HumanMessage(...)]&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>注意 &lt;code>variable_name&lt;/code> 必须和 invoke 时传入的键一致，写模板时就对清楚。&lt;/p>
&lt;h3 id="prompt-模板家族速查表">Prompt 模板家族速查表
&lt;/h3>&lt;p>完整班底如下，大部分场景只用到前几行：&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>类&lt;/th>
&lt;th>功能&lt;/th>
&lt;th>适用场景&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;code>ChatPromptTemplate&lt;/code>&lt;/td>
&lt;td>构建对话消息列表，支持多角色&lt;/td>
&lt;td>聊天机器人、多轮对话（日常默认）&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>MessagesPlaceholder&lt;/code>&lt;/td>
&lt;td>插入动态消息列表&lt;/td>
&lt;td>对话历史、动态上下文&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>PromptTemplate&lt;/code>&lt;/td>
&lt;td>格式化字符串提示，支持动态变量&lt;/td>
&lt;td>字符串模板、内部格式化片段&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>FewShotChatMessagePromptTemplate&lt;/code>&lt;/td>
&lt;td>把少样本示例格式化成对话消息&lt;/td>
&lt;td>对话任务需要示例引导&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>SystemMessagePromptTemplate&lt;/code>&lt;/td>
&lt;td>系统消息模板&lt;/td>
&lt;td>程序化拼装 system 消息&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>HumanMessagePromptTemplate&lt;/code>&lt;/td>
&lt;td>用户消息模板&lt;/td>
&lt;td>程序化拼装 human 消息&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>AIMessagePromptTemplate&lt;/code>&lt;/td>
&lt;td>AI 回复模板&lt;/td>
&lt;td>少样本对话、预置示例回答&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>ChatMessagePromptTemplate&lt;/code>&lt;/td>
&lt;td>自定义角色消息模板（如 &lt;code>&amp;quot;Jedi&amp;quot;&lt;/code>）&lt;/td>
&lt;td>非标准角色的创意对话&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>FewShotPromptTemplate&lt;/code>&lt;/td>
&lt;td>少样本示例，生成字符串&lt;/td>
&lt;td>⚠️ 官方已标记 deprecated&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>PipelinePromptTemplate&lt;/code>&lt;/td>
&lt;td>组合多个子模板&lt;/td>
&lt;td>⚠️ 已废弃&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>两个 deprecated 的类多说两句。&lt;code>FewShotPromptTemplate&lt;/code>（字符串版少样本）官方已标记 deprecated，现代做法是 ChatPromptTemplate + FewShotChatMessagePromptTemplate 组合，再配 ExampleSelector 做动态选例（见下节）；&lt;code>PipelinePromptTemplate&lt;/code> 原本用来嵌套组合多段子模板，同样废弃了，多段模板的组合用普通变量嵌套或 LCEL 就能解决，不再需要专门的类。&lt;/p>
&lt;p>安全上有一条红线：字符串模板默认用 f-string 语法，&lt;code>PromptTemplate&lt;/code> 也支持 jinja2，但&lt;strong>不要用 jinja2 加载来源不可信的模板&lt;/strong>——jinja2 模板可以执行代码，是一个真实的注入面。没有特殊语法需求，留在默认的 f-string。&lt;/p>
&lt;h3 id="fewshot从固定示例到动态选例">FewShot：从固定示例到动态选例
&lt;/h3>&lt;p>少样本提示的思路是用示例教模型任务模式。对话版写法是把示例格式化成 human/ai 消息对，作为一个整体嵌进主模板：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">langchain_core.prompts&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">FewShotChatMessagePromptTemplate&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">ChatPromptTemplate&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">examples&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>&lt;span class="s2">&amp;#34;question&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;What is 2+2?&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;answer&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;4&amp;#34;&lt;/span>&lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>&lt;span class="s2">&amp;#34;question&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;What is 3+3?&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;answer&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;6&amp;#34;&lt;/span>&lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">example_prompt&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">ChatPromptTemplate&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">from_messages&lt;/span>&lt;span class="p">([&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;human&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="si">{question}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;ai&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="si">{answer}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">])&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">few_shot_prompt&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">FewShotChatMessagePromptTemplate&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">examples&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">examples&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">example_prompt&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">example_prompt&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">final_prompt&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">ChatPromptTemplate&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">from_messages&lt;/span>&lt;span class="p">([&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;system&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;You are a math expert.&amp;#34;&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">few_shot_prompt&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;human&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="si">{input}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">])&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">prompt&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">final_prompt&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">invoke&lt;/span>&lt;span class="p">({&lt;/span>&lt;span class="s2">&amp;#34;input&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;What is 4+4?&amp;#34;&lt;/span>&lt;span class="p">})&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>固定示例有两个天然限制：示例一多就挤占上下文窗口，示例和当前输入不相关时还会稀释注意力。进阶做法是接一个 &lt;code>ExampleSelector&lt;/code>，按语义相似度动态挑出与当前输入最相近的几条示例再拼进提示——等于把 RAG 的检索思路用在了示例库上，官方实现里 &lt;code>SemanticSimilarityExampleSelector&lt;/code> 是最常用的一个。&lt;/p>
&lt;p>我试过的玩法是把相近对话入库，请求时按相似度取回再传入 FewShot 模板，跑通后的效果：&lt;/p>
&lt;p>&lt;img src="https://www.zata.cc/p/langchain_core-%E7%BB%84%E4%BB%B6%E8%AF%A6%E8%A7%A3prompt-%E6%A8%A1%E6%9D%BF%E4%B8%8E-output-parsers/images/index/image.png"
width="1456"
height="772"
srcset="https://www.zata.cc/p/langchain_core-%E7%BB%84%E4%BB%B6%E8%AF%A6%E8%A7%A3prompt-%E6%A8%A1%E6%9D%BF%E4%B8%8E-output-parsers/images/index/image_hu2773629058688531427.png 480w, https://www.zata.cc/p/langchain_core-%E7%BB%84%E4%BB%B6%E8%AF%A6%E8%A7%A3prompt-%E6%A8%A1%E6%9D%BF%E4%B8%8E-output-parsers/images/index/image_hu7467198816374065572.png 1024w"
loading="lazy"
alt="将相近对话使用RAG获取并传入"
class="gallery-image"
data-flex-grow="188"
data-flex-basis="452px"
>&lt;/p>
&lt;p>具体代码：&lt;/p>
&lt;p>&lt;img src="https://www.zata.cc/p/langchain_core-%E7%BB%84%E4%BB%B6%E8%AF%A6%E8%A7%A3prompt-%E6%A8%A1%E6%9D%BF%E4%B8%8E-output-parsers/images/index/image-1.png"
width="1064"
height="708"
srcset="https://www.zata.cc/p/langchain_core-%E7%BB%84%E4%BB%B6%E8%AF%A6%E8%A7%A3prompt-%E6%A8%A1%E6%9D%BF%E4%B8%8E-output-parsers/images/index/image-1_hu16535364611493225799.png 480w, https://www.zata.cc/p/langchain_core-%E7%BB%84%E4%BB%B6%E8%AF%A6%E8%A7%A3prompt-%E6%A8%A1%E6%9D%BF%E4%B8%8E-output-parsers/images/index/image-1_hu14518289420376064366.png 1024w"
loading="lazy"
alt="具体代码"
class="gallery-image"
data-flex-grow="150"
data-flex-basis="360px"
>&lt;/p>
&lt;p>这套做法的关键在示例库质量和 embedding 的匹配度，示例数量适中即可。再想提升效果，该调的是检索策略，不是往提示里硬塞更多示例。&lt;/p>
&lt;h2 id="二output-parsers输出侧">二、Output Parsers（输出侧）
&lt;/h2>&lt;p>&lt;img src="https://www.zata.cc/p/langchain_core-%E7%BB%84%E4%BB%B6%E8%AF%A6%E8%A7%A3prompt-%E6%A8%A1%E6%9D%BF%E4%B8%8E-output-parsers/images/index/index.png"
width="449"
height="464"
srcset="https://www.zata.cc/p/langchain_core-%E7%BB%84%E4%BB%B6%E8%AF%A6%E8%A7%A3prompt-%E6%A8%A1%E6%9D%BF%E4%B8%8E-output-parsers/images/index/index_hu2968718835055450482.png 480w, https://www.zata.cc/p/langchain_core-%E7%BB%84%E4%BB%B6%E8%AF%A6%E8%A7%A3prompt-%E6%A8%A1%E6%9D%BF%E4%B8%8E-output-parsers/images/index/index_hu7684224093898913705.png 1024w"
loading="lazy"
alt="output_parsers 包中的类"
class="gallery-image"
data-flex-grow="96"
data-flex-basis="232px"
>&lt;/p>
&lt;p>输出解析器负责把 LLM 的自由文本转换成结构化数据：从自然语言句子、JSON 字符串或列表里提取关键信息，转成 Python 对象、字典或列表这类程序友好的格式。没有它，每个应用都得自己写一遍&amp;quot;从文本里抠 JSON&amp;quot;的脏活，格式约定还会散落在提示词和解析代码两处，很难保持一致。&lt;/p>
&lt;p>在链上的位置就是开头那条：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">PromptTemplate → LLM → OutputParser
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>提示模板定义输入和输出格式要求，LLM 生成原始文本，解析器完成最后一步转换。输入侧要求什么格式，输出侧就得配对应的 parser，两头的约定靠 &lt;code>get_format_instructions()&lt;/code> 生成的格式说明对齐。&lt;/p>
&lt;h3 id="五种常用解析器">五种常用解析器
&lt;/h3>&lt;table>
&lt;thead>
&lt;tr>
&lt;th>解析器&lt;/th>
&lt;th>干什么&lt;/th>
&lt;th>典型场景&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;code>StrOutputParser&lt;/code>&lt;/td>
&lt;td>原样返回字符串&lt;/td>
&lt;td>简单问答、文本生成&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>JsonOutputParser&lt;/code>&lt;/td>
&lt;td>解析为 JSON 字典&lt;/td>
&lt;td>API 返回数据、键值对提取&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>PydanticOutputParser&lt;/code>&lt;/td>
&lt;td>解析为 Pydantic 模型&lt;/td>
&lt;td>数据验证、复杂对象处理&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>CommaSeparatedListOutputParser&lt;/code>&lt;/td>
&lt;td>逗号分隔文本 → 列表&lt;/td>
&lt;td>列表提取（选项、标签）&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>自定义 &lt;code>BaseOutputParser&lt;/code>&lt;/td>
&lt;td>自己实现 &lt;code>parse&lt;/code>&lt;/td>
&lt;td>特殊格式（分号分隔、表格解析）&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>下面五个示例共用同一个 llm 初始化（OpenAI 兼容接口，配置从 &lt;code>.env&lt;/code> 读取），后文不再重复：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">os&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">dotenv&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">load_dotenv&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">langchain_openai&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">ChatOpenAI&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">load_dotenv&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">llm&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">ChatOpenAI&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">model&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">os&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">getenv&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;model&amp;#34;&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">api_key&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">os&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">getenv&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;api_key&amp;#34;&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">base_url&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">os&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">getenv&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;base_url&amp;#34;&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">streaming&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="kc">True&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h4 id="stroutputparser什么都不做">StrOutputParser：什么都不做
&lt;/h4>&lt;p>最简单的解析器，直接返回字符串，不需要任何格式化指令：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">langchain_core.prompts&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">PromptTemplate&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">langchain_core.output_parsers&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">StrOutputParser&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">prompt&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">PromptTemplate&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">template&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;今天是星期几？（假设今天是 2025 年 3 月 16 日）&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">parser&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">StrOutputParser&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">chain&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">prompt&lt;/span> &lt;span class="o">|&lt;/span> &lt;span class="n">llm&lt;/span> &lt;span class="o">|&lt;/span> &lt;span class="n">parser&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">response&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">chain&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">invoke&lt;/span>&lt;span class="p">({})&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">response&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>输出&lt;/strong>：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">今天是星期日。
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h4 id="jsonoutputparser拿到字典">JsonOutputParser：拿到字典
&lt;/h4>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">langchain_core.prompts&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">PromptTemplate&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">langchain_core.output_parsers&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">JsonOutputParser&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">parser&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">JsonOutputParser&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">prompt&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">PromptTemplate&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">template&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;以 JSON 格式返回两种水果及其颜色。&lt;/span>&lt;span class="se">\n&lt;/span>&lt;span class="si">{format_instructions}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">partial_variables&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="s2">&amp;#34;format_instructions&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">parser&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">get_format_instructions&lt;/span>&lt;span class="p">()}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">chain&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">prompt&lt;/span> &lt;span class="o">|&lt;/span> &lt;span class="n">llm&lt;/span> &lt;span class="o">|&lt;/span> &lt;span class="n">parser&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">response&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">chain&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">invoke&lt;/span>&lt;span class="p">({})&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">response&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>输出&lt;/strong>：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">{&amp;#39;apple&amp;#39;: &amp;#39;red&amp;#39;, &amp;#39;banana&amp;#39;: &amp;#39;yellow&amp;#39;}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;code>get_format_instructions()&lt;/code> 自动生成&amp;quot;请按 JSON 格式返回&amp;quot;的说明嵌进模板，提示和解析器的约定永远对得上。但硬约束不存在：LLM 输出的不是合法 JSON 时直接抛异常。&lt;/p>
&lt;h4 id="pydanticoutputparser强类型--字段校验">PydanticOutputParser：强类型 + 字段校验
&lt;/h4>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">langchain_core.prompts&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">PromptTemplate&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">langchain_core.output_parsers&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">PydanticOutputParser&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">pydantic&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">BaseModel&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">Field&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">Book&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">BaseModel&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">title&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">Field&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">description&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;书名&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">author&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">Field&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">description&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;作者&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">year&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">int&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">Field&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">description&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;出版年份&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">parser&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">PydanticOutputParser&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">pydantic_object&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">Book&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">prompt&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">PromptTemplate&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">template&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;推荐一本书，并以指定格式返回。&lt;/span>&lt;span class="se">\n&lt;/span>&lt;span class="si">{format_instructions}&lt;/span>&lt;span class="se">\n&lt;/span>&lt;span class="s2">推荐什么书？&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">input_variables&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;question&amp;#34;&lt;/span>&lt;span class="p">],&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">partial_variables&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="s2">&amp;#34;format_instructions&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">parser&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">get_format_instructions&lt;/span>&lt;span class="p">()}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">chain&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">prompt&lt;/span> &lt;span class="o">|&lt;/span> &lt;span class="n">llm&lt;/span> &lt;span class="o">|&lt;/span> &lt;span class="n">parser&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">response&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">chain&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">invoke&lt;/span>&lt;span class="p">({&lt;/span>&lt;span class="s2">&amp;#34;question&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;推荐什么书？&amp;#34;&lt;/span>&lt;span class="p">})&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">response&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>输出&lt;/strong>：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">Book(title=&amp;#39;《活着》&amp;#39;, author=&amp;#39;余华&amp;#39;, year=1993)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Pydantic 保证字段类型（&lt;code>year&lt;/code> 必须是整数），格式化指令里还会带上每个字段的 description。&lt;code>get_format_instructions()&lt;/code> 生成的说明长这样：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">Please provide your response in the following JSON format:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">{
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &amp;#34;title&amp;#34;: &amp;#34;string&amp;#34;,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &amp;#34;author&amp;#34;: &amp;#34;string&amp;#34;,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &amp;#34;year&amp;#34;: &amp;#34;integer&amp;#34;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>实例运行截图：&lt;/p>
&lt;p>&lt;img src="https://www.zata.cc/p/langchain_core-%E7%BB%84%E4%BB%B6%E8%AF%A6%E8%A7%A3prompt-%E6%A8%A1%E6%9D%BF%E4%B8%8E-output-parsers/images/index/index-1.png"
width="430"
height="206"
srcset="https://www.zata.cc/p/langchain_core-%E7%BB%84%E4%BB%B6%E8%AF%A6%E8%A7%A3prompt-%E6%A8%A1%E6%9D%BF%E4%B8%8E-output-parsers/images/index/index-1_hu11117520121720596488.png 480w, https://www.zata.cc/p/langchain_core-%E7%BB%84%E4%BB%B6%E8%AF%A6%E8%A7%A3prompt-%E6%A8%A1%E6%9D%BF%E4%B8%8E-output-parsers/images/index/index-1_hu11890333685817076953.png 1024w"
loading="lazy"
alt="PydanticOutputParser实例输出"
class="gallery-image"
data-flex-grow="208"
data-flex-basis="500px"
>&lt;/p>
&lt;h4 id="commaseparatedlistoutputparser拿到列表">CommaSeparatedListOutputParser：拿到列表
&lt;/h4>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">langchain_core.prompts&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">PromptTemplate&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">langchain_core.output_parsers&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">CommaSeparatedListOutputParser&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">parser&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">CommaSeparatedListOutputParser&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">prompt&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">PromptTemplate&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">template&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;列出三种编程语言，用逗号分隔。&lt;/span>&lt;span class="se">\n&lt;/span>&lt;span class="si">{format_instructions}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">partial_variables&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="s2">&amp;#34;format_instructions&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">parser&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">get_format_instructions&lt;/span>&lt;span class="p">()}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">chain&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">prompt&lt;/span> &lt;span class="o">|&lt;/span> &lt;span class="n">llm&lt;/span> &lt;span class="o">|&lt;/span> &lt;span class="n">parser&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">response&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">chain&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">invoke&lt;/span>&lt;span class="p">({})&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">response&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="c1"># type: list&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>输出&lt;/strong>：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">[&amp;#39;Python&amp;#39;, &amp;#39;Java&amp;#39;, &amp;#39;C++&amp;#39;]
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>实例：&lt;/p>
&lt;p>&lt;img src="https://www.zata.cc/p/langchain_core-%E7%BB%84%E4%BB%B6%E8%AF%A6%E8%A7%A3prompt-%E6%A8%A1%E6%9D%BF%E4%B8%8E-output-parsers/images/index/index-2.png"
width="480"
height="177"
srcset="https://www.zata.cc/p/langchain_core-%E7%BB%84%E4%BB%B6%E8%AF%A6%E8%A7%A3prompt-%E6%A8%A1%E6%9D%BF%E4%B8%8E-output-parsers/images/index/index-2_hu10523795283954327661.png 480w, https://www.zata.cc/p/langchain_core-%E7%BB%84%E4%BB%B6%E8%AF%A6%E8%A7%A3prompt-%E6%A8%A1%E6%9D%BF%E4%B8%8E-output-parsers/images/index/index-2_hu10667292785974072936.png 1024w"
loading="lazy"
alt="CommaSeparatedListOutputParser"
class="gallery-image"
data-flex-grow="271"
data-flex-basis="650px"
>&lt;/p>
&lt;h4 id="自定义解析器继承-baseoutputparser">自定义解析器：继承 BaseOutputParser
&lt;/h4>&lt;p>内置解析器覆盖不到的格式，继承 &lt;code>BaseOutputParser&lt;/code> 实现 &lt;code>parse&lt;/code> 方法就行，比如解析分号分隔的列表：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">langchain_core.prompts&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">PromptTemplate&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">langchain_core.output_parsers&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">BaseOutputParser&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">SemicolonListParser&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">BaseOutputParser&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">parse&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">text&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="nb">list&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="n">item&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">strip&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="k">for&lt;/span> &lt;span class="n">item&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">text&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">split&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;;&amp;#34;&lt;/span>&lt;span class="p">)]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">parser&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">SemicolonListParser&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">prompt&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">PromptTemplate&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">template&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;列出三种城市，用分号分隔，例如：北京;上海;广州&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">chain&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">prompt&lt;/span> &lt;span class="o">|&lt;/span> &lt;span class="n">llm&lt;/span> &lt;span class="o">|&lt;/span> &lt;span class="n">parser&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">response&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">chain&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">invoke&lt;/span>&lt;span class="p">({})&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">response&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>输出&lt;/strong>：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">[&amp;#39;北京&amp;#39;, &amp;#39;上海&amp;#39;, &amp;#39;广州&amp;#39;]
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="解析失败重试async-实战">解析失败重试：async 实战
&lt;/h3>&lt;p>文本解析器没有硬约束，LLM 输出不稳定或格式要求严格时，解析失败是常态而不是异常，重试应该直接设计进链路。下面是完整跑通的 async 版本：&lt;code>PydanticOutputParser&lt;/code> 解析动词提取结果，外层 for 循环控制最多三次尝试，全部失败后再兜底一次。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">langchain_openai&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">ChatOpenAI&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">langchain_core.prompts&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">PromptTemplate&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">langchain_core.output_parsers&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">PydanticOutputParser&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">langchain_core.runnables&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">RunnableSequence&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">pydantic&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">BaseModel&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">Field&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">os&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">dotenv&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">load_dotenv&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">asyncio&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 加载环境变量&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">load_dotenv&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">model&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">os&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">getenv&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;model&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">api_key&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">os&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">getenv&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;api_key&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">base_url&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">os&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">getenv&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;base_url&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 初始化 LLM&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">llm&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">ChatOpenAI&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">model&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">model&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">api_key&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">api_key&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">base_url&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">base_url&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">streaming&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="kc">True&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 定义输出结构使用 Pydantic&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">ParseResult&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">BaseModel&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">result&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">Field&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">description&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;解析出的参数结果&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 创建输出解析器&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">output_parser&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">PydanticOutputParser&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">pydantic_object&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">ParseResult&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 创建提示模板&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">prompt_template&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">PromptTemplate&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">from_template&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;请解析以下输入并返回结果：&lt;/span>&lt;span class="si">{input}&lt;/span>&lt;span class="se">\n\n&lt;/span>&lt;span class="s2">返回格式：&lt;/span>&lt;span class="se">\n&lt;/span>&lt;span class="si">{format_instructions}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">partial_variables&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="s2">&amp;#34;format_instructions&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">output_parser&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">get_format_instructions&lt;/span>&lt;span class="p">()}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 创建可运行序列&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># chain = RunnableSequence(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># prompt_template,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># llm,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># output_parser&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># )&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">chain&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">prompt_template&lt;/span> &lt;span class="o">|&lt;/span> &lt;span class="n">llm&lt;/span> &lt;span class="o">|&lt;/span> &lt;span class="n">output_parser&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 输入数据&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">user_input&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;提取这句话中的动词：&amp;#39;我喜欢跑步和游泳&amp;#39;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">async&lt;/span> &lt;span class="k">def&lt;/span> &lt;span class="nf">run_parsing&lt;/span>&lt;span class="p">():&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">max_attempts&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">3&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="n">attempt&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="nb">range&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">max_attempts&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">try&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># 使用 invoke 方法运行链&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">result&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">await&lt;/span> &lt;span class="n">chain&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">ainvoke&lt;/span>&lt;span class="p">({&lt;/span>&lt;span class="s2">&amp;#34;input&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">user_input&lt;/span>&lt;span class="p">})&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;第 &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">attempt&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2"> 次尝试成功&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;解析结果: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">result&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">model_dump&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">result&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">except&lt;/span> &lt;span class="ne">Exception&lt;/span> &lt;span class="k">as&lt;/span> &lt;span class="n">e&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;第 &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">attempt&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2"> 次尝试失败，错误: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">e&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">attempt&lt;/span> &lt;span class="o">&amp;lt;&lt;/span> &lt;span class="n">max_attempts&lt;/span> &lt;span class="o">-&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;正在重试...&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">else&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;已达到最大尝试次数&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">try&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">result&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">await&lt;/span> &lt;span class="n">chain&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">ainvoke&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>&lt;span class="s2">&amp;#34;input&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">user_input&lt;/span>&lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">config&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="s2">&amp;#34;max_retries&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;最终修复结果: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">result&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">model_dump&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="c1"># 使用 model_dump() 替代 dict()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">result&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">except&lt;/span> &lt;span class="ne">Exception&lt;/span> &lt;span class="k">as&lt;/span> &lt;span class="n">final_error&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;最终尝试失败，错误: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">final_error&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="kc">None&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 在已有事件循环中运行&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">async&lt;/span> &lt;span class="k">def&lt;/span> &lt;span class="nf">main&lt;/span>&lt;span class="p">():&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">await&lt;/span> &lt;span class="n">run_parsing&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 如果在已有的事件循环中（比如 Jupyter），直接运行&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">if&lt;/span> &lt;span class="n">asyncio&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">get_event_loop&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">is_running&lt;/span>&lt;span class="p">():&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">await&lt;/span> &lt;span class="n">main&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="c1"># 在已有循环中运行&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">else&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">asyncio&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">run&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">main&lt;/span>&lt;span class="p">())&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>输出示例&lt;/strong>：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">解析结果： {&amp;#39;result&amp;#39;: &amp;#39;喜欢,跑步,游泳&amp;#39;}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>关于这段代码有一点必须澄清（原笔记在这里有误导）：重试全部来自手工调用——外层 for 循环跑满三次，都失败后再兜底调一次。最后那次 &lt;code>ainvoke&lt;/code> 传的 &lt;code>config={&amp;quot;max_retries&amp;quot;: 1}&lt;/code> 并不是解析重试的开关——&lt;code>prompt | llm | parser&lt;/code> 这条链上，解析失败抛出的异常只会向上传给调用方，库不会自动重跑；&lt;code>max_retries&lt;/code> 至多影响底层 API 请求层面的重试（网络错误、限流），跟解析失败是两回事，真正需要 API 层重试应该设在 &lt;code>ChatOpenAI&lt;/code> 的初始化参数里。想让模型重新生成合规输出，只能像上面这样自己捕获异常、重新调用整条链。&lt;/p>
&lt;p>重跑整条链之所以有效，是因为 LLM 输出有随机性：同一个输入再来一次，经常就能给出合规格式。捕获异常后重跑，是成本最低的重试方案。&lt;/p>
&lt;h3 id="和-with_structured_output-怎么选">和 with_structured_output 怎么选
&lt;/h3>&lt;p>回到开头第二个判断。1.x 时代做结构化输出，第一选择是模型侧的 &lt;code>with_structured_output&lt;/code>——还是上面那个 Book schema，但玩法完全变了：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="n">structured_llm&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">llm&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">with_structured_output&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">Book&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">book&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">structured_llm&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">invoke&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;推荐一本书，返回书名、作者和出版年份&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 直接拿到 Book 实例&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;table>
&lt;thead>
&lt;tr>
&lt;th>&lt;/th>
&lt;th>&lt;code>with_structured_output&lt;/code>&lt;/th>
&lt;th>文本解析器一族&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>结构由谁保证&lt;/td>
&lt;td>schema 绑在模型上，走 tool calling / JSON mode&lt;/td>
&lt;td>格式说明嵌进提示词，靠模型遵守约定&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>提示词&lt;/td>
&lt;td>不需要 format_instructions&lt;/td>
&lt;td>&lt;code>get_format_instructions()&lt;/code> 嵌入 partial_variables&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>解析&lt;/td>
&lt;td>库内部完成&lt;/td>
&lt;td>链上显式一环，失败自己处理&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>怎么选：模型支持 tool calling 或 JSON mode（主流模型都支持），直接 &lt;code>with_structured_output&lt;/code>，代码更少、格式更有保障；模型不支持原生结构化输出，或需要把格式说明精确控制进提示词（特定提示策略、兼容老链路），用文本解析器。&lt;code>StrOutputParser&lt;/code> 不受这场选型影响——拿纯文本时它仍是标准配件，也是后面排查解析问题的关键工具。&lt;/p>
&lt;h2 id="三解析报错排查">三、解析报错排查
&lt;/h2>&lt;h3 id="agent-场景outputparserexception-与-handle_parsing_errors">Agent 场景：OutputParserException 与 handle_parsing_errors
&lt;/h3>&lt;p>解析报错最密集的场景是 Agent。ReAct 式 Agent 靠解析模型文本来决定下一步动作（Thought / Action / Final Answer 的固定格式），模型一旦不合规矩就抛 &lt;code>OutputParserException&lt;/code>——典型的坏输出比如同一段回复里同时包含 &lt;code>Action&lt;/code> 和 &lt;code>Final Answer&lt;/code>。&lt;/p>
&lt;p>我跑 ReAct Agent 时撞到过这个错，解法是初始化代理时传 &lt;code>handle_parsing_errors=True&lt;/code>：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="n">agent&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">initialize_agent&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">tools&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">tools&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">llm&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">llm&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">agent_type&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;zero-shot-react-description&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">verbose&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="kc">True&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">handle_parsing_errors&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="kc">True&lt;/span> &lt;span class="c1"># 自动处理解析错误&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">response&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">agent&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">run&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;告诉我关于 LangChain 的信息&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">response&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>机制：解析失败时代理不抛异常，而是把错误信息反馈给模型，要求它重新生成符合预期的输出格式——相当于框架替你做了上一节手写的那层重试循环。&lt;/p>
&lt;p>⚠️ 此为 0.x 时代写法：&lt;code>initialize_agent&lt;/code> / AgentExecutor 属于 legacy API，LangChain 1.x 已将其移出主线。更根本的变化是，现代 tool-calling Agent 让模型直接输出结构化的 tool call，不再靠解析自由文本决定动作，这一类解析报错从源头就少了一大截。这段经验的价值在机制本身：解析失败时把错误反馈给模型让它自我修正，这个思路在哪个版本都适用。&lt;/p>
&lt;h3 id="调试心法先定位问题在模型还是解析器">调试心法：先定位问题在模型还是解析器
&lt;/h3>&lt;p>解析失败时第一个动作不是改提示词，是看原始输出。把链尾换成 &lt;code>StrOutputParser&lt;/code> 跑一遍，问题在哪一侧立刻清楚：原始输出本身不符合约定格式，问题在模型侧；原始输出看着合规但 parser 仍报错，问题在解析器侧——多半是格式约定和实际输出没对上。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="k">try&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">response&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">chain&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">invoke&lt;/span>&lt;span class="p">({})&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">response&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">except&lt;/span> &lt;span class="ne">Exception&lt;/span> &lt;span class="k">as&lt;/span> &lt;span class="n">e&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;解析失败：&lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">e&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># 用 StrOutputParser 检查原始输出&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">raw_chain&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">prompt&lt;/span> &lt;span class="o">|&lt;/span> &lt;span class="n">llm&lt;/span> &lt;span class="o">|&lt;/span> &lt;span class="n">StrOutputParser&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">raw_output&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">raw_chain&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">invoke&lt;/span>&lt;span class="p">({})&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;原始输出：&lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">raw_output&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>定位到模型侧之后，两个常用手段：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>把格式要求写得更硬。&lt;/strong> 在提示里重复强调，比如&amp;quot;严格按照 JSON 格式返回，不要添加额外说明&amp;quot;；格式说明用 &lt;code>get_format_instructions()&lt;/code> 生成后经 &lt;code>partial_variables&lt;/code> 嵌入，不要自己手写一份——手写的和解析器的约定迟早对不上。&lt;/li>
&lt;li>&lt;strong>降低 temperature。&lt;/strong> 对格式遵从度差的模型，降低采样温度能减少格式漂移。&lt;/li>
&lt;/ul>
&lt;h2 id="总结">总结
&lt;/h2>&lt;ul>
&lt;li>输入侧默认 &lt;code>ChatPromptTemplate&lt;/code> + &lt;code>MessagesPlaceholder&lt;/code>，细粒度消息类按需取用；FewShot 的进阶形态是 ExampleSelector 按相似度动态选例，示例库质量比数量重要。&lt;/li>
&lt;li>输出侧要结构化数据，1.x 优先 &lt;code>with_structured_output&lt;/code>；文本解析器是备选，五种里 &lt;code>PydanticOutputParser&lt;/code>（要校验）和 &lt;code>JsonOutputParser&lt;/code>（要字典）最常用，特殊格式继承 &lt;code>BaseOutputParser&lt;/code> 自己写 &lt;code>parse&lt;/code>。&lt;/li>
&lt;li>解析失败按常态路径处理：普通链上自己包一层循环重跑；Agent 场景（0.x）用 &lt;code>handle_parsing_errors=True&lt;/code> 让框架把错误喂回模型。&lt;/li>
&lt;li>排查顺序固定：先换 &lt;code>StrOutputParser&lt;/code> 看原始输出，定位模型侧还是解析器侧，再对应改提示（格式说明、temperature）或改解析约定。&lt;/li>
&lt;li>字符串模板留在默认的 f-string，不要用 jinja2 加载来源不可信的模板。&lt;/li>
&lt;/ul></description></item><item><title>DeepAgents完全指南</title><link>https://www.zata.cc/p/deepagents%E5%AE%8C%E5%85%A8%E6%8C%87%E5%8D%97/</link><pubDate>Tue, 08 Sep 2026 11:26:34 +0800</pubDate><guid>https://www.zata.cc/p/deepagents%E5%AE%8C%E5%85%A8%E6%8C%87%E5%8D%97/</guid><description>&lt;img src="https://www.zata.cc/p/deepagents%E5%AE%8C%E5%85%A8%E6%8C%87%E5%8D%97/images/index/index.png" alt="Featured image of post DeepAgents完全指南" />&lt;h1 id="deepagents-完全指南">DeepAgents 完全指南
&lt;/h1>&lt;blockquote>
&lt;p>本文基于 &lt;code>deepagents==0.7.15&lt;/code>（Python，2026-09-16 发布）撰写，涵盖从入门概念到生产部署的全部核心内容。0.x 版本 API 变动较快，如果你的版本低于 0.6，请先升级再对照本文——0.6.0 改了消息历史的持久化格式，升级路径见 3.5 节。&lt;/p>
&lt;/blockquote>
&lt;hr>
&lt;h2 id="第一章deepagents-是什么">第一章：DeepAgents 是什么
&lt;/h2>&lt;h3 id="11-一句话定义">1.1 一句话定义
&lt;/h3>&lt;p>DeepAgents（包名 &lt;code>deepagents&lt;/code>）是 LangChain 团队推出的一个 &lt;strong>agent harness&lt;/strong>（智能体运行框架）—— 一个开箱即用、高度可定制、面向长周期多步任务的智能体运行时。&lt;/p>
&lt;p>它不是又一个 LangGraph 图编排工具，也不只是 LangChain 的薄封装，而是一个带有以下&amp;quot;观点&amp;quot;（opinionated）的完整框架：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>默认就适合长任务&lt;/strong>：内置文件系统、上下文压缩、子智能体委派，不用你自己搭。&lt;/li>
&lt;li>&lt;strong>每个组件都可以替换&lt;/strong>：模型、文件后端、中间件、子智能体、技能，全部可插拔。&lt;/li>
&lt;li>&lt;strong>模型无关&lt;/strong>：只要模型支持 tool calling 就能用——GPT、Claude、Gemini、Qwen、本地 Ollama 都行。&lt;/li>
&lt;li>&lt;strong>生产就绪&lt;/strong>：底层是 LangGraph，天然支持流式输出、持久化、checkpoint、断点恢复。&lt;/li>
&lt;/ul>
&lt;p>灵感来源是 Claude Code：团队想搞清楚 Claude Code 为什么通用，然后把那些设计提炼成一个可编程的框架。&lt;/p>
&lt;h3 id="12-在-langchain-生态中的位置">1.2 在 LangChain 生态中的位置
&lt;/h3>&lt;p>理解 DeepAgents 的关键，是理解 LangChain 生态的三层结构：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">┌─────────────────────────────────┐
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ LangSmith │ ← 可观测性、评测、监控
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">├─────────────────────────────────┤
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ Deep Agents │ ← 完整 harness（本文主角）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">├─────────────────────────────────┤
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ LangChain create_agent │ ← 轻量 agent（无内置文件系统/子agent）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">├─────────────────────────────────┤
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ LangGraph │ ← 图运行时（状态、节点、边）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">└─────────────────────────────────┘
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;ul>
&lt;li>&lt;strong>LangGraph&lt;/strong> 是底层的图运行时，管状态、节点、边、checkpoint。&lt;/li>
&lt;li>&lt;strong>LangChain &lt;code>create_agent&lt;/code>&lt;/strong> 是最小的 agent 循环：模型 + 工具 + 消息 → 循环 → 回复。&lt;/li>
&lt;li>&lt;strong>DeepAgents&lt;/strong> 在 &lt;code>create_agent&lt;/code> 之上叠加了一整套 middleware（文件系统、子智能体、上下文压缩、技能、权限），把&amp;quot;裸 agent&amp;quot;变成一个完整的 agent harness。&lt;/li>
&lt;li>任何 LangGraph 编译后的图都可以作为 DeepAgents 的子智能体传入，实现自定义编排与 DeepAgents 默认行为的混合。&lt;/li>
&lt;/ul>
&lt;p>什么时候用什么层：&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>场景&lt;/th>
&lt;th>用什么&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>需要 planning、context 管理、子智能体委派&lt;/td>
&lt;td>&lt;strong>DeepAgents&lt;/strong>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>只需要一个简单 tool-calling 循环&lt;/td>
&lt;td>LangChain &lt;code>create_agent&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>agent 循环不是你想要的形状，需要自定义图&lt;/td>
&lt;td>LangGraph&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>需要监控、评估、trace&lt;/td>
&lt;td>LangSmith（可与上面任何层搭配）&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;h3 id="13-核心能力一览">1.3 核心能力一览
&lt;/h3>&lt;table>
&lt;thead>
&lt;tr>
&lt;th>能力&lt;/th>
&lt;th>说明&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>文件系统&lt;/td>
&lt;td>内置 &lt;code>ls / read_file / write_file / edit_file / delete / glob / grep&lt;/code>，支持多种可插拔后端&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>子智能体&lt;/td>
&lt;td>主 agent 通过 &lt;code>task&lt;/code> 工具委派任务给拥有独立上下文的子 agent&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>上下文管理&lt;/td>
&lt;td>自动摘要长对话，超大工具输出自动转存到文件系统&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Shell 执行&lt;/td>
&lt;td>通过 Sandbox 后端提供 &lt;code>execute&lt;/code> 工具&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>代码执行&lt;/td>
&lt;td>&lt;code>CodeInterpreterMiddleware&lt;/code>（实验性）在受限 QuickJS 沙箱里跑代码与编程式工具调用&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>持久记忆&lt;/td>
&lt;td>可插拔的状态和存储后端，支持跨会话记忆&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Human-in-the-Loop&lt;/td>
&lt;td>工具调用或文件操作前暂停，等待人工审批/编辑/拒绝&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Skills&lt;/td>
&lt;td>可复用的行为模板，按需加载，渐进式披露&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>结构化输出&lt;/td>
&lt;td>主 agent 和子 agent 均支持 Pydantic 模型作为输出 schema&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>MCP 工具&lt;/td>
&lt;td>可接入任意 MCP server 作为工具&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;h3 id="14-安装">1.4 安装
&lt;/h3>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 推荐&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">uv add deepagents
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 或者&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">pip install deepagents
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>要求 Python &amp;gt;= 3.11, &amp;lt; 4.0。&lt;/p>
&lt;hr>
&lt;h2 id="第二章架构与中间件协议">第二章：架构与中间件协议
&lt;/h2>&lt;p>DeepAgents 的一切能力都由**中间件栈（middleware stack）**提供。理解这个栈，就理解了 DeepAgents 的全部。&lt;/p>
&lt;h3 id="21-中间件栈的完整顺序">2.1 中间件栈的完整顺序
&lt;/h3>&lt;p>当你调用 &lt;code>create_deep_agent()&lt;/code> 时，框架会按以下顺序组装中间件：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-gdscript3" data-lang="gdscript3">&lt;span class="line">&lt;span class="cl">&lt;span class="err">┌─&lt;/span> &lt;span class="err">基础栈（&lt;/span>&lt;span class="n">Base&lt;/span> &lt;span class="n">stack&lt;/span>&lt;span class="err">）────────────────────────────────┐&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">│&lt;/span> &lt;span class="mf">1.&lt;/span> &lt;span class="n">SkillsMiddleware&lt;/span> &lt;span class="err">（仅当传入&lt;/span> &lt;span class="n">skills&lt;/span>&lt;span class="err">）&lt;/span> &lt;span class="err">│&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">│&lt;/span> &lt;span class="mf">2.&lt;/span> &lt;span class="n">FilesystemMiddleware&lt;/span> &lt;span class="err">（始终存在）&lt;/span> &lt;span class="err">│&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">│&lt;/span> &lt;span class="mf">3.&lt;/span> &lt;span class="n">SubAgentMiddleware&lt;/span> &lt;span class="err">（有子智能体时存在）&lt;/span> &lt;span class="err">│&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">│&lt;/span> &lt;span class="mf">4.&lt;/span> &lt;span class="n">SummarizationMiddleware&lt;/span> &lt;span class="err">（始终存在）&lt;/span> &lt;span class="err">│&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">│&lt;/span> &lt;span class="mf">5.&lt;/span> &lt;span class="n">PatchToolCallsMiddleware&lt;/span> &lt;span class="err">（始终存在）&lt;/span> &lt;span class="err">│&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">│&lt;/span> &lt;span class="mf">6.&lt;/span> &lt;span class="n">AsyncSubAgentMiddleware&lt;/span> &lt;span class="err">（有异步子智能体时存在）&lt;/span> &lt;span class="err">│&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">│&lt;/span> &lt;span class="mf">7.&lt;/span> &lt;span class="err">你的自定义&lt;/span> &lt;span class="n">middleware&lt;/span> &lt;span class="err">（插入到这里）&lt;/span> &lt;span class="err">│&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">├─&lt;/span> &lt;span class="err">尾部栈（&lt;/span>&lt;span class="n">Tail&lt;/span> &lt;span class="n">stack&lt;/span>&lt;span class="err">）────────────────────────────────┤&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">│&lt;/span> &lt;span class="mf">8.&lt;/span> &lt;span class="n">Harness&lt;/span> &lt;span class="n">profile&lt;/span> &lt;span class="err">额外中间件&lt;/span> &lt;span class="err">│&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">│&lt;/span> &lt;span class="mf">9.&lt;/span> &lt;span class="err">工具排除过滤（&lt;/span>&lt;span class="n">excluded_tools&lt;/span>&lt;span class="err">）&lt;/span> &lt;span class="err">│&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">│&lt;/span> &lt;span class="mf">10.&lt;/span> &lt;span class="n">AnthropicPromptCachingMiddleware&lt;/span>&lt;span class="err">（非&lt;/span> &lt;span class="n">Anthropic&lt;/span> &lt;span class="n">noop&lt;/span>&lt;span class="err">）│&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">│&lt;/span> &lt;span class="mf">11.&lt;/span> &lt;span class="n">BedrockPromptCachingMiddleware&lt;/span>&lt;span class="err">（未装则跳过）&lt;/span> &lt;span class="err">│&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">│&lt;/span> &lt;span class="mf">12.&lt;/span> &lt;span class="n">FireworksPromptCachingMiddleware&lt;/span>&lt;span class="err">（未装则跳过）&lt;/span> &lt;span class="err">│&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">│&lt;/span> &lt;span class="mf">13.&lt;/span> &lt;span class="n">MemoryMiddleware&lt;/span> &lt;span class="err">（仅当传入&lt;/span> &lt;span class="n">memory&lt;/span>&lt;span class="err">）&lt;/span> &lt;span class="err">│&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">│&lt;/span> &lt;span class="mf">14.&lt;/span> &lt;span class="n">HumanInTheLoopMiddleware&lt;/span> &lt;span class="err">（仅当传入&lt;/span> &lt;span class="n">interrupt_on&lt;/span>&lt;span class="err">）&lt;/span> &lt;span class="err">│&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">└──────────────────────────────────────────────────────┘&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>关键点：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>你的 middleware 插在位置 7&lt;/strong>，在核心中间件之后、prompt caching 和 memory 之前。&lt;/li>
&lt;li>如果你的 middleware 实例的 &lt;code>.name&lt;/code> 与某个内置中间件相同，它会&lt;strong>原地替换&lt;/strong>该内置实例，而不是追加。&lt;/li>
&lt;li>某些核心中间件（&lt;code>FilesystemMiddleware&lt;/code>、&lt;code>SubAgentMiddleware&lt;/code>、&lt;code>PatchToolCallsMiddleware&lt;/code>）受框架保护，不能通过 &lt;code>excluded_middleware&lt;/code> 移除。&lt;/li>
&lt;li>上图是&lt;strong>默认组装结果&lt;/strong>。实验性中间件（如 &lt;code>CodeInterpreterMiddleware&lt;/code>）不在其中，只有你显式加进 &lt;code>middleware&lt;/code> 才会出现。&lt;/li>
&lt;/ul>
&lt;h3 id="22-核心中间件详解">2.2 核心中间件详解
&lt;/h3>&lt;h4 id="filesystemmiddleware">FilesystemMiddleware
&lt;/h4>&lt;p>始终存在。提供以下工具：&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>工具&lt;/th>
&lt;th>功能&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;code>ls(path)&lt;/code>&lt;/td>
&lt;td>列出目录内容&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>read_file(file_path, offset, limit)&lt;/code>&lt;/td>
&lt;td>读取文件，支持分页和多模态（图片/音频/视频）&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>write_file(file_path, content)&lt;/code>&lt;/td>
&lt;td>写入文件，&lt;strong>已存在的文件直接覆盖&lt;/strong>（0.7.0 起的行为，此前是报错）&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>edit_file(file_path, old_string, new_string, replace_all)&lt;/code>&lt;/td>
&lt;td>精确字符串替换，&lt;code>old_string&lt;/code> 为空会被拒绝（0.7.14 起）&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>delete(file_path)&lt;/code>&lt;/td>
&lt;td>删除文件或递归删除目录（0.7.0 新增）&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>glob(pattern, path)&lt;/code>&lt;/td>
&lt;td>模式匹配查找文件&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>grep(pattern, path, glob)&lt;/code>&lt;/td>
&lt;td>文本搜索（优先使用 ripgrep）&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>execute(command, timeout)&lt;/code>&lt;/td>
&lt;td>执行 shell 命令（仅当后端实现 SandboxBackendProtocol）&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>0.7.0 开始，这套读写工具明显向「弱模型也能稳定用」的方向调过一遍，几个行为值得记住：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>&lt;code>write_file&lt;/code> 覆盖而不报错&lt;/strong>。以前写同名文件会失败，agent 得先 &lt;code>delete&lt;/code> 再写；现在直接覆盖。&lt;/li>
&lt;li>&lt;strong>&lt;code>read_file&lt;/code> 分页可续读&lt;/strong>。返回结果里会报总行数、剩余行数以及下一次该用的 &lt;code>offset&lt;/code>，模型不用自己猜还剩多少没读。&lt;/li>
&lt;li>&lt;strong>&lt;code>grep&lt;/code> / &lt;code>glob&lt;/code> 返回部分结果而不是卡死&lt;/strong>。在大目录树上超过上限时，结果里带一个 &lt;code>truncated&lt;/code> 标记，&lt;code>grep&lt;/code> 另有 1000 条匹配上限，并支持流式输出和可选的上下文行。&lt;/li>
&lt;li>&lt;strong>&lt;code>edit_file&lt;/code> 拒绝空的 &lt;code>old_string&lt;/code>&lt;/strong>。以前传空串会导致不可预期的替换（0.7.14 修的）。&lt;/li>
&lt;/ul>
&lt;p>如果你只想暴露其中一部分工具（比如给 agent 一个只读的文件视图），&lt;code>FilesystemMiddleware&lt;/code> 接受工具白名单：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">deepagents.middleware.filesystem&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">FilesystemMiddleware&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">fs_mw&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">FilesystemMiddleware&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">tools&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;ls&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;read_file&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;glob&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;grep&amp;#34;&lt;/span>&lt;span class="p">])&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">agent&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">create_deep_agent&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">model&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">model&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">middleware&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">fs_mw&lt;/span>&lt;span class="p">])&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>它还负责&lt;strong>大工具结果的自动转存&lt;/strong>：&lt;/p>
&lt;ul>
&lt;li>默认 20000 token 以上的工具输出会被转存到文件系统（路径如 &lt;code>/large_tool_results/&lt;/code>），上下文中只保留摘要和文件路径。&lt;/li>
&lt;li>用户消息超过 50000 token 也会被转存。&lt;/li>
&lt;li>转存文件按 &lt;code>tool_call_id&lt;/code> 命名；对于没有 ID 的调用，0.7.15 起会生成唯一路径，避免多次转存撞到同一个文件名。&lt;/li>
&lt;/ul>
&lt;h4 id="subagentmiddleware">SubAgentMiddleware
&lt;/h4>&lt;p>有子智能体时存在。提供 &lt;code>task&lt;/code> 工具，主 agent 用它委派任务给子 agent。子 agent 拥有&lt;strong>完全隔离的上下文窗口&lt;/strong>——主 agent 只看到子 agent 的最终输出。&lt;/p>
&lt;h4 id="summarizationmiddleware">SummarizationMiddleware
&lt;/h4>&lt;p>始终存在。当对话 token 数超过阈值时，自动将旧消息压缩为摘要，完整历史转存到文件系统（路径如 &lt;code>/conversation_history/&lt;/code>）。阈值的默认值会根据模型的最大输入 token 自动计算，回退方案是固定保留最近 20 条消息。&lt;/p>
&lt;h4 id="patchtoolcallsmiddleware">PatchToolCallsMiddleware
&lt;/h4>&lt;p>始终存在。修复消息历史中悬空的工具调用（例如 agent 在中断后恢复、或收到格式错误的 tool-call 参数时），确保消息流的一致性。&lt;/p>
&lt;h4 id="skillsmiddleware">SkillsMiddleware
&lt;/h4>&lt;p>仅当传入 &lt;code>skills&lt;/code> 参数时存在。将技能目录中的 &lt;code>SKILL.md&lt;/code> 元数据（name + description）注入系统提示词，让 agent 知道有哪些技能可用。Agent 判断当前任务匹配某个技能时，会主动读取该技能的完整指令。&lt;/p>
&lt;h4 id="memorymiddleware">MemoryMiddleware
&lt;/h4>&lt;p>仅当传入 &lt;code>memory&lt;/code> 参数时存在。在 agent 启动时加载指定的 &lt;code>AGENTS.md&lt;/code> 文件内容，追加到系统提示词中。这是 DeepAgents 的&amp;quot;启动记忆&amp;quot;机制。&lt;/p>
&lt;h4 id="codeinterpretermiddlewareinterpreterbeta">CodeInterpreterMiddleware（Interpreter，beta）
&lt;/h4>&lt;p>0.6.0 引入，不在默认栈里，需要你显式加进 &lt;code>middleware&lt;/code>。官方文档里这块叫 &lt;strong>Interpreters&lt;/strong>——给 agent 一个&lt;strong>进程内&lt;/strong>的可编程工作区：agent 写一段 JavaScript，运行时执行后只把结果返回，中间变量不进入模型上下文。&lt;/p>
&lt;p>它解决的是一个很具体的问题：正常的工具调用循环里，一次模型 turn 发出的那批 tool call 是&lt;strong>固定的&lt;/strong>——不能循环、不能根据结果分支、不能重试、也不能把上一个调用的输出喂给下一个，除非再来一轮模型推理，而且每个结果都会回到上下文里。让模型去分派几百个条目本身也不可靠，它往往只抽样一部分。Interpreter 把这类编排从&amp;quot;模型逐步决策&amp;quot;挪到&amp;quot;代码里跑&amp;quot;，模型只需要想清楚&lt;strong>要做什么&lt;/strong>。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">pip install -U &lt;span class="s2">&amp;#34;deepagents[quickjs]&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">deepagents&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">create_deep_agent&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">langchain_quickjs&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">CodeInterpreterMiddleware&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">agent&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">create_deep_agent&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">model&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">model&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">middleware&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">CodeInterpreterMiddleware&lt;/span>&lt;span class="p">()],&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>它给 agent 加了一个 &lt;code>eval&lt;/code> 工具，agent 写 JS 调它，你不用手动调。几个要点：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>默认零外部能力&lt;/strong>。QuickJS 环境下拿不到宿主机文件系统、网络、shell、包管理器，连时钟都没有，只能计算、保状态、往 &lt;code>console.log/warn/error&lt;/code> 写。要放开只有两条明确的口子：&lt;strong>PTC&lt;/strong>（programmatic tool calling，用 &lt;code>ptc=[&amp;quot;web_search&amp;quot;]&lt;/code> 给一个工具白名单，代码里通过 &lt;code>tools.webSearch(...)&lt;/code> 调）和&lt;strong>动态子智能体&lt;/strong>（有子智能体时自动暴露 &lt;code>task()&lt;/code> 全局，可以 &lt;code>Promise.all&lt;/code> 并行分派）。&lt;/li>
&lt;li>&lt;strong>状态持久化由 &lt;code>mode&lt;/code> 控制&lt;/strong>：&lt;code>&amp;quot;thread&amp;quot;&lt;/code>（默认，跨 turn 持久，每轮结束写快照）、&lt;code>&amp;quot;turn&amp;quot;&lt;/code>（仅一轮内保持）、&lt;code>&amp;quot;call&amp;quot;&lt;/code>（每次 &lt;code>eval&lt;/code> 全新 REPL）。快照只保留可序列化的数据——函数和类恢复后会变成不可访问的残留物。&lt;/li>
&lt;li>&lt;strong>隔离是&amp;quot;能力受限&amp;quot;而不是&amp;quot;内存隔离&amp;quot;&lt;/strong>。QuickJS 跑在同进程里，官方明确说这是 scoped interpreter runtime，不是生产级 sandbox。不可信代码要另外放到独立进程或容器里跑。&lt;/li>
&lt;li>&lt;strong>PTC 不经过常规工具调用路径&lt;/strong>，所以 &lt;code>interrupt_on&lt;/code> 的审批对 PTC 里调用的工具&lt;strong>不生效&lt;/strong>。PTC 白名单本身就是权限边界，别把能碰敏感系统、花钱或改数据的工具放进去。&lt;/li>
&lt;/ul>
&lt;p>和 &lt;code>SandboxBackend&lt;/code> 的 &lt;code>execute&lt;/code> 分工很清楚：&lt;strong>sandbox 是&amp;quot;对着环境写代码&amp;quot;&lt;/strong>（shell、装依赖、跑测试、改文件），&lt;strong>interpreter 是&amp;quot;在 agent 循环里写代码&amp;quot;&lt;/strong>（组合工具、保状态、决定什么回给模型）。&lt;/p>
&lt;h4 id="humanintheloopmiddleware">HumanInTheLoopMiddleware
&lt;/h4>&lt;p>仅当传入 &lt;code>interrupt_on&lt;/code> 或 permissions 中有 &lt;code>mode=&amp;quot;interrupt&amp;quot;&lt;/code> 的规则时存在。在指定的工具调用前暂停执行，返回中断信息，等待人工决策（approve / edit / reject / respond）。&lt;/p>
&lt;h3 id="23-系统提示词的组装">2.3 系统提示词的组装
&lt;/h3>&lt;p>最终发给模型的 system prompt 不是一个字符串拼两个变量那么简单。完整顺序是：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">1. 你传入的 system_prompt （可选的用户层）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">2. 基础 agent 提示词 （BASE）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">3. Memory 提示词 （仅当传了 memory：AGENTS.md + 使用说明）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">4. Skills 提示词 （仅当传了 skills：技能位置 + 各技能 frontmatter + 用法）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">5. 虚拟文件系统提示词 （含 execute 工具说明，如果用 sandbox 后端）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">6. 子智能体提示词 （task 工具的用法）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">7. 你自定义中间件附带的提示词 （如果自定义中间件有）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">8. Human-in-the-loop 提示词 （仅当设置了 interrupt_on）
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>HarnessProfile 则控制其中的 &lt;code>BASE&lt;/code> 和 &lt;code>SUFFIX&lt;/code> 两个变量：&lt;/p>
&lt;ul>
&lt;li>&lt;code>base_system_prompt&lt;/code>：&lt;strong>整体替换&lt;/strong>基础提示词。&lt;/li>
&lt;li>&lt;code>system_prompt_suffix&lt;/code>：在末尾&lt;strong>追加&lt;/strong>指令。&lt;/li>
&lt;/ul>
&lt;p>大部分时候你只需要关心 &lt;code>system_prompt&lt;/code> 参数。&lt;code>BASE&lt;/code> 和 &lt;code>SUFFIX&lt;/code> 是框架层面为不同模型提供的默认调优，一般不需要动。&lt;/p>
&lt;p>注意 &lt;code>system_prompt&lt;/code> 是&lt;strong>静态&lt;/strong>的——每次调用都一样。如果你需要动态提示词（比如按用户权限切换「你有管理员权限」和「你只读」，或者从长期记忆里注入用户偏好），要用 &lt;code>@dynamic_prompt&lt;/code> 写中间件，在里读 &lt;code>request.runtime.context&lt;/code> 和 &lt;code>request.runtime.store&lt;/code>。只在工具内部用到 context 时不必写中间件——工具本身就能拿到 &lt;code>ToolRuntime&lt;/code>。&lt;/p>
&lt;p>值得一提的是 0.7.0 对这块做过一次&lt;strong>大幅瘦身&lt;/strong>，动机是&amp;quot;别把工具的用法重复写两遍&amp;quot;——工具 schema 本身已经说明了怎么用，再往提示词里塞一遍纯属浪费 token。具体做法是：内置的 BASE 提示词默认改为空，同时删掉与工具 schema 重复的说明性文字。效果是默认 agent 的工具描述 token 从 4005 降到 2302（−43%）；再叠上空 BASE 和按需开启的 todos，单轮输入的 token 从 5395 降到 1895（−65%）。工具的实际行为没有变化。&lt;/p>
&lt;p>如果你在自己的 &lt;code>system_prompt&lt;/code> 里也习惯把工具用法写得很细，这一点可以直接借鉴：&lt;strong>先看 schema 已经说了什么，再决定提示词里还欠什么&lt;/strong>。&lt;/p>
&lt;h3 id="24-harnessprofile">2.4 HarnessProfile
&lt;/h3>&lt;p>HarnessProfile 是&amp;quot;每个模型的默认调优配置包&amp;quot;。当你传入一个模型时，框架会自动解析对应的 profile，决定：&lt;/p>
&lt;ul>
&lt;li>额外的中间件（如 Anthropic prompt caching、Bedrock prompt caching）&lt;/li>
&lt;li>是否禁用某些内置工具（&lt;code>excluded_tools&lt;/code>）&lt;/li>
&lt;li>是否排除某些中间件（&lt;code>excluded_middleware&lt;/code>）&lt;/li>
&lt;li>额外的系统提示词内容&lt;/li>
&lt;/ul>
&lt;p>大部分使用场景不需要手动创建 profile。框架会根据模型类型自动选择。&lt;/p>
&lt;p>内置 profile 的覆盖范围一直在扩。0.7.0 加入了 &lt;strong>NVIDIA Nemotron 3 Ultra&lt;/strong> 的 profile，同时带上了 NIM 的 app-origin 归因；prompt caching 这一侧也补齐了 Bedrock（走 &lt;code>deepagents[aws]&lt;/code> extra）和 Fireworks（自动做缓存会话亲和）。0.7.15 顺手修了两个 profile 相关的小问题：model profile 的 key 现在允许出现冒号，Nemotron 的内置 profile 也要求真正的 task transition 才生效。&lt;/p>
&lt;hr>
&lt;h2 id="第三章create_deep_agent-完整签名">第三章：&lt;code>create_deep_agent&lt;/code> 完整签名
&lt;/h2>&lt;p>这是 DeepAgents 最核心的函数，理解了每个参数，就理解了整个框架的入口。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">deepagents&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">create_deep_agent&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">agent&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">create_deep_agent&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">model&lt;/span>&lt;span class="o">=...&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="c1"># 模型&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">tools&lt;/span>&lt;span class="o">=...&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="c1"># 自定义工具&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">system_prompt&lt;/span>&lt;span class="o">=...&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="c1"># 系统提示词&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">middleware&lt;/span>&lt;span class="o">=...&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="c1"># 额外中间件&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">subagents&lt;/span>&lt;span class="o">=...&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="c1"># 子智能体配置&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">skills&lt;/span>&lt;span class="o">=...&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="c1"># 技能目录路径&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">memory&lt;/span>&lt;span class="o">=...&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="c1"># AGENTS.md 记忆文件路径&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">permissions&lt;/span>&lt;span class="o">=...&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="c1"># 文件系统权限规则&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">backend&lt;/span>&lt;span class="o">=...&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="c1"># 文件系统后端&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">interrupt_on&lt;/span>&lt;span class="o">=...&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="c1"># 人工审批的工具名映射&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">response_format&lt;/span>&lt;span class="o">=...&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="c1"># 结构化输出 schema&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">state_schema&lt;/span>&lt;span class="o">=...&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="c1"># 自定义图状态 schema&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">context_schema&lt;/span>&lt;span class="o">=...&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="c1"># 运行时上下文 schema&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">checkpointer&lt;/span>&lt;span class="o">=...&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="c1"># LangGraph checkpointer（持久化）&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">store&lt;/span>&lt;span class="o">=...&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="c1"># LangGraph store（跨会话存储）&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">debug&lt;/span>&lt;span class="o">=...&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="c1"># 调试模式&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">name&lt;/span>&lt;span class="o">=...&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="c1"># 图的名称&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">cache&lt;/span>&lt;span class="o">=...&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="c1"># 缓存&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="31-model-参数">3.1 &lt;code>model&lt;/code> 参数
&lt;/h3>&lt;p>接受三种形式：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 1. 字符串（推荐——使用 provider:model 格式）&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">agent&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">create_deep_agent&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">model&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;openai:gpt-5.5&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 2. 预初始化的 LangChain ChatModel 实例&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">langchain_openai&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">ChatOpenAI&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">model&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">ChatOpenAI&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">model&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;gpt-5.5&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">temperature&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">agent&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">create_deep_agent&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">model&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">model&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 3. 传入 None（已弃用，默认是 claude-sonnet-4-6）&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 从 0.5.3 开始弃用，将在 1.0.0 中移除&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;code>provider:model&lt;/code> 字符串由 LangChain 的 &lt;code>init_chat_model&lt;/code> 函数解析。支持的 provider 包括：&lt;code>openai&lt;/code>, &lt;code>anthropic&lt;/code>, &lt;code>google_genai&lt;/code>, &lt;code>aws&lt;/code>, &lt;code>azure_openai&lt;/code>, &lt;code>ollama&lt;/code>, &lt;code>fireworks&lt;/code>, &lt;code>together&lt;/code>, &lt;code>mistral&lt;/code>, &lt;code>groq&lt;/code>, &lt;code>xai&lt;/code> 等。&lt;/p>
&lt;h4 id="重要openai-模型的-responses-api-默认行为">重要：OpenAI 模型的 Responses API 默认行为
&lt;/h4>&lt;p>从 DeepAgents 0.6.x 开始，如果你使用 OpenAI 模型，&lt;strong>默认走 Responses API&lt;/strong>（不是 Chat Completions）。这是因为 Responses API 支持 OpenAI 的内置工具（web search、code interpreter 等）。&lt;/p>
&lt;p>如果你想强制走 Chat Completions：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">langchain.chat_models&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">init_chat_model&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">model&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">init_chat_model&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;openai:gpt-5.5&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">use_responses_api&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="kc">False&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">agent&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">create_deep_agent&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">model&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">model&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>如果你想用 Responses API 但禁用服务端数据保留：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="n">model&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">init_chat_model&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;openai:gpt-5.5&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">use_responses_api&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="kc">True&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">store&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="kc">False&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">include&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;reasoning.encrypted_content&amp;#34;&lt;/span>&lt;span class="p">],&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">agent&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">create_deep_agent&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">model&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">model&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h4 id="接入阿里云百炼dashscope">接入阿里云百炼（DashScope）
&lt;/h4>&lt;p>百炼提供 OpenAI 兼容端点，走 Chat Completions 协议：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">langchain_openai&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">ChatOpenAI&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">deepagents&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">create_deep_agent&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">model&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">ChatOpenAI&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">model&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;qwen-plus&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="c1"># 或 qwen3-max、qwen-max 等&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">api_key&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">os&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">getenv&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;DASHSCOPE_API_KEY&amp;#34;&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">base_url&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;https://dashscope.aliyuncs.com/compatible-mode/v1&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">agent&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">create_deep_agent&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">model&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">model&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">tools&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">my_tools&lt;/span>&lt;span class="p">])&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>如果需要百炼的 &lt;code>web_search&lt;/code> 等服务端内置工具，有两种策略：&lt;/p>
&lt;ol>
&lt;li>&lt;strong>在 tools 中包装&lt;/strong>：把百炼 Responses API 的 web_search 调用封装为一个普通 function tool，DeepAgents 通过 tool calling 调用它。&lt;/li>
&lt;li>&lt;strong>直接用 Responses API&lt;/strong>：绕过 DeepAgents，直接调用百炼的 Responses API（失去 DeepAgents 的 harness 能力）。&lt;/li>
&lt;/ol>
&lt;h3 id="32-tools-参数">3.2 &lt;code>tools&lt;/code> 参数
&lt;/h3>&lt;p>传入模型可以调用的自定义工具。这些工具与内置工具&lt;strong>合并&lt;/strong>（additive），不会替换内置工具：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">langchain.tools&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">tool&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nd">@tool&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">search_database&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">query&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;搜索产品数据库，返回匹配结果&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">db&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">search&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">query&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nd">@tool&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">generate_report&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">data&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">dict&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;根据数据生成分析报告&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">report_gen&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">run&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">data&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">agent&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">create_deep_agent&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">model&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">model&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">tools&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">search_database&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">generate_report&lt;/span>&lt;span class="p">])&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>要移除内置工具，需要使用 HarnessProfile 的 &lt;code>excluded_tools&lt;/code> 机制，而不是简单地不传。&lt;/p>
&lt;h3 id="33-system_prompt-参数">3.3 &lt;code>system_prompt&lt;/code> 参数
&lt;/h3>&lt;p>你自定义的系统提示词。可以是字符串或 &lt;code>SystemMessage&lt;/code>：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="n">agent&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">create_deep_agent&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">model&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">model&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">system_prompt&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;你是一个数据分析助手。在给出结论之前，先用 Python 工具验证数据。&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>如果传入 &lt;code>SystemMessage&lt;/code>，会保留其上的 &lt;code>cache_control&lt;/code> 标记（用于 Anthropic prompt caching 的显式断点）。&lt;/p>
&lt;h3 id="34-response_format-参数">3.4 &lt;code>response_format&lt;/code> 参数
&lt;/h3>&lt;p>让 agent 的最终输出符合指定的结构化 schema：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">pydantic&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">BaseModel&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">Field&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">ResearchReport&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">BaseModel&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;研究报告的结构化输出&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">title&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">Field&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">description&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;报告标题&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">summary&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">Field&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">description&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;核心发现摘要&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">key_findings&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">list&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nb">str&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">Field&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">description&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;关键发现列表&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">confidence&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">float&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">Field&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">description&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;置信度 0-1&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">sources&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">list&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nb">str&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">Field&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">description&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;参考来源&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">agent&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">create_deep_agent&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">model&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">model&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">tools&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">web_search&lt;/span>&lt;span class="p">],&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">response_format&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">ResearchReport&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">result&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">agent&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">invoke&lt;/span>&lt;span class="p">({&lt;/span>&lt;span class="s2">&amp;#34;messages&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">[{&lt;/span>&lt;span class="s2">&amp;#34;role&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;user&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;content&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;调研 LangGraph 的最新特性&amp;#34;&lt;/span>&lt;span class="p">}]})&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 访问结构化输出&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">report&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">result&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;structured_response&amp;#34;&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">report&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">title&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">report&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">confidence&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;code>response_format&lt;/code> 接受：&lt;/p>
&lt;ul>
&lt;li>Pydantic &lt;code>BaseModel&lt;/code> 子类&lt;/li>
&lt;li>Python &lt;code>dataclass&lt;/code>&lt;/li>
&lt;li>&lt;code>TypedDict&lt;/code>&lt;/li>
&lt;li>JSON Schema 字典&lt;/li>
&lt;li>&lt;code>ToolStrategy(schema)&lt;/code>：通过 tool calling 提取&lt;/li>
&lt;li>&lt;code>ProviderStrategy(schema)&lt;/code>：使用 provider 原生结构化输出&lt;/li>
&lt;li>&lt;code>AutoStrategy(schema)&lt;/code>：自动选择最佳策略&lt;/li>
&lt;/ul>
&lt;h3 id="35-checkpointer-与持久化">3.5 &lt;code>checkpointer&lt;/code> 与持久化
&lt;/h3>&lt;p>DeepAgents 底层是 LangGraph，所以天然支持持久化：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">langgraph.checkpoint.memory&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">MemorySaver&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">agent&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">create_deep_agent&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">model&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">model&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">tools&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">tools&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">checkpointer&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">MemorySaver&lt;/span>&lt;span class="p">(),&lt;/span> &lt;span class="c1"># 内存持久化&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 使用 thread_id 区分不同会话&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">config&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span>&lt;span class="s2">&amp;#34;configurable&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">{&lt;/span>&lt;span class="s2">&amp;#34;thread_id&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;user-123-session-456&amp;#34;&lt;/span>&lt;span class="p">}}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">result&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">agent&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">invoke&lt;/span>&lt;span class="p">({&lt;/span>&lt;span class="s2">&amp;#34;messages&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="o">...&lt;/span>&lt;span class="p">]},&lt;/span> &lt;span class="n">config&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">config&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>生产环境使用 PostgreSQL 或 SQLite：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">langgraph.checkpoint.postgres&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">PostgresSaver&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">checkpointer&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">PostgresSaver&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">from_conn_string&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;postgresql://...&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">agent&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">create_deep_agent&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">model&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">model&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">checkpointer&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">checkpointer&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>文件系统（&lt;code>StateBackend&lt;/code>）的内容也会随 checkpoint 持久化——同一 thread 内跨多轮对话，文件不会丢失。&lt;/p>
&lt;h4 id="deltachannel060-起的持久化格式变更">DeltaChannel：0.6.0 起的持久化格式变更
&lt;/h4>&lt;p>从 0.6.0 开始，消息历史和 agent 文件改用 &lt;code>DeltaChannel&lt;/code> 存储。原来的做法是每一步都把累积后的&lt;strong>全量&lt;/strong>值重新序列化进 checkpoint；&lt;code>DeltaChannel&lt;/code> 只存这一步写入的&lt;strong>增量&lt;/strong>。对长线程来说这是数量级的差别——checkpoint 的大小不再随对话变长而线性膨胀。相应地，可以配 &lt;code>snapshot_frequency=K&lt;/code> 每 K 步写一次全量快照，用来给读取延迟封顶。&lt;/p>
&lt;p>&lt;strong>这里有一条必须知道的兼容性红线：一旦 thread 用 0.6.0+ 持久化过，就不支持回滚。&lt;/strong>&lt;/p>
&lt;p>DeltaChannel 写出的 checkpoint 是新格式，0.6.0 之前的版本读不了。降级时这些 channel 会切回非 delta 实现，而已有的 delta checkpoint 就变成不可读了——症状是状态重建不完整或直接出错，而且不一定当场暴露。&lt;/p>
&lt;p>真的需要降级，只有两条路：&lt;/p>
&lt;ol>
&lt;li>先用 &lt;code>delta-channel-dump&lt;/code> 恢复脚本把受影响的 thread 迁移掉；&lt;/li>
&lt;li>或者直接丢弃这些 thread。&lt;/li>
&lt;/ol>
&lt;p>推广来说：&lt;strong>不要让同一个 channel 在 delta 和非 delta 两种表示之间来回切换&lt;/strong>。升级前先想清楚要不要留退路，留退路就在升级前把 thread 导出。&lt;/p>
&lt;hr>
&lt;h2 id="第四章文件系统与后端">第四章：文件系统与后端
&lt;/h2>&lt;p>DeepAgents 内置的文件系统是一个&lt;strong>虚拟文件系统&lt;/strong>（VFS）。它不是直接操作你的操作系统文件，而是通过**后端协议（backend protocol）**抽象，让文件操作落到不同的存储介质上。&lt;/p>
&lt;h3 id="41-内置工具">4.1 内置工具
&lt;/h3>&lt;table>
&lt;thead>
&lt;tr>
&lt;th>工具名&lt;/th>
&lt;th>签名&lt;/th>
&lt;th>说明&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;code>ls&lt;/code>&lt;/td>
&lt;td>&lt;code>ls(path: str) -&amp;gt; list[str]&lt;/code>&lt;/td>
&lt;td>列出目录内容&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>read_file&lt;/code>&lt;/td>
&lt;td>&lt;code>read_file(file_path: str, offset: int = 0, limit: int = 500) -&amp;gt; str&lt;/code>&lt;/td>
&lt;td>读取文件内容，支持偏移和限制&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>write_file&lt;/code>&lt;/td>
&lt;td>&lt;code>write_file(file_path: str, content: str) -&amp;gt; str&lt;/code>&lt;/td>
&lt;td>写入文件，已存在则直接覆盖&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>edit_file&lt;/code>&lt;/td>
&lt;td>&lt;code>edit_file(file_path: str, old_string: str, new_string: str, replace_all: bool = False) -&amp;gt; str&lt;/code>&lt;/td>
&lt;td>精确字符串替换&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>delete&lt;/code>&lt;/td>
&lt;td>&lt;code>delete(file_path: str) -&amp;gt; str&lt;/code>&lt;/td>
&lt;td>删除文件（需要后端支持删除操作）&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>glob&lt;/code>&lt;/td>
&lt;td>&lt;code>glob(pattern: str, path: str = &amp;quot;/&amp;quot;) -&amp;gt; list[str]&lt;/code>&lt;/td>
&lt;td>模式匹配查找&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>grep&lt;/code>&lt;/td>
&lt;td>&lt;code>grep(pattern: str, path: str = &amp;quot;/&amp;quot;, glob: str = None) -&amp;gt; list[str]&lt;/code>&lt;/td>
&lt;td>内容搜索（优先使用 ripgrep）&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>execute&lt;/code>&lt;/td>
&lt;td>&lt;code>execute(command: str, timeout: int = 300) -&amp;gt; str&lt;/code>&lt;/td>
&lt;td>执行 shell 命令（仅当后端实现 SandboxBackendProtocol）&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>读类工具在 0.7.0 之后都改成了「&lt;strong>宁可返回部分结果，也不要卡住&lt;/strong>」的语义，这是专门为开源模型调过的——弱模型容易在大目录树上触发一次巨量 &lt;code>grep&lt;/code>，然后整轮就废了：&lt;/p>
&lt;ul>
&lt;li>&lt;code>read_file&lt;/code> 会告诉你总共多少行、还剩多少行，以及下一次该传的 &lt;code>offset&lt;/code>，模型可以稳定地一页页往下读。&lt;/li>
&lt;li>&lt;code>grep&lt;/code> / &lt;code>glob&lt;/code> 命中过多时返回带 &lt;code>truncated&lt;/code> 标记的部分结果；&lt;code>grep&lt;/code> 另有 1000 条匹配上限，支持流式输出和可选上下文行。&lt;/li>
&lt;li>&lt;code>glob&lt;/code> 在路径不合理时会主动提示该往哪个 &lt;code>path&lt;/code> 下找（0.7.14 起）。&lt;/li>
&lt;li>顺带一提，&lt;code>edit_file&lt;/code> 从上到下按 &lt;code>old_string&lt;/code> 精确匹配，传空串会被直接拒绝。&lt;/li>
&lt;/ul>
&lt;h3 id="42-后端协议">4.2 后端协议
&lt;/h3>&lt;p>DeepAgents 的文件后端遵循 &lt;code>BackendProtocol&lt;/code>。核心方法是：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">BackendProtocol&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">Protocol&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">ls&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">path&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="nb">list&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nb">str&lt;/span>&lt;span class="p">]:&lt;/span> &lt;span class="o">...&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">read&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">file_path&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">offset&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">int&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">None&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">limit&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">int&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">None&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="o">...&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">write&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">file_path&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">content&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="kc">None&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="o">...&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">edit&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">file_path&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">old_string&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">new_string&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">replace_all&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">bool&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">False&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="kc">None&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="o">...&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">delete&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">file_path&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="kc">None&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="o">...&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">glob&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">pattern&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">path&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;/&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="nb">list&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nb">str&lt;/span>&lt;span class="p">]:&lt;/span> &lt;span class="o">...&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">grep&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">pattern&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">path&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;/&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">glob&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">None&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="nb">list&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="nb">str&lt;/span>&lt;span class="p">]:&lt;/span> &lt;span class="o">...&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>如果后端实现了 &lt;code>SandboxBackendProtocol&lt;/code>（继承自 BackendProtocol，额外要求 &lt;code>execute&lt;/code> 方法），就会额外暴露 &lt;code>execute&lt;/code> 工具。&lt;/p>
&lt;h3 id="43-statebackend默认">4.3 StateBackend（默认）
&lt;/h3>&lt;p>&lt;strong>StateBackend&lt;/strong> 是默认后端。文件内容直接存储在 LangGraph 的 state 中：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 不传 backend 参数，默认使用 StateBackend&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">agent&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">create_deep_agent&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">model&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">model&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">tools&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">tools&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 文件存在 state[&amp;#34;files&amp;#34;] 字典中，key 是路径，value 是内容字符串&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">result&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">agent&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">invoke&lt;/span>&lt;span class="p">({&lt;/span>&lt;span class="s2">&amp;#34;messages&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">[{&lt;/span>&lt;span class="s2">&amp;#34;role&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;user&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;content&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;写一个 hello.py&amp;#34;&lt;/span>&lt;span class="p">}]})&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">result&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;files&amp;#34;&lt;/span>&lt;span class="p">])&lt;/span> &lt;span class="c1"># {&amp;#34;/hello.py&amp;#34;: &amp;#34;print(&amp;#39;hello world&amp;#39;)&amp;#34;}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>特点：&lt;/p>
&lt;ul>
&lt;li>文件随 checkpoint 持久化，同一 thread 内跨会话不丢失。&lt;/li>
&lt;li>&lt;strong>跨 thread 不共享&lt;/strong>（每个 thread 有独立的 state）。&lt;/li>
&lt;li>无需外部依赖。&lt;/li>
&lt;li>适合快速原型和不需要真正落盘的场景。&lt;/li>
&lt;/ul>
&lt;h3 id="44-compositebackend">4.4 CompositeBackend
&lt;/h3>&lt;p>CompositeBackend 允许你把不同路径前缀路由到不同后端，实现&amp;quot;混合存储&amp;quot;：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">deepagents.backends&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">CompositeBackend&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">StateBackend&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">StoreBackend&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">SandboxBackend&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">backend&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">CompositeBackend&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">default&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">StateBackend&lt;/span>&lt;span class="p">(),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">routes&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;/workspace&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">StateBackend&lt;/span>&lt;span class="p">(),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;/artifacts&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">StoreBackend&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">store&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">my_store&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">namespace&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;artifacts&amp;#34;&lt;/span>&lt;span class="p">,)),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;/memory&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">StoreBackend&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">store&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">my_store&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">namespace&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;agent_memory&amp;#34;&lt;/span>&lt;span class="p">,)),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;/sandbox&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">SandboxBackend&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">root_dir&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;/tmp/agent_sandbox&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">timeout&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="mi">60&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;/data&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">CompositeBackend&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">FilesystemBackend&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">root_dir&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;/path/to/shared_data&amp;#34;&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">agent&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">create_deep_agent&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">model&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">model&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">backend&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">backend&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="45-sandboxbackend">4.5 SandboxBackend
&lt;/h3>&lt;p>SandboxBackend 允许 agent 在一个受控的目录中执行 shell 命令：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">deepagents&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">create_deep_agent&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">deepagents.backends&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">SandboxBackend&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">backend&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">SandboxBackend&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">root_dir&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;/tmp/agent_workspace&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">timeout&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="mi">120&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="c1"># 命令超时（秒）&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">max_output_size&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="mi">50000&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">agent&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">create_deep_agent&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">model&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">model&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">backend&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">backend&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># agent 可以调用 execute(&amp;#34;pip install requests&amp;#34;) 或 execute(&amp;#34;python analysis.py&amp;#34;)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>实现 &lt;code>SandboxBackendProtocol&lt;/code> 后，&lt;code>execute&lt;/code> 工具会自动暴露给 agent。&lt;/p>
&lt;h3 id="46-filesystembackend本地真实文件系统">4.6 FilesystemBackend（本地真实文件系统）
&lt;/h3>&lt;p>如果你想让 agent 直接操作本地磁盘（例如代码编辑、数据处理），可以使用 FilesystemBackend：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">deepagents.backends.filesystem&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">FilesystemBackend&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">backend&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">FilesystemBackend&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">root_dir&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;/Users/zata/code/my-project&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">agent&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">create_deep_agent&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">model&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">model&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">backend&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">backend&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># agent 可以 read_file / write_file / edit_file / glob / grep 操作真实文件&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="47-storebackend跨会话存储">4.7 StoreBackend（跨会话存储）
&lt;/h3>&lt;p>StoreBackend 利用 LangGraph 的 Store 接口，将文件内容存储在一个跨 thread 共享的键值存储中：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">langgraph.store.memory&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">InMemoryStore&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">deepagents.backends&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">StoreBackend&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">store&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">InMemoryStore&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">backend&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">StoreBackend&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">store&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">store&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">namespace&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;shared_files&amp;#34;&lt;/span>&lt;span class="p">,))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">agent&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">create_deep_agent&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">model&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">model&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">backend&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">backend&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 文件在不同 thread 之间共享（只要使用同一个 store）&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>生产环境搭配 PostgreSQL Store：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">langgraph.store.postgres&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">PostgresStore&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">store&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">PostgresStore&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">from_conn_string&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;postgresql://...&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">backend&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">StoreBackend&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">store&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">store&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">namespace&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;knowledge_base&amp;#34;&lt;/span>&lt;span class="p">,))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">agent&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">create_deep_agent&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">model&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">model&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">backend&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">backend&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h4 id="contexthubbackend用-langsmith-hub-当存储060-新增">ContextHubBackend：用 LangSmith Hub 当存储（0.6.0 新增）
&lt;/h4>&lt;p>如果你已经在用 LangSmith，&lt;code>ContextHubBackend&lt;/code> 省掉了单独部署一个 LangGraph store 的麻烦——它把 agent 的文件（技能、记忆、其他需要持久化的上下文）直接存成 &lt;strong>LangSmith Hub 上的 commit&lt;/strong>。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">deepagents.backends&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">ContextHubBackend&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">backend&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">ContextHubBackend&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="c1"># 走 LangSmith 凭据&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">agent&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">create_deep_agent&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">model&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">model&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">backend&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">backend&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>它真正的价值在&lt;strong>每次写入都带版本历史&lt;/strong>：skill 和 memory 这类文件最需要的就是「改错了能回退、能看出是谁在什么时候改的」，而这些能力在 Hub 上是现成的。代价是绑定了 LangSmith，纯本地或自托管场景还是用 StoreBackend。&lt;/p>
&lt;h3 id="48-权限控制">4.8 权限控制
&lt;/h3>&lt;p>通过 &lt;code>permissions&lt;/code> 参数精细控制文件系统访问：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="n">agent&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">create_deep_agent&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">model&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">model&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">backend&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">backend&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">permissions&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>&lt;span class="s2">&amp;#34;path&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;/workspace/*&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;operations&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;read&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;write&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;edit&amp;#34;&lt;/span>&lt;span class="p">],&lt;/span> &lt;span class="s2">&amp;#34;mode&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;allow&amp;#34;&lt;/span>&lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>&lt;span class="s2">&amp;#34;path&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;/memory/*&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;operations&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;read&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;write&amp;#34;&lt;/span>&lt;span class="p">],&lt;/span> &lt;span class="s2">&amp;#34;mode&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;allow&amp;#34;&lt;/span>&lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>&lt;span class="s2">&amp;#34;path&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;/system/*&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;mode&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;deny&amp;#34;&lt;/span>&lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>&lt;span class="s2">&amp;#34;path&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;/*&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;operations&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;read&amp;#34;&lt;/span>&lt;span class="p">],&lt;/span> &lt;span class="s2">&amp;#34;mode&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;allow&amp;#34;&lt;/span>&lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>&lt;span class="s2">&amp;#34;path&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;/*&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;operations&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;write&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;edit&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;delete&amp;#34;&lt;/span>&lt;span class="p">],&lt;/span> &lt;span class="s2">&amp;#34;mode&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;interrupt&amp;#34;&lt;/span>&lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">],&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>权限模式：&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>模式&lt;/th>
&lt;th>行为&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;code>allow&lt;/code>&lt;/td>
&lt;td>直接放行&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>deny&lt;/code>&lt;/td>
&lt;td>直接拒绝，返回错误信息给 agent&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>interrupt&lt;/code>&lt;/td>
&lt;td>暂停执行，等待人工审批&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>权限规则按顺序匹配，第一条匹配的规则生效。&lt;/p>
&lt;hr>
&lt;h2 id="第五章子智能体">第五章：子智能体
&lt;/h2>&lt;p>子智能体是 DeepAgents 最有特色的能力之一。核心思想：主 agent 通过 &lt;code>task&lt;/code> 工具把一个任务委派给一个拥有&lt;strong>独立上下文窗口&lt;/strong>的子 agent。主 agent 只看到子 agent 的最终输出，不会看到子 agent 的中间过程。&lt;/p>
&lt;p>这解决了长任务中的关键问题：&lt;strong>上下文污染&lt;/strong>。当主 agent 执行搜索、阅读、分析等中间步骤时，会产生大量 token。如果这些 token 都堆在主 agent 的上下文里，很快就会超出限制或降低推理质量。子智能体隔离了这些噪音。&lt;/p>
&lt;h3 id="51-预配置子智能体">5.1 预配置子智能体
&lt;/h3>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">deepagents&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">create_deep_agent&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">deepagents.subagents&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">SubAgent&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">researcher&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">SubAgent&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">name&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;research-agent&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">description&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;负责搜索和整理信息，输出结构化研究报告&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">system_prompt&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;你是一个信息研究员。用 web_search 和 read_file 工具完成调研任务。&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">model&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;openai:gpt-5.5&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="c1"># 子智能体可以用不同模型&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">tools&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">web_search&lt;/span>&lt;span class="p">],&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">analyst&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">SubAgent&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">name&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;data-analyst&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">description&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;负责数据分析和可视化&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">system_prompt&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;你是一个数据分析师。用 Python 分析数据，生成报告。&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">model&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;anthropic:claude-sonnet-4-6&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">tools&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">execute_python&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">read_file&lt;/span>&lt;span class="p">],&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">agent&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">create_deep_agent&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">model&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;openai:gpt-5.5&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">subagents&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">researcher&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">analyst&lt;/span>&lt;span class="p">],&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">system_prompt&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;你是一个团队负责人。用 task 工具委派任务给子智能体。&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>当主 agent 判断需要调研时，会调用：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-json" data-lang="json">&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;tool&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;task&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;args&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;description&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;调研 LangGraph 最新特性并输出报告&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;subagent_type&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;research-agent&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="52-自定义子智能体传入编译后的-langgraph">5.2 自定义子智能体（传入编译后的 LangGraph）
&lt;/h3>&lt;p>任何 LangGraph &lt;code>CompiledStateGraph&lt;/code> 都可以作为子智能体传入：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">langgraph.graph&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">StateGraph&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">MessagesState&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">END&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 构建一个自定义图&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">workflow&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">StateGraph&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">MessagesState&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">workflow&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">add_node&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;analyze&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">analyze_node&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">workflow&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">add_node&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;report&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">report_node&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">workflow&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">set_entry_point&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;analyze&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">workflow&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">add_edge&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;analyze&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;report&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">workflow&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">add_edge&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;report&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">END&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">custom_graph&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">workflow&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">compile&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">agent&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">create_deep_agent&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">model&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">model&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">subagents&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;name&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;custom-analyst&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;description&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;自定义分析管道&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;graph&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">custom_graph&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">],&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="53-主子上下文隔离原理">5.3 主/子上下文隔离原理
&lt;/h3>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-gdscript3" data-lang="gdscript3">&lt;span class="line">&lt;span class="cl">&lt;span class="err">主&lt;/span> &lt;span class="n">Agent&lt;/span> &lt;span class="err">上下文&lt;/span> &lt;span class="err">子&lt;/span> &lt;span class="n">Agent&lt;/span> &lt;span class="err">上下文&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">┌─────────────────────┐&lt;/span> &lt;span class="err">┌─────────────────────────────┐&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">│&lt;/span> &lt;span class="n">system&lt;/span> &lt;span class="n">prompt&lt;/span> &lt;span class="err">│&lt;/span> &lt;span class="err">│&lt;/span> &lt;span class="err">子智能体的&lt;/span> &lt;span class="n">system&lt;/span> &lt;span class="n">prompt&lt;/span> &lt;span class="err">│&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">│&lt;/span> &lt;span class="n">user&lt;/span> &lt;span class="n">message&lt;/span> &lt;span class="err">│&lt;/span> &lt;span class="err">│&lt;/span> &lt;span class="n">task&lt;/span> &lt;span class="n">description&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="err">作为输入&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="err">│&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">│&lt;/span> &lt;span class="o">...&lt;/span> &lt;span class="err">历史消息&lt;/span> &lt;span class="o">...&lt;/span> &lt;span class="err">│&lt;/span> &lt;span class="err">│&lt;/span> &lt;span class="err">子智能体的工具和文件系统&lt;/span> &lt;span class="err">│&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">│&lt;/span> &lt;span class="k">tool&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">task&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="o">...&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="err">│──委派──────────→│&lt;/span> &lt;span class="err">│&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">│&lt;/span> &lt;span class="err">│&lt;/span> &lt;span class="err">│&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="err">子&lt;/span> &lt;span class="n">agent&lt;/span> &lt;span class="err">内部循环&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="err">│&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">│&lt;/span> &lt;span class="err">│←───────────────│&lt;/span> &lt;span class="err">最终输出（文本摘要）&lt;/span> &lt;span class="err">│&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">│&lt;/span> &lt;span class="k">tool&lt;/span> &lt;span class="n">result&lt;/span> &lt;span class="err">│&lt;/span> &lt;span class="err">└─────────────────────────────┘&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">│&lt;/span> &lt;span class="o">...&lt;/span> &lt;span class="err">继续&lt;/span> &lt;span class="o">...&lt;/span> &lt;span class="err">│&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">└─────────────────────┘&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;ul>
&lt;li>子 agent 看不到主 agent 的对话历史，只看到任务描述。&lt;/li>
&lt;li>主 agent 看不到子 agent 的中间 tool calls、搜索结果、文件内容。&lt;/li>
&lt;li>子 agent 的文件系统与主 agent &lt;strong>共享&lt;/strong>（默认使用同一个 backend）。&lt;/li>
&lt;li>如果子 agent 需要向主 agent 传递大文件，通过文件系统写入，主 agent 用 &lt;code>read_file&lt;/code> 读取。&lt;/li>
&lt;/ul>
&lt;h3 id="54-什么时候用子智能体">5.4 什么时候用子智能体
&lt;/h3>&lt;table>
&lt;thead>
&lt;tr>
&lt;th>场景&lt;/th>
&lt;th>是否需要子智能体&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>简单搜索 + 回答&lt;/td>
&lt;td>不需要，一个 agent + web_search 就够&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>多步调研：搜索多个主题、阅读多个来源、综合报告&lt;/td>
&lt;td>需要——用 researcher 子智能体隔离搜索噪音&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>多文件代码修改&lt;/td>
&lt;td>需要——用 coder 子智能体隔离文件操作&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>多源数据采集和分析&lt;/td>
&lt;td>需要——不同子智能体处理不同数据源&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>单一任务，不需要委派&lt;/td>
&lt;td>不需要，直接用主 agent&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;h3 id="55-异步子智能体并行委派">5.5 异步子智能体：并行委派
&lt;/h3>&lt;p>DeepAgents 同时提供同步和异步两套接口。当需要并行处理多个独立任务时，可以在异步运行环境中用 &lt;code>asyncio.gather&lt;/code> 同时发起多个调用，每个子智能体在自己的隔离上下文中工作，主流程只汇总最终结果。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">asyncio&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 同一 agent 的多个独立任务并行执行&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">results&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">await&lt;/span> &lt;span class="n">asyncio&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">gather&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">agent&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">ainvoke&lt;/span>&lt;span class="p">({&lt;/span>&lt;span class="s2">&amp;#34;messages&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">[(&lt;/span>&lt;span class="s2">&amp;#34;user&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;研究主题 A&amp;#34;&lt;/span>&lt;span class="p">)]},&lt;/span> &lt;span class="n">config&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">agent&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">ainvoke&lt;/span>&lt;span class="p">({&lt;/span>&lt;span class="s2">&amp;#34;messages&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">[(&lt;/span>&lt;span class="s2">&amp;#34;user&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;研究主题 B&amp;#34;&lt;/span>&lt;span class="p">)]},&lt;/span> &lt;span class="n">config&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>如果子智能体内部执行的是 I/O 密集操作（网络搜索、文件读写），并行能显著缩短总耗时。注意每个并行调用会消耗独立的模型调用和 token 配额；&lt;code>asyncio.gather&lt;/code> 默认在任一任务异常时取消全部，生产代码建议加 &lt;code>return_exceptions=True&lt;/code> 后逐个检查。&lt;/p>
&lt;h3 id="56-多级子智能体">5.6 多级子智能体
&lt;/h3>&lt;p>子智能体的 &lt;code>subagents&lt;/code> 字段可以继续传入子智能体配置，形成&amp;quot;主 agent → 一级子 agent → 二级子 agent&amp;quot;的层级结构。每一层都有独立的上下文窗口，适合大型项目中&amp;quot;项目经理 → 领域专家 → 执行者&amp;quot;的分工模式。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="n">deep_researcher&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">SubAgent&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">name&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;deep-researcher&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">model&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;openai:gpt-5.5&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">system_prompt&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;深入研究并写报告&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">subagents&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;subagents&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">SubAgent&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">name&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;fact-checker&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">model&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;openai:gpt-5.5&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">system_prompt&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;验证研究结论中的关键事实&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">],&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>层级不宜过深（建议不超过 2-3 层）：每加一层，主 agent 看到的信息就更间接，且 token 消耗按乘数增长。&lt;/p>
&lt;hr>
&lt;h2 id="第六章上下文管理">第六章：上下文管理
&lt;/h2>&lt;p>DeepAgents 内置了两层上下文管理机制：&lt;strong>摘要压缩&lt;/strong>和&lt;strong>大工具输出转存&lt;/strong>。&lt;/p>
&lt;h3 id="61-summarizationmiddleware对话摘要压缩">6.1 SummarizationMiddleware：对话摘要压缩
&lt;/h3>&lt;p>当对话的 token 数超过阈值时，SummarizationMiddleware 会：&lt;/p>
&lt;ol>
&lt;li>将旧消息替换为一条摘要消息&lt;/li>
&lt;li>将完整的旧消息转存到文件系统（路径如 &lt;code>/conversation_history/{message_id}.json&lt;/code>）&lt;/li>
&lt;li>保留最近的消息在上下文中&lt;/li>
&lt;/ol>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">压缩前：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">[sys] [msg1] [msg2] [msg3] [msg4] [msg5] ... [msgN]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ↑ 全部保留在上下文中
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">压缩后：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">[sys] [摘要消息（包含历史概要+文件路径）] [msgN-2] [msgN-1] [msgN]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ↑ 完整历史在 /conversation_history/
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>阈值配置：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">deepagents&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">create_deep_agent&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">deepagents.middleware.summarization&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">SummarizationMiddleware&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">agent&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">create_deep_agent&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">model&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">model&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">tools&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">tools&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">middleware&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">SummarizationMiddleware&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">token_threshold&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="mi">100000&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="c1"># 超过 100k token 触发压缩&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">keep_messages&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="mi">10&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="c1"># 保留最近 10 条消息&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">],&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>阈值默认值根据模型的最大输入 token 自动计算（官方口径是达到模型上下文窗口的约 85%）。例如：&lt;/p>
&lt;ul>
&lt;li>GPT-5（128k context）→ 阈值约 100k token&lt;/li>
&lt;li>Claude（200k context）→ 阈值约 160k token&lt;/li>
&lt;li>回退方案：保留最近 20 条消息&lt;/li>
&lt;/ul>
&lt;h4 id="按需压缩让-agent-自己决定什么时候压">按需压缩：让 agent 自己决定什么时候压
&lt;/h4>&lt;p>自动摘要只在逼近阈值时才动手，但有些场景你想让它&lt;strong>主动&lt;/strong>在任务边界处压缩——比如一个子任务刚交付完，历史已经没用但离阈值还远。&lt;/p>
&lt;p>加一个 &lt;code>compact_conversation&lt;/code> 工具就能做到：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">deepagents&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">create_deep_agent&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">deepagents.backends&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">StateBackend&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">deepagents.middleware.summarization&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">create_summarization_tool_middleware&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">backend&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">StateBackend&lt;/span> &lt;span class="c1"># 用默认后端时这样写&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">agent&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">create_deep_agent&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">model&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">model&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">middleware&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">create_summarization_tool_middleware&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">model&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">backend&lt;/span>&lt;span class="p">)],&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>注意这不是替代品：&lt;strong>加上它并不会关掉 85% 的自动压缩&lt;/strong>，两者共用同一个摘要引擎和状态。自定义中间件会被插到 &lt;code>PatchToolCallsMiddleware&lt;/code> 之后（也就是 2.1 节栈里的位置 7）。&lt;/p>
&lt;h3 id="62-大工具输出转存">6.2 大工具输出转存
&lt;/h3>&lt;p>FilesystemMiddleware 监控所有工具的输出。当输出超过阈值（默认 20000 token）时：&lt;/p>
&lt;ol>
&lt;li>将完整输出写入文件系统（路径如 &lt;code>/large_tool_results/{tool_call_id}.txt&lt;/code>）&lt;/li>
&lt;li>在上下文中只保留摘要消息：&lt;code>[输出过大，已转存到 /large_tool_results/xxx.txt，请用 read_file 读取]&lt;/code>&lt;/li>
&lt;/ol>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 你可以自定义阈值&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">deepagents.middleware.filesystem&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">FilesystemMiddleware&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">fs_mw&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">FilesystemMiddleware&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">large_output_threshold&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="mi">10000&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="c1"># 10000 token 超限&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">large_output_dir&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;/my_tool_results&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">agent&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">create_deep_agent&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">model&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">model&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">middleware&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">fs_mw&lt;/span>&lt;span class="p">])&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="63-记忆与跨会话">6.3 记忆与跨会话
&lt;/h3>&lt;p>通过 &lt;code>memory&lt;/code> 参数加载 AGENTS.md：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="n">agent&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">create_deep_agent&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">model&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">model&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">tools&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">tools&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">memory&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;/path/to/AGENTS.md&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="c1"># 启动时注入到系统提示词&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>对于需要动态更新的长期记忆，使用 StoreBackend：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">deepagents.backends&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">StoreBackend&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">langgraph.store.memory&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">InMemoryStore&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">store&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">InMemoryStore&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">memory_backend&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">StoreBackend&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">store&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">store&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">namespace&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;agent_memory&amp;#34;&lt;/span>&lt;span class="p">,))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">agent&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">create_deep_agent&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">model&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">model&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">backend&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">memory_backend&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">system_prompt&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;你是一个有记忆的助手。在 /memory 目录中维护长期记忆。&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Agent 可以通过文件工具读写 &lt;code>/memory/&lt;/code> 目录，实现跨会话的记忆更新。&lt;/p>
&lt;hr>
&lt;h2 id="第七章skills技能系统">第七章：Skills（技能系统）
&lt;/h2>&lt;p>Skills 是 DeepAgents 的渐进式披露机制——将复杂的指令和工具组合打包成可复用的模块，按需加载。&lt;/p>
&lt;h3 id="71-基本用法">7.1 基本用法
&lt;/h3>&lt;p>每个技能是一个目录，包含一个 &lt;code>SKILL.md&lt;/code> 文件：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">my_skills/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">├── code-review/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ └── SKILL.md
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">├── data-viz/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ ├── SKILL.md
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ └── templates/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ └── chart.py
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">└── web-research/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> └── SKILL.md
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;code>SKILL.md&lt;/code> 的格式：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-markdown" data-lang="markdown">&lt;span class="line">&lt;span class="cl">---
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">name: code-review
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">description: 对代码库进行系统性审查，检查安全性、性能、可维护性
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">---
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="gu">## 步骤
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="gu">&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">1.&lt;/span> 读取目标文件列表（用 glob 工具）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">2.&lt;/span> 逐一读取关键文件
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">3.&lt;/span> 检查以下方面：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">-&lt;/span> 安全性：SQL注入、XSS、不安全的反序列化
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">-&lt;/span> 性能：N+1 查询、内存泄漏、不必要的全表扫描
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">-&lt;/span> 可维护性：函数长度、重复代码、缺失的类型注解
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">4.&lt;/span> 用 write_file 工具输出审查报告到 /reports/code-review-{date}.md
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="gu">## 工具
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="gu">&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">优先使用 grep 搜索特定模式，用 read_file 读取上下文。
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="n">agent&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">create_deep_agent&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">model&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">model&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">tools&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">tools&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">skills&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;/path/to/my_skills&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>SkillsMiddleware 会将每个技能的 &lt;code>name&lt;/code> 和 &lt;code>description&lt;/code> 注入系统提示词。Agent 判断当前任务匹配某个技能时，会主动 &lt;code>read_file&lt;/code> 读取该技能的完整指令。&lt;/p>
&lt;h3 id="72-为什么是渐进式披露">7.2 为什么是&amp;quot;渐进式披露&amp;quot;
&lt;/h3>&lt;p>如果所有技能的完整内容都塞在系统提示词里，token 浪费严重。渐进式披露的设计是：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>系统提示词只包含元数据&lt;/strong>（name + description），几十个 token 每技能。&lt;/li>
&lt;li>&lt;strong>完整指令在文件系统中&lt;/strong>，agent 按需读取。&lt;/li>
&lt;li>10 个技能的元数据 ≈ 300 token，比全量内容（可能几千 token）节省 90% 以上。&lt;/li>
&lt;/ul>
&lt;h3 id="73-带资源的技能">7.3 带资源的技能
&lt;/h3>&lt;p>技能目录中可以包含额外文件（模板、脚本、配置等），agent 可以通过文件系统工具访问：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">my_skills/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">└── report-generator/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ├── SKILL.md
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ├── templates/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │ ├── report_template.md
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │ └── chart_template.py
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> └── config.yaml
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Agent 读取 &lt;code>SKILL.md&lt;/code> 后，可以根据指令进一步读取 &lt;code>templates/&lt;/code> 中的文件。&lt;/p>
&lt;h3 id="74-社区技能示例研究评估评分标准">7.4 社区技能示例：研究评估评分标准
&lt;/h3>&lt;p>官方仓库提供了多个可复用的技能示例。下面是一个&amp;quot;研究评估&amp;quot;技能（rubrics 模式）的典型结构——把评审专家的评分维度固化成可复用的指令文件：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-markdown" data-lang="markdown">&lt;span class="line">&lt;span class="cl">---
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">name: evaluate-research
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">description: 用评分标准评估研究报告质量
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">---
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="gu">## 评分标准
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="gu">&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">按以下维度评估研究报告（每项 1-5 分）：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">1.&lt;/span> &lt;span class="gs">**事实准确性**&lt;/span>：关键事实是否有可靠来源支撑
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">2.&lt;/span> &lt;span class="gs">**逻辑连贯性**&lt;/span>：结论是否从证据自然推出
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">3.&lt;/span> &lt;span class="gs">**完整性**&lt;/span>：是否遗漏了重要角度
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">4.&lt;/span> &lt;span class="gs">**可操作性**&lt;/span>：读者能否据此做出决策
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="gu">## 流程
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="gu">&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">1.&lt;/span> 阅读研究报告全文
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">2.&lt;/span> 逐项打分并给出具体依据
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">3.&lt;/span> 汇总总分和改进建议
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>将此文件放入技能目录后，agent 在收到&amp;quot;评估报告&amp;quot;类任务时会自动加载该技能。你可以基于自己的领域编写类似的评分模板——技能系统的本质就是把&amp;quot;怎么做&amp;quot;的专家知识封装成可复用的指令文件。&lt;/p>
&lt;hr>
&lt;h2 id="第八章human-in-the-loop人机协作">第八章：Human-in-the-Loop（人机协作）
&lt;/h2>&lt;p>DeepAgents 允许在工具调用前暂停，等待人工决策。这在生产环境中至关重要——你不希望 agent 在没有人工审核的情况下执行危险操作（删除文件、发送邮件、执行数据库变更等）。&lt;/p>
&lt;h3 id="81-interrupt_on-参数">8.1 &lt;code>interrupt_on&lt;/code> 参数
&lt;/h3>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="n">agent&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">create_deep_agent&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">model&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">model&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">tools&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">send_email&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">delete_record&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">web_search&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">write_file&lt;/span>&lt;span class="p">],&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">interrupt_on&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;send_email&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">{&lt;/span>&lt;span class="s2">&amp;#34;allowed_decisions&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;approve&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;edit&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;reject&amp;#34;&lt;/span>&lt;span class="p">]},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;delete_record&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">{&lt;/span>&lt;span class="s2">&amp;#34;allowed_decisions&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;approve&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;reject&amp;#34;&lt;/span>&lt;span class="p">]},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 第一次 invoke 会触发中断&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">result&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">agent&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">invoke&lt;/span>&lt;span class="p">({&lt;/span>&lt;span class="s2">&amp;#34;messages&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="o">...&lt;/span>&lt;span class="p">]})&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 检查是否中断&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">if&lt;/span> &lt;span class="n">result&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;__interrupt__&amp;#34;&lt;/span>&lt;span class="p">]:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># 获取中断信息&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">interrupt_info&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">result&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;__interrupt__&amp;#34;&lt;/span>&lt;span class="p">][&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;工具: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">interrupt_info&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">value&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s1">&amp;#39;action_request&amp;#39;&lt;/span>&lt;span class="p">][&lt;/span>&lt;span class="s1">&amp;#39;action&amp;#39;&lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;参数: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">interrupt_info&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">value&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s1">&amp;#39;action_request&amp;#39;&lt;/span>&lt;span class="p">][&lt;/span>&lt;span class="s1">&amp;#39;args&amp;#39;&lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># 人工决策&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">human_decision&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">interrupt_info&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">value&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;resume&amp;#34;&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># 或者用 resume 重新启动&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">resumed&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">agent&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">invoke&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>&lt;span class="s2">&amp;#34;__interrupt__&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">[{&lt;/span>&lt;span class="s2">&amp;#34;resume&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">{&lt;/span>&lt;span class="s2">&amp;#34;type&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;accept&amp;#34;&lt;/span>&lt;span class="p">}}]},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">config&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">config&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="82-通过-permissions-实现文件操作审批">8.2 通过 permissions 实现文件操作审批
&lt;/h3>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="n">agent&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">create_deep_agent&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">model&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">model&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">backend&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">backend&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">permissions&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>&lt;span class="s2">&amp;#34;path&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;/workspace/*&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;mode&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;allow&amp;#34;&lt;/span>&lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>&lt;span class="s2">&amp;#34;path&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;/*&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;operations&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;write&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;edit&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;delete&amp;#34;&lt;/span>&lt;span class="p">],&lt;/span> &lt;span class="s2">&amp;#34;mode&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;interrupt&amp;#34;&lt;/span>&lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">],&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>所有 workspace 外的写操作都会触发人工审批。&lt;/p>
&lt;h3 id="83-中断恢复协议">8.3 中断恢复协议
&lt;/h3>&lt;p>DeepAgents 的 HITL 基于 LangGraph 的 interrupt 机制。中断后：&lt;/p>
&lt;ol>
&lt;li>图的状态（包括 messages、files）已通过 checkpointer 保存。&lt;/li>
&lt;li>&lt;code>agent.invoke()&lt;/code> 返回 &lt;code>__interrupt__&lt;/code> 信息。&lt;/li>
&lt;li>人工决策通过 &lt;code>agent.invoke()&lt;/code> resume：&lt;/li>
&lt;/ol>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="n">config&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span>&lt;span class="s2">&amp;#34;configurable&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">{&lt;/span>&lt;span class="s2">&amp;#34;thread_id&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;session-1&amp;#34;&lt;/span>&lt;span class="p">}}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 第一次调用，触发中断&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">result1&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">agent&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">invoke&lt;/span>&lt;span class="p">({&lt;/span>&lt;span class="s2">&amp;#34;messages&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="o">...&lt;/span>&lt;span class="p">]},&lt;/span> &lt;span class="n">config&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">config&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 检查中断并恢复&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">if&lt;/span> &lt;span class="s2">&amp;#34;__interrupt__&amp;#34;&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">result1&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">decision&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nb">input&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;批准/编辑/拒绝？(approve/edit/reject): &amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">result2&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">agent&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">invoke&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">Command&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">resume&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="s2">&amp;#34;type&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">decision&lt;/span>&lt;span class="p">}),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">config&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">config&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>支持的决策类型：&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>决策&lt;/th>
&lt;th>行为&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;code>{&amp;quot;type&amp;quot;: &amp;quot;accept&amp;quot;}&lt;/code>&lt;/td>
&lt;td>批准，继续执行工具调用&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>{&amp;quot;type&amp;quot;: &amp;quot;edit&amp;quot;, &amp;quot;args&amp;quot;: {...}}&lt;/code>&lt;/td>
&lt;td>编辑参数后继续执行&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>{&amp;quot;type&amp;quot;: &amp;quot;reject&amp;quot;, &amp;quot;message&amp;quot;: &amp;quot;...&amp;quot;}&lt;/code>&lt;/td>
&lt;td>拒绝，将拒绝理由传回给 agent&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>{&amp;quot;type&amp;quot;: &amp;quot;respond&amp;quot;, &amp;quot;message&amp;quot;: &amp;quot;...&amp;quot;}&lt;/code>&lt;/td>
&lt;td>不执行工具，向 agent 提供额外信息&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;hr>
&lt;h2 id="第九章流式输出与中间过程">第九章：流式输出与中间过程
&lt;/h2>&lt;p>DeepAgents 基于 LangGraph，支持多种流式模式：&lt;/p>
&lt;h3 id="91-流式-token">9.1 流式 token
&lt;/h3>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">langchain_core.messages&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">AIMessageChunk&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">config&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span>&lt;span class="s2">&amp;#34;configurable&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">{&lt;/span>&lt;span class="s2">&amp;#34;thread_id&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;session-1&amp;#34;&lt;/span>&lt;span class="p">}}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">for&lt;/span> &lt;span class="n">event&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">agent&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">stream&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>&lt;span class="s2">&amp;#34;messages&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">[{&lt;/span>&lt;span class="s2">&amp;#34;role&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;user&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;content&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;分析这份数据&amp;#34;&lt;/span>&lt;span class="p">}]},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">config&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">config&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">stream_mode&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;messages&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># event 是 (message_chunk, metadata) 元组&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">msg_chunk&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">metadata&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">event&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="nb">hasattr&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">msg_chunk&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;content&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="ow">and&lt;/span> &lt;span class="nb">isinstance&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">msg_chunk&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">content&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">msg_chunk&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">content&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">end&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">flush&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="kc">True&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="92-流式工具调用">9.2 流式工具调用
&lt;/h3>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="k">for&lt;/span> &lt;span class="n">event&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">agent&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">stream&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">input_data&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">config&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">config&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">stream_mode&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;updates&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># 每个节点的更新&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="n">node_name&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">node_update&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">event&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">items&lt;/span>&lt;span class="p">():&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;节点: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">node_name&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="s2">&amp;#34;messages&amp;#34;&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">node_update&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="n">msg&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">node_update&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;messages&amp;#34;&lt;/span>&lt;span class="p">]:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34; &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">msg&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">type&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">msg&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">content&lt;/span>&lt;span class="p">[:&lt;/span>&lt;span class="mi">100&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="k">if&lt;/span> &lt;span class="nb">isinstance&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">msg&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">content&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="k">else&lt;/span> &lt;span class="n">msg&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">content&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="93-流式文件操作">9.3 流式文件操作
&lt;/h3>&lt;p>通过 &lt;code>stream_mode=&amp;quot;updates&amp;quot;&lt;/code>，可以看到文件系统的变化：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="k">for&lt;/span> &lt;span class="n">event&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">agent&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">stream&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">input_data&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">config&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">config&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">stream_mode&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;updates&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="n">node_name&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">node_update&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">event&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">items&lt;/span>&lt;span class="p">():&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="s2">&amp;#34;files&amp;#34;&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">node_update&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;文件变化: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">node_update&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s1">&amp;#39;files&amp;#39;&lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">keys&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="94-事件流v3stream_events-与类型化投影">9.4 事件流（v3）：&lt;code>stream_events&lt;/code> 与类型化投影
&lt;/h3>&lt;p>上面几种 &lt;code>stream_mode&lt;/code> 是 LangGraph 的&lt;strong>原始&lt;/strong>流式接口——你拿到的是 dict，要自己判断这一块是什么。LangGraph 1.2.0 / DeepAgents 0.6.0 起多了一套更适合应用层写代码的东西：&lt;strong>事件流&lt;/strong>（event streaming），传 &lt;code>version=&amp;quot;v3&amp;quot;&lt;/code> 开启。&lt;/p>
&lt;p>核心变化是：一次 run 产出的事件流被归一化后，经过一层 transformer 管线，暴露成几个&lt;strong>带类型的投影&lt;/strong>，可以同时被多个消费者读——读 &lt;code>stream.messages&lt;/code> 不会消耗 &lt;code>stream.values&lt;/code> 需要的事件。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="n">stream&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">agent&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">stream_events&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>&lt;span class="s2">&amp;#34;messages&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">[{&lt;/span>&lt;span class="s2">&amp;#34;role&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;user&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;content&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;分析这份数据&amp;#34;&lt;/span>&lt;span class="p">}]},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">version&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;v3&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 只看模型输出：token 级&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">for&lt;/span> &lt;span class="n">message&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">stream&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">messages&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nb">str&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">message&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">text&lt;/span>&lt;span class="p">),&lt;/span> &lt;span class="n">end&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">flush&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="kc">True&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 最终状态&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">final_state&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">stream&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">output&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>可用的投影：&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>投影&lt;/th>
&lt;th>用途&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;code>stream&lt;/code>&lt;/td>
&lt;td>遍历所有协议事件（原始层）&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>stream.messages&lt;/code>&lt;/td>
&lt;td>模型消息与 token 增量，另有 &lt;code>.reasoning&lt;/code> 和 &lt;code>.tool_calls&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>stream.values&lt;/code>&lt;/td>
&lt;td>每一步之后的状态快照，&lt;code>stream.output&lt;/code> 拿最终值&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>stream.subgraphs&lt;/code>&lt;/td>
&lt;td>发现并观察嵌套图/子智能体执行&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>stream.interrupts&lt;/code> / &lt;code>stream.interrupted&lt;/code>&lt;/td>
&lt;td>人机协作中断的载荷与判定&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>stream.extensions&lt;/code>&lt;/td>
&lt;td>自定义 transformer 的投影&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>对 DeepAgents 来说，&lt;code>stream.subgraphs&lt;/code> 特别有用——&lt;strong>子智能体的执行会作为嵌套作用域出现在这里&lt;/strong>，直接看 &lt;code>subgraph.graph_name&lt;/code> 和 &lt;code>subgraph.path&lt;/code> 就行，不用去解析 namespace 字符串：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="n">stream&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">agent&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">stream_events&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">input_data&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">config&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">config&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">version&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;v3&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">for&lt;/span> &lt;span class="n">subgraph&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">stream&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">subgraphs&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">subgraph&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">graph_name&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">subgraph&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">path&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="n">message&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">subgraph&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">messages&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">message&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">text&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>异步场景下可以并发消费多个投影：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="n">stream&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">await&lt;/span> &lt;span class="n">agent&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">astream_events&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">input_data&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">version&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;v3&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">async&lt;/span> &lt;span class="k">def&lt;/span> &lt;span class="nf">consume_messages&lt;/span>&lt;span class="p">():&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">async&lt;/span> &lt;span class="k">for&lt;/span> &lt;span class="n">message&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">stream&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">messages&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;[llm] node=&lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">message&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">node&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">async&lt;/span> &lt;span class="k">def&lt;/span> &lt;span class="nf">consume_subgraphs&lt;/span>&lt;span class="p">():&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">async&lt;/span> &lt;span class="k">for&lt;/span> &lt;span class="n">subgraph&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">stream&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">subgraphs&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;[subgraph] path=&lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">subgraph&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">path&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">await&lt;/span> &lt;span class="n">asyncio&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">gather&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">consume_messages&lt;/span>&lt;span class="p">(),&lt;/span> &lt;span class="n">consume_subgraphs&lt;/span>&lt;span class="p">())&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>同步代码想在&lt;strong>严格的到达顺序&lt;/strong>里混着读多个投影，用 &lt;code>stream.interleave(...)&lt;/code>：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="k">for&lt;/span> &lt;span class="n">name&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">item&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">stream&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">interleave&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;values&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;messages&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;subgraphs&amp;#34;&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">name&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="s2">&amp;#34;values&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;[state] keys=&lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="nb">list&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">item&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">elif&lt;/span> &lt;span class="n">name&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="s2">&amp;#34;messages&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;[llm] node=&lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">item&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">node&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">elif&lt;/span> &lt;span class="n">name&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="s2">&amp;#34;subgraphs&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;[subgraph] path=&lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">item&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">path&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>底层通道（&lt;code>event[&amp;quot;method&amp;quot;]&lt;/code>）有 &lt;code>values&lt;/code>、&lt;code>updates&lt;/code>、&lt;code>messages&lt;/code>、&lt;code>tools&lt;/code>、&lt;code>lifecycle&lt;/code>、&lt;code>checkpoints&lt;/code>、&lt;code>input&lt;/code>、&lt;code>tasks&lt;/code>、&lt;code>custom&lt;/code>。其中 &lt;code>messages&lt;/code> 是按 &lt;strong>content block&lt;/strong> 建模的，事件序列固定为 &lt;code>message-start&lt;/code> → &lt;code>content-block-start&lt;/code> → &lt;code>content-block-delta&lt;/code> → &lt;code>content-block-finish&lt;/code> → &lt;code>message-finish&lt;/code>，文本、推理、工具调用、多模态内容都有显式边界，不再依赖各家 provider 的格式。&lt;/p>
&lt;p>&lt;code>v1&lt;/code> 和 &lt;code>v2&lt;/code> 的写法保持兼容，没有破坏性变更——现有代码不用动，新代码建议直接从 v3 起步。&lt;/p>
&lt;hr>
&lt;h2 id="第十章生产部署">第十章：生产部署
&lt;/h2>&lt;h3 id="101-部署选项">10.1 部署选项
&lt;/h3>&lt;table>
&lt;thead>
&lt;tr>
&lt;th>方案&lt;/th>
&lt;th>适用场景&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>LangGraph Platform&lt;/td>
&lt;td>托管部署，LangSmith 集成，自动扩缩&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Docker + 自建&lt;/td>
&lt;td>需要完全控制的私有化部署&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Serverless（Vercel/Modal/Cloud Run）&lt;/td>
&lt;td>事件驱动、低流量场景&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>LangSmith Deployment&lt;/td>
&lt;td>与 LangSmith 评估和监控深度集成&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;h3 id="102-docker-部署示例">10.2 Docker 部署示例
&lt;/h3>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-dockerfile" data-lang="dockerfile">&lt;span class="line">&lt;span class="cl">&lt;span class="k">FROM&lt;/span>&lt;span class="s"> python:3.12-slim&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="k">WORKDIR&lt;/span>&lt;span class="s"> /app&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="c"># 安装依赖&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="k">COPY&lt;/span> pyproject.toml uv.lock ./&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="k">RUN&lt;/span> pip install uv &lt;span class="o">&amp;amp;&amp;amp;&lt;/span> uv sync --frozen&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="c"># 复制代码&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="k">COPY&lt;/span> . .&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="c"># 暴露端口&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="k">EXPOSE&lt;/span>&lt;span class="s"> 8000&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="c"># 启动（LangGraph serve）&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="k">CMD&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;uv&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;run&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;langgraph&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;serve&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;--host&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;0.0.0.0&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;--port&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;8000&amp;#34;&lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="103-环境变量">10.3 环境变量
&lt;/h3>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 模型 API Key&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">OPENAI_API_KEY&lt;/span>&lt;span class="o">=&lt;/span>sk-...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">ANTHROPIC_API_KEY&lt;/span>&lt;span class="o">=&lt;/span>sk-...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># LangSmith（可选，但强烈推荐）&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">LANGSMITH_TRACING&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="nb">true&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">LANGSMITH_API_KEY&lt;/span>&lt;span class="o">=&lt;/span>lsv2_...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">LANGSMITH_PROJECT&lt;/span>&lt;span class="o">=&lt;/span>my-agent
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 数据库（生产 checkpointer）&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">DATABASE_URL&lt;/span>&lt;span class="o">=&lt;/span>postgresql://user:pass@host:5432/db
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="104-安全模型">10.4 安全模型
&lt;/h3>&lt;p>DeepAgents 遵循&amp;quot;信任 LLM&amp;quot;模型。Agent 可以做任何工具允许的事情。安全边界必须在你这一侧强制执行：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>后端层面&lt;/strong>：使用 SandboxBackend 或 FilesystemBackend 的 &lt;code>root_dir&lt;/code> 限制 agent 能访问的目录。&lt;/li>
&lt;li>&lt;strong>权限层面&lt;/strong>：使用 &lt;code>permissions&lt;/code> 参数控制读写和删除操作。&lt;/li>
&lt;li>&lt;strong>工具层面&lt;/strong>：不要暴露你不想让 agent 执行的工具。&lt;/li>
&lt;li>&lt;strong>HITL 层面&lt;/strong>：对危险操作（删除、发送、支付）设置 &lt;code>interrupt_on&lt;/code>。&lt;/li>
&lt;li>&lt;strong>网络层面&lt;/strong>：如果 agent 有 web 访问能力，确保它不能访问内网端点。&lt;/li>
&lt;/ul>
&lt;h3 id="105-可观测性">10.5 可观测性
&lt;/h3>&lt;p>LangSmith 集成：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">&lt;span class="nb">export&lt;/span> &lt;span class="nv">LANGSMITH_TRACING&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="nb">true&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">export&lt;/span> &lt;span class="nv">LANGSMITH_API_KEY&lt;/span>&lt;span class="o">=&lt;/span>your_key
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">export&lt;/span> &lt;span class="nv">LANGSMITH_PROJECT&lt;/span>&lt;span class="o">=&lt;/span>production-agent
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>设置环境变量后，所有 agent 调用自动产生 trace。无需改代码。&lt;/p>
&lt;h3 id="106-评测">10.6 评测
&lt;/h3>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">langsmith&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">Client&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">client&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">Client&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 创建评测数据集&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">dataset&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">client&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">create_dataset&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;deep-agents-eval&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">client&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">create_examples&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">dataset_id&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">dataset&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">id&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">inputs&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>&lt;span class="s2">&amp;#34;messages&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">[{&lt;/span>&lt;span class="s2">&amp;#34;role&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;user&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;content&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;调研 X 并写报告&amp;#34;&lt;/span>&lt;span class="p">}]},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>&lt;span class="s2">&amp;#34;messages&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">[{&lt;/span>&lt;span class="s2">&amp;#34;role&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;user&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;content&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;修复 bug #123&amp;#34;&lt;/span>&lt;span class="p">}]},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">],&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 运行评测&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">client&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">evaluate&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">agent&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">data&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">dataset&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">id&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">evaluators&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># 自定义评估器&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">lambda&lt;/span> &lt;span class="n">outputs&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">reference&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">{&lt;/span>&lt;span class="s2">&amp;#34;accuracy&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">compute_match&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">outputs&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">reference&lt;/span>&lt;span class="p">)},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">],&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;hr>
&lt;h2 id="第十一章实战模式">第十一章：实战模式
&lt;/h2>&lt;h3 id="111-研究智能体">11.1 研究智能体
&lt;/h3>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">deepagents&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">create_deep_agent&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">deepagents.subagents&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">SubAgent&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">langchain.tools&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">tool&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nd">@tool&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">web_search&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">query&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;搜索互联网获取信息&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># 接入 Tavily / Bocha / 百炼 WebSearch MCP&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">...&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nd">@tool&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">web_extract&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">url&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;抽取网页正文内容&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">...&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">research_subagent&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">SubAgent&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">name&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;researcher&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">description&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;搜索和整理信息&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">system_prompt&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;你是一个信息研究员。用 web_search 和 web_extract 完成调研。&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">model&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;openai:gpt-5.5&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">tools&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">web_search&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">web_extract&lt;/span>&lt;span class="p">],&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">agent&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">create_deep_agent&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">model&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;openai:gpt-5.5&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">subagents&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">research_subagent&lt;/span>&lt;span class="p">],&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">system_prompt&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;你是一个研究助手。
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"> 收到调研任务后，用 task 工具委派给 researcher 子智能体。
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"> 基于子智能体的输出，综合并生成最终回答。&amp;#34;&amp;#34;&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="112-代码智能体">11.2 代码智能体
&lt;/h3>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">deepagents.backends.filesystem&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">FilesystemBackend&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">backend&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">FilesystemBackend&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">root_dir&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;/path/to/repo&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">agent&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">create_deep_agent&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">model&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;anthropic:claude-sonnet-4-6&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">backend&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">backend&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">system_prompt&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;你是一个代码编辑助手。
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"> 先用 glob 和 grep 了解代码库结构。
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"> 修改代码前先 read_file 阅读上下文。
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"> 修改后用 execute 工具运行测试验证。
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"> 修改必须最小化，不要过度工程化。&amp;#34;&amp;#34;&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="113-数据分析智能体">11.3 数据分析智能体
&lt;/h3>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">deepagents.backends&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">SandboxBackend&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">backend&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">SandboxBackend&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">root_dir&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;/tmp/data_sandbox&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">timeout&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="mi">300&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">agent&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">create_deep_agent&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">model&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;openai:gpt-5.5&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">backend&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">backend&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">system_prompt&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;你是一个数据分析师。
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"> 先用 execute(&amp;#34;ls /&amp;#34;) 查看数据文件。
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"> 用 execute(&amp;#34;python -c &lt;/span>&lt;span class="se">\&amp;#34;&lt;/span>&lt;span class="s2">...&lt;/span>&lt;span class="se">\\&lt;/span>&lt;span class="s2">&amp;#34;&amp;#34;) 或 execute(&amp;#34;head -n 100 data.csv&amp;#34;) 探索数据。
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"> 用 execute(&amp;#34;python analysis.py&amp;#34;) 执行分析脚本。
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"> 用 write_file 生成分析报告。&amp;#34;&amp;#34;&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="114-混合模式deepagents--百炼-responses-api">11.4 混合模式：DeepAgents + 百炼 Responses API
&lt;/h3>&lt;p>当你需要 DeepAgents 的 harness 能力 + 百炼的 web_search：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">os&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">openai&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">OpenAI&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">deepagents&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">create_deep_agent&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">langchain_openai&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">ChatOpenAI&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 百炼 Responses API 客户端（用于 web_search）&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">bailian&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">OpenAI&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">api_key&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">os&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">getenv&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;DASHSCOPE_API_KEY&amp;#34;&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">base_url&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;https://dashscope.aliyuncs.com/compatible-mode/v1&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nd">@tool&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">bailian_web_search&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">query&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;搜索互联网获取最新信息（使用百炼 web_search）&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">response&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">bailian&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">responses&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">create&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">model&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;qwen3-max&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">input&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">query&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">tools&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">[{&lt;/span>&lt;span class="s2">&amp;#34;type&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;web_search&amp;#34;&lt;/span>&lt;span class="p">}],&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># 提取搜索结果&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">results&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="n">item&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">response&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">output&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="nb">getattr&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">item&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;type&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kc">None&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="s2">&amp;#34;web_search_call&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">results&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">append&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nb">str&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">item&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">elif&lt;/span> &lt;span class="nb">getattr&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">item&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;type&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kc">None&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="s2">&amp;#34;message&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="n">content&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="nb">getattr&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">item&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;content&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="p">[]):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="nb">hasattr&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">content&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;text&amp;#34;&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">results&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">append&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">content&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">text&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="se">\n&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">join&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">results&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># DeepAgents 走 Chat Completions&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">model&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">ChatOpenAI&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">model&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;qwen-plus&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">api_key&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">os&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">getenv&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;DASHSCOPE_API_KEY&amp;#34;&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">base_url&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;https://dashscope.aliyuncs.com/compatible-mode/v1&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">agent&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">create_deep_agent&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">model&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">model&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">tools&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">bailian_web_search&lt;/span>&lt;span class="p">],&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">system_prompt&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;你是一个研究助手。用 bailian_web_search 查询最新信息。&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;hr>
&lt;h2 id="第十二章协议对比总结">第十二章：协议对比总结
&lt;/h2>&lt;h3 id="120-deep-agents-codedcode">12.0 Deep Agents Code（dcode）
&lt;/h3>&lt;p>除了可编程的 Python/JS 库，LangChain 还提供了 &lt;strong>Deep Agents Code&lt;/strong>（&lt;code>dcode&lt;/code>）——一个预构建的终端编码智能体，定位类似 Claude Code 或 Cursor CLI，底层就是 DeepAgents harness。它让你不用写代码就能体验文件系统、子智能体、上下文管理等全部能力。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 一键安装&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">curl -LsSf https://langch.in/dcode &lt;span class="p">|&lt;/span> bash
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 在项目目录中启动&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">cd&lt;/span> your-project
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">dcode
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>dcode 使用任何支持 tool calling 的 LLM 作为后端。对于想先&amp;quot;用&amp;quot;再&amp;quot;编&amp;quot;的用户，建议先试 dcode 感受 harness 行为，再用 &lt;code>create_deep_agent&lt;/code> 定制自己的流程。&lt;/p>
&lt;p>dcode 迭代很快（几乎每周发版），近期（0.1.67 / 0.1.68）值得关注的更新：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>工作区隔离增强&lt;/strong>：项目策略按 workspace 解析、dotenv 按 workspace 作用域隔离、LangSmith 认证与 tracing 隔离；跨工作区切换时复用 server 并在重启前请求确认。&lt;/li>
&lt;li>&lt;strong>模型支持&lt;/strong>：新增 Fireworks GLM-5.3 / GLM-5.3 Flash 的用量计价，推荐模型列表纳入 GLM 5.3 系列。&lt;/li>
&lt;li>&lt;strong>subagent 行为&lt;/strong>：general-purpose 子智能体默认以 fork 模式运行（继承完整上下文），并支持 fresh subagent 与按任务选择工具。&lt;/li>
&lt;/ul>
&lt;p>官方文档：&lt;a class="link" href="https://docs.langchain.com/deepagents-code" target="_blank" rel="noopener"
>https://docs.langchain.com/deepagents-code&lt;/a>&lt;/p>
&lt;h3 id="121-deepagents-的模型协议">12.1 DeepAgents 的模型协议
&lt;/h3>&lt;p>DeepAgents 对模型的唯一要求是 &lt;strong>tool calling&lt;/strong>。它不关心底层是 Chat Completions 还是 Responses API，只要 LangChain ChatModel 能完成以下循环：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-gdscript3" data-lang="gdscript3">&lt;span class="line">&lt;span class="cl">&lt;span class="err">发送&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="n">system&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">user&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">assistant&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">tool_calls&lt;/span>&lt;span class="p">),&lt;/span> &lt;span class="k">tool&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">results&lt;/span>&lt;span class="p">)]&lt;/span> &lt;span class="err">→&lt;/span> &lt;span class="err">模型返回&lt;/span> &lt;span class="n">tool_calls&lt;/span> &lt;span class="err">或&lt;/span> &lt;span class="n">text&lt;/span> &lt;span class="err">→&lt;/span> &lt;span class="err">循环&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;ul>
&lt;li>Chat Completions 兼容端点（OpenAI、DashScope、Ollama、vLLM）：直接用 &lt;code>ChatOpenAI&lt;/code>。&lt;/li>
&lt;li>Responses API（OpenAI / 百炼）：通过 &lt;code>init_chat_model(&amp;quot;openai:...&amp;quot;, use_responses_api=True)&lt;/code>。&lt;/li>
&lt;li>Anthropic：直接用 &lt;code>ChatAnthropic&lt;/code> 或 &lt;code>&amp;quot;anthropic:claude-sonnet-4-6&amp;quot;&lt;/code>。&lt;/li>
&lt;li>本地模型：&lt;code>ChatOllama&lt;/code> 或 &lt;code>ChatVLLM&lt;/code>。&lt;/li>
&lt;/ul>
&lt;h3 id="122-与百炼-responses-api-的关系">12.2 与百炼 Responses API 的关系
&lt;/h3>&lt;table>
&lt;thead>
&lt;tr>
&lt;th>维度&lt;/th>
&lt;th>DeepAgents + Chat Completions&lt;/th>
&lt;th>百炼 Responses API&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>协议&lt;/td>
&lt;td>Chat Completions（&lt;code>messages&lt;/code>）&lt;/td>
&lt;td>Responses（&lt;code>input&lt;/code>）&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>工具调用&lt;/td>
&lt;td>客户端执行（LangChain tool calling）&lt;/td>
&lt;td>服务端内置（web_search 等）&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Agent 循环&lt;/td>
&lt;td>DeepAgents 框架管&lt;/td>
&lt;td>你自己管&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>搜索&lt;/td>
&lt;td>需要自己包装工具&lt;/td>
&lt;td>服务端内置&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>文件系统&lt;/td>
&lt;td>内置&lt;/td>
&lt;td>无&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>子智能体&lt;/td>
&lt;td>内置&lt;/td>
&lt;td>无&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>上下文管理&lt;/td>
&lt;td>内置&lt;/td>
&lt;td>无&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>HITL&lt;/td>
&lt;td>内置&lt;/td>
&lt;td>无&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;h3 id="123-选型决策树">12.3 选型决策树
&lt;/h3>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-gdscript3" data-lang="gdscript3">&lt;span class="line">&lt;span class="cl">&lt;span class="err">需要&lt;/span> &lt;span class="n">DeepAgents&lt;/span> &lt;span class="err">的&lt;/span> &lt;span class="n">harness&lt;/span> &lt;span class="err">能力？&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">├─&lt;/span> &lt;span class="err">是&lt;/span> &lt;span class="err">→&lt;/span> &lt;span class="err">需要&lt;/span> &lt;span class="n">web_search&lt;/span>&lt;span class="err">？&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">│&lt;/span> &lt;span class="err">├─&lt;/span> &lt;span class="err">是&lt;/span> &lt;span class="err">→&lt;/span> &lt;span class="err">自己包装搜索为&lt;/span> &lt;span class="n">function&lt;/span> &lt;span class="k">tool&lt;/span>&lt;span class="err">（推荐）&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">│&lt;/span> &lt;span class="err">│&lt;/span> &lt;span class="err">或混合模式（&lt;/span>&lt;span class="n">DeepAgents&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="err">百炼&lt;/span> &lt;span class="n">Responses&lt;/span> &lt;span class="n">API&lt;/span>&lt;span class="err">）&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">│&lt;/span> &lt;span class="err">└─&lt;/span> &lt;span class="err">否&lt;/span> &lt;span class="err">→&lt;/span> &lt;span class="err">直接用&lt;/span> &lt;span class="n">DeepAgents&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="n">Chat&lt;/span> &lt;span class="n">Completions&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">└─&lt;/span> &lt;span class="err">否&lt;/span> &lt;span class="err">→&lt;/span> &lt;span class="err">需要服务端内置工具（&lt;/span>&lt;span class="n">web_search&lt;/span> &lt;span class="err">等）？&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="err">├─&lt;/span> &lt;span class="err">是&lt;/span> &lt;span class="err">→&lt;/span> &lt;span class="err">直接用百炼&lt;/span> &lt;span class="n">Responses&lt;/span> &lt;span class="n">API&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">└─&lt;/span> &lt;span class="err">否&lt;/span> &lt;span class="err">→&lt;/span> &lt;span class="n">LangChain&lt;/span> &lt;span class="n">create_agent&lt;/span> &lt;span class="o">/&lt;/span> &lt;span class="n">LangGraph&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="124-rubricmiddleware自评迭代循环">12.4 RubricMiddleware：自评迭代循环
&lt;/h3>&lt;p>DeepAgents 内置了 &lt;code>RubricMiddleware&lt;/code>，让你用&amp;quot;评分标准&amp;quot;定义&lt;strong>什么叫做完成&lt;/strong>。当 agent 准备结束回复时，中间件会调用一个独立的&amp;quot;评分器&amp;quot;子智能体，根据 rubric 逐条检查 agent 的输出。如果评分器返回 &lt;code>needs_revision&lt;/code>，其反馈会作为 &lt;code>HumanMessage&lt;/code> 注入上下文，agent 循环继续——直到评分器返回 &lt;code>satisfied&lt;/code>、&lt;code>failed&lt;/code> 或达到最大迭代次数。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">deepagents&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">create_deep_agent&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">deepagents.middleware&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">RubricMiddleware&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">rubric_mw&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">RubricMiddleware&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">rubric&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>&lt;span class="s2">&amp;#34;criterion&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;所有关键论断都有来源引用&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;weight&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">3&lt;/span>&lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>&lt;span class="s2">&amp;#34;criterion&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;结论与证据逻辑一致&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;weight&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">2&lt;/span>&lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>&lt;span class="s2">&amp;#34;criterion&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;无事实错误&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;weight&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">3&lt;/span>&lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">],&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">grader_model&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;openai:gpt-5.5&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">max_iterations&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="mi">3&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">agent&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">create_deep_agent&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">model&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;openai:gpt-5.5&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">middleware&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">rubric_mw&lt;/span>&lt;span class="p">],&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">system_prompt&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;你是一个研究助手，写完报告后自我检查再提交。&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>这个模式非常适合对输出质量有硬性要求的场景（合规文档、技术报告、代码审查），把&amp;quot;人工再审一遍&amp;quot;变成可编程的自动质量门。&lt;/p>
&lt;h3 id="125-官方示例集导读">12.5 官方示例集导读
&lt;/h3>&lt;p>GitHub 仓库的 &lt;a class="link" href="https://github.com/langchain-ai/deepagents/tree/main/examples" target="_blank" rel="noopener"
>examples/&lt;/a> 目录包含十多个端到端案例，按学习优先级推荐如下：&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>示例&lt;/th>
&lt;th>亮点&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;code>content-builder-agent&lt;/code>&lt;/td>
&lt;td>用 Memory + Skills + Subagents 三原语构建内容写作 agent，最适合入门&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>deep_research&lt;/code>&lt;/td>
&lt;td>完整的深度研究 agent（多轮搜索 + 报告生成），展示实际 token 管理&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>text-to-sql-agent&lt;/code>&lt;/td>
&lt;td>自然语言 → SQL，展示 planning + filesystem + subagent 组合&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>rubric_middleware&lt;/code>&lt;/td>
&lt;td>RubricMiddleware + LangSmith trace，展示自评迭代全流程&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>async-subagent-server&lt;/code>&lt;/td>
&lt;td>异步子智能体 + 服务端部署，生产级 FastAPI 集成&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>ralph_mode&lt;/code>&lt;/td>
&lt;td>Ralph 自主循环模式（Geoff Huntley），&lt;code>while :; do agent; done&lt;/code> 的 DeepAgents 实现&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>better-harness&lt;/code>&lt;/td>
&lt;td>用一个 DeepAgent 优化另一个 agent harness 的元循环（harness engineering）&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>建议先跑 &lt;code>content-builder-agent&lt;/code> 理解三原语（Memory / Skills / Subagents），再按需深入其他案例。&lt;/p>
&lt;hr>
&lt;h2 id="附录">附录
&lt;/h2>&lt;h3 id="a-常用环境变量">A. 常用环境变量
&lt;/h3>&lt;table>
&lt;thead>
&lt;tr>
&lt;th>变量&lt;/th>
&lt;th>说明&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;code>OPENAI_API_KEY&lt;/code>&lt;/td>
&lt;td>OpenAI API key&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>ANTHROPIC_API_KEY&lt;/code>&lt;/td>
&lt;td>Anthropic API key&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>DASHSCOPE_API_KEY&lt;/code>&lt;/td>
&lt;td>阿里云百炼 API key&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>LANGSMITH_TRACING&lt;/code>&lt;/td>
&lt;td>启用 LangSmith trace&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>LANGSMITH_API_KEY&lt;/code>&lt;/td>
&lt;td>LangSmith API key&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>LANGSMITH_PROJECT&lt;/code>&lt;/td>
&lt;td>LangSmith 项目名&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>LANGCHAIN_TRACING_V2&lt;/code>&lt;/td>
&lt;td>启用 LangSmith trace（旧变量名）&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>LANGCHAIN_API_KEY&lt;/code>&lt;/td>
&lt;td>LangSmith API key（旧变量名）&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>LANGCHAIN_PROJECT&lt;/code>&lt;/td>
&lt;td>LangSmith 项目名（旧变量名）&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;h3 id="b-版本注意事项">B. 版本注意事项
&lt;/h3>&lt;ul>
&lt;li>&lt;code>deepagents&lt;/code> 0.7.x 是当前最新版本系列（最新 &lt;code>0.7.15&lt;/code>，2026-09-16），&lt;strong>尚未发布 0.8 或 1.0&lt;/strong>。&lt;/li>
&lt;li>0.5.x → 0.6.x 有重大变化：&lt;code>SubAgent&lt;/code> 类型变化、&lt;code>response_format&lt;/code> 新增，&lt;strong>并且消息历史与 agent 文件切到 DeltaChannel 持久化，此变更不可回滚&lt;/strong>（见 3.5 节）。&lt;/li>
&lt;li>0.6.x → 0.7.x 新增了 skills、memory、permissions、&lt;code>delete&lt;/code> 工具、&lt;code>FilesystemMiddleware&lt;/code> 工具白名单，同时做了一次提示词瘦身（见 2.3 节）。&lt;/li>
&lt;li>0.7.13 → 0.7.14 → 0.7.15 都是修补性质：&lt;code>read_file&lt;/code> 输出格式调整、&lt;code>edit_file&lt;/code> 拒绝空 &lt;code>old_string&lt;/code>、&lt;code>ls&lt;/code>/&lt;code>glob&lt;/code> 字符计数修正、无 ID 的工具结果转存改用唯一路径、subagent state key 传递修复。&lt;/li>
&lt;li>如果从 0.3.x 升级，&lt;code>SubAgent&lt;/code> 已从 dict 改为类型化对象，&lt;code>create_deep_agent&lt;/code> 的参数名有变化。&lt;/li>
&lt;li>0.x 版本 API 变动快，生产环境建议锁定版本（&lt;code>uv add deepagents==0.7.15&lt;/code>）。&lt;/li>
&lt;/ul>
&lt;h4 id="本文覆盖的版本快照截至-2026-09-21">本文覆盖的版本快照（截至 2026-09-21）
&lt;/h4>&lt;table>
&lt;thead>
&lt;tr>
&lt;th>包&lt;/th>
&lt;th>本文基准版本&lt;/th>
&lt;th>发布日期&lt;/th>
&lt;th>备注&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;code>deepagents&lt;/code>（Python）&lt;/td>
&lt;td>0.7.15&lt;/td>
&lt;td>2026-09-16&lt;/td>
&lt;td>核心库，本文主体内容&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>deepagents-code&lt;/code>（dcode）&lt;/td>
&lt;td>0.1.72&lt;/td>
&lt;td>2026-09-21&lt;/td>
&lt;td>终端编码智能体，见 12.0 节&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>deepagents-talon&lt;/code>&lt;/td>
&lt;td>0.0.8&lt;/td>
&lt;td>2026-09-11&lt;/td>
&lt;td>独立发布，本文未覆盖&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>langchain-quickjs&lt;/code>&lt;/td>
&lt;td>0.3.7&lt;/td>
&lt;td>2026-09-06&lt;/td>
&lt;td>JS 运行时，&lt;code>CodeInterpreterMiddleware&lt;/code> 依赖它，本文未展开&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>langchain&lt;/code>&lt;/td>
&lt;td>1.4.0&lt;/td>
&lt;td>2026-09-01&lt;/td>
&lt;td>MCP 支持已内建进 &lt;code>langchain.mcp&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>langchain-mcp-adapters&lt;/code>&lt;/td>
&lt;td>—&lt;/td>
&lt;td>—&lt;/td>
&lt;td>&lt;strong>已被 &lt;code>langchain.mcp&lt;/code> 取代，不再维护&lt;/strong>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>deepagents.js&lt;/code>（JS/TS）&lt;/td>
&lt;td>—&lt;/td>
&lt;td>—&lt;/td>
&lt;td>独立仓库，见附录 C&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;h4 id="下次更新检查清单">下次更新检查清单
&lt;/h4>&lt;ol>
&lt;li>查看 &lt;a class="link" href="https://github.com/langchain-ai/deepagents/releases" target="_blank" rel="noopener"
>GitHub Releases&lt;/a> 确认 &lt;code>deepagents&lt;/code> 最新 minor 版本（当前 0.7.x；如果发布 0.8 或 1.0，优先检查 breaking changes）。&lt;/li>
&lt;li>对比 &lt;a class="link" href="https://github.com/langchain-ai/deepagents/blob/main/CHANGELOG.md" target="_blank" rel="noopener"
>CHANGELOG&lt;/a>（或 release notes），重点看：
&lt;ul>
&lt;li>新增/移除的中间件（&lt;code>libs/deepagents/deepagents/middleware/&lt;/code> 目录）&lt;/li>
&lt;li>&lt;code>create_deep_agent&lt;/code> 签名变化&lt;/li>
&lt;li>新增的 examples 目录&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>检查 &lt;code>deepagents-code&lt;/code> 是否有新 major 功能（dcode 迭代很快，几乎每周发版）。&lt;/li>
&lt;li>确认本文引用的文档链接是否仍然有效（LangChain 文档路径经常重组）。&lt;/li>
&lt;li>如果有新的 middleware 或新的 examples，优先补充到第 2 章（中间件详解）和 12.5 节（示例导读）。&lt;/li>
&lt;/ol>
&lt;h3 id="c-生态与跨语言">C. 生态与跨语言
&lt;/h3>&lt;ul>
&lt;li>&lt;strong>JavaScript/TypeScript&lt;/strong>：DeepAgents 提供了 &lt;code>deepagents.js&lt;/code> 库（&lt;a class="link" href="https://github.com/langchain-ai/deepagentsjs" target="_blank" rel="noopener"
>GitHub&lt;/a>），适合在 Node.js 环境中构建 agent。&lt;/li>
&lt;li>&lt;strong>Deep Agents Code&lt;/strong>：终端编码智能体，安装命令见 &lt;a class="link" href="#120-deep-agents-codedcode" >12.0 节&lt;/a>。&lt;/li>
&lt;/ul>
&lt;h3 id="d-相关资源">D. 相关资源
&lt;/h3>&lt;ul>
&lt;li>&lt;a class="link" href="https://github.com/langchain-ai/deepagents" target="_blank" rel="noopener"
>GitHub 仓库&lt;/a>&lt;/li>
&lt;li>&lt;a class="link" href="https://docs.langchain.com/oss/python/deepagents/overview" target="_blank" rel="noopener"
>官方文档&lt;/a>&lt;/li>
&lt;li>&lt;a class="link" href="https://reference.langchain.com/python/deepagents/" target="_blank" rel="noopener"
>API Reference&lt;/a>&lt;/li>
&lt;li>&lt;a class="link" href="https://github.com/langchain-ai/deepagents/tree/main/examples" target="_blank" rel="noopener"
>示例代码&lt;/a>&lt;/li>
&lt;li>&lt;a class="link" href="https://docs.langchain.com/oss/python/concepts/products" target="_blank" rel="noopener"
>LangChain 生态概览&lt;/a>&lt;/li>
&lt;/ul>
&lt;hr>
&lt;p>&lt;em>本文基于 DeepAgents 0.7.15 版本编写，2026 年 9 月。API 在 1.0 之前可能变化，请以官方文档为准。&lt;/em>&lt;/p></description></item><item><title>LangChain 模型接入指南：OpenAI 兼容协议与多平台调用</title><link>https://www.zata.cc/p/langchain-%E6%A8%A1%E5%9E%8B%E6%8E%A5%E5%85%A5%E6%8C%87%E5%8D%97openai-%E5%85%BC%E5%AE%B9%E5%8D%8F%E8%AE%AE%E4%B8%8E%E5%A4%9A%E5%B9%B3%E5%8F%B0%E8%B0%83%E7%94%A8/</link><pubDate>Tue, 08 Sep 2026 11:00:00 +0800</pubDate><guid>https://www.zata.cc/p/langchain-%E6%A8%A1%E5%9E%8B%E6%8E%A5%E5%85%A5%E6%8C%87%E5%8D%97openai-%E5%85%BC%E5%AE%B9%E5%8D%8F%E8%AE%AE%E4%B8%8E%E5%A4%9A%E5%B9%B3%E5%8F%B0%E8%B0%83%E7%94%A8/</guid><description>&lt;img src="https://www.zata.cc/p/langchain-%E6%A8%A1%E5%9E%8B%E6%8E%A5%E5%85%A5%E6%8C%87%E5%8D%97openai-%E5%85%BC%E5%AE%B9%E5%8D%8F%E8%AE%AE%E4%B8%8E%E5%A4%9A%E5%B9%B3%E5%8F%B0%E8%B0%83%E7%94%A8/images/index/index.png" alt="Featured image of post LangChain 模型接入指南：OpenAI 兼容协议与多平台调用" />&lt;h1 id="langchain-模型接入指南openai-兼容协议与多平台调用">LangChain 模型接入指南：OpenAI 兼容协议与多平台调用
&lt;/h1>&lt;p>&lt;code>langchain_openai&lt;/code> 名字里带 openai，能做的事却比名字大得多：任何兼容 OpenAI 接口协议的模型服务，它都能接。而到今天，&lt;strong>OpenAI API 格式已经成了事实上的工业标准&lt;/strong>——阿里百炼、DeepSeek、vLLM、Ollama，无一例外都把&amp;quot;兼容 OpenAI 协议&amp;quot;当成默认姿势。&lt;/p>
&lt;p>所以在 LangChain 里接模型这件事，可以收敛成一套打法：用 &lt;code>ChatOpenAI&lt;/code> 和 &lt;code>OpenAIEmbeddings&lt;/code>，配合目标平台的三要素。我把这套打法叫**&amp;ldquo;换头&amp;rdquo;**——换平台时业务代码一行不动，只改三处：&lt;code>base_url&lt;/code>、&lt;code>api_key&lt;/code>、&lt;code>model&lt;/code>。一个原本基于 GPT-4 写的应用，改完这三处就直接跑在通义千问上。&lt;/p>
&lt;p>这篇文章做两件事：以阿里百炼为例，把对话、流式、嵌入、工具调用四类调用完整走一遍（嵌入那里有个很容易漏的坑）；然后给一份多平台速查表，覆盖 6 家 ChatModel 和 5 家 Embeddings 的包名与初始化要点，换平台时直接查表。&lt;/p>
&lt;h2 id="一百炼实战用-chatopenai-跑通四类调用">一、百炼实战：用 ChatOpenAI 跑通四类调用
&lt;/h2>&lt;h3 id="11-准备包key-和-base-url">1.1 准备：包、Key 和 Base URL
&lt;/h3>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">pip install langchain-openai langchain-core
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>去&lt;a class="link" href="https://bailian.console.aliyun.com/" target="_blank" rel="noopener"
>阿里云百炼控制台&lt;/a>拿两样东西：&lt;/p>
&lt;ol>
&lt;li>&lt;strong>API Key&lt;/strong>（&lt;code>sk-&lt;/code> 开头）&lt;/li>
&lt;li>&lt;strong>Base URL&lt;/strong>：百炼的 OpenAI 兼容端点是 &lt;code>https://dashscope.aliyuncs.com/compatible-mode/v1&lt;/code>&lt;/li>
&lt;/ol>
&lt;p>组件上只需要认识两个类，正好对应 LLM 应用的两块基石：&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>类&lt;/th>
&lt;th>用途&lt;/th>
&lt;th>百炼上的对应模型&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;code>ChatOpenAI&lt;/code>&lt;/td>
&lt;td>对话、指令跟随、逻辑推理、工具调用（Function Calling）&lt;/td>
&lt;td>通义千问系列（&lt;code>qwen-plus&lt;/code>、&lt;code>qwen-max&lt;/code>、&lt;code>qwen-turbo&lt;/code>、&lt;code>qwen-long&lt;/code>）&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>OpenAIEmbeddings&lt;/code>&lt;/td>
&lt;td>文本转向量，构建 RAG 的基础&lt;/td>
&lt;td>向量模型（&lt;code>text-embedding-v2&lt;/code>、&lt;code>text-embedding-v3&lt;/code> 等）&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>&lt;code>ChatOpenAI&lt;/code> 走 Chat Completion 接口：输入是消息列表（&lt;code>SystemMessage&lt;/code>、&lt;code>HumanMessage&lt;/code>、&lt;code>AIMessage&lt;/code>），输出是 &lt;code>AIMessage&lt;/code>。千问的具体型号清单见百炼官方的&lt;a class="link" href="https://help.aliyun.com/zh/model-studio/getting-started/models" target="_blank" rel="noopener"
>模型列表&lt;/a>。&lt;/p>
&lt;h3 id="12-基础对话">1.2 基础对话
&lt;/h3>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">langchain_openai&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">ChatOpenAI&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">langchain_core.messages&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">HumanMessage&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">SystemMessage&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 为清晰起见先写成明文变量；生产环境放环境变量，做法见第三节&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">ALIBABA_API_KEY&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;sk-你的阿里百炼API_KEY&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">ALIBABA_BASE_URL&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;https://dashscope.aliyuncs.com/compatible-mode/v1&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">chat_model&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">ChatOpenAI&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">model&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;qwen-plus&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="c1"># 指向百炼的模型 ID&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">api_key&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">ALIBABA_API_KEY&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">base_url&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">ALIBABA_BASE_URL&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">temperature&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="mf">0.7&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">streaming&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="kc">True&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">messages&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">SystemMessage&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">content&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;你是一个专业的Python代码助手。&amp;#34;&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">HumanMessage&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">content&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;请用Python写一个冒泡排序，并解释其时间复杂度。&amp;#34;&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">chat_model&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">invoke&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">messages&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">content&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>除了 &lt;code>model&lt;/code> / &lt;code>api_key&lt;/code> / &lt;code>base_url&lt;/code> 三处&amp;quot;换头&amp;quot;参数，其余写法与调 GPT-4 完全一致——这正是兼容协议的价值。&lt;/p>
&lt;h3 id="13-流式输出">1.3 流式输出
&lt;/h3>&lt;p>把 &lt;code>invoke&lt;/code> 换成 &lt;code>stream&lt;/code> 就是流式：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="k">for&lt;/span> &lt;span class="n">chunk&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">chat_model&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">stream&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">messages&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">chunk&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">content&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">end&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">flush&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="kc">True&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;code>chunk.content&lt;/code> 是每次生成的一小段文本。流式对大模型应用的用户体验提升非常直接，做对话类产品基本是必选项。&lt;/p>
&lt;h3 id="14-向量嵌入最不能漏的一行">1.4 向量嵌入：最不能漏的一行
&lt;/h3>&lt;p>做 RAG（知识库助手）时用 &lt;code>OpenAIEmbeddings&lt;/code> 调百炼的向量模型。这里有个坑，也是全文最值得记住的一行参数：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">langchain_openai&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">OpenAIEmbeddings&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">embeddings&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">OpenAIEmbeddings&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">model&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;text-embedding-v2&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">api_key&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">ALIBABA_API_KEY&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">base_url&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">ALIBABA_BASE_URL&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">check_embedding_ctx_length&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="kc">False&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="c1"># 关键：关掉 OpenAI 特有的长度检查，防止报错&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">vector&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">embeddings&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">embed_query&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;阿里百炼是一个大模型服务平台。&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;生成的向量维度: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="nb">len&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">vector&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;向量前 5 位: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">vector&lt;/span>&lt;span class="p">[:&lt;/span>&lt;span class="mi">5&lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;code>OpenAIEmbeddings&lt;/code> 默认开着一段 OpenAI 特有的逻辑：发送前先按 OpenAI 的分词器做上下文长度检查，超长文本会在本地被切块再分批发送。接到百炼这种非 OpenAI 端点上，这套本地检查既无必要，又可能在调用时直接报错。显式传 &lt;code>check_embedding_ctx_length=False&lt;/code> 把它关掉就正常了。第一次接百炼嵌入如果莫名报错，先看是不是漏了这个参数。&lt;/p>
&lt;h3 id="15-工具调用bind_tools">1.5 工具调用：bind_tools
&lt;/h3>&lt;p>百炼的 Qwen 模型对 Function Calling 的支持很好，且完全走 OpenAI 的格式。用 &lt;code>bind_tools&lt;/code> 把工具绑定到模型上，模型决定调用时，请求会出现在返回消息的 &lt;code>tool_calls&lt;/code> 属性里，而不是正文里：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">langchain_core.tools&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">tool&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nd">@tool&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">get_weather&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">city&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;查询某个城市的天气信息。&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">city&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2"> 的天气是晴天，气温 25 度。&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">llm_with_tools&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">chat_model&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">bind_tools&lt;/span>&lt;span class="p">([&lt;/span>&lt;span class="n">get_weather&lt;/span>&lt;span class="p">])&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">response&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">llm_with_tools&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">invoke&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;杭州今天天气怎么样？&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;工具调用请求: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">response&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">tool_calls&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>拿到 &lt;code>tool_calls&lt;/code> 之后，取出参数、真正执行工具、把结果作为 ToolMessage 回传给模型，就是一个完整的工具调用循环——LangChain 的 Agent 封装和 LangGraph 处理的就是这个循环。&lt;/p>
&lt;h2 id="二为什么推荐用-langchain_openai-接百炼">二、为什么推荐用 langchain_openai 接百炼
&lt;/h2>&lt;p>一个自然的疑问：阿里有官方 SDK，LangChain 里也有 &lt;code>ChatTongyi&lt;/code> 这种原生类，为什么绕道 &lt;code>langchain_openai&lt;/code>？理由有三。&lt;/p>
&lt;ol>
&lt;li>
&lt;p>&lt;strong>代码零迁移成本。&lt;/strong> 项目原本基于 GPT-4 开发的话，不需要改动任何业务逻辑，把环境变量里的 &lt;code>BASE_URL&lt;/code> 和 &lt;code>API_KEY&lt;/code> 换掉，应用就从 OpenAI 切到了百炼 Qwen。把 &lt;code>langchain_openai&lt;/code> 当默认接入层，各平台互为&amp;quot;平替&amp;quot;，切换成本约等于零。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>生态支持最完善。&lt;/strong> LangChain 社区对 OpenAI 类的支持是优先级最高的；走标准接口，后续接 LangGraph、LangSmith 这些工具也最顺。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>工具调用格式统一。&lt;/strong> OpenAI 的 Tool Calling 格式是当前的黄金标准。百炼通过兼容接口让 Qwen 像 GPT 一样精准地调用工具，不需要自己处理格式转换。&lt;/p>
&lt;/li>
&lt;/ol>
&lt;p>反过来说，只有当你要用 DashScope 原生协议里、兼容层没覆盖的能力时，才需要回到原生 SDK——这条备选路线放在第五节。&lt;/p>
&lt;h2 id="三环境变量与-key-管理">三、环境变量与 Key 管理
&lt;/h2>&lt;h3 id="31-永久环境变量cmd--gui--zsh--bash">3.1 永久环境变量：CMD / GUI / zsh / bash
&lt;/h3>&lt;p>以百炼的 &lt;code>DASHSCOPE_API_KEY&lt;/code> 为例。这个名字不是随便起的：第五节的 &lt;code>ChatTongyi&lt;/code>、&lt;code>DashScopeEmbeddings&lt;/code> 都默认读它。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Windows CMD&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">setx DASHSCOPE_API_KEY &lt;span class="s2">&amp;#34;YOUR_DASHSCOPE_API_KEY&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">echo&lt;/span> %DASHSCOPE_API_KEY% &lt;span class="c1"># 验证&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Windows 图形界面&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 系统属性 → 环境变量，手动添加，不再赘述&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># zsh&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">echo&lt;/span> &lt;span class="s2">&amp;#34;export DASHSCOPE_API_KEY=&amp;#39;YOUR_DASHSCOPE_API_KEY&amp;#39;&amp;#34;&lt;/span> &amp;gt;&amp;gt; ~/.zshrc
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">source&lt;/span> ~/.zshrc
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">echo&lt;/span> &lt;span class="nv">$DASHSCOPE_API_KEY&lt;/span> &lt;span class="c1"># 验证&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># bash&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">echo&lt;/span> &lt;span class="s2">&amp;#34;export DASHSCOPE_API_KEY=&amp;#39;YOUR_DASHSCOPE_API_KEY&amp;#39;&amp;#34;&lt;/span> &amp;gt;&amp;gt; ~/.bashrc
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">source&lt;/span> ~/.bashrc
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">echo&lt;/span> &lt;span class="nv">$DASHSCOPE_API_KEY&lt;/span> &lt;span class="c1"># 验证&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="32-env-模板多平台共存">3.2 .env 模板：多平台共存
&lt;/h3>&lt;p>写进 shell 配置不适合多项目切换，更常见的做法是每个项目放一个 &lt;code>.env&lt;/code>，用 &lt;code>python-dotenv&lt;/code> 加载：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 阿里云百炼&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">DASHSCOPE_API_KEY&lt;/span>&lt;span class="o">=&lt;/span>sk-你的key
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">DASHSCOPE_BASE_URL&lt;/span>&lt;span class="o">=&lt;/span>https://dashscope.aliyuncs.com/compatible-mode/v1
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># OpenAI 官方（或另一个兼容端点）&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">OPENAI_API_KEY&lt;/span>&lt;span class="o">=&lt;/span>sk-你的key
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># OPENAI_BASE_URL=&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">dotenv&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">load_dotenv&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">os&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">load_dotenv&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">llm&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">ChatOpenAI&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">model&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;qwen-plus&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">api_key&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">os&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">getenv&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;DASHSCOPE_API_KEY&amp;#34;&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">base_url&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">os&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">getenv&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;DASHSCOPE_BASE_URL&amp;#34;&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">streaming&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="kc">True&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>再进一步还有个偷懒技巧：openai 官方 SDK 本身就认 &lt;code>OPENAI_API_KEY&lt;/code> 和 &lt;code>OPENAI_BASE_URL&lt;/code> 两个环境变量。把这两个变量指向百炼，代码里连 &lt;code>api_key&lt;/code>、&lt;code>base_url&lt;/code> 都不用写，&lt;code>ChatOpenAI(model=&amp;quot;qwen-plus&amp;quot;)&lt;/code> 就能直接跑——切换平台时只动环境变量，一行代码不改。&lt;/p>
&lt;h3 id="33-百炼计费预警">3.3 百炼计费预警
&lt;/h3>&lt;p>百炼有个和 OpenAI 不一样的计费方式，要提前知道：它没办法单独充值消费，费用会直接从控制台里扣钱。所以动手跑代码之前，先把控制台的高消费预警设上。&lt;/p>
&lt;h2 id="四多平台速查表">四、多平台速查表
&lt;/h2>&lt;p>&amp;ldquo;换头&amp;quot;的前提是各家有自己的接入包。LangChain 0.2 之后，集成被拆成一个个独立的合作包，下面两张表按&amp;quot;提供商 → 安装包 → 初始化要点&amp;quot;整理。表里的型号名只是写作时存在过的示例，各家更新很快，使用前以官方现役型号为准。&lt;/p>
&lt;h3 id="41-chatmodel6-家">4.1 ChatModel（6 家）
&lt;/h3>&lt;table>
&lt;thead>
&lt;tr>
&lt;th>提供商&lt;/th>
&lt;th>安装包&lt;/th>
&lt;th>初始化要点&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>OpenAI / 任意 OpenAI 兼容端点&lt;/td>
&lt;td>&lt;code>langchain-openai&lt;/code>&lt;/td>
&lt;td>&lt;code>ChatOpenAI(model=&amp;quot;gpt-4o-mini&amp;quot;)&lt;/code>，默认读 &lt;code>OPENAI_API_KEY&lt;/code>；接百炼则加 &lt;code>base_url=&amp;quot;https://dashscope.aliyuncs.com/compatible-mode/v1&amp;quot;&lt;/code>，&lt;code>model&lt;/code> 换成 &lt;code>qwen-plus&lt;/code> 等&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Anthropic&lt;/td>
&lt;td>&lt;code>langchain-anthropic&lt;/code>&lt;/td>
&lt;td>&lt;code>ChatAnthropic(model=&amp;quot;claude-sonnet-4-5-20250929&amp;quot;)&lt;/code>，默认读 &lt;code>ANTHROPIC_API_KEY&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Google&lt;/td>
&lt;td>&lt;code>langchain-google-genai&lt;/code> / &lt;code>langchain-google-vertexai&lt;/code>&lt;/td>
&lt;td>Gemini API 走 &lt;code>ChatGoogleGenerativeAI(model=&amp;quot;gemini-2.5-flash&amp;quot;)&lt;/code>，读 &lt;code>GOOGLE_API_KEY&lt;/code>；GCP 走 &lt;code>ChatVertexAI&lt;/code>，需要 gcloud 登录或 &lt;code>GOOGLE_APPLICATION_CREDENTIALS&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Hugging Face&lt;/td>
&lt;td>&lt;code>langchain-huggingface&lt;/code>&lt;/td>
&lt;td>&lt;code>HuggingFaceEndpoint(repo_id=&amp;quot;google/flan-t5-large&amp;quot;, task=&amp;quot;text-generation&amp;quot;)&lt;/code>，读 &lt;code>HUGGINGFACEHUB_API_TOKEN&lt;/code>；本地 pipeline 用 &lt;code>HuggingFacePipeline&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Cohere&lt;/td>
&lt;td>&lt;code>langchain-cohere&lt;/code>&lt;/td>
&lt;td>&lt;code>ChatCohere(model=&amp;quot;command-r&amp;quot;)&lt;/code>，默认读 &lt;code>COHERE_API_KEY&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Ollama（本地）&lt;/td>
&lt;td>&lt;code>langchain-ollama&lt;/code>&lt;/td>
&lt;td>&lt;code>ChatOllama(model=&amp;quot;llama3.2&amp;quot;)&lt;/code>；服务默认在 &lt;code>http://localhost:11434&lt;/code>，不在默认地址时传 &lt;code>base_url&lt;/code>&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;h3 id="42-embeddings5-家">4.2 Embeddings（5 家）
&lt;/h3>&lt;table>
&lt;thead>
&lt;tr>
&lt;th>提供商&lt;/th>
&lt;th>安装包&lt;/th>
&lt;th>初始化要点&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>OpenAI / 任意 OpenAI 兼容端点&lt;/td>
&lt;td>&lt;code>langchain-openai&lt;/code>&lt;/td>
&lt;td>&lt;code>OpenAIEmbeddings(model=&amp;quot;text-embedding-3-small&amp;quot;)&lt;/code>；接百炼：&lt;code>model=&amp;quot;text-embedding-v2&amp;quot;&lt;/code> + &lt;code>base_url&lt;/code> + &lt;code>check_embedding_ctx_length=False&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Google&lt;/td>
&lt;td>&lt;code>langchain-google-genai&lt;/code> / &lt;code>langchain-google-vertexai&lt;/code>&lt;/td>
&lt;td>&lt;code>GoogleGenerativeAIEmbeddings(model=&amp;quot;models/gemini-embedding-001&amp;quot;)&lt;/code>；Vertex 用 &lt;code>VertexAIEmbeddings(model_name=&amp;quot;text-embedding-005&amp;quot;)&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Hugging Face（本地）&lt;/td>
&lt;td>&lt;code>langchain-huggingface&lt;/code> + &lt;code>sentence-transformers&lt;/code>&lt;/td>
&lt;td>&lt;code>HuggingFaceEmbeddings(model_name=&amp;quot;sentence-transformers/all-MiniLM-L6-v2&amp;quot;)&lt;/code>，可用 &lt;code>model_kwargs={'device': 'cuda'}&lt;/code> 指定设备&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Cohere&lt;/td>
&lt;td>&lt;code>langchain-cohere&lt;/code>&lt;/td>
&lt;td>&lt;code>CohereEmbeddings(model=&amp;quot;embed-v4.0&amp;quot;)&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Ollama（本地）&lt;/td>
&lt;td>&lt;code>langchain-ollama&lt;/code>&lt;/td>
&lt;td>&lt;code>OllamaEmbeddings(model=&amp;quot;nomic-embed-text&amp;quot;)&lt;/code>&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;h3 id="43-相对旧版速查的更新">4.3 相对旧版速查的更新
&lt;/h3>&lt;p>这张表由 2025 年初那版速查表更新而来，几处变化需要说明：&lt;/p>
&lt;ul>
&lt;li>&lt;code>langchain_community.llms.HuggingFaceHub&lt;/code> → &lt;code>langchain-huggingface&lt;/code> 包的 &lt;code>HuggingFaceEndpoint&lt;/code>。Hugging Face 的推理接入这几年变动很大（Inference API 已让位给 Inference Providers），这条路线以官方文档为准。&lt;/li>
&lt;li>&lt;code>langchain_community.chat_models.ChatOllama&lt;/code> → &lt;code>langchain-ollama&lt;/code> 包的 &lt;code>ChatOllama&lt;/code>，&lt;code>OllamaEmbeddings&lt;/code> 同步从 &lt;code>langchain_community.embeddings&lt;/code> 迁到 &lt;code>langchain-ollama&lt;/code>。&lt;/li>
&lt;li>&lt;code>langchain_community.embeddings.HuggingFaceEmbeddings&lt;/code> → &lt;code>langchain-huggingface&lt;/code> 的同名类。&lt;/li>
&lt;li>旧表里的 &lt;code>gpt-3.5-turbo&lt;/code>、&lt;code>claude-3-sonnet-20240229&lt;/code>、&lt;code>gemini-pro&lt;/code>、&lt;code>llama2&lt;/code> 等型号已过时，表中换成了更新的型号；它们同样会过期，以各家官方型号页为准。&lt;/li>
&lt;/ul>
&lt;h3 id="44-两个容易忽略的差异">4.4 两个容易忽略的差异
&lt;/h3>&lt;ul>
&lt;li>&lt;strong>&lt;code>embed_query&lt;/code> 与 &lt;code>embed_documents&lt;/code> 分工不同&lt;/strong>：前者嵌入单条查询文本（用户搜索的那句话），返回一个向量；后者批量嵌入多个文档，返回向量列表。检索系统的两侧——入库文档和用户查询——必须用同一个嵌入模型，否则维度和语义空间都对不上。&lt;/li>
&lt;li>&lt;strong>向量维度因模型而异&lt;/strong>：&lt;code>text-embedding-3-small&lt;/code> 是 1536 维，&lt;code>text-embedding-3-large&lt;/code> 是 3072 维，常见的 sentence-transformers 模型多为 384 或 768 维。维度决定了向量库的结构和相似度计算，入库后换嵌入模型基本等于重建整个库。&lt;/li>
&lt;/ul>
&lt;h2 id="五备选路线原生-dashscope-与-init_chat_model">五、备选路线：原生 DashScope 与 init_chat_model
&lt;/h2>&lt;h3 id="51-chattongyi--dashscopeembeddingslegacy">5.1 ChatTongyi / DashScopeEmbeddings（legacy）
&lt;/h3>&lt;p>LangChain 社区包里保留了走 DashScope 原生 SDK 的两个类。⚠️ 以下是 0.x 时代的社区包写法，能用，但新项目建议优先走 OpenAI 兼容层：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">pip install langchain-community dashscope
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># ⚠️ 此为 langchain_community 0.x 时代写法&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">langchain_community.chat_models.tongyi&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">ChatTongyi&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">langchain_core.messages&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">HumanMessage&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">chatLLM&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">ChatTongyi&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">model&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;qwen-max&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">streaming&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="kc">True&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">res&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">chatLLM&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">stream&lt;/span>&lt;span class="p">([&lt;/span>&lt;span class="n">HumanMessage&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">content&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;hi&amp;#34;&lt;/span>&lt;span class="p">)],&lt;/span> &lt;span class="n">streaming&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="kc">True&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">for&lt;/span> &lt;span class="n">r&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">res&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;chat resp:&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">r&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">content&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>嵌入对应 &lt;code>DashScopeEmbeddings&lt;/code>：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># ⚠️ 此为 langchain_community 0.x 时代写法&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">langchain_community.embeddings&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">DashScopeEmbeddings&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">embeddings&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">DashScopeEmbeddings&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">model&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;text-embedding-v2&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">query_result&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">embeddings&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">embed_query&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;This is a test document.&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;文本向量长度：&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nb">len&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">query_result&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">doc_results&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">embeddings&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">embed_documents&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;Hi there!&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;Oh, hello!&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;What&amp;#39;s your name?&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;My friends call me World&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;Hello World!&amp;#34;&lt;/span>&lt;span class="p">])&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;文本向量数量：&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nb">len&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">doc_results&lt;/span>&lt;span class="p">),&lt;/span> &lt;span class="s2">&amp;#34;，文本向量长度：&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nb">len&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">doc_results&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">]))&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>两个类都默认读 &lt;code>DASHSCOPE_API_KEY&lt;/code> 环境变量。什么时候才需要这条路线：要用 DashScope 原生协议里、兼容层没覆盖的能力时。纯粹跑对话和嵌入，没有理由不用兼容层。&lt;/p>
&lt;h3 id="52-init_chat_model">5.2 init_chat_model
&lt;/h3>&lt;p>LangChain 官方还提供了统一初始化入口 &lt;code>init_chat_model&lt;/code>，按模型名和 provider 一步创建实例，不用记各家类名：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">langchain.chat_models&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">init_chat_model&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">llm&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">init_chat_model&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;qwen-plus&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">model_provider&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;openai&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="c1"># 指定走 langchain_openai 的 ChatOpenAI&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">api_key&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;sk-你的key&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">base_url&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;https://dashscope.aliyuncs.com/compatible-mode/v1&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>其余参数原样透传给对应类，细节以官方文档为准。&lt;/p>
&lt;h2 id="总结">总结
&lt;/h2>&lt;p>把这篇文章压成几条可执行的结论：&lt;/p>
&lt;ol>
&lt;li>&lt;strong>把 &lt;code>langchain_openai&lt;/code> 当默认接入层&lt;/strong>。OpenAI API 格式已是事实工业标准，&lt;code>ChatOpenAI&lt;/code> + &lt;code>OpenAIEmbeddings&lt;/code> 能接一切兼容端点。&lt;/li>
&lt;li>&lt;strong>换平台只改三处&lt;/strong>：&lt;code>base_url&lt;/code>、&lt;code>api_key&lt;/code>、&lt;code>model&lt;/code>，业务代码不动；配合环境变量或 &lt;code>.env&lt;/code>，可以做到一行代码不换平台。&lt;/li>
&lt;li>&lt;strong>接百炼做嵌入，&lt;code>check_embedding_ctx_length=False&lt;/code> 必加&lt;/strong>，这是最容易漏的一行。&lt;/li>
&lt;li>&lt;strong>跑代码前先设百炼高消费预警&lt;/strong>，它的费用直接从控制台账户扣，没有独立余额。&lt;/li>
&lt;li>&lt;strong>新项目直接装独立集成包&lt;/strong>（&lt;code>langchain-ollama&lt;/code>、&lt;code>langchain-huggingface&lt;/code> 等），&lt;code>langchain_community&lt;/code> 里的模型类当 legacy 看。&lt;/li>
&lt;/ol>
&lt;p>下一步：拿 1.2 的基础对话代码，把三个&amp;quot;换头&amp;quot;参数换成你手上的平台——百炼、DeepSeek 或本地 Ollama——跑通第一条消息。然后做两件事：用 &lt;code>bind_tools&lt;/code> 试一次工具调用，用 &lt;code>OpenAIEmbeddings&lt;/code> 把一段文本打成向量。这三步走完，LangChain 接模型这件事就算真正上手了。&lt;/p></description></item><item><title>Graph RAG 开源项目全景：从微软 GraphRAG 到 LightRAG</title><link>https://www.zata.cc/p/graph-rag-%E5%BC%80%E6%BA%90%E9%A1%B9%E7%9B%AE%E5%85%A8%E6%99%AF%E4%BB%8E%E5%BE%AE%E8%BD%AF-graphrag-%E5%88%B0-lightrag/</link><pubDate>Fri, 27 Mar 2026 16:00:00 +0800</pubDate><guid>https://www.zata.cc/p/graph-rag-%E5%BC%80%E6%BA%90%E9%A1%B9%E7%9B%AE%E5%85%A8%E6%99%AF%E4%BB%8E%E5%BE%AE%E8%BD%AF-graphrag-%E5%88%B0-lightrag/</guid><description>&lt;img src="https://www.zata.cc/p/graph-rag-%E5%BC%80%E6%BA%90%E9%A1%B9%E7%9B%AE%E5%85%A8%E6%99%AF%E4%BB%8E%E5%BE%AE%E8%BD%AF-graphrag-%E5%88%B0-lightrag/images/index/index.png" alt="Featured image of post Graph RAG 开源项目全景：从微软 GraphRAG 到 LightRAG" />&lt;p>传统 RAG 的核心思路是&amp;quot;向量检索 + 生成&amp;quot;，但在处理需要多跳推理、全局理解的问题时，效果往往不理想。&lt;/p>
&lt;p>Graph RAG（知识图谱 + RAG）通过构建文档间的实体关系网络，让检索不再局限于局部相似度匹配，而是能够进行图遍历、社区发现、路径推理。这让 RAG 系统能回答更复杂的问题。&lt;/p>
&lt;p>这篇文章系统梳理 Graph RAG 的主流开源项目，帮你快速了解和选型。&lt;/p>
&lt;h2 id="一为什么需要-graph-rag">一、为什么需要 Graph RAG
&lt;/h2>&lt;h3 id="传统-rag-的局限">传统 RAG 的局限
&lt;/h3>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">问题 1：多跳推理
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">用户问：&amp;#34;A 公司的合作方的竞争对手有哪些？&amp;#34;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">传统 RAG：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">1. 检索&amp;#34;A 公司的合作方&amp;#34; → 找到 B 公司
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">2. 检索&amp;#34;B 公司的竞争对手&amp;#34; → 找到 C、D 公司
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">问题：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 需要两轮检索，中间结果可能丢失
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 无法自动发现推理路径
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Graph RAG：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">A 公司 --合作--&amp;gt; B 公司 --竞争--&amp;gt; C、D 公司
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">一次图遍历即可完成
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">问题 2：全局理解
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">用户问：&amp;#34;这篇文章的核心观点是什么？&amp;#34;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">传统 RAG：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 检索 Top-K 相关段落
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 但核心观点可能分散在各处
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 无法形成全局视角
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Graph RAG：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 通过社区发现，将文档划分为多个主题
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 每个社区生成摘要
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 整合社区摘要，形成全局理解
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="graph-rag-的核心思路">Graph RAG 的核心思路
&lt;/h3>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">传统 RAG：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">文档 → Chunk → 向量化 → 向量检索
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Graph RAG：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">文档 → 实体抽取 → 关系抽取 → 知识图谱
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ↓
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 图检索 + 向量检索
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ↓
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> LLM 生成
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="二主流框架概览">二、主流框架概览
&lt;/h2>&lt;table>
&lt;thead>
&lt;tr>
&lt;th>框架&lt;/th>
&lt;th>Stars&lt;/th>
&lt;th>作者&lt;/th>
&lt;th>核心特点&lt;/th>
&lt;th>适用场景&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;strong>LightRAG&lt;/strong>&lt;/td>
&lt;td>36K+&lt;/td>
&lt;td>港大 HKUDS&lt;/td>
&lt;td>轻量快速，双层检索&lt;/td>
&lt;td>中小规模，实时更新&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>GraphRAG&lt;/strong>&lt;/td>
&lt;td>33K+&lt;/td>
&lt;td>Microsoft&lt;/td>
&lt;td>社区摘要，全局理解&lt;/td>
&lt;td>大规模文档&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>HippoRAG&lt;/strong>&lt;/td>
&lt;td>3.5K+&lt;/td>
&lt;td>俄亥俄州立&lt;/td>
&lt;td>模拟海马体记忆&lt;/td>
&lt;td>长期记忆系统&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Fast-GraphRAG&lt;/strong>&lt;/td>
&lt;td>3.8K+&lt;/td>
&lt;td>Circlemind&lt;/td>
&lt;td>自适应，快速&lt;/td>
&lt;td>快速部署&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Neo4j Graph Builder&lt;/strong>&lt;/td>
&lt;td>4.7K+&lt;/td>
&lt;td>Neo4j&lt;/td>
&lt;td>企业级图数据库&lt;/td>
&lt;td>可视化，企业应用&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>R2R&lt;/strong>&lt;/td>
&lt;td>7.8K+&lt;/td>
&lt;td>SciPhi AI&lt;/td>
&lt;td>生产级，完整方案&lt;/td>
&lt;td>企业部署&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>下面逐一详细介绍。&lt;/p>
&lt;h2 id="三microsoft-graphrag">三、Microsoft GraphRAG
&lt;/h2>&lt;h3 id="基本信息">基本信息
&lt;/h3>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">GitHub: https://github.com/microsoft/graphrag
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Stars: 33,800+
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">论文: GraphRAG: Unlocking LLM Discovery on Narrative Private Data
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">语言: Python
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="核心原理">核心原理
&lt;/h3>&lt;p>GraphRAG 的核心创新是&lt;strong>社区发现 + 社区摘要&lt;/strong>：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">Step 1: 知识图谱构建
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">文档 → 实体抽取 → 关系抽取 → 图存储
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Step 2: 社区发现
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">用 Leiden 算法将图划分为多个社区
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">每个社区是一个紧密相关的实体群组
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Step 3: 社区摘要
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">对每个社区，用 LLM 生成摘要
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">摘要包含社区的核心实体和关系
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Step 4: 检索
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">局部查询：从相关实体出发，图遍历
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">全局查询：检索社区摘要，整合回答
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="架构图">架构图
&lt;/h3>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">┌──────────────────────────────────────────────────────┐
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ Microsoft GraphRAG Pipeline │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">└──────────────────────────────────────────────────────┘
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ▼
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ┌──────────────────┐
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │ 文档输入 │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> └────────┬─────────┘
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ┌──────────────┼───────────────┐
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ▼ ▼ ▼
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ┌─────────┐ ┌──────────┐ ┌──────────┐
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │ 文本分块 │ │ 实体抽取 │ │ 关系抽取 │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> └────┬────┘ └────┬─────┘ └────┬─────┘
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │ │ │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> └──────────────┼───────────────┘
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ▼
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ┌──────────────────┐
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │ 知识图谱 │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │ (NetworkX) │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> └────────┬─────────┘
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ▼
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ┌──────────────────┐
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │ 社区发现 │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │ (Leiden) │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> └────────┬─────────┘
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ▼
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ┌──────────────────┐
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │ 社区摘要 │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │ (LLM 生成) │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> └────────┬─────────┘
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ┌──────────────┼───────────────┐
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ▼ ▼ ▼
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ┌─────────┐ ┌──────────┐ ┌──────────┐
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │ 局部查询 │ │ 全局查询 │ │ 混合查询 │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> └────┬────┘ └────┬─────┘ └────┬─────┘
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │ │ │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> └──────────────┼───────────────┘
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ▼
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ┌──────────────────┐
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │ LLM 生成答案 │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> └──────────────────┘
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="快速开始">快速开始
&lt;/h3>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 安装&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">pip&lt;/span> &lt;span class="n">install&lt;/span> &lt;span class="n">graphrag&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 初始化项目&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">python&lt;/span> &lt;span class="o">-&lt;/span>&lt;span class="n">m&lt;/span> &lt;span class="n">graphrag&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">index&lt;/span> &lt;span class="o">--&lt;/span>&lt;span class="n">init&lt;/span> &lt;span class="o">--&lt;/span>&lt;span class="n">root&lt;/span> &lt;span class="o">./&lt;/span>&lt;span class="n">ragtest&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 配置（settings.yaml）&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">graphrag&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">llm&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">type&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">openai_chat&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">model&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">gpt&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="mi">4&lt;/span>&lt;span class="n">o&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">embeddings&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">type&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">openai_embedding&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">model&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">text&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="n">embedding&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="mi">3&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="n">small&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 构建索引&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">python&lt;/span> &lt;span class="o">-&lt;/span>&lt;span class="n">m&lt;/span> &lt;span class="n">graphrag&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">index&lt;/span> &lt;span class="o">--&lt;/span>&lt;span class="n">root&lt;/span> &lt;span class="o">./&lt;/span>&lt;span class="n">ragtest&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 查询&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">python&lt;/span> &lt;span class="o">-&lt;/span>&lt;span class="n">m&lt;/span> &lt;span class="n">graphrag&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">query&lt;/span> &lt;span class="o">--&lt;/span>&lt;span class="n">root&lt;/span> &lt;span class="o">./&lt;/span>&lt;span class="n">ragtest&lt;/span> &lt;span class="o">--&lt;/span>&lt;span class="n">method&lt;/span> &lt;span class="n">local&lt;/span> &lt;span class="s2">&amp;#34;What is the main topic?&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">python&lt;/span> &lt;span class="o">-&lt;/span>&lt;span class="n">m&lt;/span> &lt;span class="n">graphrag&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">query&lt;/span> &lt;span class="o">--&lt;/span>&lt;span class="n">root&lt;/span> &lt;span class="o">./&lt;/span>&lt;span class="n">ragtest&lt;/span> &lt;span class="o">--&lt;/span>&lt;span class="n">method&lt;/span> &lt;span class="k">global&lt;/span> &lt;span class="s2">&amp;#34;What are the key themes?&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="查询模式">查询模式
&lt;/h3>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">Local Search（局部查询）：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 从问题中识别实体
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 扩展实体的邻居节点
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 用局部子图生成答案
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 适合：具体事实查询
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Global Search（全局查询）：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 检索相关社区摘要
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 整合多个社区的信息
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 形成全局视角
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 适合：&amp;#34;核心观点&amp;#34;、&amp;#34;主要主题&amp;#34;类问题
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">DRIFT Search（混合查询）：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 结合局部和全局
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 动态调整检索范围
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 适合：复杂推理问题
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="优缺点">优缺点
&lt;/h3>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">优点：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">✅ 全局理解能力强（社区摘要）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">✅ 微软官方维护，质量有保障
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">✅ 论文驱动，原理清晰
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">✅ 支持多种查询模式
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">缺点：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">❌ 索引构建慢（需要多次 LLM 调用）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">❌ 成本高（实体抽取、关系抽取、社区摘要）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">❌ 不适合实时更新（重建索引代价大）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">❌ 需要一定学习成本
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="适用场景">适用场景
&lt;/h3>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">✅ 推荐：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 大规模文档库（10K+ 文档）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 需要全局理解（&amp;#34;核心观点&amp;#34;、&amp;#34;主要趋势&amp;#34;）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 需要多跳推理
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 离线构建，在线查询
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">❌ 不推荐：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 小规模知识库
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 需要实时更新
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 成本敏感
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="四lightrag">四、LightRAG
&lt;/h2>&lt;h3 id="基本信息-1">基本信息
&lt;/h3>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">GitHub: https://github.com/HKUDS/LightRAG
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Stars: 36,600+
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">论文: LightRAG: Simple and Fast Retrieval-Augmented Generation (EMNLP 2025)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">作者: 香港大学数据科学实验室
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">语言: Python
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="核心创新">核心创新
&lt;/h3>&lt;p>LightRAG 针对 GraphRAG 的痛点做了优化：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">GraphRAG 的问题：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">1. 索引构建慢：需要多次 LLM 调用
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">2. 成本高：实体抽取、关系抽取、社区摘要
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">3. 不支持实时更新
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">LightRAG 的解决方案：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">1. 双层检索：低层（实体）+ 高层（关键概念）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">2. 无需社区发现：用双层图代替
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">3. 流式插入：支持实时更新
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">4. 轻量存储：无需图数据库，文件即可
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="双层检索原理">双层检索原理
&lt;/h3>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">传统 GraphRAG：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">文档 → 实体 → 图 → 社区发现 → 社区摘要 → 检索
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">LightRAG：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">文档 → 实体 + 关键概念 → 双层图 → 直接检索
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">双层图结构：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">┌─────────────────────────────────────┐
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ 高层（关键概念层） │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ [AI] ── [机器学习] ── [深度学习] │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ │ │ │ │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">├─────────┼───────────┼───────────┼───┤
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ │ │ │ │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ ▼ ▼ ▼ │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ 低层（实体层） │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ [GPT-4] [神经网络] [CNN/RNN] │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">└─────────────────────────────────────┘
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">查询时：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">1. 先在高层找到相关概念
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">2. 再在低层找到具体实体
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">3. 双层结果融合
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="快速开始-1">快速开始
&lt;/h3>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 安装&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">pip&lt;/span> &lt;span class="n">install&lt;/span> &lt;span class="n">lightrag&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="n">hku&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 基础使用&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">lightrag&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">LightRAG&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">lightrag.llm&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">openai_complete_if_cache&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">openai_embedding&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 初始化&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">rag&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">LightRAG&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">working_dir&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;./rag_storage&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">llm_model_func&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">openai_complete_if_cache&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">embedding_func&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">openai_embedding&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 插入文档&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">with&lt;/span> &lt;span class="nb">open&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;document.txt&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;r&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="k">as&lt;/span> &lt;span class="n">f&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">rag&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">insert&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">f&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">read&lt;/span>&lt;span class="p">())&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 查询&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">result&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">rag&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">query&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;What is machine learning?&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">mode&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;hybrid&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 查询模式&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">result&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">rag&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">query&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;...&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">mode&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;naive&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="c1"># 纯向量检索&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">result&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">rag&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">query&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;...&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">mode&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;local&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="c1"># 局部图检索&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">result&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">rag&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">query&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;...&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">mode&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;global&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="c1"># 全局图检索&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">result&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">rag&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">query&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;...&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">mode&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;hybrid&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="c1"># 混合检索&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="性能对比官方数据">性能对比（官方数据）
&lt;/h3>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">索引构建时间：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- GraphRAG: 100 分钟
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- LightRAG: 10 分钟
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 提升: 10x
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">查询时间：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- GraphRAG: 2 秒
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- LightRAG: 0.3 秒
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 提升: 6x
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">成本（Token 消耗）：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- GraphRAG: $10
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- LightRAG: $1
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 降低: 90%
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">准确率：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- GraphRAG: 45%
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- LightRAG: 52%
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 提升: 7%
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="优缺点-1">优缺点
&lt;/h3>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">优点：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">✅ 快：索引和查询都很快
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">✅ 省：成本降低 50-90%
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">✅ 灵活：支持实时插入更新
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">✅ 简单：无需图数据库
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">✅ 准确：双层检索更精准
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">缺点：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">❌ 全局理解不如 GraphRAG（无社区摘要）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">❌ 大规模场景下，图文件可能很大
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">❌ 功能相对简单
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="适用场景-1">适用场景
&lt;/h3>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">✅ 推荐：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 中小规模知识库（&amp;lt; 10K 文档）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 需要实时更新
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 成本敏感
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 快速原型
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">❌ 不推荐：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 超大规模文档
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 需要深度全局理解
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="五hipporag">五、HippoRAG
&lt;/h2>&lt;h3 id="基本信息-2">基本信息
&lt;/h3>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">GitHub: https://github.com/OSU-NLP-Group/HippoRAG
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Stars: 3,500+
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">论文: HippoRAG: A Neurobiological Framework for Long-Term Memory (NeurIPS 2024)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">作者: 俄亥俄州立大学
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">语言: Python
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="核心原理模拟海马体">核心原理：模拟海马体
&lt;/h3>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">人类记忆机制：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">海马体（Hippocampus）：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 索引新记忆
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 将记忆整合到大脑皮层
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">大脑皮层：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 存储长期记忆
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 知识以网络形式组织
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">检索时：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 从海马体获取线索
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 在大脑皮层中激活相关记忆
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 通过联想找到答案
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">HippoRAG 的实现：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">海马体 → 向量索引（快速找到入口）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">大脑皮层 → 知识图谱（关联检索）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">检索 → Personalized PageRank（模拟联想）
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="架构图-1">架构图
&lt;/h3>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">┌──────────────────────────────────────────────────────┐
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ HippoRAG 架构 │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">└──────────────────────────────────────────────────────┘
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">索引阶段：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">文档 → Passage 节点 → 向量化（海马体索引）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> → 实体抽取 → 实体节点
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> → 关系抽取 → 边
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> → 知识图谱（大脑皮层）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">检索阶段：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Query → 向量化 → 找到相关 Passage 节点
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ↓
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Personalized PageRank
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> （从入口节点扩散）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ↓
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 激活相关实体和 Passage
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ↓
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> LLM 生成答案
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="personalized-pagerank">Personalized PageRank
&lt;/h3>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">传统 PageRank：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 所有节点平等
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 计算全局重要性
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Personalized PageRank：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 从特定节点出发（Query 相关的 Passage）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 计算相对这些节点的重要性
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 模拟&amp;#34;联想&amp;#34;过程
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">示例：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Query: &amp;#34;GPT-4 的训练数据&amp;#34;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Step 1: 向量检索找到入口 Passage
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">入口节点: [Passage_A, Passage_B]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Step 2: Personalized PageRank
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">从 Passage_A、Passage_B 出发
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">计算其他节点的激活程度
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Step 3: 激活扩散
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Passage_A → 实体&amp;#34;GPT-4&amp;#34; → 实体&amp;#34;训练数据&amp;#34; → Passage_C
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Passage_B → 实体&amp;#34;OpenAI&amp;#34; → 实体&amp;#34;GPT-4&amp;#34; → ...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Step 4: 收集高激活度的 Passage
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Top-K: [Passage_A, Passage_B, Passage_C, ...]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Step 5: LLM 生成答案
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="快速开始-2">快速开始
&lt;/h3>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 安装&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">pip&lt;/span> &lt;span class="n">install&lt;/span> &lt;span class="n">hipporag&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 使用&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">hipporag&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">HippoRAG&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">rag&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">HippoRAG&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">graph_db&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;neo4j&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="c1"># 支持 Neo4j 或 NetworkX&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">llm_model&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;gpt-4&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">embedding_model&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;text-embedding-3-small&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 索引&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">rag&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">index&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;./documents&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 查询&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">result&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">rag&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">query&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;What are the key features of GPT-4?&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="适用场景-2">适用场景
&lt;/h3>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">✅ 推荐：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 长期记忆系统
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 持续学习的知识库
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 需要精准检索
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 个性化 RAG
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">❌ 不推荐：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 简单问答场景
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 成本敏感
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="六neo4j--rag-方案">六、Neo4j + RAG 方案
&lt;/h2>&lt;h3 id="neo4j-llm-graph-builder">Neo4j LLM Graph Builder
&lt;/h3>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">GitHub: https://github.com/neo4j-labs/llm-graph-builder
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Stars: 4,700+
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">作者: Neo4j 官方
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">语言: Python
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="核心特点">核心特点
&lt;/h3>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">Neo4j 的优势：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">1. 成熟的图数据库
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">2. 强大的 Cypher 查询语言
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">3. 可视化工具（Neo4j Browser）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">4. 企业级支持
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">LLM Graph Builder 的功能：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">1. 从非结构化文本构建知识图谱
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">2. 支持多种 LLM（OpenAI、Azure、Gemini、Ollama）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">3. 实体和关系抽取
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">4. 图谱可视化
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="工作流程">工作流程
&lt;/h3>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">┌──────────────────────────────────────────────────────┐
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ Neo4j Graph Builder Pipeline │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">└──────────────────────────────────────────────────────┘
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ▼
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ┌──────────────────┐
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │ 文档输入 │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │ (PDF/TXT/URL) │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> └────────┬─────────┘
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ▼
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ┌──────────────────┐
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │ 文本分块 │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> └────────┬─────────┘
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ▼
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ┌──────────────────┐
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │ LLM 实体抽取 │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │ &amp;#34;苹果公司&amp;#34; → Organization │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │ &amp;#34;库克&amp;#34; → Person │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> └────────┬─────────┘
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ▼
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ┌──────────────────┐
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │ LLM 关系抽取 │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │ (苹果公司, CEO, 库克) │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> └────────┬─────────┘
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ▼
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ┌──────────────────┐
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │ 存入 Neo4j │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │ CREATE (a:Org {name:&amp;#34;苹果&amp;#34;})│
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │ CREATE (b:Person {name:&amp;#34;库克&amp;#34;})│
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │ CREATE (a)-[:CEO]-&amp;gt;(b) │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> └────────┬─────────┘
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ▼
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ┌──────────────────┐
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │ 图检索 + LLM │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> └──────────────────┘
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="快速开始-3">快速开始
&lt;/h3>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 安装&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">pip&lt;/span> &lt;span class="n">install&lt;/span> &lt;span class="n">neo4j&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="n">graphrag&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">neo4j&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">GraphDatabase&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">neo4j_graphrag.llm&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">OpenAILLM&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">neo4j_graphrag.embeddings&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">OpenAIEmbeddings&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">neo4j_graphrag.generation&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">GraphRAG&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 连接 Neo4j&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">driver&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">GraphDatabase&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">driver&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;bolt://localhost:7687&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">auth&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;neo4j&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;password&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 初始化&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">llm&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">OpenAILLM&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">model_name&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;gpt-4&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">embedder&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">OpenAIEmbeddings&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">rag&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">GraphRAG&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">driver&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">llm&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">embedder&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 构建图谱&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">rag&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">build_graph&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;./documents&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 查询&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">result&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">rag&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">search&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;Who is the CEO of Apple?&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="适用场景-3">适用场景
&lt;/h3>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">✅ 推荐：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 企业级应用
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 需要可视化
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 已有 Neo4j 基础设施
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 复杂图查询
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">❌ 不推荐：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 小规模、轻量场景
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 不想维护图数据库
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="七其他值得关注的项目">七、其他值得关注的项目
&lt;/h2>&lt;h3 id="1-fast-graphrag">1. Fast-GraphRAG
&lt;/h3>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">GitHub: https://github.com/circlemind-ai/fast-graphrag
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Stars: 3,800+
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">特点：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 自适应：根据数据和查询自动调整
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 快速：优化的检索算法
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 简单：开箱即用
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">适用：快速部署，数据多样
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="2-r2r-reason-to-retrieve">2. R2R (Reason to Retrieve)
&lt;/h3>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">GitHub: https://github.com/SciPhi-AI/R2R
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Stars: 7,800+
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">特点：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 生产级 RAG 系统
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- Agentic RAG（Agent + RAG）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 支持知识图谱
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 完整 RESTful API
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 评估工具
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">适用：企业级部署
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="3-graph-rag-agent拼好rag">3. graph-rag-agent（拼好RAG）
&lt;/h3>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">GitHub: https://github.com/1517005260/graph-rag-agent
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Stars: 2,200+
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">特点：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 融合 GraphRAG + LightRAG + Neo4j
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- DeepSearch 推理能力
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 自制 GraphRAG 评估框架
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 国产项目，中文友好
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">适用：学习对比，评估测试
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="4-medical-graph-rag">4. Medical-Graph-RAG
&lt;/h3>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">GitHub: https://github.com/ImprintLab/Medical-Graph-RAG
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Stars: 800+
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">论文: ACL 2025
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">特点：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 医疗领域专用
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 循证医学信息检索
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 医学实体识别优化
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">适用：医疗知识库
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="5-graphrag-local-ui">5. GraphRAG-Local-UI
&lt;/h3>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">GitHub: https://github.com/severian42/GraphRAG-Local-UI
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Stars: 2,300+
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">特点：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 支持本地 LLM（Ollama）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 完整 UI 界面
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 索引/调参/查询/可视化
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 无需云端 API
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">适用：本地部署，隐私敏感
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="八框架对比">八、框架对比
&lt;/h2>&lt;h3 id="性能对比">性能对比
&lt;/h3>&lt;table>
&lt;thead>
&lt;tr>
&lt;th>指标&lt;/th>
&lt;th>GraphRAG&lt;/th>
&lt;th>LightRAG&lt;/th>
&lt;th>HippoRAG&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>索引速度&lt;/td>
&lt;td>慢&lt;/td>
&lt;td>快 10x&lt;/td>
&lt;td>中等&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>查询速度&lt;/td>
&lt;td>中等&lt;/td>
&lt;td>快 5x&lt;/td>
&lt;td>快&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>成本&lt;/td>
&lt;td>高&lt;/td>
&lt;td>低 50-90%&lt;/td>
&lt;td>中等&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>全局理解&lt;/td>
&lt;td>强&lt;/td>
&lt;td>中等&lt;/td>
&lt;td>中等&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>局部检索&lt;/td>
&lt;td>强&lt;/td>
&lt;td>强&lt;/td>
&lt;td>很强&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>实时更新&lt;/td>
&lt;td>❌&lt;/td>
&lt;td>✅&lt;/td>
&lt;td>✅&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;h3 id="功能对比">功能对比
&lt;/h3>&lt;table>
&lt;thead>
&lt;tr>
&lt;th>功能&lt;/th>
&lt;th>GraphRAG&lt;/th>
&lt;th>LightRAG&lt;/th>
&lt;th>HippoRAG&lt;/th>
&lt;th>Neo4j&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>社区发现&lt;/td>
&lt;td>✅&lt;/td>
&lt;td>❌&lt;/td>
&lt;td>❌&lt;/td>
&lt;td>✅&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>双层检索&lt;/td>
&lt;td>❌&lt;/td>
&lt;td>✅&lt;/td>
&lt;td>❌&lt;/td>
&lt;td>❌&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>PageRank&lt;/td>
&lt;td>❌&lt;/td>
&lt;td>❌&lt;/td>
&lt;td>✅&lt;/td>
&lt;td>✅&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>可视化&lt;/td>
&lt;td>❌&lt;/td>
&lt;td>❌&lt;/td>
&lt;td>❌&lt;/td>
&lt;td>✅&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>本地部署&lt;/td>
&lt;td>✅&lt;/td>
&lt;td>✅&lt;/td>
&lt;td>✅&lt;/td>
&lt;td>✅&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>流式插入&lt;/td>
&lt;td>❌&lt;/td>
&lt;td>✅&lt;/td>
&lt;td>✅&lt;/td>
&lt;td>✅&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;h3 id="适用场景对比">适用场景对比
&lt;/h3>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">大规模文档（10K+）：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">1. GraphRAG（全局理解）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">2. Neo4j + RAG（企业级）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">中小规模（&amp;lt; 10K）：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">1. LightRAG（快速、低成本）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">2. Fast-GraphRAG（简单部署）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">长期记忆：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">1. HippoRAG（模拟海马体）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">医疗/专业领域：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">1. Medical-Graph-RAG
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">2. KG_RAG
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">本地部署：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">1. GraphRAG-Local-UI
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">2. LightRAG + Ollama
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="九选型决策树">九、选型决策树
&lt;/h2>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">开始选型
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ├─ 需要全局理解（&amp;#34;核心观点&amp;#34;、&amp;#34;主要趋势&amp;#34;）？
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │ ├─ 是 → Microsoft GraphRAG
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │ └─ 否 → 继续
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ├─ 需要实时更新？
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │ ├─ 是 → LightRAG
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │ └─ 否 → 继续
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ├─ 需要可视化？
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │ ├─ 是 → Neo4j + RAG
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │ └─ 否 → 继续
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ├─ 长期记忆系统？
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │ ├─ 是 → HippoRAG
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │ └─ 否 → 继续
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ├─ 成本敏感？
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │ ├─ 是 → LightRAG
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │ └─ 否 → 继续
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ├─ 快速原型？
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │ ├─ 是 → LightRAG / Fast-GraphRAG
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │ └─ 否 → 继续
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> └─ 企业级部署？
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ├─ 是 → R2R / Neo4j + RAG
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> └─ 否 → LightRAG
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="十学习资源">十、学习资源
&lt;/h2>&lt;h3 id="必读论文">必读论文
&lt;/h3>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">1. GraphRAG: Unlocking LLM Discovery on Narrative Private Data
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - Microsoft GraphRAG 原始论文
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - 社区发现 + 社区摘要的核心思想
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">2. LightRAG: Simple and Fast Retrieval-Augmented Generation
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - EMNLP 2025
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - 双层检索的设计
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">3. HippoRAG: A Neurobiological Framework for Long-Term Memory
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - NeurIPS 2024
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - 模拟海马体的记忆机制
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">4. From Local to Global: A Graph RAG Approach to Query-Focused Summarization
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - 图检索到全局理解的演进
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="开源资源">开源资源
&lt;/h3>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">Awesome-GraphRAG:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">https://github.com/DEEP-PolyU/Awesome-GraphRAG
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 论文、项目、基准测试汇总
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">GraphRAG 深度学习:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">https://github.com/JayLZhou/GraphRAG
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- GraphRAG 源码解读
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">LightRAG 实验对比:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">https://github.com/NanGePlus/LightRAGTest
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- LightRAG vs GraphRAG 性能对比
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="官方文档">官方文档
&lt;/h3>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">Microsoft GraphRAG:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">https://microsoft.github.io/graphrag/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">LightRAG:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">https://github.com/HKUDS/LightRAG/blob/main/README.md
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Neo4j GraphRAG:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">https://neo4j.com/docs/graphrag-manual/
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="十一总结">十一、总结
&lt;/h2>&lt;p>Graph RAG 是 RAG 技术的重要演进方向，让检索从&amp;quot;局部相似度匹配&amp;quot;升级为&amp;quot;全局图推理&amp;quot;。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">核心框架对比：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Microsoft GraphRAG：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 定位：大规模文档，全局理解
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 优势：社区摘要，全局视角
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 代价：构建慢，成本高
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">LightRAG：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 定位：中小规模，快速部署
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 优势：快速、低成本、实时更新
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 代价：全局理解较弱
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">HippoRAG：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 定位：长期记忆，精准检索
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 优势：模拟海马体，联想检索
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 代价：相对复杂
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Neo4j + RAG：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 定位：企业级，可视化
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 优势：成熟图数据库，生态完善
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 代价：需要维护数据库
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>选型建议&lt;/strong>：&lt;/p>
&lt;ul>
&lt;li>快速上手：LightRAG&lt;/li>
&lt;li>全局理解：Microsoft GraphRAG&lt;/li>
&lt;li>企业部署：Neo4j + RAG 或 R2R&lt;/li>
&lt;li>长期记忆：HippoRAG&lt;/li>
&lt;li>本地部署：GraphRAG-Local-UI&lt;/li>
&lt;/ul>
&lt;p>没有银弹，关键是根据场景选择合适的工具。&lt;/p>
&lt;hr>
&lt;p>&lt;strong>相关文章&lt;/strong>：&lt;/p>
&lt;ul>
&lt;li>&lt;a class="link" href="https://www.zata.cc/p/rag-%E6%8A%80%E6%9C%AF%E5%85%A8%E6%99%AF%E4%BB%8E%E5%85%A5%E9%97%A8%E5%88%B0%E8%BF%9B%E9%98%B6/" >RAG 技术全景：从入门到进阶&lt;/a>&lt;/li>
&lt;li>&lt;a class="link" href="https://www.zata.cc/p/ragflow-%E6%B7%B1%E5%BA%A6%E8%A7%A3%E6%9E%90%E4%B8%BA%E4%BB%80%E4%B9%88%E5%AE%83%E6%98%AF%E6%9C%80%E5%80%BC%E5%BE%97%E5%85%B3%E6%B3%A8%E7%9A%84-rag-%E5%BC%80%E6%BA%90%E9%A1%B9%E7%9B%AE/" >RAGFlow 深度解析：为什么它是最值得关注的 RAG 开源项目&lt;/a>&lt;/li>
&lt;/ul></description></item><item><title>RAG 技术全景：从入门到进阶</title><link>https://www.zata.cc/p/rag-%E6%8A%80%E6%9C%AF%E5%85%A8%E6%99%AF%E4%BB%8E%E5%85%A5%E9%97%A8%E5%88%B0%E8%BF%9B%E9%98%B6/</link><pubDate>Thu, 26 Mar 2026 16:00:00 +0800</pubDate><guid>https://www.zata.cc/p/rag-%E6%8A%80%E6%9C%AF%E5%85%A8%E6%99%AF%E4%BB%8E%E5%85%A5%E9%97%A8%E5%88%B0%E8%BF%9B%E9%98%B6/</guid><description>&lt;img src="https://www.zata.cc/p/rag-%E6%8A%80%E6%9C%AF%E5%85%A8%E6%99%AF%E4%BB%8E%E5%85%A5%E9%97%A8%E5%88%B0%E8%BF%9B%E9%98%B6/images/index/index.png" alt="Featured image of post RAG 技术全景：从入门到进阶" />&lt;p>前面我们分析了 RAGFlow，它解决了&amp;quot;从文档到答案&amp;quot;的核心问题。但 RAG 领域发展极快，除了文档解析、Chunking、混合检索这些基础能力，还有很多值得关注的技术方向。&lt;/p>
&lt;p>这篇文章会系统梳理 RAG 的技术演进路线，帮你建立完整的知识图谱。&lt;/p>
&lt;h2 id="一rag-的技术演进">一、RAG 的技术演进
&lt;/h2>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">RAG 1.0：检索 + 生成
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Query → 向量检索 → LLM 生成
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">RAG 2.0：多路检索 + Rerank + 引用溯源
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Query → 多路检索 → 融合 → Rerank → 生成 + 引用
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">RAG 3.0：Agentic RAG + Graph RAG + 多模态
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Query → Agent 规划 → 多轮检索/推理 → 反思修正 → 生成
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>下面按技术方向逐一展开。&lt;/p>
&lt;h2 id="二graph-rag知识图谱--rag">二、Graph RAG：知识图谱 + RAG
&lt;/h2>&lt;blockquote>
&lt;p>本文对 Graph RAG 做概要介绍，详细的开源项目分析和选型指南请参阅 &lt;a class="link" href="https://www.zata.cc/p/graph-rag-%E5%BC%80%E6%BA%90%E9%A1%B9%E7%9B%AE%E5%85%A8%E6%99%AF%E4%BB%8E%E5%BE%AE%E8%BD%AF-graphrag-%E5%88%B0-lightrag/" >Graph RAG 开源项目全景：从微软 GraphRAG 到 LightRAG&lt;/a>。&lt;/p>
&lt;/blockquote>
&lt;h3 id="为什么需要-graph-rag">为什么需要 Graph RAG
&lt;/h3>&lt;p>传统 RAG 的局限：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">问题：&amp;#34;A 公司的合作方的竞争对手有哪些？&amp;#34;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">传统 RAG：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">1. 检索&amp;#34;A 公司的合作方&amp;#34; → 找到 B 公司
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">2. 检索&amp;#34;B 公司的竞争对手&amp;#34; → 找到 C、D 公司
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">问题：需要多轮检索，且中间结果可能丢失
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Graph RAG：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">1. 从知识图谱中找到 A → 合作 → B
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">2. 从知识图谱中找到 B → 竞争 → C、D
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">优势：一次图遍历即可完成多跳推理
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="graph-rag-的架构">Graph RAG 的架构
&lt;/h3>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">┌──────────────────────────────────────────────────────┐
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ Graph RAG Pipeline │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">└──────────────────────────────────────────────────────┘
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ┌───────────────┼───────────────┐
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ▼ ▼ ▼
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ┌─────────┐ ┌──────────┐ ┌──────────┐
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │ 文档输入 │ │ 实体抽取 │ │ 关系抽取 │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> └────┬────┘ └────┬─────┘ └────┬─────┘
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │ │ │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> └──────────────┼───────────────┘
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ▼
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ┌──────────────────┐
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │ 知识图谱构建 │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │ (Neo4j / NetworkX) │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> └────────┬─────────┘
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ┌──────────────┼───────────────┐
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ▼ ▼ ▼
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ┌─────────┐ ┌──────────┐ ┌──────────┐
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │ 向量检索 │ │ 图检索 │ │ 混合检索 │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> └────┬────┘ └────┬─────┘ └────┬─────┘
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │ │ │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> └──────────────┼───────────────┘
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ▼
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ┌──────────────────┐
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │ LLM 生成答案 │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> └──────────────────┘
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="核心技术点">核心技术点
&lt;/h3>&lt;p>&lt;strong>1. 实体抽取&lt;/strong>&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">输入文档：&amp;#34;苹果公司由史蒂夫·乔布斯于 1976 年创立，总部位于加利福尼亚州库比蒂诺。&amp;#34;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">实体抽取结果：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 苹果公司（Organization）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 史蒂夫·乔布斯（Person）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 1976 年（Date）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 加利福尼亚州库比蒂诺（Location）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">技术方案：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- NER 模型（spaCy、Stanford NER）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- LLM 抽取（GPT-4、Qwen）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 开源工具（DeepKE、OpenIE）
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>2. 关系抽取&lt;/strong>&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">关系抽取结果：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 苹果公司 -- 创立者 --&amp;gt; 史蒂夫·乔布斯
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 苹果公司 -- 创立时间 --&amp;gt; 1976 年
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 苹果公司 -- 总部位置 --&amp;gt; 加利福尼亚州库比蒂诺
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>3. 图检索策略&lt;/strong>&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">策略 1：子图检索
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 找到问题中的实体节点
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 扩展 N 跳邻居
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 将子图转为文本，喂给 LLM
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">策略 2：路径检索
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 找到起点和终点实体
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 检索两点间的路径
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 路径上的节点/边作为上下文
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">策略 3：社区检索
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 预先对图谱做社区发现
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 检索相关社区
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 社区摘要作为上下文
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="代表项目">代表项目
&lt;/h3>&lt;table>
&lt;thead>
&lt;tr>
&lt;th>项目&lt;/th>
&lt;th>特点&lt;/th>
&lt;th>适用场景&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;strong>Microsoft GraphRAG&lt;/strong>&lt;/td>
&lt;td>微软官方，社区摘要&lt;/td>
&lt;td>大规模文档全局理解&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>LightRAG&lt;/strong>&lt;/td>
&lt;td>轻量级，无图数据库依赖&lt;/td>
&lt;td>快速部署&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Neo4j + LangChain&lt;/strong>&lt;/td>
&lt;td>成熟图数据库，生态完善&lt;/td>
&lt;td>企业级应用&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>LlamaIndex GraphRAG&lt;/strong>&lt;/td>
&lt;td>与 LlamaIndex 深度集成&lt;/td>
&lt;td>已有 LlamaIndex 项目&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;h3 id="适用场景">适用场景
&lt;/h3>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">✅ 推荐使用 Graph RAG：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 需要多跳推理（&amp;#34;A 的 B 的 C 是什么&amp;#34;）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 需要全局理解（&amp;#34;这篇文章的核心观点&amp;#34;）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 实体关系密集（知识库、企业图谱）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 需要可解释的推理路径
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">❌ 不推荐：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 文档间无关联
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 问题简单，单次检索可解
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 实体稀疏，图构建成本高
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="三agentic-ragagent--rag">三、Agentic RAG：Agent + RAG
&lt;/h2>&lt;h3 id="从被动检索到主动规划">从被动检索到主动规划
&lt;/h3>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">传统 RAG（被动）：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Query → 检索 → 生成
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">问题：检索策略固定，无法应对复杂问题
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Agentic RAG（主动）：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Query → Agent 规划 → 执行 → 反思 → 再规划 → ...
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="agentic-rag-的核心能力">Agentic RAG 的核心能力
&lt;/h3>&lt;p>&lt;strong>1. 查询理解与改写&lt;/strong>&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">原始问题：&amp;#34;那个产品怎么样&amp;#34;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">↓ LLM 分析
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">问题太模糊，需要澄清
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">改写策略：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">1. 分解问题：&amp;#34;那个产品&amp;#34; → 识别具体产品
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">2. 扩展问题：生成多个相关 Query
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">3. HyDE：生成假设性答案，用答案检索
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">示例：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Query: &amp;#34;报销流程&amp;#34;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">↓
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">改写为：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">1. &amp;#34;公司报销流程步骤&amp;#34;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">2. &amp;#34;报销需要哪些材料&amp;#34;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">3. &amp;#34;报销审批流程&amp;#34;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>2. 迭代检索&lt;/strong>&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">第一轮检索：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Query: &amp;#34;A 公司的融资情况&amp;#34;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">结果: A 公司 B 轮融资 5000 万
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">判断：信息不完整，缺少投资方
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">↓
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">第二轮检索：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Query: &amp;#34;A 公司 B 轮投资方&amp;#34;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">结果: 红杉资本领投
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">判断：信息完整
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">↓
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">生成答案
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>3. 工具调用&lt;/strong>&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">Agent 可用的工具：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 向量检索：检索内部知识库
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 关键词检索：BM25 搜索
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 联网搜索：Google/Bing API
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- SQL 查询：查询结构化数据
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- API 调用：获取实时信息
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">示例：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Query: &amp;#34;今天北京天气怎么样，适合户外活动吗？&amp;#34;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">↓ Agent 规划
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">1. 调用天气 API 获取北京天气
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">2. 检索知识库中&amp;#34;户外活动适宜条件&amp;#34;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">3. 综合判断并回答
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>4. 自我反思&lt;/strong>&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">生成答案后，Agent 自我评估：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">评估维度：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 答案是否完整？
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 是否需要补充检索？
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 是否有矛盾信息？
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">示例：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">答案：&amp;#34;A 公司成立于 2010 年&amp;#34;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">反思：检索结果中有两个年份（2010 和 2012），需要验证
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">↓
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">再次检索：&amp;#34;A 公司成立时间 官方&amp;#34;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">↓
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">修正答案
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="agentic-rag-架构">Agentic RAG 架构
&lt;/h3>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">┌──────────────────────────────────────────────────────┐
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ Agentic RAG │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">└──────────────────────────────────────────────────────┘
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ▼
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ┌──────────────────┐
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │ Query 分析 │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │ - 意图识别 │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │ - 实体抽取 │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │ - 查询改写 │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> └────────┬─────────┘
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ▼
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ┌──────────────────┐
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │ 规划器 │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │ - 分解任务 │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │ - 选择工具 │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │ - 生成执行计划 │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> └────────┬─────────┘
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ┌──────────────┼───────────────┐
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ▼ ▼ ▼
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ┌─────────┐ ┌──────────┐ ┌──────────┐
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │ 向量检索 │ │ 联网搜索 │ │ SQL 查询 │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> └────┬────┘ └────┬─────┘ └────┬─────┘
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │ │ │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> └──────────────┼───────────────┘
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ▼
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ┌──────────────────┐
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │ 反思器 │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │ - 结果评估 │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │ - 是否继续检索 │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │ - 是否需要修正 │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> └────────┬─────────┘
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ▼
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ┌──────────────────┐
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │ LLM 生成答案 │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> └──────────────────┘
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="代表框架">代表框架
&lt;/h3>&lt;table>
&lt;thead>
&lt;tr>
&lt;th>框架&lt;/th>
&lt;th>特点&lt;/th>
&lt;th>适用场景&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;strong>LangGraph&lt;/strong>&lt;/td>
&lt;td>LangChain 出品，状态机模型&lt;/td>
&lt;td>复杂工作流&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>AutoGPT + RAG&lt;/strong>&lt;/td>
&lt;td>自主 Agent&lt;/td>
&lt;td>开放域任务&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>CrewAI&lt;/strong>&lt;/td>
&lt;td>多 Agent 协作&lt;/td>
&lt;td>团队式任务&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>LlamaIndex Agent&lt;/strong>&lt;/td>
&lt;td>内置 RAG Agent&lt;/td>
&lt;td>快速构建&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;h3 id="适用场景-1">适用场景
&lt;/h3>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">✅ 推荐使用 Agentic RAG：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 问题复杂，需要多步推理
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 需要调用多种工具
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 需要实时信息（联网、API）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 需要自我纠错
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">❌ 不推荐：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 问题简单，单次检索可解
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 对延迟敏感（Agent 会增加延迟）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 成本敏感（多轮 LLM 调用）
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="四多模态-rag">四、多模态 RAG
&lt;/h2>&lt;h3 id="为什么需要多模态">为什么需要多模态
&lt;/h3>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">企业知识库不只是文本：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 产品手册：文字 + 图片 + 示意图
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 技术文档：文字 + 代码 + 架构图
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 财务报告：文字 + 表格 + 图表
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 培训视频：音频 + 视频 + 字幕
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="多模态-rag-架构">多模态 RAG 架构
&lt;/h3>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">┌──────────────────────────────────────────────────────┐
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ 多模态 RAG Pipeline │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">└──────────────────────────────────────────────────────┘
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ┌───────────────┼───────────────┐
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ▼ ▼ ▼
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ┌─────────┐ ┌──────────┐ ┌──────────┐
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │ 文本 │ │ 图片 │ │ 表格 │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │ 处理 │ │ 处理 │ │ 处理 │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> └────┬────┘ └────┬─────┘ └────┬─────┘
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │ │ │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ▼ ▼ ▼
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ┌─────────┐ ┌──────────┐ ┌──────────┐
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │ 文本 │ │ VLM 描述 │ │ 结构化 │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │ Embedding│ │ Embedding│ │ 存储 │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> └────┬────┘ └────┬─────┘ └────┬─────┘
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │ │ │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> └──────────────┼───────────────┘
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ▼
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ┌──────────────────┐
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │ 统一向量存储 │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> └────────┬─────────┘
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ▼
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ┌──────────────────┐
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │ 多模态检索 │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │ - 文本 Query │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │ - 图片 Query │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> └────────┬─────────┘
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ▼
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ┌──────────────────┐
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │ 多模态生成 │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │ (GPT-4V, Qwen-VL)│
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> └──────────────────┘
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="各模态的处理方式">各模态的处理方式
&lt;/h3>&lt;p>&lt;strong>1. 图片&lt;/strong>&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">方案 1：VLM 描述
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">图片 → VLM → 文字描述 → Embedding
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">方案 2：直接 Embedding
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">图片 → CLIP/SigLIP → 图片向量
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">方案 3：混合
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">图片 → VLM 描述 + CLIP 向量 → 双路检索
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>2. 表格&lt;/strong>&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">方案 1：Markdown 化
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">表格 → Markdown 格式 → Embedding
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">方案 2：结构化存储
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">表格 → DataFrame → SQL 数据库
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">查询时：自然语言 → SQL → 执行查询
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">方案 3：行列切分
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">表格 → 每行一个 Chunk → 保留表头上下文
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>3. 音频/视频&lt;/strong>&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">音频：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">音频 → ASR → 文字 → Embedding
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">视频：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">视频 → 关键帧提取 → VLM 描述 → Embedding
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> → ASR（音轨） → 文字 → Embedding
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="代表技术">代表技术
&lt;/h3>&lt;table>
&lt;thead>
&lt;tr>
&lt;th>技术&lt;/th>
&lt;th>用途&lt;/th>
&lt;th>特点&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;strong>CLIP / SigLIP&lt;/strong>&lt;/td>
&lt;td>图文联合 Embedding&lt;/td>
&lt;td>支持以图搜文、以文搜图&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>GPT-4V / Qwen-VL&lt;/strong>&lt;/td>
&lt;td>视觉语言模型&lt;/td>
&lt;td>理解图片内容，生成描述&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>ColPali&lt;/strong>&lt;/td>
&lt;td>端到端文档检索&lt;/td>
&lt;td>直接用文档图片检索，无需 OCR&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Whisper&lt;/strong>&lt;/td>
&lt;td>语音识别&lt;/td>
&lt;td>音频转文字&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;h3 id="适用场景-2">适用场景
&lt;/h3>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">✅ 推荐使用多模态 RAG：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 知识库包含大量图片/图表
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 需要检索产品图片
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 需要理解架构图/流程图
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 视频内容检索
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">❌ 不推荐：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 纯文本知识库
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 对成本敏感（VLM 调用昂贵）
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="五长上下文-vs-rag">五、长上下文 vs RAG
&lt;/h2>&lt;h3 id="长上下文模型的崛起">长上下文模型的崛起
&lt;/h3>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">2023 年：Claude 2（100K）、GPT-4 Turbo（128K）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">2024 年：Claude 3（200K）、Gemini 1.5 Pro（1M）、Kimi（200K）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">2025 年：上下文窗口继续扩大...
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="长上下文能替代-rag-吗">长上下文能替代 RAG 吗？
&lt;/h3>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">长上下文方案：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Query + 全部文档 → LLM → 答案
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">优势：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 不需要检索，不会遗漏信息
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 全局理解能力强
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 实现简单
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">劣势：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 成本高（每 token 都要计费）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 延迟高（处理长上下文需要时间）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 不适合大规模知识库（100 万 token 放不下）
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="rag-的优势">RAG 的优势
&lt;/h3>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">RAG 方案：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Query → 检索 Top-K → LLM → 答案
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">优势：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 成本可控（只处理相关文档）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 延迟低
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 可扩展（知识库无限大）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 可追溯（知道答案来自哪里）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">劣势：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 可能检索不准，遗漏信息
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 缺乏全局理解
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="结论互补而非替代">结论：互补而非替代
&lt;/h3>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">场景分析：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| 场景 | 推荐方案 | 理由 |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">|------|----------|------|
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| 小规模文档（&amp;lt;100K） | 长上下文 | 简单，不会遗漏 |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| 大规模知识库 | RAG | 成本、延迟可控 |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| 需要全局理解 | 长上下文 | RAG 擅长局部检索 |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| 需要精确引用 | RAG | 可追溯来源 |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| 实时更新知识库 | RAG | 长上下文需要重新处理 |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">最佳实践：RAG + 长上下文
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- RAG 检索相关文档
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 长上下文模型处理检索结果
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 兼顾成本和效果
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="六检索技术进阶">六、检索技术进阶
&lt;/h2>&lt;h3 id="1-dense-retrieval-的演进">1. Dense Retrieval 的演进
&lt;/h3>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">第一代：Single Vector（单向量）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">文档 → 一个向量
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">问题：语义压缩过度，细节丢失
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">第二代：Multi-Vector（多向量，ColBERT）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">文档 → 每个 token 一个向量
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">问题：存储成本高，但精度提升
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">第三代：Late Interaction（延迟交互，ColPali）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">检索时不压缩，最后才做交互
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">优势：精度最高，适合文档检索
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>ColBERT 的核心思想&lt;/strong>&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">传统向量检索：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Query: &amp;#34;机器学习&amp;#34; → 向量 Q
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Doc: &amp;#34;机器学习是人工智能的分支...&amp;#34; → 向量 D
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">相似度 = Q · D
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">ColBERT：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Query: &amp;#34;机器学习&amp;#34; → [q1, q2]（每个 token 一个向量）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Doc: &amp;#34;机器学习是人工智能的分支...&amp;#34; → [d1, d2, d3, d4, ...]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">相似度 = Σ max(qi · dj) ← 每个 Query token 找最相似的 Doc token
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">优势：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- &amp;#34;机器学习&amp;#34;中的&amp;#34;机器&amp;#34;和&amp;#34;学习&amp;#34;分别匹配
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 精度更高
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="2-query-理解技术">2. Query 理解技术
&lt;/h3>&lt;p>&lt;strong>HyDE（Hypothetical Document Embeddings）&lt;/strong>&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-gdscript3" data-lang="gdscript3">&lt;span class="line">&lt;span class="cl">&lt;span class="err">问题：&lt;/span>&lt;span class="n">Query&lt;/span> &lt;span class="err">很短，语义信息不足&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">HyDE&lt;/span> &lt;span class="err">的思路：&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="mf">1.&lt;/span> &lt;span class="err">让&lt;/span> &lt;span class="n">LLM&lt;/span> &lt;span class="err">生成一个假设性答案&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="mf">2.&lt;/span> &lt;span class="err">用假设答案的向量检索&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="mf">3.&lt;/span> &lt;span class="err">假设答案与真实文档更相似&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">示例：&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">Query&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;Python 如何读取 JSON 文件？&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">↓&lt;/span> &lt;span class="n">LLM&lt;/span> &lt;span class="err">生成假设答案&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">假设答案&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;可以使用 json.load() 函数读取 JSON 文件。首先导入 json 模块，然后用 open() 打开文件...&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">↓&lt;/span> &lt;span class="err">用假设答案检索&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">检索结果&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="err">找到真实的&lt;/span> &lt;span class="n">JSON&lt;/span> &lt;span class="err">读取教程&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>Multi-Query（多查询）&lt;/strong>&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">问题：用户的 Query 可能表述不清
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Multi-Query 的思路：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">1. 用 LLM 生成多个相关 Query
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">2. 多路检索
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">3. 结果融合
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">示例：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Query: &amp;#34;报销流程&amp;#34;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">↓ LLM 生成
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Query 1: &amp;#34;公司报销流程步骤&amp;#34;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Query 2: &amp;#34;报销需要哪些材料&amp;#34;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Query 3: &amp;#34;报销审批流程&amp;#34;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">↓ 多路检索，融合结果
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="3-自适应检索self-rag">3. 自适应检索（Self-RAG）
&lt;/h3>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">问题：不是所有问题都需要检索
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Self-RAG 的思路：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">1. LLM 自己判断是否需要检索
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">2. 如果需要，检索并生成
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">3. 生成后，LLM 自我评估答案质量
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">4. 如果不满意，重新检索
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">示例：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Query: &amp;#34;写一个 Python Hello World&amp;#34;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">↓ LLM 判断
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">不需要检索（LLM 自己会写）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">↓ 直接生成
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Query: &amp;#34;公司最新的报销政策&amp;#34;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">↓ LLM 判断
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">需要检索（LLM 不知道）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">↓ 检索 → 生成
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="七生成技术进阶">七、生成技术进阶
&lt;/h2>&lt;h3 id="1-citation引用溯源">1. Citation（引用溯源）
&lt;/h3>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">RAG 的可信度关键：答案来自哪里？
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">输出格式：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">答案：参数 timeout 的类型是 int，默认值是 30 秒。
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">引用：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> [1] API 文档.pdf, 第 3 页, 表格 2
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> [2] 配置说明.md, 第 15 行
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">实现方式：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">1. 检索时记录每个 Chunk 的来源
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">2. 生成时让 LLM 标注使用了哪些 Chunk
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">3. 前端支持点击引用跳转原文
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="2-fact-checking事实核查">2. Fact Checking（事实核查）
&lt;/h3>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">问题：RAG 也可能产生幻觉
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">方案：生成后验证
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">流程：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">1. LLM 生成答案
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">2. 提取答案中的事实陈述
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">3. 再次检索验证每个事实
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">4. 标注置信度或修正错误
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">示例：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">答案：&amp;#34;A 公司成立于 2010 年，创始人张三&amp;#34;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">↓ 提取事实
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">事实 1: A 公司成立于 2010 年
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">事实 2: 创始人是张三
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">↓ 验证
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">事实 1: ✅ 文档确认
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">事实 2: ❌ 文档说是李四，不是张三
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">↓ 修正
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">答案：&amp;#34;A 公司成立于 2010 年，创始人李四&amp;#34;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="八工程优化">八、工程优化
&lt;/h2>&lt;h3 id="1-增量索引">1. 增量索引
&lt;/h3>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">问题：知识库每天都在更新
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">方案：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 只索引新增/修改的文档
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 向量数据库支持增量写入
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 版本管理（可回滚）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">实现：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">1. 记录每个文档的 hash
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">2. 检测变更
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">3. 只索引变更部分
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">4. 定期全量重建（避免碎片化）
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="2-多级缓存">2. 多级缓存
&lt;/h3>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">缓存层级：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Level 1: Exact Match Cache
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">完全相同的问题 → 直接返回缓存答案
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">命中率：10-20%
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Level 2: Semantic Cache
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">语义相似的问题 → 返回缓存答案
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">示例：&amp;#34;报销流程&amp;#34; ≈ &amp;#34;如何报销&amp;#34;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">命中率：20-30%
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Level 3: Query Cache
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">缓存检索结果，只重新生成
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">命中率：30-40%
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">效果：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 延迟降低 60-80%
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 成本降低 50-70%
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="3-rag-评估">3. RAG 评估
&lt;/h3>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">评估维度：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">检索质量：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- Recall@K：前 K 个结果中相关文档的比例
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- MRR：正确答案的平均排名
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- NDCG：考虑排序的检索质量
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">生成质量：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- Faithfulness：答案是否忠实于检索内容
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- Relevance：答案是否回答了问题
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- Coherence：答案是否连贯
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">端到端：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- Correctness：答案是否正确
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- Completeness：答案是否完整
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- Latency：响应延迟
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">评估工具：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- RAGAS：自动化 RAG 评估
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- TruLens：LLM 应用评估
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- DeepEval：开源评估框架
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="九技术选型指南">九、技术选型指南
&lt;/h2>&lt;h3 id="按场景选择">按场景选择
&lt;/h3>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">场景 1：企业知识库（文档密集）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">推荐：RAGFlow / Dify / FastGPT
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">理由：开箱即用，文档解析强
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">场景 2：需要多跳推理
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">推荐：GraphRAG / LightRAG
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">理由：知识图谱支持复杂推理
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">场景 3：需要 Agent 编排
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">推荐：LangGraph / CrewAI
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">理由：灵活的工作流控制
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">场景 4：多模态知识库
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">推荐：RAGFlow / LlamaIndex + VLM
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">理由：支持图片、表格、视频
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">场景 5：快速原型
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">推荐：Dify / AnythingLLM
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">理由：低代码，快速落地
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">场景 6：深度定制
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">推荐：LangChain / LlamaIndex
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">理由：底层控制能力强
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="按技术栈选择">按技术栈选择
&lt;/h3>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">已有 LangChain 项目：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">→ 继续用 LangChain + LangGraph
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">已有 LlamaIndex 项目：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">→ 继续用 LlamaIndex + GraphRAG
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">从零开始：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">→ 考虑 RAGFlow（一站式）或 Dify（低代码）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">需要图数据库：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">→ Neo4j + LangChain/LlamaIndex
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">需要极致性能：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">→ 自建向量库 + 自定义检索逻辑
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="十开源项目速览">十、开源项目速览
&lt;/h2>&lt;table>
&lt;thead>
&lt;tr>
&lt;th>项目&lt;/th>
&lt;th>Stars&lt;/th>
&lt;th>特点&lt;/th>
&lt;th>适用场景&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;strong>RAGFlow&lt;/strong>&lt;/td>
&lt;td>82K+&lt;/td>
&lt;td>DeepDoc 文档解析&lt;/td>
&lt;td>企业知识库&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>MinerU&lt;/strong>&lt;/td>
&lt;td>67K+&lt;/td>
&lt;td>通用文档解析&lt;/td>
&lt;td>文档转 Markdown&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Dify&lt;/strong>&lt;/td>
&lt;td>50K+&lt;/td>
&lt;td>RAG + Agent 平台&lt;/td>
&lt;td>低代码构建&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>FastGPT&lt;/strong>&lt;/td>
&lt;td>20K+&lt;/td>
&lt;td>国产 RAG 平台&lt;/td>
&lt;td>快速落地&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>LangGraph&lt;/strong>&lt;/td>
&lt;td>10K+&lt;/td>
&lt;td>Agent 编排&lt;/td>
&lt;td>复杂工作流&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>LightRAG&lt;/strong>&lt;/td>
&lt;td>10K+&lt;/td>
&lt;td>轻量级 Graph RAG&lt;/td>
&lt;td>多跳推理&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>GraphRAG&lt;/strong>&lt;/td>
&lt;td>10K+&lt;/td>
&lt;td>Microsoft 出品&lt;/td>
&lt;td>企业级图谱&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>QAnything&lt;/strong>&lt;/td>
&lt;td>10K+&lt;/td>
&lt;td>多模态 RAG&lt;/td>
&lt;td>端到端方案&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>AnythingLLM&lt;/strong>&lt;/td>
&lt;td>20K+&lt;/td>
&lt;td>全功能平台&lt;/td>
&lt;td>企业部署&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;h2 id="十一学习资源">十一、学习资源
&lt;/h2>&lt;p>&lt;strong>必读论文&lt;/strong>：&lt;/p>
&lt;ul>
&lt;li>Retrieval-Augmented Generation for Knowledge-Intensive NLP Tasks（RAG 原始论文）&lt;/li>
&lt;li>Self-RAG: Learning to Retrieve, Generate, and Critique through Self-Reflection&lt;/li>
&lt;li>GraphRAG: Unlocking LLM Discovery on Narrative Private Data&lt;/li>
&lt;li>Dense Passage Retrieval for Open-Domain Question Answering&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>官方文档&lt;/strong>：&lt;/p>
&lt;ul>
&lt;li>LlamaIndex Documentation&lt;/li>
&lt;li>LangChain RAG Tutorial&lt;/li>
&lt;li>RAGFlow Documentation&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>评估框架&lt;/strong>：&lt;/p>
&lt;ul>
&lt;li>RAGAS: &lt;a class="link" href="https://github.com/explodinggradients/ragas" target="_blank" rel="noopener"
>https://github.com/explodinggradients/ragas&lt;/a>&lt;/li>
&lt;li>TruLens: &lt;a class="link" href="https://www.trulens.org/" target="_blank" rel="noopener"
>https://www.trulens.org/&lt;/a>&lt;/li>
&lt;li>DeepEval: &lt;a class="link" href="https://github.com/confident-ai/deepeval" target="_blank" rel="noopener"
>https://github.com/confident-ai/deepeval&lt;/a>&lt;/li>
&lt;/ul>
&lt;h2 id="十二总结">十二、总结
&lt;/h2>&lt;p>RAG 技术正在快速演进，从简单的&amp;quot;检索 + 生成&amp;quot;发展为复杂的多技术融合系统。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">技术演进路线：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">RAG 1.0 → RAG 2.0 → RAG 3.0
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">基础检索 → 多路融合 → Agent + Graph + 多模态
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">核心趋势：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">1. 从被动检索到主动规划
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">2. 从单一模态到多模态融合
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">3. 从局部检索到全局理解
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">4. 从固定流程到自适应调整
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>选择技术方案时，关键是明确需求：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>简单场景&lt;/strong>：用 RAGFlow、Dify 这类一站式方案&lt;/li>
&lt;li>&lt;strong>复杂推理&lt;/strong>：引入 Graph RAG&lt;/li>
&lt;li>&lt;strong>Agent 编排&lt;/strong>：用 LangGraph&lt;/li>
&lt;li>&lt;strong>多模态&lt;/strong>：选择支持 VLM 的方案&lt;/li>
&lt;li>&lt;strong>深度定制&lt;/strong>：用 LangChain/LlamaIndex 自建&lt;/li>
&lt;/ul>
&lt;p>没有银弹，只有最适合的方案。&lt;/p>
&lt;hr>
&lt;p>&lt;strong>相关文章&lt;/strong>：&lt;/p>
&lt;ul>
&lt;li>&lt;a class="link" href="https://www.zata.cc/p/graph-rag-%E5%BC%80%E6%BA%90%E9%A1%B9%E7%9B%AE%E5%85%A8%E6%99%AF%E4%BB%8E%E5%BE%AE%E8%BD%AF-graphrag-%E5%88%B0-lightrag/" >Graph RAG 开源项目全景：从微软 GraphRAG 到 LightRAG&lt;/a>&lt;/li>
&lt;li>&lt;a class="link" href="https://www.zata.cc/p/ragflow-%E6%B7%B1%E5%BA%A6%E8%A7%A3%E6%9E%90%E4%B8%BA%E4%BB%80%E4%B9%88%E5%AE%83%E6%98%AF%E6%9C%80%E5%80%BC%E5%BE%97%E5%85%B3%E6%B3%A8%E7%9A%84-rag-%E5%BC%80%E6%BA%90%E9%A1%B9%E7%9B%AE/" >RAGFlow 深度解析：为什么它是最值得关注的 RAG 开源项目&lt;/a>&lt;/li>
&lt;/ul></description></item><item><title>RAGFlow 深度解析：为什么它是最值得关注的 RAG 开源项目</title><link>https://www.zata.cc/p/ragflow-%E6%B7%B1%E5%BA%A6%E8%A7%A3%E6%9E%90%E4%B8%BA%E4%BB%80%E4%B9%88%E5%AE%83%E6%98%AF%E6%9C%80%E5%80%BC%E5%BE%97%E5%85%B3%E6%B3%A8%E7%9A%84-rag-%E5%BC%80%E6%BA%90%E9%A1%B9%E7%9B%AE/</link><pubDate>Wed, 25 Mar 2026 16:00:00 +0800</pubDate><guid>https://www.zata.cc/p/ragflow-%E6%B7%B1%E5%BA%A6%E8%A7%A3%E6%9E%90%E4%B8%BA%E4%BB%80%E4%B9%88%E5%AE%83%E6%98%AF%E6%9C%80%E5%80%BC%E5%BE%97%E5%85%B3%E6%B3%A8%E7%9A%84-rag-%E5%BC%80%E6%BA%90%E9%A1%B9%E7%9B%AE/</guid><description>&lt;img src="https://www.zata.cc/p/ragflow-%E6%B7%B1%E5%BA%A6%E8%A7%A3%E6%9E%90%E4%B8%BA%E4%BB%80%E4%B9%88%E5%AE%83%E6%98%AF%E6%9C%80%E5%80%BC%E5%BE%97%E5%85%B3%E6%B3%A8%E7%9A%84-rag-%E5%BC%80%E6%BA%90%E9%A1%B9%E7%9B%AE/images/index.png" alt="Featured image of post RAGFlow 深度解析：为什么它是最值得关注的 RAG 开源项目" />&lt;p>过去两年，RAG 领域涌现了无数框架：LangChain、LlamaIndex、Haystack……它们各有侧重，但解决的核心问题相似——如何让 LLM 接地气。&lt;/p>
&lt;p>但如果你实际做过企业级 RAG 项目，会发现一个被忽视的问题：&lt;strong>文档解析&lt;/strong>。&lt;/p>
&lt;p>大部分框架默认你已经有干净、结构化的文本。现实却是：PDF 扫描件、嵌套表格、数学公式、多栏排版……这些&amp;quot;脏活累活&amp;quot;往往占据项目 60% 以上的时间。&lt;/p>
&lt;p>RAGFlow 的定位很明确：&lt;strong>从文档到答案的全流程解决方案&lt;/strong>，而不是又一个&amp;quot;需要你自己处理文档&amp;quot;的检索框架。&lt;/p>
&lt;p>这篇文章会深入分析 RAGFlow 的设计思路、核心技术和适用场景。&lt;/p>
&lt;h2 id="一ragflow-的差异化定位">一、RAGFlow 的差异化定位
&lt;/h2>&lt;h3 id="现有框架的盲区">现有框架的盲区
&lt;/h3>&lt;p>LangChain 和 LlamaIndex 的设计假设是：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">输入：干净的文本 / 简单的 PDF
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">处理：Chunk → Embed → Retrieve → Generate
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">输出：答案
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>但企业真实场景是：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">输入：扫描件 PDF、带表格的年报、技术手册、合同......
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">预处理：OCR、布局分析、表格识别、公式识别......
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">处理：Chunk → Embed → Retrieve → Generate
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">输出：答案 + 引用
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>预处理阶段的复杂度，往往被框架忽略。&lt;/strong>&lt;/p>
&lt;h3 id="ragflow-的思路">RAGFlow 的思路
&lt;/h3>&lt;p>RAGFlow 的核心定位：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">不只是 RAG 框架，而是&amp;#34;文档理解 + RAG&amp;#34;的一体化方案
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>它的技术栈覆盖：&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>阶段&lt;/th>
&lt;th>能力&lt;/th>
&lt;th>技术方案&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>文档解析&lt;/td>
&lt;td>OCR、布局分析、表格识别&lt;/td>
&lt;td>DeepDoc（自研）&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>文档切分&lt;/td>
&lt;td>8 种切分策略&lt;/td>
&lt;td>按文档类型选择&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>检索&lt;/td>
&lt;td>向量 + 关键词 + Rerank&lt;/td>
&lt;td>混合检索 + RRF 融合&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>生成&lt;/td>
&lt;td>多模型支持&lt;/td>
&lt;td>OpenAI、Qwen、Ollama 等&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>运维&lt;/td>
&lt;td>可视化 UI、API、监控&lt;/td>
&lt;td>企业级特性&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>&lt;strong>一句话总结&lt;/strong>：RAGFlow 解决的是&amp;quot;从 PDF 到答案&amp;quot;的完整链路，而不是&amp;quot;从文本到答案&amp;quot;。&lt;/p>
&lt;h2 id="二deepdocragflow-的核心护城河">二、DeepDoc：RAGFlow 的核心护城河
&lt;/h2>&lt;p>DeepDoc 是 RAGFlow 自研的文档理解引擎，也是它区别于其他框架的核心竞争力。&lt;/p>
&lt;h3 id="为什么需要-deepdoc">为什么需要 DeepDoc
&lt;/h3>&lt;p>传统 PDF 解析的问题：&lt;/p>
&lt;p>&lt;strong>问题 1：OCR 质量差&lt;/strong>&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">原文：错误码 E0028
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">OCR 结果：错误码 E002B（8 被识别成 B）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">用户问：错误码 E0028 是什么意思
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">检索：找不到（因为文档里是 E002B）
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>问题 2：布局信息丢失&lt;/strong>&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">原文（两栏排版）：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">┌──────────────┬──────────────┐
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ 左栏内容 │ 右栏内容 │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ 跨多行... │ 跨多行... │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">└──────────────┴──────────────┘
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">普通解析结果：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">左栏内容
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">右栏内容 ← 两栏混在一起，语义被打断
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>问题 3：表格结构破坏&lt;/strong>&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">原文表格：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">┌────────┬────────┬────────┐
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ 参数 │ 类型 │ 说明 │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">├────────┼────────┼────────┤
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ timeout│ int │ 超时时间│
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ retry │ bool │ 是否重试│
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">└────────┴────────┴────────┘
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">普通解析结果：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">参数 类型 说明 timeout int 超时时间 retry bool 是否重试
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">← 变成一行，结构全丢了
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="deepdoc-的技术方案">DeepDoc 的技术方案
&lt;/h3>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">DeepDoc Pipeline：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">┌──────────┐ ┌──────────┐ ┌──────────────┐
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ Input │───▶│ Layout │───▶│ OCR Engine │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ File │ │ Analysis│ │ (PaddleOCR) │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">└──────────┘ └──────────┘ └──────┬───────┘
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ┌──────────────────────┘
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ▼
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">┌──────────────────────────────────────────────┐
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ Structure Recognition │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ ┌──────────┐ ┌──────────┐ ┌──────────────┐ │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ │ Table │ │ Figure │ │ Formula │ │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ │ Parser │ │ Parser │ │ Parser │ │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ └──────────┘ └──────────┘ └──────────────┘ │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">└──────────────────────────────────────────────┘
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ▼
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">┌──────────────────────────────────────────────┐
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ Semantic Understanding │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ • 标题识别 │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ • 章节分割 │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ • 关系提取 │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">└──────────────────────────────────────────────┘
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ▼
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">┌──────────────────────────────────────────────┐
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ Markdown / Structured Output │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">└──────────────────────────────────────────────┘
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>核心技术点&lt;/strong>：&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>模块&lt;/th>
&lt;th>技术方案&lt;/th>
&lt;th>解决的问题&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;strong>OCR&lt;/strong>&lt;/td>
&lt;td>PaddleOCR&lt;/td>
&lt;td>高精度文字识别&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>布局分析&lt;/strong>&lt;/td>
&lt;td>YOLO/LayoutLM&lt;/td>
&lt;td>检测文本、表格、图片区域&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>表格识别&lt;/strong>&lt;/td>
&lt;td>Table-Transformer&lt;/td>
&lt;td>还原表格结构&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>公式识别&lt;/strong>&lt;/td>
&lt;td>LaTeX-OCR&lt;/td>
&lt;td>数学公式转 LaTeX&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>图片描述&lt;/strong>&lt;/td>
&lt;td>VLM&lt;/td>
&lt;td>生成图片文字描述&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;h3 id="表格识别的原理">表格识别的原理
&lt;/h3>&lt;p>表格是最难处理的文档元素之一。DeepDoc 的表格识别流程：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">Step 1: 表格检测
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">用目标检测模型定位表格区域
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Step 2: 结构识别
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Table-Transformer 识别：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 行边界
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 列边界
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 单元格合并关系
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Step 3: 内容识别
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">对每个单元格做 OCR
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Step 4: 结构化输出
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">┌────────┬────────┐
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ A1 │ A2 │ → [{&amp;#34;row&amp;#34;:1,&amp;#34;col&amp;#34;:1,&amp;#34;text&amp;#34;:&amp;#34;A1&amp;#34;}, ...]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">├────────┼────────┤
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ B1 │ B2 │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">└────────┴────────┘
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>为什么这件事重要&lt;/strong>？&lt;/p>
&lt;p>如果表格结构丢了，用户问&amp;quot;参数 timeout 的类型是什么&amp;quot;，系统找不到答案——因为表格已经变成一团乱码。&lt;/p>
&lt;h3 id="与其他解析方案的对比">与其他解析方案的对比
&lt;/h3>&lt;p>|| 方案 | OCR | 布局分析 | 表格识别 | 公式识别 | 输出格式 |
|&amp;mdash;&amp;mdash;|&amp;mdash;&amp;ndash;|&amp;mdash;&amp;mdash;&amp;mdash;-|&amp;mdash;&amp;mdash;&amp;mdash;-|&amp;mdash;&amp;mdash;&amp;mdash;-|&amp;mdash;&amp;mdash;&amp;mdash;-|
| PyPDF2 | ❌ | ❌ | ❌ | ❌ | 纯文本 |
| pdfplumber | ❌ | ⚠️ 基础 | ⚠️ 基础 | ❌ | 纯文本 |
| Unstructured | ⚠️ 外部 | ✅ | ⚠️ 基础 | ❌ | 结构化 |
| &lt;strong>MinerU&lt;/strong> | ✅ | ✅ | ✅ | ✅ | Markdown/JSON |
| &lt;strong>DeepDoc&lt;/strong> | ✅ | ✅ | ✅ | ✅ | 结构化 + 语义信息 |&lt;/p>
&lt;h3 id="deepdoc-vs-mineru不同的设计哲学">DeepDoc vs MinerU：不同的设计哲学
&lt;/h3>&lt;p>MinerU 是 2024 年上海人工智能实验室开源的文档解析工具，同样具备强大的 OCR、表格识别、公式识别能力。但 DeepDoc 和 MinerU 的设计目标有本质区别：&lt;/p>
&lt;p>&lt;strong>MinerU：通用文档解析工具&lt;/strong>&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">定位：将文档转换为 LLM 可用的 Markdown/JSON
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">输出：标准化的文本格式
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">目标：服务于各种下游应用（RAG、Agent、知识库等）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">集成：支持 LangChain、Dify、FastGPT、RAGFlow 等
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>DeepDoc：RAG 原生文档理解引擎&lt;/strong>&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">定位：RAGFlow 的核心组件，深度集成
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">输出：保留语义结构的中间表示（标题层级、章节边界、表格结构等）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">目标：直接服务于 Chunking 和检索策略
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">特点：与后续流程紧密耦合，非独立工具
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>核心差异：输出是否保留结构语义&lt;/strong>&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">MinerU 输出（Markdown）：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"># 第一章 概述
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">## 1.1 背景
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">文本内容...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">## 1.2 目标
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">文本内容...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">↓ 丢失了什么？
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 标题的层级深度信息
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 章节边界的精确位置
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 表格的行列结构（变成 Markdown 表格，但丢失原始坐标）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">DeepDoc 输出（结构化表示）：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">{
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &amp;#34;chunks&amp;#34;: [
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &amp;#34;text&amp;#34;: &amp;#34;文本内容...&amp;#34;,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &amp;#34;metadata&amp;#34;: {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &amp;#34;hierarchy&amp;#34;: [&amp;#34;第一章&amp;#34;, &amp;#34;概述&amp;#34;, &amp;#34;1.1&amp;#34;, &amp;#34;背景&amp;#34;],
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &amp;#34;level&amp;#34;: 3,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &amp;#34;bbox&amp;#34;: [x1, y1, x2, y2],
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &amp;#34;type&amp;#34;: &amp;#34;paragraph&amp;#34;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> }
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> }
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ],
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &amp;#34;tables&amp;#34;: [
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &amp;#34;cells&amp;#34;: [[...]],
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &amp;#34;headers&amp;#34;: [&amp;#34;参数&amp;#34;, &amp;#34;类型&amp;#34;, &amp;#34;说明&amp;#34;],
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &amp;#34;bbox&amp;#34;: [...]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> }
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>为什么 RAGFlow 不用 MinerU？&lt;/strong>&lt;/p>
&lt;ol>
&lt;li>&lt;strong>时间线&lt;/strong>：RAGFlow（2023.12）比 MinerU（2024.02）更早，DeepDoc 已经开发成熟&lt;/li>
&lt;li>&lt;strong>深度集成&lt;/strong>：DeepDoc 的输出直接适配 8 种 Chunking 策略，需要语义和结构信息&lt;/li>
&lt;li>&lt;strong>核心护城河&lt;/strong>：文档理解是 RAGFlow 的差异化竞争力，不依赖外部工具&lt;/li>
&lt;li>&lt;strong>输出差异&lt;/strong>：MinerU 输出 Markdown，DeepDoc 输出保留更多结构信息&lt;/li>
&lt;/ol>
&lt;p>&lt;strong>选择建议&lt;/strong>&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>场景&lt;/th>
&lt;th>推荐&lt;/th>
&lt;th>理由&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>只需要文档转 Markdown&lt;/td>
&lt;td>MinerU&lt;/td>
&lt;td>开箱即用，输出标准&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>构建企业 RAG 系统&lt;/td>
&lt;td>RAGFlow + DeepDoc&lt;/td>
&lt;td>文档解析与检索一体化&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>已有 RAG 框架，需补充解析能力&lt;/td>
&lt;td>MinerU&lt;/td>
&lt;td>作为独立组件集成&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>需要自定义 Chunking 逻辑&lt;/td>
&lt;td>MinerU + 自己实现&lt;/td>
&lt;td>更灵活&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>DeepDoc 是目前开源方案中唯一为 RAG 场景深度优化的文档解析引擎。&lt;/p>
&lt;h2 id="三chunking-策略一种文档一种切法">三、Chunking 策略：一种文档一种切法
&lt;/h2>&lt;p>RAGFlow 提供了 8 种 Chunking 策略，针对不同文档类型：&lt;/p>
&lt;h3 id="为什么需要多种策略">为什么需要多种策略
&lt;/h3>&lt;p>不同类型文档的结构差异很大：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">FAQ 文档：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Q: 问题
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">A: 答案
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">─────────
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">每个 QA 对是独立的语义单元
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">技术手册：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">## 安装
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">步骤 1...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">步骤 2...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">## 配置
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">参数 1...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">参数 2...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">─────────
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">按章节切分，保留标题上下文
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">法律文档：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">第一条 ...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">第二条 ...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">第三条 ...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">─────────
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">按条款切分，每条有独立法律意义
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>用同一种&amp;quot;固定长度切分&amp;quot;处理所有文档，效果必然不好。&lt;/p>
&lt;h3 id="ragflow-的-8-种策略">RAGFlow 的 8 种策略
&lt;/h3>&lt;table>
&lt;thead>
&lt;tr>
&lt;th>策略&lt;/th>
&lt;th>适用文档&lt;/th>
&lt;th>切分依据&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;strong>Naive&lt;/strong>&lt;/td>
&lt;td>简单文本&lt;/td>
&lt;td>固定字符数&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Q&amp;amp;A&lt;/strong>&lt;/td>
&lt;td>FAQ 文档&lt;/td>
&lt;td>问题-答案对&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Book&lt;/strong>&lt;/td>
&lt;td>书籍、长文档&lt;/td>
&lt;td>章节层级&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Manual&lt;/strong>&lt;/td>
&lt;td>技术手册&lt;/td>
&lt;td>小节&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Law&lt;/strong>&lt;/td>
&lt;td>法律、合同&lt;/td>
&lt;td>条款&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Paper&lt;/strong>&lt;/td>
&lt;td>学术论文&lt;/td>
&lt;td>摘要、章节&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Table&lt;/strong>&lt;/td>
&lt;td>表格数据&lt;/td>
&lt;td>行/单元格&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Picture&lt;/strong>&lt;/td>
&lt;td>图片&lt;/td>
&lt;td>VLM 描述&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;h3 id="核心思路保留语义完整性">核心思路：保留语义完整性
&lt;/h3>&lt;p>无论哪种策略，核心原则相同：&lt;strong>一个 chunk 应该能独立回答一个问题&lt;/strong>。&lt;/p>
&lt;p>以 Book Chunking 为例：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">原始文档：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"># 第一章 概述
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">## 1.1 背景
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">文本内容...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">## 1.2 目标
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">文本内容...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"># 第二章 设计
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">## 2.1 架构
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">文本内容...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">切分结果：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Chunk 1: 【第一章 概述 &amp;gt; 1.1 背景】文本内容...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Chunk 2: 【第一章 概述 &amp;gt; 1.2 目标】文本内容...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Chunk 3: 【第二章 设计 &amp;gt; 2.1 架构】文本内容...
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>关键设计：&lt;strong>把父级标题拼到 chunk 前面&lt;/strong>。&lt;/p>
&lt;p>这样做的原因我在 RAG 进阶篇详细讲过：Embedding 只能看到 chunk 内部的文字。如果 chunk 里没有&amp;quot;第一章&amp;quot;、&amp;ldquo;概述&amp;rdquo;、&amp;ldquo;背景&amp;quot;这些关键词，用户问&amp;quot;第一章的背景是什么&amp;quot;时就召不回。&lt;/p>
&lt;h3 id="table-chunking-的特殊性">Table Chunking 的特殊性
&lt;/h3>&lt;p>表格数据的切分需要特殊处理：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">方案 1：行级切分
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">每行一个 chunk，保留表头
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">方案 2：单元格级切分
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">每个单元格一个 chunk，附带行列信息
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">RAGFlow 的做法：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 检测表格结构
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 保留表头作为上下文
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 行级切分
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 每个 chunk 包含：表头 + 该行内容
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">示例：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">原始表格：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| 参数 | 类型 | 说明 |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">|------|------|------|
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">| timeout | int | 超时时间 |
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">切分后的 chunk：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">【表：API 参数配置】
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">参数: timeout | 类型: int | 说明: 超时时间
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>这样用户问&amp;quot;timeout 参数的类型是什么&amp;rdquo;，就能精确召回这一行。&lt;/p>
&lt;h2 id="四检索架构混合检索--rrf-融合">四、检索架构：混合检索 + RRF 融合
&lt;/h2>&lt;p>RAGFlow 的检索架构是标准的混合检索方案：&lt;/p>
&lt;h3 id="整体架构">整体架构
&lt;/h3>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">Query
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ├─── 向量检索（Dense）───┐
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │ │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ├─── 关键词检索（BM25）──┤──→ RRF 融合 ──→ Rerank ──→ Top-K
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │ │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> └─── Metadata 过滤 ─────┘
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="rrf-融合算法">RRF 融合算法
&lt;/h3>&lt;p>RRF（Reciprocal Rank Fusion）的核心思想：&lt;strong>不依赖分数，只依赖排名&lt;/strong>。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">RRF 公式：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">score(d) = Σ 1 / (k + rank_i(d))
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">其中：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- d：文档
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- rank_i(d)：文档 d 在第 i 路检索中的排名
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- k：平滑参数（通常 60）
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>为什么有效&lt;/strong>？&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">问题：向量分数（0.82）和 BM25 分数（5.2）不在同一尺度
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">传统方案：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 分数归一化：复杂，不稳定
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 加权融合：权重难调
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">RRF 的做法：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 不管分数多少
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 只看排名
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 排名第 1 → 得分 1/61
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 排名第 2 → 得分 1/62
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 两路都召回的文档，得分更高
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>示例&lt;/strong>：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">Query：&amp;#34;错误码 401&amp;#34;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">向量检索：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">#1 Doc_A
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">#2 Doc_B
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">#3 Doc_C
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">BM25 检索：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">#1 Doc_D
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">#2 Doc_C ← 两路都有
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">#3 Doc_A ← 两路都有
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">RRF 融合：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Doc_C: 1/(60+3) + 1/(60+2) = 0.032 ← 最高
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Doc_A: 1/(60+1) + 1/(60+3) = 0.032
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Doc_B: 1/(60+2) + 0 = 0.016
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Doc_D: 0 + 1/(60+1) = 0.016
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>两路都召回的文档，得分更高，自然排在前面。&lt;/p>
&lt;h3 id="rerank-实现">Rerank 实现
&lt;/h3>&lt;p>RAGFlow 内置 Cross-Encoder Rerank：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">原理：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Query + Document → Transformer → Relevance Score
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">为什么比向量检索更准：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 向量检索：Query 和 Document 各自编码，交互少
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- Cross-Encoder：Query 和 Document 拼在一起编码，充分交互
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>支持的 Rerank 模型：&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>模型&lt;/th>
&lt;th>特点&lt;/th>
&lt;th>延迟（100 候选）&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>bge-reranker-v2-m3&lt;/td>
&lt;td>多语言，效果好&lt;/td>
&lt;td>~180ms&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>bge-reranker-large&lt;/td>
&lt;td>效果好&lt;/td>
&lt;td>~150ms&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>ms-marco-MiniLM&lt;/td>
&lt;td>轻量，快速&lt;/td>
&lt;td>~45ms&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>Rerank 的成本很高，只能对小规模候选做精排。RAGFlow 的默认配置：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">召回：top-50（向量 + BM25）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Rerank：top-50 全部重排
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">输出：top-5
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="五与其他框架的对比">五、与其他框架的对比
&lt;/h2>&lt;h3 id="功能对比">功能对比
&lt;/h3>&lt;table>
&lt;thead>
&lt;tr>
&lt;th>维度&lt;/th>
&lt;th>RAGFlow&lt;/th>
&lt;th>LangChain&lt;/th>
&lt;th>LlamaIndex&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;strong>文档解析&lt;/strong>&lt;/td>
&lt;td>⭐⭐⭐⭐⭐ DeepDoc&lt;/td>
&lt;td>⭐⭐⭐ 基础&lt;/td>
&lt;td>⭐⭐⭐ 基础&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Chunking&lt;/strong>&lt;/td>
&lt;td>⭐⭐⭐⭐⭐ 8 种策略&lt;/td>
&lt;td>⭐⭐⭐ 需自己实现&lt;/td>
&lt;td>⭐⭐⭐⭐ 较丰富&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>检索&lt;/strong>&lt;/td>
&lt;td>⭐⭐⭐⭐⭐ 混合 + RRF&lt;/td>
&lt;td>⭐⭐⭐ 需组合&lt;/td>
&lt;td>⭐⭐⭐⭐ 较完整&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>可视化 UI&lt;/strong>&lt;/td>
&lt;td>⭐⭐⭐⭐⭐ 完整&lt;/td>
&lt;td>⭐⭐ LangSmith&lt;/td>
&lt;td>⭐⭐⭐ LlamaCloud&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>开箱即用&lt;/strong>&lt;/td>
&lt;td>⭐⭐⭐⭐⭐ 高&lt;/td>
&lt;td>⭐⭐⭐ 需组装&lt;/td>
&lt;td>⭐⭐⭐⭐ 较高&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>自定义扩展&lt;/strong>&lt;/td>
&lt;td>⭐⭐⭐ 中等&lt;/td>
&lt;td>⭐⭐⭐⭐⭐ 高&lt;/td>
&lt;td>⭐⭐⭐⭐⭐ 高&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;h3 id="架构理念对比">架构理念对比
&lt;/h3>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">RAGFlow：应用导向
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&amp;#34;我要做一个企业知识库&amp;#34; → RAGFlow 提供完整方案
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">特点：一站式，开箱即用
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">LangChain：组件导向
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&amp;#34;我要组装一个 Agent&amp;#34; → LangChain 提供各种组件
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">特点：灵活，但需要自己组装
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">LlamaIndex：数据导向
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&amp;#34;我要索引和查询数据&amp;#34; → LlamaIndex 提供数据框架
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">特点：数据处理能力强，适合研究
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="代码风格对比">代码风格对比
&lt;/h3>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># RAGFlow：高层抽象，配置驱动&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">ragflow&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">RAGFlow&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">app&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">RAGFlow&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">dataset&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">app&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">create_dataset&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">name&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;knowledge&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">dataset&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">add_documents&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;./docs&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">chat&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">app&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">create_chat&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">dataset&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">dataset&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">response&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">chat&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">query&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;What is RAG?&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># LangChain：Chain 组合式&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">langchain.chains&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">RetrievalQA&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">langchain.vectorstores&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">Chroma&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">langchain.embeddings&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">OpenAIEmbeddings&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">vectorstore&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">Chroma&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">from_documents&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">docs&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">OpenAIEmbeddings&lt;/span>&lt;span class="p">())&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">qa&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">RetrievalQA&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">from_chain_type&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">llm&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">ChatOpenAI&lt;/span>&lt;span class="p">(),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">retriever&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">vectorstore&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">as_retriever&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">response&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">qa&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">run&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;What is RAG?&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># LlamaIndex：索引为中心&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">llama_index&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">VectorStoreIndex&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">SimpleDirectoryReader&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">documents&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">SimpleDirectoryReader&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;./docs&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">load_data&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">index&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">VectorStoreIndex&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">from_documents&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">documents&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">query_engine&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">index&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">as_query_engine&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">response&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">query_engine&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">query&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;What is RAG?&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="选型建议">选型建议
&lt;/h3>&lt;table>
&lt;thead>
&lt;tr>
&lt;th>场景&lt;/th>
&lt;th>推荐&lt;/th>
&lt;th>理由&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>企业知识库&lt;/td>
&lt;td>RAGFlow&lt;/td>
&lt;td>开箱即用，DeepDoc 解析复杂文档&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>复杂 Agent 应用&lt;/td>
&lt;td>LangChain&lt;/td>
&lt;td>灵活的 Chain/Tool 组合&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>数据分析研究&lt;/td>
&lt;td>LlamaIndex&lt;/td>
&lt;td>强大的索引策略&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>快速原型&lt;/td>
&lt;td>RAGFlow&lt;/td>
&lt;td>最快落地&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>高度定制化&lt;/td>
&lt;td>LangChain/LlamaIndex&lt;/td>
&lt;td>更底层的控制&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;h2 id="六生产级特性">六、生产级特性
&lt;/h2>&lt;h3 id="api-设计">API 设计
&lt;/h3>&lt;p>RAGFlow 提供完整的 REST API：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 创建数据集&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">POST /api/dataset
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">{&lt;/span>&lt;span class="s2">&amp;#34;name&amp;#34;&lt;/span>: &lt;span class="s2">&amp;#34;my_knowledge&amp;#34;&lt;/span>&lt;span class="o">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 上传文档&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">POST /api/document/upload
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">-F &lt;span class="s2">&amp;#34;file=@document.pdf&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">-F &lt;span class="s2">&amp;#34;dataset_id={id}&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 对话&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">POST /api/chat/completion
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;question&amp;#34;&lt;/span>: &lt;span class="s2">&amp;#34;什么是 RAG?&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;dataset_ids&amp;#34;&lt;/span>: &lt;span class="o">[&lt;/span>&lt;span class="s2">&amp;#34;{id}&amp;#34;&lt;/span>&lt;span class="o">]&lt;/span>,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;stream&amp;#34;&lt;/span>: &lt;span class="nb">true&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="部署方案">部署方案
&lt;/h3>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">最小部署（Docker Compose）：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">├── ragflow（API 服务）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">├── milvus（向量库）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">├── mysql（元数据）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">└── elasticsearch（可选，全文检索）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">生产部署（Kubernetes）：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">├── Ingress
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">├── ragflow deployment（多副本）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">├── milvus cluster
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">├── mysql primary/replica
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">└── 监控（Prometheus + Grafana）
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="多租户与权限">多租户与权限
&lt;/h3>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">租户隔离：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 每个租户独立的数据集
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 数据集级别的访问控制
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- API Key 认证
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">权限模型：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 管理员：全部权限
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 编辑者：上传、解析、编辑
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 查看者：仅查询
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="可观测性">可观测性
&lt;/h3>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">指标监控：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 文档处理吞吐量
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 检索延迟（P50/P95/P99）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- LLM 调用耗时
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 向量库查询性能
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">日志追踪：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 请求级 Trace ID
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 结构化日志
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 全链路追踪
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="七ragflow-的优势与局限">七、RAGFlow 的优势与局限
&lt;/h2>&lt;h3 id="优势">优势
&lt;/h3>&lt;table>
&lt;thead>
&lt;tr>
&lt;th>维度&lt;/th>
&lt;th>说明&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;strong>DeepDoc&lt;/strong>&lt;/td>
&lt;td>业界最强的开源文档解析能力&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>开箱即用&lt;/strong>&lt;/td>
&lt;td>完整 UI + API，快速落地&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>企业级&lt;/strong>&lt;/td>
&lt;td>多租户、权限、监控完备&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>多策略 Chunking&lt;/strong>&lt;/td>
&lt;td>针对不同文档类型优化&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>混合检索&lt;/strong>&lt;/td>
&lt;td>向量 + BM25 + Rerank 完整支持&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;h3 id="局限">局限
&lt;/h3>&lt;table>
&lt;thead>
&lt;tr>
&lt;th>维度&lt;/th>
&lt;th>说明&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;strong>灵活性&lt;/strong>&lt;/td>
&lt;td>不如 LangChain/LlamaIndex 灵活&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Agent 能力&lt;/strong>&lt;/td>
&lt;td>不适合复杂多 Agent 场景&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>定制成本&lt;/strong>&lt;/td>
&lt;td>深度定制需要理解源码&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>学习曲线&lt;/strong>&lt;/td>
&lt;td>概念较多，需要时间熟悉&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;h3 id="适用场景">适用场景
&lt;/h3>&lt;p>&lt;strong>强烈推荐&lt;/strong>：&lt;/p>
&lt;ul>
&lt;li>企业内部知识库&lt;/li>
&lt;li>需要处理复杂文档（PDF、表格）&lt;/li>
&lt;li>追求快速落地&lt;/li>
&lt;li>需要可视化管理和审计&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>谨慎选择&lt;/strong>：&lt;/p>
&lt;ul>
&lt;li>复杂 Agent 编排&lt;/li>
&lt;li>需要深度定制检索逻辑&lt;/li>
&lt;li>已有 LangChain/LlamaIndex 生态&lt;/li>
&lt;/ul>
&lt;h2 id="八快速入门">八、快速入门
&lt;/h2>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 克隆项目&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">git clone https://github.com/infiniflow/ragflow.git
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">cd&lt;/span> ragflow
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Docker Compose 启动&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">docker compose up -d
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 访问 Web UI&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">open http://localhost:9380
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 创建数据集、上传文档、开始对话&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>核心概念：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">Dataset（数据集）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">├── Document（文档）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ ├── Chunk（切片）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ └── Embedding（向量）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">└── Chat（对话）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">流程：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">上传文档 → DeepDoc 解析 → 切分 → 向量化 → 存储
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ↓
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Query → 检索 → Rerank → LLM 生成 → 答案 + 引用
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="九总结">九、总结
&lt;/h2>&lt;p>RAGFlow 不是&amp;quot;又一个 RAG 框架&amp;quot;，而是&lt;strong>从文档到答案的全流程解决方案&lt;/strong>。&lt;/p>
&lt;p>它的核心价值：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">1. DeepDoc：解决被忽视的文档解析问题
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">2. 多策略 Chunking：不同文档不同切法
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">3. 混合检索：向量 + BM25 + Rerank
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">4. 开箱即用：UI + API + 部署方案
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>如果你在做企业级 RAG 项目，尤其是需要处理复杂文档的场景，RAGFlow 是目前最值得尝试的开源方案。&lt;/p>
&lt;p>它不一定是灵活性最高的，但可能是落地最快的。&lt;/p>
&lt;hr>
&lt;p>&lt;strong>相关链接&lt;/strong>：&lt;/p>
&lt;ul>
&lt;li>GitHub: &lt;a class="link" href="https://github.com/infiniflow/ragflow" target="_blank" rel="noopener"
>https://github.com/infiniflow/ragflow&lt;/a>&lt;/li>
&lt;li>文档: &lt;a class="link" href="https://ragflow.io/docs" target="_blank" rel="noopener"
>https://ragflow.io/docs&lt;/a>&lt;/li>
&lt;li>Demo: &lt;a class="link" href="https://ragflow.io" target="_blank" rel="noopener"
>https://ragflow.io&lt;/a>&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>相关文章&lt;/strong>：&lt;/p>
&lt;ul>
&lt;li>&lt;a class="link" href="https://www.zata.cc/p/rag-%E6%8A%80%E6%9C%AF%E5%85%A8%E6%99%AF%E4%BB%8E%E5%85%A5%E9%97%A8%E5%88%B0%E8%BF%9B%E9%98%B6/" >RAG 技术全景：从入门到进阶&lt;/a>&lt;/li>
&lt;li>&lt;a class="link" href="https://www.zata.cc/p/graph-rag-%E5%BC%80%E6%BA%90%E9%A1%B9%E7%9B%AE%E5%85%A8%E6%99%AF%E4%BB%8E%E5%BE%AE%E8%BD%AF-graphrag-%E5%88%B0-lightrag/" >Graph RAG 开源项目全景：从微软 GraphRAG 到 LightRAG&lt;/a>&lt;/li>
&lt;/ul></description></item><item><title>RAG生产实践：常见坑、性能优化与迭代路线图</title><link>https://www.zata.cc/p/rag%E7%94%9F%E4%BA%A7%E5%AE%9E%E8%B7%B5%E5%B8%B8%E8%A7%81%E5%9D%91%E6%80%A7%E8%83%BD%E4%BC%98%E5%8C%96%E4%B8%8E%E8%BF%AD%E4%BB%A3%E8%B7%AF%E7%BA%BF%E5%9B%BE/</link><pubDate>Wed, 25 Mar 2026 15:55:00 +0800</pubDate><guid>https://www.zata.cc/p/rag%E7%94%9F%E4%BA%A7%E5%AE%9E%E8%B7%B5%E5%B8%B8%E8%A7%81%E5%9D%91%E6%80%A7%E8%83%BD%E4%BC%98%E5%8C%96%E4%B8%8E%E8%BF%AD%E4%BB%A3%E8%B7%AF%E7%BA%BF%E5%9B%BE/</guid><description>&lt;img src="https://www.zata.cc/p/rag%E7%94%9F%E4%BA%A7%E5%AE%9E%E8%B7%B5%E5%B8%B8%E8%A7%81%E5%9D%91%E6%80%A7%E8%83%BD%E4%BC%98%E5%8C%96%E4%B8%8E%E8%BF%AD%E4%BB%A3%E8%B7%AF%E7%BA%BF%E5%9B%BE/images/index/index.png" alt="Featured image of post RAG生产实践：常见坑、性能优化与迭代路线图" />&lt;p>RAG 的 Demo 很容易做，生产环境却很容易失控。&lt;/p>
&lt;p>一开始你可能只要：&lt;/p>
&lt;ul>
&lt;li>一个 embedding 模型&lt;/li>
&lt;li>一个向量库&lt;/li>
&lt;li>一个聊天模型&lt;/li>
&lt;/ul>
&lt;p>但真到线上，问题会迅速变成：&lt;/p>
&lt;ul>
&lt;li>更新怎么做&lt;/li>
&lt;li>成本怎么控&lt;/li>
&lt;li>延迟怎么降&lt;/li>
&lt;li>数据怎么隔离&lt;/li>
&lt;li>错误怎么排查&lt;/li>
&lt;li>版本怎么回归&lt;/li>
&lt;/ul>
&lt;p>所以生产级 RAG 的重点是&amp;quot;工程稳定性&amp;quot;，不只是&amp;quot;回答效果&amp;quot;。&lt;/p>
&lt;p>这篇文章会讲：&lt;/p>
&lt;ul>
&lt;li>生产环境最常见的坑&lt;/li>
&lt;li>性能优化的思路&lt;/li>
&lt;li>一个可执行的迭代路线&lt;/li>
&lt;/ul>
&lt;h2 id="一最常见的-10-个坑">一、最常见的 10 个坑
&lt;/h2>&lt;h3 id="坑-1把所有文档放进一个大集合">坑 1：把所有文档放进一个大集合
&lt;/h3>&lt;p>&lt;strong>现象&lt;/strong>：&lt;/p>
&lt;p>公司有产品文档、技术规范、内部 wiki、销售话术、HR 制度……全部丢进一个向量库。&lt;/p>
&lt;p>&lt;strong>问题&lt;/strong>：&lt;/p>
&lt;p>用户问&amp;quot;API 超时怎么配置&amp;quot;，检索结果混入了销售话术（因为提到了&amp;quot;时间&amp;quot;）、HR 制度（因为提到了&amp;quot;加班时间&amp;quot;）。正确答案被噪声淹没。&lt;/p>
&lt;p>&lt;strong>原理&lt;/strong>：&lt;/p>
&lt;p>向量检索基于语义相似度。&amp;ldquo;时间&amp;quot;这个概念在很多文档里都出现，如果检索范围太大，语义相似的噪声也会被召回。&lt;/p>
&lt;p>&lt;strong>解决&lt;/strong>：&lt;/p>
&lt;p>按业务边界拆分 source，检索时指定来源。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">知识库结构：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">├── product_docs/ # 产品文档
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">├── api_specs/ # API 规范
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">├── internal_wiki/ # 内部 wiki
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">└── hr_policies/ # HR 制度
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">检索时：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">filter = {&amp;#34;source&amp;#34;: &amp;#34;api_specs&amp;#34;}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>效果&lt;/strong>：&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>配置&lt;/th>
&lt;th>Recall@5&lt;/th>
&lt;th>噪声比例&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>全量检索&lt;/td>
&lt;td>58%&lt;/td>
&lt;td>62%&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>按 source 过滤&lt;/td>
&lt;td>84%&lt;/td>
&lt;td>18%&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;h3 id="坑-2chunk-没有业务上下文">坑 2：chunk 没有业务上下文
&lt;/h3>&lt;p>&lt;strong>现象&lt;/strong>：&lt;/p>
&lt;p>按段落切分后，某个 chunk 只剩一句话：&amp;ldquo;将旋钮调至 3 档，等待指示灯变绿。&amp;rdquo;&lt;/p>
&lt;p>&lt;strong>问题&lt;/strong>：&lt;/p>
&lt;p>用户问&amp;quot;ZK-200 怎么校准&amp;rdquo;，这个 chunk 不会召回。因为产品名、设备类型、操作名称都在标题里——被切掉了。&lt;/p>
&lt;p>&lt;strong>原理&lt;/strong>：&lt;/p>
&lt;p>Embedding 只能看到 chunk 内部的文字。如果 chunk 里没有&amp;quot;ZK-200&amp;quot;和&amp;quot;校准&amp;quot;这些关键词，向量就不会和这些问题相似。&lt;/p>
&lt;p>&lt;strong>解决&lt;/strong>：&lt;/p>
&lt;p>把父级标题拼到 chunk 前面：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">原始 chunk：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&amp;#34;将旋钮调至 3 档，等待指示灯变绿。&amp;#34;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">补充上下文：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&amp;#34;【ZK-200 操作手册 &amp;gt; 2. 使用方法 &amp;gt; 2.3 校准流程】
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">将旋钮调至 3 档，等待指示灯变绿。&amp;#34;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>效果&lt;/strong>：&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>配置&lt;/th>
&lt;th>Recall@5&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>原始 chunk&lt;/td>
&lt;td>41%&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>补充上下文后&lt;/td>
&lt;td>78%&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>这是 RAG 里性价比最高的优化之一：成本几乎为零，效果翻倍。&lt;/p>
&lt;h3 id="坑-3只做-dense-search">坑 3：只做 dense search
&lt;/h3>&lt;p>&lt;strong>现象&lt;/strong>：&lt;/p>
&lt;p>用户问&amp;quot;错误码 E0028 是什么意思&amp;quot;，向量检索召回的是&amp;quot;常见错误码及解决方案&amp;quot;这种泛泛而谈的文档，而不是精确包含&amp;quot;E0028&amp;quot;的那段。&lt;/p>
&lt;p>&lt;strong>原理&lt;/strong>：&lt;/p>
&lt;p>向量检索擅长语义相似，不擅长精确匹配。&amp;ldquo;E0028&amp;quot;作为一个编号，在向量空间里没有特殊意义，它只是一个 token。&lt;/p>
&lt;p>&lt;strong>解决&lt;/strong>：&lt;/p>
&lt;p>引入 BM25 或 sparse 检索，做混合检索。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">混合检索：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">向量检索 → 召回语义相似的文档
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">BM25 检索 → 召回包含精确关键词的文档
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">RRF 融合 → 合并两路结果
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>效果&lt;/strong>：&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>检索方式&lt;/th>
&lt;th>精确匹配查询 Recall@5&lt;/th>
&lt;th>语义查询 Recall@5&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>向量检索&lt;/td>
&lt;td>47%&lt;/td>
&lt;td>82%&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>BM25&lt;/td>
&lt;td>76%&lt;/td>
&lt;td>58%&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>混合检索&lt;/td>
&lt;td>84%&lt;/td>
&lt;td>85%&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;h3 id="坑-4假-rerank">坑 4：假 rerank
&lt;/h3>&lt;p>&lt;strong>现象&lt;/strong>：&lt;/p>
&lt;p>代码里写着&amp;quot;rerank&amp;rdquo;，但只是把向量分数重新排了一次序。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">def rerank(candidates):
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> return sorted(candidates, key=lambda x: x[&amp;#39;score&amp;#39;], reverse=True)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>问题&lt;/strong>：&lt;/p>
&lt;p>这不是真正的 rerank，只是把已有的分数重新排序，效果和没有 rerank 一样。&lt;/p>
&lt;p>&lt;strong>原理&lt;/strong>：&lt;/p>
&lt;p>真正的 Rerank 需要用 Cross-Encoder：&lt;/p>
&lt;ul>
&lt;li>把 Query 和每个候选 Document 拼在一起&lt;/li>
&lt;li>丢进 Transformer，让模型学习两者的交互&lt;/li>
&lt;li>输出新的相关性分数&lt;/li>
&lt;/ul>
&lt;p>这比双塔结构的向量检索更精确，因为 Query 和 Document 能充分交互。&lt;/p>
&lt;p>&lt;strong>解决&lt;/strong>：&lt;/p>
&lt;p>使用真正的 Cross-Encoder 模型：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">候选文档 → Cross-Encoder → 新分数 → 重新排序
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>效果&lt;/strong>：&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>配置&lt;/th>
&lt;th>Recall@5&lt;/th>
&lt;th>正确答案平均排名&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>无 Rerank&lt;/td>
&lt;td>72%&lt;/td>
&lt;td>4.2&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>假 Rerank&lt;/td>
&lt;td>72%&lt;/td>
&lt;td>4.1&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>真 Rerank&lt;/td>
&lt;td>88%&lt;/td>
&lt;td>1.8&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;h3 id="坑-5top_k-和阈值写死">坑 5：top_k 和阈值写死
&lt;/h3>&lt;p>&lt;strong>现象&lt;/strong>：&lt;/p>
&lt;p>所有场景都用固定的 top_k=5 和 score_threshold=0.3。&lt;/p>
&lt;p>&lt;strong>问题&lt;/strong>：&lt;/p>
&lt;ul>
&lt;li>简单问题：只需要 3 个候选就够了，多余的候选增加噪声&lt;/li>
&lt;li>复杂问题：需要 10 个候选才能覆盖，5 个不够&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>原理&lt;/strong>：&lt;/p>
&lt;p>检索参数取决于问题的复杂度、文档的分布、embedding 模型的特性。不同场景的最优参数不同。&lt;/p>
&lt;p>&lt;strong>解决&lt;/strong>：&lt;/p>
&lt;p>参数可配置，甚至动态调整：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">简单事实问题 → top_k=3, threshold=0.5（少而精）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">复杂问题 → top_k=15, threshold=0（多召回）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">对比类问题 → top_k=20（需要更多信息）
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="坑-6没有-context-budget-设计">坑 6：没有 context budget 设计
&lt;/h3>&lt;p>&lt;strong>现象&lt;/strong>：&lt;/p>
&lt;p>检索到 15 个 chunk，全部塞进 prompt，总共 12000 tokens。&lt;/p>
&lt;p>&lt;strong>问题&lt;/strong>：&lt;/p>
&lt;ul>
&lt;li>成本高：token 消耗大&lt;/li>
&lt;li>延迟高：模型处理慢&lt;/li>
&lt;li>效果差：内容太长，模型注意力分散（Lost in the Middle 问题）&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>原理&lt;/strong>：&lt;/p>
&lt;p>LLM 的注意力机制在长文本上会分散。研究表明，当相关信息在 prompt 中间位置时，模型更容易忽略它。&lt;/p>
&lt;p>&lt;strong>解决&lt;/strong>：&lt;/p>
&lt;p>设计 context budget：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">预算：4000 tokens
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">组装策略：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">1. 优先高分 chunk
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">2. 控制 token 总量
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">3. 必要时截断过长的 chunk
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">4. 保持来源多样性（不同 source 的 chunk 都有）
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>经验值&lt;/strong>：&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>场景&lt;/th>
&lt;th>推荐 token 预算&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>简单问答&lt;/td>
&lt;td>2000-3000&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>复杂问答&lt;/td>
&lt;td>3000-5000&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>文档总结&lt;/td>
&lt;td>5000-8000&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;h3 id="坑-7文档更新了索引没同步">坑 7：文档更新了，索引没同步
&lt;/h3>&lt;p>&lt;strong>现象&lt;/strong>：&lt;/p>
&lt;p>周一用户问&amp;quot;报销流程&amp;quot;，答案正确。周三财务更新了报销规范，周五用户再问，答案还是旧的。&lt;/p>
&lt;p>&lt;strong>问题&lt;/strong>：&lt;/p>
&lt;p>文档更新后，向量库没有同步，还在用旧版本。&lt;/p>
&lt;p>&lt;strong>解决&lt;/strong>：&lt;/p>
&lt;p>建立版本管理机制：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">方案 1：定时全量同步
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 每天凌晨重建索引
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 简单但成本高
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">方案 2：增量更新
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 监控文档变更
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 只更新变化的文档
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 复杂但成本低
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">方案 3：版本标记
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 每个文档有版本号
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 检索时检查版本是否最新
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 过期则重新索引
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>关键&lt;/strong>：保留文档的 hash 或时间戳，便于判断是否需要更新。&lt;/p>
&lt;h3 id="坑-8没有证据引用">坑 8：没有证据引用
&lt;/h3>&lt;p>&lt;strong>现象&lt;/strong>：&lt;/p>
&lt;p>模型回答：&amp;ldquo;报销需要提交发票和审批单。&amp;rdquo;&lt;/p>
&lt;p>用户：&amp;ldquo;这个答案依据是什么？&amp;rdquo;&lt;/p>
&lt;p>系统无法回答。&lt;/p>
&lt;p>&lt;strong>问题&lt;/strong>：&lt;/p>
&lt;p>答案和来源脱节，用户无法验证答案的可信度。&lt;/p>
&lt;p>&lt;strong>解决&lt;/strong>：&lt;/p>
&lt;p>Prompt 中强制要求引用：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">要求：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">1. 答案必须基于参考资料
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">2. 回答时标注引用来源，格式：[来源: 文档名 &amp;gt; 章节]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">3. 如果不确定，说明不确定
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>答案示例：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">报销需要提交发票和审批单 [来源: 财务规范 &amp;gt; 3.2 报销流程]。
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>好处&lt;/strong>：&lt;/p>
&lt;ul>
&lt;li>用户可以验证答案&lt;/li>
&lt;li>系统可以追溯错误&lt;/li>
&lt;li>增加答案可信度&lt;/li>
&lt;/ul>
&lt;h3 id="坑-9没有离线-benchmark">坑 9：没有离线 benchmark
&lt;/h3>&lt;p>&lt;strong>现象&lt;/strong>：&lt;/p>
&lt;p>开发者说&amp;quot;效果提升了&amp;quot;，产品说&amp;quot;感觉变差了&amp;quot;，谁也说服不了谁。&lt;/p>
&lt;p>&lt;strong>问题&lt;/strong>：&lt;/p>
&lt;p>没有客观的评价标准，优化靠主观感受。&lt;/p>
&lt;p>&lt;strong>解决&lt;/strong>：&lt;/p>
&lt;p>建立固定 benchmark：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">评测集：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 50-100 个问题
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 每个问题有期望召回的 chunk
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 每个问题有参考答案
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">定期评测：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 每次改动后跑 benchmark
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 对比历史版本
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 设置阈值（如 Recall@5 &amp;gt; 80%）
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>原则&lt;/strong>：&lt;/p>
&lt;ul>
&lt;li>评测集要隔离，不参与训练&lt;/li>
&lt;li>定期从线上采样真实问题，更新评测集&lt;/li>
&lt;li>多指标综合评估&lt;/li>
&lt;/ul>
&lt;h3 id="坑-10没有-inspect-能力">坑 10：没有 inspect 能力
&lt;/h3>&lt;p>&lt;strong>现象&lt;/strong>：&lt;/p>
&lt;p>答案错了，不知道是检索错、排序错、还是模型错。&lt;/p>
&lt;p>&lt;strong>问题&lt;/strong>：&lt;/p>
&lt;p>无法定位问题，优化靠猜。&lt;/p>
&lt;p>&lt;strong>解决&lt;/strong>：&lt;/p>
&lt;p>保留每次请求的 Trace：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">Trace 包含：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 用户问题
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 检索候选（top-10）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- Rerank 结果
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 最终 prompt
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 模型答案
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">有 Inspect 页面，可以查看每一步的中间结果。
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>好处&lt;/strong>：&lt;/p>
&lt;ul>
&lt;li>快速定位问题&lt;/li>
&lt;li>积累 bad cases&lt;/li>
&lt;li>指导优化方向&lt;/li>
&lt;/ul>
&lt;h2 id="二性能优化的思路">二、性能优化的思路
&lt;/h2>&lt;h3 id="延迟分析">延迟分析
&lt;/h3>&lt;p>一个典型 RAG 请求的延迟分布：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">总延迟：400-600ms
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">├── Query Embedding：30-50ms（5-10%）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">├── 向量检索：30-50ms（5-10%）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">├── BM25 检索：10-20ms（2-5%）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">├── RRF 融合：&amp;lt;5ms（&amp;lt;1%）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">├── Rerank：150-200ms（30-50%）← 主要瓶颈
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">├── Prompt 组装：&amp;lt;10ms（&amp;lt;1%）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">└── LLM 生成：150-300ms（30-50%）← 主要瓶颈
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>两个主要瓶颈：&lt;strong>Rerank&lt;/strong> 和 &lt;strong>LLM 生成&lt;/strong>。&lt;/p>
&lt;h3 id="rerank-优化">Rerank 优化
&lt;/h3>&lt;p>&lt;strong>思路 1：减少候选数量&lt;/strong>&lt;/p>
&lt;p>Rerank 的计算量与候选数量成正比。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">100 个候选 × Cross-Encoder ≈ 200ms
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">30 个候选 × Cross-Encoder ≈ 70ms
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>但不能减太少，否则会漏掉正确答案。建议：&lt;/p>
&lt;ul>
&lt;li>召回 50-100 个&lt;/li>
&lt;li>Rerank 前 20-30 个&lt;/li>
&lt;li>输出 top 5-10 个&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>思路 2：用更快的模型&lt;/strong>&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>模型&lt;/th>
&lt;th>延迟（100 候选）&lt;/th>
&lt;th>Recall@5&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>bge-reranker-large&lt;/td>
&lt;td>~180ms&lt;/td>
&lt;td>88%&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>bge-reranker-base&lt;/td>
&lt;td>~95ms&lt;/td>
&lt;td>85%&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>ms-marco-MiniLM&lt;/td>
&lt;td>~45ms&lt;/td>
&lt;td>80%&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>速度和效果的权衡。&lt;/p>
&lt;p>&lt;strong>思路 3：跳过简单问题&lt;/strong>&lt;/p>
&lt;p>对于简单问题，可能不需要 Rerank：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">简单问题（候选集中度高）→ 跳过 Rerank
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">复杂问题（候选分散）→ 需要 Rerank
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>判断标准：看 top-5 候选的分数分布。如果第一名分数远高于其他，说明检索已经足够准确。&lt;/p>
&lt;h3 id="llm-优化">LLM 优化
&lt;/h3>&lt;p>&lt;strong>思路 1：用更快的模型&lt;/strong>&lt;/p>
&lt;p>GPT-4o-mini 比 GPT-4o 快 3-5 倍，成本是 1/10。&lt;/p>
&lt;p>对于大部分 RAG 场景，mini 版足够用。&lt;/p>
&lt;p>&lt;strong>思路 2：流式输出&lt;/strong>&lt;/p>
&lt;p>不要等完整答案生成，边生成边返回：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">传统模式：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">用户等待 → 完整答案 → 一次返回（延迟高）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">流式模式：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">用户等待 → 第一个字 → 第二个字 → ...（感知延迟低）
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>流式不会减少实际延迟，但能改善用户体验。&lt;/p>
&lt;p>&lt;strong>思路 3：缓存热门 query&lt;/strong>&lt;/p>
&lt;p>相同问题直接返回缓存：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">热门问题 TOP 100：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&amp;#34;报销流程&amp;#34; → 缓存答案
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&amp;#34;API 认证&amp;#34; → 缓存答案
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">命中率：10-30%
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">节省：10-30% 的 LLM 调用
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="检索优化">检索优化
&lt;/h3>&lt;p>&lt;strong>思路 1：缓存 query embedding&lt;/strong>&lt;/p>
&lt;p>同一个问题的 embedding 可以缓存：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">Query → Embedding（40ms）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Query（重复）→ 缓存（&amp;lt;1ms）
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>思路 2：预计算&lt;/strong>&lt;/p>
&lt;p>某些场景可以预计算：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">FAQ 场景：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 预计算每个 FAQ 的 embedding
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 预计算每个 FAQ 的最佳答案
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 用户问题直接匹配 FAQ
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">适合问题集合固定的场景。
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>思路 3：减少召回数量&lt;/strong>&lt;/p>
&lt;p>如果检索质量已经很高，可以减少召回数量：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">top-50 → top-30
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">节省：向量检索和 Rerank 的时间
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">风险：可能漏掉正确答案
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="三成本优化">三、成本优化
&lt;/h2>&lt;h3 id="token-消耗分析">Token 消耗分析
&lt;/h3>&lt;p>单次请求的 token 消耗：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">输入 token：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- Query：20-50
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- Chunks（5个）：2000-5000
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- System prompt：100-200
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">总输入：2000-6000 tokens
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">输出 token：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- Answer：100-500
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">按 GPT-4o-mini 定价：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">输入：$0.15/1M tokens
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">输出：$0.60/1M tokens
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">单次请求成本：$0.0003-0.001
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">日均 10000 次 → 月成本：$100-300
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="成本优化思路">成本优化思路
&lt;/h3>&lt;p>&lt;strong>1. 控制上下文长度&lt;/strong>&lt;/p>
&lt;p>上下文太长不仅成本高，效果还差。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">经验值：3000-4000 tokens
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">超过预算时：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 截断过长的 chunk
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 减少进入 prompt 的 chunk 数量
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 优先保留高分的 chunk
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>2. 用更便宜的模型&lt;/strong>&lt;/p>
&lt;p>GPT-4o-mini 的成本是 GPT-4o 的 1/10，效果差距不大。&lt;/p>
&lt;p>&lt;strong>3. 缓存热门问题&lt;/strong>&lt;/p>
&lt;p>10-30% 的请求是重复问题，直接返回缓存。&lt;/p>
&lt;p>&lt;strong>4. 优化 prompt&lt;/strong>&lt;/p>
&lt;p>去掉冗余的 prompt 内容，精简指令。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">差的 prompt（冗长）：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&amp;#34;你是一个非常专业的技术文档助手，你的任务是根据用户提供的参考资料
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">来回答用户的问题。请注意，你的回答必须严格基于参考资料...&amp;#34;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">好的 prompt（精简）：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&amp;#34;根据参考资料回答问题。答案必须基于资料，标注来源[数字]。&amp;#34;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="四迭代路线图">四、迭代路线图
&lt;/h2>&lt;h3 id="v1能跑通1-2-周">V1：能跑通（1-2 周）
&lt;/h3>&lt;p>&lt;strong>目标&lt;/strong>：回答对 60% 的核心问题&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">配置：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 基础 chunking（按段落）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 纯向量检索（top_k=5）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 基础 prompt + 引用要求
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">验收：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 能回答常见问题
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 有引用来源
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 能 demo 演示
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>不要做&lt;/strong>：&lt;/p>
&lt;ul>
&lt;li>不要全量导入所有文档&lt;/li>
&lt;li>不要追求复杂架构&lt;/li>
&lt;li>不要过度优化&lt;/li>
&lt;/ul>
&lt;h3 id="v2能诊断2-3-周">V2：能诊断（2-3 周）
&lt;/h3>&lt;p>&lt;strong>目标&lt;/strong>：知道为什么对/错&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">配置：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- Inspect 页面（看检索候选）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- Metadata 过滤
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 参数可配置
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 离线评测集（50 条）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">验收：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 能看到检索的 top-10
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 能定位问题在哪一层
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 有 Recall@5 指标
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="v3能优化3-4-周">V3：能优化（3-4 周）
&lt;/h3>&lt;p>&lt;strong>目标&lt;/strong>：召回率 80%+&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">配置：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- Hybrid Retrieval（向量 + BM25）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 真 Rerank
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- Chunking 优化（补充上下文）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 评测集扩展（100 条）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">验收：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- Recall@5 &amp;gt; 80%
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 能回答复杂问题
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- Bad cases 持续跟踪
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="v4能运营持续">V4：能运营（持续）
&lt;/h3>&lt;p>&lt;strong>目标&lt;/strong>：持续监控、持续迭代&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">配置：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- Trace 存储
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 监控大盘
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 告警规则
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 用户反馈收集
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- CI/CD 集成
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">验收：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 有质量监控
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 能快速定位线上问题
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 每次改动有回归测试
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="收益递减规律">收益递减规律
&lt;/h3>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">60% → 80%：基础优化（chunking、混合检索）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">80% → 90%：系统优化（Rerank、prompt 调优）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">90% → 95%：高级优化（query rewrite、模型微调）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">95% → 99%：难度极大，投入产出比低
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">建议：先做到 85%，再决定是否值得继续投入。
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="五团队协作">五、团队协作
&lt;/h2>&lt;h3 id="角色分工">角色分工
&lt;/h3>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">产品/运营：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 定义知识边界
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 收集 bad cases
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 标注评测集
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 用户反馈归类
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">算法/NLP：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- Embedding 模型选择
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- Rerank 模型调优
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- Chunking 方案
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 高级策略
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">后端工程师：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- Ingestion pipeline
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 向量库维护
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- Trace/Inspect 系统
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 性能优化
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 监控告警
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="协作流程">协作流程
&lt;/h3>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">日常迭代：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">1. 产品收集 bad cases
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">2. 算法分析问题（定位到哪一层）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">3. 算法 + 后端共同优化
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">4. 跑 benchmark 验证
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">5. 上线 + 监控
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="问题归属">问题归属
&lt;/h3>&lt;table>
&lt;thead>
&lt;tr>
&lt;th>问题&lt;/th>
&lt;th>归属&lt;/th>
&lt;th>方向&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>召不回正确内容&lt;/td>
&lt;td>算法&lt;/td>
&lt;td>Chunking、检索策略&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>候选噪声多&lt;/td>
&lt;td>算法&lt;/td>
&lt;td>Rerank、过滤&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>答案格式不对&lt;/td>
&lt;td>算法&lt;/td>
&lt;td>Prompt&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>延迟高&lt;/td>
&lt;td>后端&lt;/td>
&lt;td>缓存、异步&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>系统不稳定&lt;/td>
&lt;td>后端&lt;/td>
&lt;td>稳定性&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>模型幻觉&lt;/td>
&lt;td>算法&lt;/td>
&lt;td>Prompt、模型选择&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>知识库没更新&lt;/td>
&lt;td>产品/运营&lt;/td>
&lt;td>文档维护流程&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;h2 id="六判断系统成熟度">六、判断系统成熟度
&lt;/h2>&lt;p>如果满足以下条件，说明系统开始成熟：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">□ 知道每次回答用了哪些证据
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> （有 Inspect，能追溯）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">□ 能复现错误回答的完整链路
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> （有 Trace 存储）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">□ 有固定 benchmark
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> （定期跑，有阈值）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">□ 能对比不同版本的质量
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> （有版本管理，能 A/B）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">□ 知道下一步优化哪一层
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> （有监控，不是盲调）
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>如果这 5 条还做不到，最应该投入的不是&amp;quot;换更强模型&amp;quot;，而是把&lt;strong>检索、Inspect 和评测体系&lt;/strong>补完整。&lt;/p>
&lt;h2 id="七系列总结">七、系列总结
&lt;/h2>&lt;p>这四篇文章回答了 RAG 的核心问题：&lt;/p>
&lt;p>&lt;strong>入门&lt;/strong>：&lt;/p>
&lt;ul>
&lt;li>RAG 的本质：把知识从参数里剥离出来&lt;/li>
&lt;li>Embedding 的原理：对比学习、向量空间&lt;/li>
&lt;li>向量检索的局限：对精确匹配不稳定&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>进阶&lt;/strong>：&lt;/p>
&lt;ul>
&lt;li>Chunking：按语义切分 + 补充上下文&lt;/li>
&lt;li>混合检索：向量 + BM25 + RRF 融合&lt;/li>
&lt;li>Rerank：Cross-Encoder 做真正的精排&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>评测&lt;/strong>：&lt;/p>
&lt;ul>
&lt;li>分层评测：检索、重排、组装、生成&lt;/li>
&lt;li>LLM-as-Judge：让大模型评判答案质量&lt;/li>
&lt;li>Inspect：保留完整 Trace，定位单次问题&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>生产&lt;/strong>：&lt;/p>
&lt;ul>
&lt;li>常见坑：上下文丢失、假 rerank、无引用&lt;/li>
&lt;li>性能优化：缓存、模型选择、候选数量控制&lt;/li>
&lt;li>迭代路线：从能跑到能运营&lt;/li>
&lt;/ul>
&lt;p>RAG 不是银弹，但它是目前让 LLM 接地气的最实用方案。关键在于：&lt;strong>不要追求一步到位，而是建立可迭代的能力&lt;/strong>。&lt;/p>
&lt;hr>
&lt;p>&lt;strong>后续可以深入的方向&lt;/strong>：&lt;/p>
&lt;ol>
&lt;li>Query Rewrite 和 Query Decomposition&lt;/li>
&lt;li>Contextual Retrieval（Anthropic 的方法）&lt;/li>
&lt;li>多租户与权限设计&lt;/li>
&lt;li>RAG 与 Agent 的结合&lt;/li>
&lt;li>多模态 RAG（图片、表格）&lt;/li>
&lt;/ol></description></item><item><title>RAG评测与 Inspect：如何知道问题出在检索、重排还是生成</title><link>https://www.zata.cc/p/rag%E8%AF%84%E6%B5%8B%E4%B8%8E-inspect%E5%A6%82%E4%BD%95%E7%9F%A5%E9%81%93%E9%97%AE%E9%A2%98%E5%87%BA%E5%9C%A8%E6%A3%80%E7%B4%A2%E9%87%8D%E6%8E%92%E8%BF%98%E6%98%AF%E7%94%9F%E6%88%90/</link><pubDate>Wed, 25 Mar 2026 15:50:00 +0800</pubDate><guid>https://www.zata.cc/p/rag%E8%AF%84%E6%B5%8B%E4%B8%8E-inspect%E5%A6%82%E4%BD%95%E7%9F%A5%E9%81%93%E9%97%AE%E9%A2%98%E5%87%BA%E5%9C%A8%E6%A3%80%E7%B4%A2%E9%87%8D%E6%8E%92%E8%BF%98%E6%98%AF%E7%94%9F%E6%88%90/</guid><description>&lt;img src="https://www.zata.cc/p/rag%E8%AF%84%E6%B5%8B%E4%B8%8E-inspect%E5%A6%82%E4%BD%95%E7%9F%A5%E9%81%93%E9%97%AE%E9%A2%98%E5%87%BA%E5%9C%A8%E6%A3%80%E7%B4%A2%E9%87%8D%E6%8E%92%E8%BF%98%E6%98%AF%E7%94%9F%E6%88%90/images/index/index.png" alt="Featured image of post RAG评测与 Inspect：如何知道问题出在检索、重排还是生成" />&lt;p>很多 RAG 项目最开始都能跑起来，但一到线上就会卡在一个非常现实的问题上：&lt;/p>
&lt;p>用户说答案不对，可你根本不知道问题出在：&lt;/p>
&lt;ol>
&lt;li>没召回到正确内容&lt;/li>
&lt;li>召回到了，但排序不对&lt;/li>
&lt;li>排好了，但没进最终 prompt&lt;/li>
&lt;li>进了 prompt，但模型没用&lt;/li>
&lt;li>模型用了，但引用展示错了&lt;/li>
&lt;/ol>
&lt;p>所以 RAG 一定不能只做&amp;quot;生成结果观测&amp;quot;，还要做&amp;quot;阶段化 inspect&amp;quot;。&lt;/p>
&lt;p>这篇文章会讲：&lt;/p>
&lt;ul>
&lt;li>如何分层定位问题&lt;/li>
&lt;li>检索层和生成层的评测指标&lt;/li>
&lt;li>LLM-as-a-Judge 的原理&lt;/li>
&lt;li>Inspect 系统的设计思路&lt;/li>
&lt;/ul>
&lt;h2 id="一为什么评测这么难">一、为什么评测这么难
&lt;/h2>&lt;p>RAG 的评测比传统检索或传统 NLP 都复杂，因为它是多阶段的 pipeline。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">用户问题 → 检索 → 重排 → 组装 → 生成 → 答案
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>最终答案错了，可能的原因：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">❌ 检索层：正确内容根本没召回
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">❌ 重排层：正确内容召回但被排到后面
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">❌ 组装层：正确内容被截断或过滤掉了
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">❌ 生成层：模型无视证据自由发挥
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>如果不分阶段评测，你只能知道&amp;quot;答案错了&amp;quot;，但不知道&amp;quot;为什么错&amp;quot;。&lt;/p>
&lt;h3 id="一个真实的问题定位案例">一个真实的问题定位案例
&lt;/h3>&lt;p>用户投诉：&amp;ldquo;问错误码 401 的意思，答案完全不对。&amp;rdquo;&lt;/p>
&lt;p>&lt;strong>定位过程&lt;/strong>：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">第 1 步：检查检索层
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 用户问题：&amp;#34;错误码 401 是什么意思&amp;#34;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 检索候选 top-10：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 1. 常见 HTTP 错误码列表
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 2. 认证失败怎么办
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 3. 权限问题排查
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 8. 错误码详解：401 Unauthorized ← 正确答案
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">结论：正确答案在候选里，但排第 8
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">第 2 步：检查重排层
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- Rerank 后：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 1. 错误码详解：401 Unauthorized ← 正确答案排到第 1
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 2. 常见 HTTP 错误码列表
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">结论：重排正常
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">第 3 步：检查组装层
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 最终进入 prompt 的 chunk：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 1. 错误码详解：401 Unauthorized ✅
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 2. 常见 HTTP 错误码列表 ✅
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 3. 认证失败怎么办 ✅
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">结论：组装正常
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">第 4 步：检查生成层
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- Prompt 包含正确证据
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 模型输出：&amp;#34;错误码 401 表示服务器内部错误...&amp;#34;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">结论：模型幻觉！401 是认证错误，不是服务器内部错误
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">最终诊断：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">✅ 检索层：正常
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">✅ 重排层：正常
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">✅ 组装层：正常
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">❌ 生成层：模型无视证据，产生幻觉
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">解决方案：优化 prompt，强化&amp;#34;必须依据证据&amp;#34;的约束
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>这就是为什么需要分阶段评测——不同阶段的问题，解决方案完全不同。&lt;/p>
&lt;h2 id="二问题分层框架">二、问题分层框架
&lt;/h2>&lt;p>建议用下面这套分层框架，每层有明确的责任边界和常见问题：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">┌─────────────────────────────────────────────────────────────────┐
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ 第 1 层：检索层（Retrieval） │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ 职责：从知识库中召回候选内容 │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ 常见问题： │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ ❌ 正确 chunk 不在候选里 → chunking、embedding、检索策略 │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ ❌ 正确 chunk 排名太低 → 混合检索、Rerank │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ ❌ 候选噪声太多 → metadata 过滤、召回数量调整 │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ 评测指标：Recall@K、MRR │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">└────────────────────────┬────────────────────────────────────────┘
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ▼
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">┌─────────────────────────────────────────────────────────────────┐
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ 第 2 层：重排层（Rerank） │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ 职责：对候选重新排序，提升相关性 │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ 常见问题： │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ ❌ 正确 chunk 被排到后面 → Rerank 模型问题 │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ ❌ Rerank 效果不明显 → 模型选择、候选数量 │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ 评测指标：排序提升比例、NDCG │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">└────────────────────────┬────────────────────────────────────────┘
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ▼
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">┌─────────────────────────────────────────────────────────────────┐
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ 第 3 层：上下文组装层（Context Assembly） │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ 职责：控制进入 prompt 的内容和数量 │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ 常见问题： │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ ❌ 正确 chunk 被截断 → 调整 token 预算 │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ ❌ 正确 chunk 被过滤 → 检查过滤规则 │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ ❌ 内容太长噪声多 → 优化组装策略 │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ 评测指标：正确 chunk 进入 prompt 的比例 │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">└────────────────────────┬────────────────────────────────────────┘
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ▼
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">┌─────────────────────────────────────────────────────────────────┐
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ 第 4 层：生成层（Generation） │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ 职责：基于证据生成答案 │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ 常见问题： │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ ❌ 模型无视证据 → Prompt 优化 │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ ❌ 产生幻觉 → 强化约束、Few-shot │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ ❌ 引用错误 → 后处理校验 │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ 评测指标：答案正确率、幻觉率、引用准确率 │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">└─────────────────────────────────────────────────────────────────┘
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="分层的意义">分层的意义
&lt;/h3>&lt;p>每一层的问题，需要不同的解决方案：&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>问题层&lt;/th>
&lt;th>典型问题&lt;/th>
&lt;th>解决方向&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>检索层&lt;/td>
&lt;td>召不回正确内容&lt;/td>
&lt;td>Chunking、混合检索&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>重排层&lt;/td>
&lt;td>排序不对&lt;/td>
&lt;td>Rerank 模型&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>组装层&lt;/td>
&lt;td>内容被过滤&lt;/td>
&lt;td>组装策略、预算调整&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>生成层&lt;/td>
&lt;td>模型幻觉&lt;/td>
&lt;td>Prompt 优化、模型选择&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>&lt;strong>不分层的问题&lt;/strong>：你发现答案错了，只能&amp;quot;盲调&amp;quot;——换模型、调参数，但不知道调的是哪个环节。&lt;/p>
&lt;p>&lt;strong>分层的好处&lt;/strong>：知道问题在哪一层，定向优化。&lt;/p>
&lt;h2 id="三检索层评测">三、检索层评测
&lt;/h2>&lt;p>检索层的核心问题是：&lt;strong>正确的 chunk 召回了吗？排得够靠前吗？&lt;/strong>&lt;/p>
&lt;h3 id="recallk">Recall@K
&lt;/h3>&lt;p>最直观的指标：正确答案是否在前 K 个候选里。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">Recall@K = 正确答案在前 K 的请求数 / 总请求数
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>示例：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">评测集 100 个问题
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">每个问题有 1-3 个正确 chunk
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Recall@5 = 82/100 = 82%（82 个问题的正确 chunk 在前 5）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Recall@10 = 91/100 = 91%（91 个问题的正确 chunk 在前 10）
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>解读&lt;/strong>：&lt;/p>
&lt;ul>
&lt;li>Recall@5 &amp;gt; 80%：检索质量良好&lt;/li>
&lt;li>Recall@5 &amp;lt; 60%：检索需要优化&lt;/li>
&lt;li>Recall@5 ≈ Recall@10：排序效果好（正确答案排在前面）&lt;/li>
&lt;li>Recall@5 &amp;laquo; Recall@10：排序效果差（正确答案在后面）&lt;/li>
&lt;/ul>
&lt;h3 id="mrrmean-reciprocal-rank">MRR（Mean Reciprocal Rank）
&lt;/h3>&lt;p>Recall@K 只看&amp;quot;是否在前 K&amp;quot;，MRR 还看&amp;quot;具体排第几&amp;quot;。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">MRR = Σ (1 / 正确答案的排名) / 总请求数
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>示例：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">问题 1：正确答案排第 1 → 1/1 = 1.0
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">问题 2：正确答案排第 3 → 1/3 = 0.33
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">问题 3：正确答案排第 5 → 1/5 = 0.20
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">问题 4：正确答案未召回 → 1/∞ = 0
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">MRR = (1.0 + 0.33 + 0.20 + 0) / 4 = 0.38
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>解读&lt;/strong>：&lt;/p>
&lt;ul>
&lt;li>MRR &amp;gt; 0.6：排序质量好&lt;/li>
&lt;li>MRR &amp;lt; 0.4：排序需要优化&lt;/li>
&lt;/ul>
&lt;h3 id="ndcgnormalized-discounted-cumulative-gain">NDCG（Normalized Discounted Cumulative Gain）
&lt;/h3>&lt;p>更精细的指标，考虑多个正确答案的情况。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">DCG = Σ (2^rel_i - 1) / log2(i + 1)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">其中：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- rel_i：第 i 个位置的相关性（1=相关，0=不相关）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- i：排名位置
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">NDCG = DCG / IDCG
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">IDCG 是理想情况下的 DCG（所有相关文档都排在最前面）
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>直觉&lt;/strong>：正确答案排得越靠前，分数越高；排在后面会被惩罚（除以 log）。&lt;/p>
&lt;h3 id="不同指标的选择">不同指标的选择
&lt;/h3>&lt;table>
&lt;thead>
&lt;tr>
&lt;th>指标&lt;/th>
&lt;th>适用场景&lt;/th>
&lt;th>优点&lt;/th>
&lt;th>缺点&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>Recall@K&lt;/td>
&lt;td>只关心是否召回&lt;/td>
&lt;td>直观、易解释&lt;/td>
&lt;td>不关心具体排名&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>MRR&lt;/td>
&lt;td>关心正确答案的排名&lt;/td>
&lt;td>考虑排名&lt;/td>
&lt;td>只考虑第一个正确答案&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>NDCG&lt;/td>
&lt;td>有多个正确答案&lt;/td>
&lt;td>全面&lt;/td>
&lt;td>计算复杂&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>&lt;strong>建议&lt;/strong>：日常用 Recall@5 + Recall@10，深入分析用 MRR 和 NDCG。&lt;/p>
&lt;h2 id="四生成层评测">四、生成层评测
&lt;/h2>&lt;p>检索层评测相对客观，生成层评测更主观——&amp;ldquo;答案好不好&amp;quot;没有标准答案。&lt;/p>
&lt;h3 id="传统指标">传统指标
&lt;/h3>&lt;p>&lt;strong>1. BLEU / ROUGE&lt;/strong>&lt;/p>
&lt;p>来自机器翻译和摘要领域，计算模型输出和参考答案的 n-gram 重叠度。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">BLEU-1: 单词重叠
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">BLEU-2: 二元组重叠
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">ROUGE-L: 最长公共子序列
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>问题&lt;/strong>：RAG 答案不追求和参考答案&amp;quot;字面相似&amp;rdquo;，而是追求&amp;quot;语义正确&amp;quot;。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">参考答案：&amp;#34;使用 Bearer Token 认证&amp;#34;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">模型答案：&amp;#34;认证方式是 Bearer Token&amp;#34;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">BLEU 分数不高，但语义完全正确。
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>2. BERTScore&lt;/strong>&lt;/p>
&lt;p>用 BERT 编码后计算语义相似度。比 n-gram 更好，但对 RAG 仍不够精确。&lt;/p>
&lt;h3 id="llm-as-a-judge">LLM-as-a-Judge
&lt;/h3>&lt;p>让大模型来评判答案质量，是目前最实用的方法。&lt;/p>
&lt;p>&lt;strong>思路&lt;/strong>：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">输入：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 用户问题
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 参考答案（可选）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 模型答案
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 证据内容（检索到的 chunk）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">输出：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 分数（1-5）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 是否正确
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 是否有幻觉
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 具体问题
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>Prompt 设计&lt;/strong>：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">你是一个答案质量评判专家。请评判以下答案的质量。
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">问题：{question}
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">参考资料：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">{context}
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">参考答案：{expected_answer}
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">模型答案：{actual_answer}
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">请评判：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">1. 正确性（1-5分）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">2. 是否包含幻觉（编造参考资料中没有的信息）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">3. 是否遗漏关键信息
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">4. 引用是否正确
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">以 JSON 格式输出：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">{
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &amp;#34;score&amp;#34;: 1-5,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &amp;#34;is_correct&amp;#34;: true/false,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &amp;#34;has_hallucination&amp;#34;: true/false,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &amp;#34;issues&amp;#34;: [&amp;#34;问题1&amp;#34;, &amp;#34;问题2&amp;#34;],
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &amp;#34;explanation&amp;#34;: &amp;#34;评分理由&amp;#34;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>为什么有效&lt;/strong>？&lt;/p>
&lt;p>LLM 本身有很强的语义理解能力，能判断&amp;quot;语义相似&amp;quot;而非&amp;quot;字面相似&amp;quot;。而且 LLM 能理解上下文，判断答案是否&amp;quot;基于证据&amp;quot;。&lt;/p>
&lt;p>&lt;strong>注意事项&lt;/strong>：&lt;/p>
&lt;ol>
&lt;li>&lt;strong>评判模型要强&lt;/strong>：用 GPT-4 或 Claude，不要用小模型&lt;/li>
&lt;li>&lt;strong>避免自我评判&lt;/strong>：生成模型和评判模型要不同&lt;/li>
&lt;li>&lt;strong>Few-shot 示例&lt;/strong>：给几个评分示例，提高一致性&lt;/li>
&lt;li>&lt;strong>多次评判取平均&lt;/strong>：减少随机性&lt;/li>
&lt;/ol>
&lt;h3 id="评测指标的层次">评测指标的层次
&lt;/h3>&lt;p>生成层评测应该回答几个不同的问题：&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>问题&lt;/th>
&lt;th>指标&lt;/th>
&lt;th>怎么测&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>答案对吗？&lt;/td>
&lt;td>正确率&lt;/td>
&lt;td>LLM 评判 + 人工抽查&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>有幻觉吗？&lt;/td>
&lt;td>幻觉率&lt;/td>
&lt;td>检查答案是否超出证据范围&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>引用对吗？&lt;/td>
&lt;td>引用准确率&lt;/td>
&lt;td>检查引用的 chunk 是否支持答案&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>用户满意吗？&lt;/td>
&lt;td>满意度&lt;/td>
&lt;td>用户反馈&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>&lt;strong>幻觉检测&lt;/strong>是最重要的，因为 RAG 的核心价值是&amp;quot;有据可查&amp;quot;。&lt;/p>
&lt;h3 id="幻觉检测的方法">幻觉检测的方法
&lt;/h3>&lt;p>&lt;strong>方法 1：LLM 评判&lt;/strong>&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">Prompt：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&amp;#34;以下答案是否包含参考资料中没有的信息？&amp;#34;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">答案：{answer}
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">参考资料：{context}
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">请指出答案中的每一句话，并判断是否有证据支持。
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>方法 2：NLI（自然语言推理）&lt;/strong>&lt;/p>
&lt;p>用 NLI 模型判断答案和证据之间的关系：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">前提（证据）：&amp;#34;Token 有效期为 24 小时&amp;#34;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">假设（答案）：&amp;#34;Token 有效期为 48 小时&amp;#34;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">NLI 输出：Contradiction（矛盾）
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>方法 3：关键词匹配&lt;/strong>&lt;/p>
&lt;p>简单但粗糙：检查答案中的关键信息是否在证据中出现。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">答案：&amp;#34;API 超时时间是 30 秒&amp;#34;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">证据：&amp;#34;API 的默认超时时间为 30 秒&amp;#34;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">匹配：✅（&amp;#34;超时&amp;#34;、&amp;#34;30&amp;#34;、&amp;#34;秒&amp;#34;都在证据中）
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="五离线评测-vs-在线评测">五、离线评测 vs 在线评测
&lt;/h2>&lt;h3 id="离线评测">离线评测
&lt;/h3>&lt;p>&lt;strong>目的&lt;/strong>：在上线前验证系统质量&lt;/p>
&lt;p>&lt;strong>方法&lt;/strong>：&lt;/p>
&lt;ol>
&lt;li>准备评测集（50-100 条）&lt;/li>
&lt;li>定期跑评测&lt;/li>
&lt;li>对比不同版本&lt;/li>
&lt;/ol>
&lt;p>&lt;strong>评测集构建&lt;/strong>：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">评测集应该包含：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 核心问题（30%）：业务最常见的问题
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 边界问题（30%）：难度高、容易出错的
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 长尾问题（20%）：覆盖更多场景
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 坏案例（20%）：之前出过问题的
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">每条评测数据：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">{
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &amp;#34;id&amp;#34;: &amp;#34;eval_001&amp;#34;,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &amp;#34;question&amp;#34;: &amp;#34;API 的认证方式是什么&amp;#34;,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &amp;#34;expected_chunks&amp;#34;: [&amp;#34;chunk_012&amp;#34;], // 期望召回的 chunk
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &amp;#34;expected_answer&amp;#34;: &amp;#34;Bearer Token&amp;#34;, // 参考答案
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &amp;#34;category&amp;#34;: &amp;#34;技术问题&amp;#34;,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &amp;#34;difficulty&amp;#34;: &amp;#34;easy&amp;#34;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>评测流程&lt;/strong>：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">1. 运行 RAG 系统，获取结果
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">2. 计算检索指标（Recall@K、MRR）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">3. 计算生成指标（正确率、幻觉率）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">4. 汇总报告
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">5. 对比历史版本
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="在线评测">在线评测
&lt;/h3>&lt;p>&lt;strong>目的&lt;/strong>：在生产环境持续监控&lt;/p>
&lt;p>&lt;strong>方法&lt;/strong>：&lt;/p>
&lt;ol>
&lt;li>&lt;strong>用户反馈&lt;/strong>：点赞/点踩、修改问题、人工标注&lt;/li>
&lt;li>&lt;strong>自动评分&lt;/strong>：用 LLM 对线上答案打分&lt;/li>
&lt;li>&lt;strong>行为分析&lt;/strong>：用户是否追问、是否放弃&lt;/li>
&lt;/ol>
&lt;p>&lt;strong>采样策略&lt;/strong>：&lt;/p>
&lt;p>不是所有请求都需要详细评测（成本高）。建议：&lt;/p>
&lt;ul>
&lt;li>100% 记录基本信息（延迟、召回数量）&lt;/li>
&lt;li>10% 详细评分（LLM 评判）&lt;/li>
&lt;li>1% 人工审核&lt;/li>
&lt;/ul>
&lt;h3 id="评测陷阱">评测陷阱
&lt;/h3>&lt;p>&lt;strong>陷阱 1：评测集泄露&lt;/strong>&lt;/p>
&lt;p>训练 Embedding 模型或 Rerank 模型时，不小心用了评测集里的内容。评测分数虚高，线上效果差。&lt;/p>
&lt;p>&lt;strong>解决&lt;/strong>：评测集严格隔离，不参与任何训练。&lt;/p>
&lt;p>&lt;strong>陷阱 2：评测集不代表真实&lt;/strong>&lt;/p>
&lt;p>评测集都是&amp;quot;理想问题&amp;quot;，线上用户的问题五花八门：口语化、错别字、表达不清。&lt;/p>
&lt;p>&lt;strong>解决&lt;/strong>：定期从线上采样真实问题，加入评测集。&lt;/p>
&lt;p>&lt;strong>陷阱 3：只看单一指标&lt;/strong>&lt;/p>
&lt;p>只看 Recall@5，可能忽略了答案质量。检索到了，但答案仍然不好。&lt;/p>
&lt;p>&lt;strong>解决&lt;/strong>：多指标综合评估，建立指标大盘。&lt;/p>
&lt;h2 id="六inspect-系统定位单次问题">六、Inspect 系统：定位单次问题
&lt;/h2>&lt;p>评测解决&amp;quot;系统整体质量&amp;quot;，Inspect 解决&amp;quot;某次请求为什么出错&amp;quot;。&lt;/p>
&lt;h3 id="inspect-系统的核心能力">Inspect 系统的核心能力
&lt;/h3>&lt;table>
&lt;thead>
&lt;tr>
&lt;th>能力&lt;/th>
&lt;th>回答的问题&lt;/th>
&lt;th>需要的数据&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>检索可视化&lt;/td>
&lt;td>召回了什么？排第几？&lt;/td>
&lt;td>候选列表、分数&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>重排对比&lt;/td>
&lt;td>Rerank 前后有什么变化？&lt;/td>
&lt;td>重排前后列表&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Prompt 快照&lt;/td>
&lt;td>模型看到了什么？&lt;/td>
&lt;td>最终 prompt&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>引用追溯&lt;/td>
&lt;td>答案来自哪？&lt;/td>
&lt;td>引用的 chunk&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;h3 id="trace-的数据结构">Trace 的数据结构
&lt;/h3>&lt;p>每次请求都应该保存完整的 Trace：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">Trace 结构：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">{
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &amp;#34;trace_id&amp;#34;: &amp;#34;唯一标识&amp;#34;,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &amp;#34;timestamp&amp;#34;: &amp;#34;请求时间&amp;#34;,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &amp;#34;query&amp;#34;: &amp;#34;用户问题&amp;#34;,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &amp;#34;retrieval&amp;#34;: {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &amp;#34;dense_results&amp;#34;: [
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> {&amp;#34;chunk_id&amp;#34;: &amp;#34;xxx&amp;#34;, &amp;#34;score&amp;#34;: 0.82, &amp;#34;rank&amp;#34;: 1},
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ],
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &amp;#34;bm25_results&amp;#34;: [
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> {&amp;#34;chunk_id&amp;#34;: &amp;#34;xxx&amp;#34;, &amp;#34;score&amp;#34;: 5.2, &amp;#34;rank&amp;#34;: 1},
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ],
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &amp;#34;merged_results&amp;#34;: [...], // RRF 融合后
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &amp;#34;reranked_results&amp;#34;: [...] // Rerank 后
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> },
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &amp;#34;context&amp;#34;: {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &amp;#34;final_chunks&amp;#34;: [...], // 最终进入 prompt 的
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &amp;#34;prompt&amp;#34;: &amp;#34;...&amp;#34;, // 完整 prompt
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &amp;#34;token_count&amp;#34;: 3200
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> },
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &amp;#34;generation&amp;#34;: {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &amp;#34;answer&amp;#34;: &amp;#34;模型回答&amp;#34;,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &amp;#34;citations&amp;#34;: [...],
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &amp;#34;model&amp;#34;: &amp;#34;gpt-4o-mini&amp;#34;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> },
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &amp;#34;latency&amp;#34;: {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &amp;#34;total_ms&amp;#34;: 450,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &amp;#34;retrieval_ms&amp;#34;: 50,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &amp;#34;rerank_ms&amp;#34;: 180,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &amp;#34;generation_ms&amp;#34;: 220
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> }
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="inspect-页面的设计">Inspect 页面的设计
&lt;/h3>&lt;p>一个实用的 Inspect 页面应该包含：&lt;/p>
&lt;p>&lt;strong>1. 概览区&lt;/strong>&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">问题：错误码 401 是什么意思
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">答案：401 表示认证失败...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">耗时：450ms
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">状态：❌ 答案错误（模型幻觉）
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>2. 检索区&lt;/strong>&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">Dense 检索结果：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">#1 [0.82] 常见 HTTP 错误码
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">#2 [0.79] 认证失败怎么办
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">#8 [0.65] 错误码详解：401 ← 正确答案
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">BM25 检索结果：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">#1 [5.2] 错误码详解：401 ← 正确答案
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">#2 [4.8] 常见 HTTP 错误码
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Rerank 后：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">#1 [0.92] 错误码详解：401 ← 正确答案排到第 1
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">...
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>3. Prompt 区&lt;/strong>&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">最终进入 Prompt 的 chunk：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">[1] 错误码详解：401 Unauthorized
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Token 无效或已过期...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">[2] 常见 HTTP 错误码
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">完整 Prompt：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">[显示完整 prompt 内容]
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>4. 评分区&lt;/strong>&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">检索是否正确：✅
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">答案是否正确：❌
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">是否有幻觉：✅（模型说&amp;#34;服务器内部错误&amp;#34;）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">备注：________________
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="trace-的存储">Trace 的存储
&lt;/h3>&lt;table>
&lt;thead>
&lt;tr>
&lt;th>方案&lt;/th>
&lt;th>优点&lt;/th>
&lt;th>缺点&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>SQLite&lt;/td>
&lt;td>简单、本地&lt;/td>
&lt;td>不适合大规模&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>PostgreSQL&lt;/td>
&lt;td>功能强&lt;/td>
&lt;td>需要运维&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Elasticsearch&lt;/td>
&lt;td>搜索方便&lt;/td>
&lt;td>成本高&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>专用平台（LangSmith、Langfuse）&lt;/td>
&lt;td>功能全&lt;/td>
&lt;td>可能有成本&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>&lt;strong>建议&lt;/strong>：小规模用 SQLite/PostgreSQL，大规模用专用平台。&lt;/p>
&lt;h2 id="七排障流程">七、排障流程
&lt;/h2>&lt;p>当你发现某个问题回答不对时，按这个流程排查：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">第 1 步：获取 Trace ID
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">从用户反馈或日志中获取
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">第 2 步：检查检索层
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">正确 chunk 是否在候选里？
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">❌ 不在候选：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> → 检查 chunking（是否被切碎）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> → 检查 embedding（是否理解正确）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> → 检索策略（是否需要混合检索）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">✅ 在候选但排名靠后：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> → 继续检查重排层
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">第 3 步：检查重排层
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Rerank 后排名是否提升？
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">❌ 排名下降或不变：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> → Rerank 模型可能不适合这个场景
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> → 考虑换模型或调整权重
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">✅ 排名提升但仍不在 top-5：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> → 召回数量不够
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> → 增加召回数量
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">第 4 步：检查组装层
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">正确 chunk 是否进入 prompt？
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">❌ 未进入：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> → 被截断：调整 token 预算
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> → 被过滤：检查过滤规则
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">✅ 已进入：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> → 继续检查生成层
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">第 5 步：检查生成层
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">模型是否正确使用证据？
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">❌ 模型无视证据：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> → 强化 prompt 约束
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> → 添加 Few-shot 示例
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">❌ 模型幻觉：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> → 明确&amp;#34;不确定时说明不确定&amp;#34;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> → 后处理校验
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">❌ 引用错误：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> → 优化引用格式要求
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> → 后处理修正
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="八本文小结">八、本文小结
&lt;/h2>&lt;p>RAG 要做到可迭代，必须建立完整的评测和观测体系：&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>能力&lt;/th>
&lt;th>解决的问题&lt;/th>
&lt;th>核心方法&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>分层评测&lt;/td>
&lt;td>定位问题在哪一层&lt;/td>
&lt;td>Recall@K、LLM-as-Judge&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>离线评测&lt;/td>
&lt;td>验证系统质量&lt;/td>
&lt;td>评测集 + 定期跑分&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>在线评测&lt;/td>
&lt;td>监控生产质量&lt;/td>
&lt;td>采样 + 自动评分&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Inspect&lt;/td>
&lt;td>定位单次问题&lt;/td>
&lt;td>Trace + 可视化&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>&lt;strong>核心建议&lt;/strong>：&lt;/p>
&lt;ol>
&lt;li>&lt;strong>先建评测集&lt;/strong>：没有评测集，优化都是盲人摸象&lt;/li>
&lt;li>&lt;strong>分层评测&lt;/strong>：检索和生成分开评，知道优化哪一层&lt;/li>
&lt;li>&lt;strong>保留 Trace&lt;/strong>：每次请求都记录中间结果&lt;/li>
&lt;li>&lt;strong>持续监控&lt;/strong>：建立指标大盘，设置告警&lt;/li>
&lt;/ol>
&lt;p>只有这样，才能从&amp;quot;会用 RAG&amp;quot;走到&amp;quot;会调 RAG&amp;quot;。&lt;/p>
&lt;p>下一篇会讲生产环境最常见的坑，以及一条可执行的迭代路线。&lt;/p></description></item><item><title>RAG进阶：Chunking、召回、Hybrid Search 与 Rerank</title><link>https://www.zata.cc/p/rag%E8%BF%9B%E9%98%B6chunking%E5%8F%AC%E5%9B%9Ehybrid-search-%E4%B8%8E-rerank/</link><pubDate>Wed, 25 Mar 2026 15:45:00 +0800</pubDate><guid>https://www.zata.cc/p/rag%E8%BF%9B%E9%98%B6chunking%E5%8F%AC%E5%9B%9Ehybrid-search-%E4%B8%8E-rerank/</guid><description>&lt;img src="https://www.zata.cc/p/rag%E8%BF%9B%E9%98%B6chunking%E5%8F%AC%E5%9B%9Ehybrid-search-%E4%B8%8E-rerank/images/index/index.png" alt="Featured image of post RAG进阶：Chunking、召回、Hybrid Search 与 Rerank" />&lt;p>很多人第一次做 RAG，默认流程都是：&lt;/p>
&lt;ol>
&lt;li>文档切 chunk&lt;/li>
&lt;li>生成 embedding&lt;/li>
&lt;li>向量检索 top_k&lt;/li>
&lt;li>拼 prompt&lt;/li>
&lt;li>让模型回答&lt;/li>
&lt;/ol>
&lt;p>这条路径能跑，但往往不稳。真正让 RAG 质量提升一个台阶的，通常是三件事：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>chunking 做得更合理&lt;/strong> — 切出来的 chunk 能独立成为证据&lt;/li>
&lt;li>&lt;strong>召回从单路 dense 变成多路召回&lt;/strong> — 结合语义和关键词&lt;/li>
&lt;li>&lt;strong>用真正的 rerank 做精排&lt;/strong> — 不是简单重排，而是用 cross-encoder 重新打分&lt;/li>
&lt;/ul>
&lt;p>这篇文章会深入讲这三个环节的算法原理。&lt;/p>
&lt;h2 id="一chunking为什么切短一点不简单">一、Chunking：为什么&amp;quot;切短一点&amp;quot;不简单
&lt;/h2>&lt;p>Chunking 的目标不是凑 embedding 输入长度，而是让&amp;quot;一个 chunk 刚好能独立成为证据&amp;quot;。&lt;/p>
&lt;h3 id="好的-chunk-应该满足什么">好的 Chunk 应该满足什么
&lt;/h3>&lt;p>四个标准：&lt;/p>
&lt;ol>
&lt;li>&lt;strong>语义完整&lt;/strong> — 一个 chunk 能回答一个独立的问题&lt;/li>
&lt;li>&lt;strong>粒度适中&lt;/strong> — 太大噪声多，太小信息不够&lt;/li>
&lt;li>&lt;strong>上下文自洽&lt;/strong> — 不依赖外部信息也能理解&lt;/li>
&lt;li>&lt;strong>可追溯&lt;/strong> — 知道来自哪个文档、哪个章节&lt;/li>
&lt;/ol>
&lt;h3 id="固定长度切分的问题">固定长度切分的问题
&lt;/h3>&lt;p>最简单的做法是按字符数切：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">每 500 个字符切一段
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>问题：&lt;/p>
&lt;p>&lt;strong>问题 1：语义被切断&lt;/strong>&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">原文：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&amp;#34;所有 API 请求必须在 Header 中携带 Bearer Token。Token 有效期为 24 小时，
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">过期后需重新获取。获取方式：调用 /auth/token 接口，传入 client_id
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">和 client_secret。&amp;#34;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">固定切分后，某个 chunk 可能变成：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&amp;#34;过期后需重新获取。获取方式：调用 /auth/token 接口，传入 client_id&amp;#34;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">用户问&amp;#34;Token 有效期多久？&amp;#34;，这个 chunk 不会召回。
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>问题 2：上下文丢失&lt;/strong>&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">原文：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&amp;#34;### 2.3 校准流程
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">将旋钮调至 3 档，等待指示灯变为绿色后松开。&amp;#34;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">按段落切分后：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&amp;#34;将旋钮调至 3 档，等待指示灯变为绿色后松开。&amp;#34;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">用户问&amp;#34;ZK-200 怎么校准&amp;#34;，这个 chunk 不会召回。
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">因为&amp;#34;ZK-200&amp;#34;在标题里，&amp;#34;校准&amp;#34;在章节名里——都被切掉了。
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="语义切分的思路">语义切分的思路
&lt;/h3>&lt;p>更好的方法是&lt;strong>按文档的自然结构切分&lt;/strong>：&lt;/p>
&lt;p>&lt;strong>1. 按 Markdown 标题切分&lt;/strong>&lt;/p>
&lt;p>Markdown 文档有天然的层级结构：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl"># 一级标题
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">## 二级标题
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">### 三级标题
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>切分策略：每个标题下的内容作为一个 chunk，并保留标题作为上下文。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">Chunk 1:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">【API 规范 &amp;gt; 2. 认证 &amp;gt; 2.1 认证方式】
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">所有 API 请求必须在 Header 中携带 Bearer Token...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Chunk 2:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">【API 规范 &amp;gt; 2. 认证 &amp;gt; 2.2 Token 获取】
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">调用 /auth/token 接口获取 Token...
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>2. 按段落切分&lt;/strong>&lt;/p>
&lt;p>对于没有明确结构的文档，按段落切分比按字符数好：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">段落边界通常是语义边界。
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">一个段落讲一个观点，不会把一句话切两半。
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>3. 语义切分（高级）&lt;/strong>&lt;/p>
&lt;p>用 Embedding 模型判断相邻段落的语义相似度：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">步骤：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">1. 把文档按句子切分
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">2. 计算相邻句子的 embedding 相似度
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">3. 相似度低的地方 = 语义边界，切分
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>直觉：如果两句话语义相似度低，说明在讲不同的话题，应该分开。&lt;/p>
&lt;p>代价：计算量大，不一定比结构化切分更好。&lt;/p>
&lt;h3 id="contextual-chunking最重要的一步">Contextual Chunking：最重要的一步
&lt;/h3>&lt;p>无论用什么切分方法，都建议做一步：&lt;strong>把父级标题拼到 chunk 前面。&lt;/strong>&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">原始 chunk：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&amp;#34;将旋钮调至 3 档，等待指示灯变为绿色后松开。&amp;#34;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">补充上下文后：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&amp;#34;【ZK-200 操作手册 &amp;gt; 2. 使用方法 &amp;gt; 2.3 校准流程】
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">将旋钮调至 3 档，等待指示灯变为绿色后松开。&amp;#34;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>为什么有效？&lt;/p>
&lt;p>&lt;strong>原理&lt;/strong>：Embedding 模型会把整个 chunk 编码成一个向量。如果 chunk 里包含&amp;quot;ZK-200&amp;quot;和&amp;quot;校准&amp;quot;，当用户问&amp;quot;ZK-200 怎么校准&amp;quot;时，相似度就会高。&lt;/p>
&lt;p>&lt;strong>实测效果&lt;/strong>：&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>配置&lt;/th>
&lt;th>Recall@5&lt;/th>
&lt;th>说明&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>原始 chunk&lt;/td>
&lt;td>41%&lt;/td>
&lt;td>大量 chunk 缺上下文&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>补充标题上下文&lt;/td>
&lt;td>78%&lt;/td>
&lt;td>效果翻倍&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>成本几乎为零，但效果提升明显。这是 RAG 里性价比最高的优化之一。&lt;/p>
&lt;h3 id="chunk-粒度的权衡">Chunk 粒度的权衡
&lt;/h3>&lt;p>太大 vs 太小：&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>问题&lt;/th>
&lt;th>太大（1000+ 字）&lt;/th>
&lt;th>太小（&amp;lt;100 字）&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>噪声&lt;/td>
&lt;td>噪声多，关键信息被埋&lt;/td>
&lt;td>信息不够，上下文缺失&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>检索&lt;/td>
&lt;td>容易召回不相关内容&lt;/td>
&lt;td>可能召回片段，语义不完整&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>成本&lt;/td>
&lt;td>Token 消耗大&lt;/td>
&lt;td>Chunk 数量多，索引大&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>&lt;strong>经验值&lt;/strong>：300-500 tokens（中文约 200-350 字）&lt;/p>
&lt;p>但比长度更重要的是&lt;strong>语义完整性&lt;/strong>。宁可长度不均，也不要语义被切断。&lt;/p>
&lt;h3 id="不同文档类型的切分策略">不同文档类型的切分策略
&lt;/h3>&lt;table>
&lt;thead>
&lt;tr>
&lt;th>文档类型&lt;/th>
&lt;th>推荐策略&lt;/th>
&lt;th>原因&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>FAQ&lt;/td>
&lt;td>一条一 chunk&lt;/td>
&lt;td>每条 FAQ 天然独立&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>技术文档&lt;/td>
&lt;td>按标题/小节&lt;/td>
&lt;td>标题是天然语义边界&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>产品手册&lt;/td>
&lt;td>按操作步骤&lt;/td>
&lt;td>一个步骤一个 chunk&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>合同/法规&lt;/td>
&lt;td>按条款&lt;/td>
&lt;td>每条条款有独立法律意义&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>对话记录&lt;/td>
&lt;td>按问答对&lt;/td>
&lt;td>一问一答是完整语义单元&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;h2 id="二bm25关键词检索的经典算法">二、BM25：关键词检索的经典算法
&lt;/h2>&lt;p>向量检索擅长语义相似，但对精确匹配不稳定。解决方案是引入关键词检索。&lt;/p>
&lt;p>最经典的关键词检索算法是 &lt;strong>BM25&lt;/strong>（Best Matching 25）。&lt;/p>
&lt;h3 id="bm25-的原理">BM25 的原理
&lt;/h3>&lt;p>BM25 是 TF-IDF 的改进版。先理解 TF-IDF：&lt;/p>
&lt;p>&lt;strong>TF-IDF = TF × IDF&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>
&lt;p>&lt;strong>TF（Term Frequency）&lt;/strong>：词在文档中出现的次数&lt;/p>
&lt;ul>
&lt;li>出现越多，越重要？不完全是，需要惩罚高频词&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>IDF（Inverse Document Frequency）&lt;/strong>：词的稀有程度&lt;/p>
&lt;ul>
&lt;li>在所有文档中很少出现 → 区分度高 → 权重高&lt;/li>
&lt;li>在所有文档中都出现（如&amp;quot;的&amp;quot;、&amp;ldquo;是&amp;rdquo;）→ 区分度低 → 权重低&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>问题&lt;/strong>：TF 会过度奖励高频词。一个词出现 10 次不代表比出现 1 次重要 10 倍。&lt;/p>
&lt;p>&lt;strong>BM25 的改进&lt;/strong>：对 TF 做饱和处理。&lt;/p>
&lt;h3 id="bm25-公式">BM25 公式
&lt;/h3>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">score(D, Q) = Σ IDF(qi) × (f(qi, D) × (k1 + 1)) / (f(qi, D) + k1 × (1 - b + b × |D|/avgdl))
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">其中：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- D：文档
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- Q：查询（包含多个词 q1, q2, ...）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- qi：查询中的第 i 个词
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- f(qi, D)：词 qi 在文档 D 中的词频
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- |D|：文档 D 的长度
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- avgdl：平均文档长度
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- k1：词频饱和参数（通常 1.2-2.0）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- b：长度归一化参数（通常 0.75）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">IDF(qi) = log((N - n(qi) + 0.5) / (n(qi) + 0.5) + 1)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- N：总文档数
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- n(qi)：包含词 qi 的文档数
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>直觉理解&lt;/strong>：&lt;/p>
&lt;ol>
&lt;li>
&lt;p>&lt;strong>词频饱和&lt;/strong>：&lt;code>(f × (k1+1)) / (f + k1)&lt;/code> 是一个饱和函数。当 f 很大时，增长变慢。一个词出现 10 次，权重不是 10 倍，而是趋于 k1+1。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>长度归一化&lt;/strong>：长文档天然有更高的词频，需要惩罚。&lt;code>|D|/avgdl&lt;/code> 是相对长度，通过参数 b 控制惩罚程度。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>IDF 奖励稀有词&lt;/strong>：在少数文档中出现的词，IDF 高，权重高。&lt;/p>
&lt;/li>
&lt;/ol>
&lt;h3 id="bm25-的特点">BM25 的特点
&lt;/h3>&lt;table>
&lt;thead>
&lt;tr>
&lt;th>优点&lt;/th>
&lt;th>缺点&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>对精确匹配敏感&lt;/td>
&lt;td>不理解语义&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>对编号、术语稳定&lt;/td>
&lt;td>同义词问题（&amp;ldquo;电脑&amp;quot;和&amp;quot;计算机&amp;quot;是两个词）&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>计算快，可解释&lt;/td>
&lt;td>需要分词（中文需要额外的分词步骤）&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>不需要训练&lt;/td>
&lt;td>不适合自然语言问题&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>&lt;strong>适用场景&lt;/strong>：&lt;/p>
&lt;ul>
&lt;li>用户查询包含编号、版本号、专业术语&lt;/li>
&lt;li>文档中有大量缩写、代码&lt;/li>
&lt;li>需要精确匹配的场景&lt;/li>
&lt;/ul>
&lt;h3 id="中文分词">中文分词
&lt;/h3>&lt;p>BM25 依赖词的边界。英文天然有空格分隔，中文需要额外的分词步骤。&lt;/p>
&lt;p>常用分词工具：&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>工具&lt;/th>
&lt;th>特点&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>jieba&lt;/td>
&lt;td>最流行，词典+HMM&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>pkuseg&lt;/td>
&lt;td>北大出品，准确率高&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>HanLP&lt;/td>
&lt;td>功能全，支持多种任务&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>分词质量直接影响 BM25 效果。比如：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">&amp;#34;用户体验设计&amp;#34;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">分词 1：用户 / 体验 / 设计（三个词）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">分词 2：用户体验 / 设计（两个词）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">分词 3：用户 / 体验设计（两个词）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">不同的分词，检索结果不同。
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="三hybrid-retrieval语义--关键词">三、Hybrid Retrieval：语义 + 关键词
&lt;/h2>&lt;p>向量检索和 BM25 各有优劣，互补性很强。&lt;/p>
&lt;h3 id="为什么需要混合">为什么需要混合
&lt;/h3>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">向量检索：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 擅长：语义相似、同义词、自然语言问题
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 弱项：精确匹配、编号、专业术语
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">BM25：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 擅长：精确匹配、编号、专业术语
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 弱项：语义理解、同义词、自然语言问题
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>实测对比&lt;/strong>：&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>查询类型&lt;/th>
&lt;th>向量检索 Recall@5&lt;/th>
&lt;th>BM25 Recall@5&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>语义查询（&amp;ldquo;怎么获取令牌&amp;rdquo;）&lt;/td>
&lt;td>82%&lt;/td>
&lt;td>58%&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>精确匹配（&amp;ldquo;错误码 401&amp;rdquo;）&lt;/td>
&lt;td>47%&lt;/td>
&lt;td>76%&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>编号查询（&amp;ldquo;版本 v2.3.1&amp;rdquo;）&lt;/td>
&lt;td>38%&lt;/td>
&lt;td>69%&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>混合查询（&amp;ldquo;API v2 认证方式&amp;rdquo;）&lt;/td>
&lt;td>64%&lt;/td>
&lt;td>71%&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>没有哪种方法在所有场景下都最优。混合检索可以兼顾两者。&lt;/p>
&lt;h3 id="混合检索的架构">混合检索的架构
&lt;/h3>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">Query
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ├─── 向量检索 ──→ Dense 候选集（top-50）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ├─── BM25 检索 ──→ Sparse 候选集（top-50）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> └─── Metadata 过滤 ──→ 过滤后的候选集
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ▼
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 结果融合
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ▼
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Rerank（可选）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ▼
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 最终结果（top-5）
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="结果融合rrfreciprocal-rank-fusion">结果融合：RRF（Reciprocal Rank Fusion）
&lt;/h3>&lt;p>两路检索结果怎么合并？简单的方法是按分数加权，但问题：向量分数和 BM25 分数不在同一个尺度，不好比较。&lt;/p>
&lt;p>更优雅的方法是 &lt;strong>RRF（Reciprocal Rank Fusion）&lt;/strong>：不依赖分数，只依赖排名。&lt;/p>
&lt;p>&lt;strong>RRF 公式&lt;/strong>：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">RRF_score(d) = Σ 1 / (k + rank_i(d))
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">其中：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- d：文档
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- rank_i(d)：文档 d 在第 i 路检索中的排名
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- k：平滑参数（通常取 60）
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>直觉&lt;/strong>：&lt;/p>
&lt;ul>
&lt;li>排名第 1 的文档得分：1/(60+1) ≈ 0.0164&lt;/li>
&lt;li>排名第 2 的文档得分：1/(60+2) ≈ 0.0159&lt;/li>
&lt;li>排名第 10 的文档得分：1/(60+10) ≈ 0.0143&lt;/li>
&lt;/ul>
&lt;p>排名越高，得分越高，但差距被平滑。k 越大，不同排名之间的得分差距越小。&lt;/p>
&lt;p>&lt;strong>为什么有效&lt;/strong>？&lt;/p>
&lt;ol>
&lt;li>&lt;strong>不依赖分数&lt;/strong>：避免了分数归一化的问题&lt;/li>
&lt;li>&lt;strong>鲁棒性强&lt;/strong>：单路检索的异常高分不会主导结果&lt;/li>
&lt;li>&lt;strong>简单高效&lt;/strong>：只需要排名信息，计算量小&lt;/li>
&lt;/ol>
&lt;p>&lt;strong>示例&lt;/strong>：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">Query：&amp;#34;错误码 401&amp;#34;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">向量检索结果：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">1. Doc_A（向量分数 0.82）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">2. Doc_B（向量分数 0.79）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">3. Doc_C（向量分数 0.76）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">BM25 检索结果：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">1. Doc_D（BM25 分数 5.2）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">2. Doc_C（BM25 分数 4.8）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">3. Doc_A（BM25 分数 4.1）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">RRF 融合（k=60）：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Doc_A: 1/(60+1) + 1/(60+3) = 0.0164 + 0.0159 = 0.0323
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Doc_B: 1/(60+2) + 0 = 0.0159（BM25 没召回）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Doc_C: 1/(60+3) + 1/(60+2) = 0.0159 + 0.0159 = 0.0318
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Doc_D: 0 + 1/(60+1) = 0.0164（向量没召回）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">最终排序：Doc_A &amp;gt; Doc_C &amp;gt; Doc_B &amp;gt; Doc_D
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Doc_A 在两路都有出现，最终得分最高。这就是 RRF 的魅力：&lt;strong>两路都召回的文档，更有可能是相关的。&lt;/strong>&lt;/p>
&lt;h3 id="metadata-过滤">Metadata 过滤
&lt;/h3>&lt;p>除了向量检索和 BM25，还可以利用元数据做过滤：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">用户问：&amp;#34;销售合同模板在哪下载？&amp;#34;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">先过滤：source = &amp;#34;sales_docs&amp;#34;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">再检索：在销售文档范围内检索
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">好处：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 缩小检索范围，减少噪声
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 提高检索精度
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 支持权限隔离（不同用户可见不同来源）
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>过滤可以在检索前（pre-filtering）或检索后（post-filtering）。主流向量库都支持 pre-filtering，效率更高。&lt;/p>
&lt;h2 id="四rerank精排的原理">四、Rerank：精排的原理
&lt;/h2>&lt;p>召回阶段的目标是&amp;quot;别漏掉&amp;rdquo;，所以召回数量较多，难免混入噪声。Rerank 阶段的目标是&amp;quot;降噪&amp;quot;，从候选中选出最相关的。&lt;/p>
&lt;h3 id="为什么需要-rerank">为什么需要 Rerank
&lt;/h3>&lt;p>向量检索和 BM25 都是&amp;quot;双塔&amp;quot;结构：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">Query → Encoder → Query Vector ─┐
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ├→ Similarity
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Document → Encoder → Doc Vector ─┘
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Query 和 Document 各自编码，只在最后计算相似度。&lt;/p>
&lt;p>&lt;strong>问题&lt;/strong>：Query 和 Document 之间的交互太少。编码时看不到对方，只能依赖各自的信息。&lt;/p>
&lt;p>&lt;strong>Rerank 的思路&lt;/strong>：让 Query 和 Document 充分交互，更精确地判断相关性。&lt;/p>
&lt;h3 id="cross-encoder-的工作原理">Cross-Encoder 的工作原理
&lt;/h3>&lt;p>Cross-Encoder 是&amp;quot;单塔&amp;quot;结构：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">Query + Document → Transformer → Relevance Score
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>把 Query 和 Document 拼在一起，丢进 Transformer，让模型自己学习两者的交互。&lt;/p>
&lt;p>&lt;strong>示意&lt;/strong>：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">输入：[CLS] Query [SEP] Document [SEP]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Transformer 会计算每个 token 和其他所有 token 的注意力，
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">包括 Query token 和 Document token 之间的交互。
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">输出：[CLS] 位置的向量 → 全连接层 → 相关性分数
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>为什么比双塔更准&lt;/strong>？&lt;/p>
&lt;p>双塔：Query 编码时看不到 Document，只能用通用的语义表示。
单塔：Query 和 Document 一起编码，模型可以看到两者之间的关系。&lt;/p>
&lt;p>比如：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">Query：&amp;#34;苹果的价格&amp;#34;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Document 1：&amp;#34;苹果公司股价下跌&amp;#34;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Document 2：&amp;#34;水果店苹果 5 元一斤&amp;#34;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">双塔可能把两个 Document 都召回（都包含&amp;#34;苹果&amp;#34;和&amp;#34;价格&amp;#34;相关词）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Cross-Encoder 能判断：Document 2 更相关（语义上&amp;#34;价格&amp;#34;指水果价格，不是股价）
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>为什么不能替代检索&lt;/strong>？&lt;/p>
&lt;p>Cross-Encoder 需要把 Query 和每个 Document 都拼起来过一遍模型。&lt;/p>
&lt;p>计算量 = 候选数量 × Transformer 计算量&lt;/p>
&lt;p>如果候选是 100 万，计算量巨大，延迟无法接受。&lt;/p>
&lt;p>所以只能用在召回之后，对小规模候选做精排。&lt;/p>
&lt;h3 id="rerank-模型">Rerank 模型
&lt;/h3>&lt;p>常用 Rerank 模型：&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>模型&lt;/th>
&lt;th>特点&lt;/th>
&lt;th>延迟（100 候选）&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>bge-reranker-large&lt;/td>
&lt;td>开源，效果好&lt;/td>
&lt;td>~180ms&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>bge-reranker-base&lt;/td>
&lt;td>开源，更快&lt;/td>
&lt;td>~95ms&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>cohere rerank-3&lt;/td>
&lt;td>API，效果好&lt;/td>
&lt;td>~120ms&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>ms-marco-MiniLM&lt;/td>
&lt;td>开源，轻量&lt;/td>
&lt;td>~45ms&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>jina-reranker-v2&lt;/td>
&lt;td>开源，多语言&lt;/td>
&lt;td>~80ms&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>选择考虑：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>追求效果&lt;/strong>：bge-reranker-large 或 cohere rerank-3&lt;/li>
&lt;li>&lt;strong>追求速度&lt;/strong>：ms-marco-MiniLM 或 bge-reranker-base&lt;/li>
&lt;li>&lt;strong>中文场景&lt;/strong>：bge-reranker-large 或 jina-reranker-v2&lt;/li>
&lt;li>&lt;strong>私有化部署&lt;/strong>：bge 系列&lt;/li>
&lt;/ul>
&lt;h3 id="rerank-的效果">Rerank 的效果
&lt;/h3>&lt;p>实测对比：&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>配置&lt;/th>
&lt;th>Recall@5&lt;/th>
&lt;th>正确答案平均排名&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>向量检索&lt;/td>
&lt;td>72%&lt;/td>
&lt;td>4.2&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>混合检索（无 Rerank）&lt;/td>
&lt;td>79%&lt;/td>
&lt;td>3.5&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>混合 + Rerank&lt;/td>
&lt;td>88%&lt;/td>
&lt;td>1.8&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>Rerank 后，正确答案的平均排名从 3.5 提升到 1.8——明显更靠前，更容易进入最终的 prompt。&lt;/p>
&lt;h2 id="五完整的检索流程">五、完整的检索流程
&lt;/h2>&lt;p>结合以上内容，一个完整的检索流程如下：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">┌─────────────────────────────────────────────────────────────────┐
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ 用户 Query │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ &amp;#34;错误码 401 是什么意思&amp;#34; │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">└────────────────────────┬────────────────────────────────────────┘
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ▼
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">┌─────────────────────────────────────────────────────────────────┐
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ 阶段 1：候选召回（Recall） │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ 目标：不漏掉，召回足够多的候选 │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ ┌─────────────────┐ ┌─────────────────┐ │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ │ 向量检索 │ │ BM25 检索 │ │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ │ top-50 │ │ top-50 │ │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ │ │ │ │ │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ │ 擅长语义相似 │ │ 擅长精确匹配 │ │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ └────────┬────────┘ └────────┬────────┘ │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ │ │ │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ └─────────┬──────────┘ │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ ▼ │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ ┌─────────────┐ │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ │ RRF 融合 │ │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ │ ~80 候选 │ │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ └──────┬──────┘ │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ │ │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ ┌──────▼──────┐ │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ │ Metadata │ │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ │ 过滤 │ │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ │ ~60 候选 │ │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ └──────┬──────┘ │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">└─────────────────────┼──────────────────────────────────────────┘
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ▼
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">┌─────────────────────────────────────────────────────────────────┐
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ 阶段 2：精排（Rerank） │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ 目标：从候选中选出最相关的 │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ 输入：~60 个候选 │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ 处理：Cross-Encoder 重新打分 │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ 输出：top-10 │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ 耗时：~150ms │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">└────────────────────────┬────────────────────────────────────────┘
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ▼
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">┌─────────────────────────────────────────────────────────────────┐
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ 阶段 3：上下文组装 │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ 目标：控制进入 prompt 的内容 │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ 输入：top-10 候选 │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ 处理： │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ 1. 控制总长度（如 4000 tokens） │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ 2. 保持多样性（不同 source 的 chunk） │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ 3. 去重 │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ 输出：5-8 个 chunk │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">└────────────────────────┬────────────────────────────────────────┘
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ▼
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 构建 Prompt
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 调用 LLM
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 答案
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="各阶段的参数建议">各阶段的参数建议
&lt;/h3>&lt;table>
&lt;thead>
&lt;tr>
&lt;th>阶段&lt;/th>
&lt;th>参数&lt;/th>
&lt;th>推荐值&lt;/th>
&lt;th>说明&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>向量召回&lt;/td>
&lt;td>top_k&lt;/td>
&lt;td>50-100&lt;/td>
&lt;td>宁多勿少&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>BM25 召回&lt;/td>
&lt;td>top_k&lt;/td>
&lt;td>50-100&lt;/td>
&lt;td>宁多勿少&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>RRF&lt;/td>
&lt;td>k&lt;/td>
&lt;td>60&lt;/td>
&lt;td>经验值，一般不用调&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Rerank&lt;/td>
&lt;td>输入&lt;/td>
&lt;td>20-30&lt;/td>
&lt;td>不用太大，计算慢&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Rerank&lt;/td>
&lt;td>输出&lt;/td>
&lt;td>10-15&lt;/td>
&lt;td>给 prompt 组装留空间&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Prompt 组装&lt;/td>
&lt;td>最终数量&lt;/td>
&lt;td>3-8&lt;/td>
&lt;td>不宜过多&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Prompt 组装&lt;/td>
&lt;td>token 预算&lt;/td>
&lt;td>3000-4000&lt;/td>
&lt;td>根据模型调整&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;h3 id="各阶段的延迟占比">各阶段的延迟占比
&lt;/h3>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">总延迟：~300-500ms
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">向量检索：30-50ms（10%）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">BM25 检索：10-20ms（5%）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">RRF 融合：&amp;lt;5ms（&amp;lt;1%）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Rerank：150-200ms（50%）← 主要瓶颈
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Prompt 组装：&amp;lt;10ms（&amp;lt;5%）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">LLM 调用：100-200ms（35%）← 另一个瓶颈
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>优化方向：&lt;/p>
&lt;ol>
&lt;li>&lt;strong>减少 Rerank 候选数&lt;/strong>：从 30 减到 20，节省 ~50ms&lt;/li>
&lt;li>&lt;strong>用更快的 Rerank 模型&lt;/strong>：bge-reranker-base 比 large 快 ~80ms&lt;/li>
&lt;li>&lt;strong>缓存热门 query&lt;/strong>：相同问题直接返回，跳过整个流程&lt;/li>
&lt;/ol>
&lt;h2 id="六query-rewrite高级优化">六、Query Rewrite：高级优化
&lt;/h2>&lt;p>检索质量不仅取决于检索算法，还取决于 Query 本身。&lt;/p>
&lt;p>用户的问题往往不够清晰，需要&amp;quot;改写&amp;quot;才能更好地检索。&lt;/p>
&lt;h3 id="常见的改写策略">常见的改写策略
&lt;/h3>&lt;p>&lt;strong>1. 同义词扩展&lt;/strong>&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">原始：&amp;#34;怎么获取令牌&amp;#34;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">改写：&amp;#34;怎么获取令牌 OR token OR access_token&amp;#34;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>2. 拼写纠正&lt;/strong>&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">原始：&amp;#34;错误码 4O1&amp;#34;（字母 O）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">改写：&amp;#34;错误码 401&amp;#34;（数字 0）
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>3. 核心词提取&lt;/strong>&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">原始：&amp;#34;我想了解一下关于 API 超时时间的配置方法&amp;#34;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">改写：&amp;#34;API 超时时间 配置&amp;#34;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>去掉无意义的词，保留核心检索词。&lt;/p>
&lt;p>&lt;strong>4. Query 分解&lt;/strong>&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">原始：&amp;#34;对比 v2.0 和 v2.1 版本的 API 差异&amp;#34;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">分解：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- Query 1：&amp;#34;v2.0 版本 API 特性&amp;#34;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- Query 2：&amp;#34;v2.1 版本 API 特性&amp;#34;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- Query 3：&amp;#34;v2.0 v2.1 版本差异&amp;#34;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>复杂问题拆成多个简单问题，分别检索，再合并。&lt;/p>
&lt;p>&lt;strong>5. 假设文档生成（HyDE）&lt;/strong>&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">原始：&amp;#34;API 的认证方式是什么&amp;#34;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">生成假设文档：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&amp;#34;API 的认证方式使用 Bearer Token。所有请求需要在 Header 中携带
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Authorization: Bearer &amp;lt;token&amp;gt;。Token 有效期 24 小时...&amp;#34;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">用假设文档去检索，而不是原始 query。
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>直觉：假设文档和真实文档的相似度，可能比 query 和文档的相似度更高。&lt;/p>
&lt;h3 id="query-rewrite-的权衡">Query Rewrite 的权衡
&lt;/h3>&lt;table>
&lt;thead>
&lt;tr>
&lt;th>优点&lt;/th>
&lt;th>缺点&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>提高检索质量&lt;/td>
&lt;td>增加 LLM 调用成本&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>处理复杂问题&lt;/td>
&lt;td>增加延迟&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>适应用户表达习惯&lt;/td>
&lt;td>可能改写错误&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>建议：&lt;/p>
&lt;ul>
&lt;li>简单问题不需要改写&lt;/li>
&lt;li>复杂问题、长尾问题适合改写&lt;/li>
&lt;li>可以用规则+LLM结合，降低成本&lt;/li>
&lt;/ul>
&lt;h2 id="七一个推荐的升级路线">七、一个推荐的升级路线
&lt;/h2>&lt;p>如果你现在还是单路向量检索，建议按这个顺序升级：&lt;/p>
&lt;h3 id="第一步优化-chunking成本低效果好">第一步：优化 Chunking（成本低，效果好）
&lt;/h3>&lt;ul>
&lt;li>从固定长度改为按段落/标题切分&lt;/li>
&lt;li>给每个 chunk 补充父级标题上下文&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>预期效果&lt;/strong>：Recall@5 从 60% 提升到 75%&lt;/p>
&lt;h3 id="第二步引入-bm25成本中">第二步：引入 BM25（成本中）
&lt;/h3>&lt;ul>
&lt;li>部署 BM25 检索&lt;/li>
&lt;li>实现 RRF 融合&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>预期效果&lt;/strong>：Recall@5 从 75% 提升到 82%&lt;/p>
&lt;h3 id="第三步接入-rerank成本中">第三步：接入 Rerank（成本中）
&lt;/h3>&lt;ul>
&lt;li>部署 Cross-Encoder 模型&lt;/li>
&lt;li>调整召回数量和精排数量&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>预期效果&lt;/strong>：Recall@5 从 82% 提升到 88%&lt;/p>
&lt;h3 id="第四步高级优化成本高">第四步：高级优化（成本高）
&lt;/h3>&lt;ul>
&lt;li>Query Rewrite&lt;/li>
&lt;li>Query Decomposition&lt;/li>
&lt;li>HyDE&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>预期效果&lt;/strong>：Recall@5 从 88% 提升到 92%+&lt;/p>
&lt;h3 id="收益递减规律">收益递减规律
&lt;/h3>&lt;p>从 60% 到 80%：相对容易，基础优化即可
从 80% 到 90%：需要系统优化，混合检索+Rerank
从 90% 到 95%：难度大增，需要高级策略和大量调试&lt;/p>
&lt;p>&lt;strong>建议&lt;/strong>：先做到 85%，再考虑是否值得继续投入。&lt;/p>
&lt;h2 id="八本文小结">八、本文小结
&lt;/h2>&lt;p>RAG 做得好不好，很多时候不是模型不够强，而是：&lt;/p>
&lt;ol>
&lt;li>&lt;strong>Chunk 没切好&lt;/strong> — 语义不完整、缺上下文&lt;/li>
&lt;li>&lt;strong>候选召回太单一&lt;/strong> — 只用向量，对精确匹配不稳定&lt;/li>
&lt;li>&lt;strong>Rerank 没真正落地&lt;/strong> — 用&amp;quot;假 rerank&amp;quot;或根本没 rerank&lt;/li>
&lt;/ol>
&lt;p>核心要点：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Chunking&lt;/strong>：按语义结构切分 + 补充上下文&lt;/li>
&lt;li>&lt;strong>混合检索&lt;/strong>：向量（语义）+ BM25（精确）+ RRF 融合&lt;/li>
&lt;li>&lt;strong>Rerank&lt;/strong>：Cross-Encoder 做真正的精排&lt;/li>
&lt;li>&lt;strong>Query Rewrite&lt;/strong>：处理复杂和长尾问题&lt;/li>
&lt;/ul>
&lt;p>下一篇会讲：如何做 RAG 的评测和 Inspect，才能知道问题到底出在哪一层。&lt;/p></description></item><item><title>RAG入门：从问题定义到系统设计</title><link>https://www.zata.cc/p/rag%E5%85%A5%E9%97%A8%E4%BB%8E%E9%97%AE%E9%A2%98%E5%AE%9A%E4%B9%89%E5%88%B0%E7%B3%BB%E7%BB%9F%E8%AE%BE%E8%AE%A1/</link><pubDate>Wed, 25 Mar 2026 15:40:00 +0800</pubDate><guid>https://www.zata.cc/p/rag%E5%85%A5%E9%97%A8%E4%BB%8E%E9%97%AE%E9%A2%98%E5%AE%9A%E4%B9%89%E5%88%B0%E7%B3%BB%E7%BB%9F%E8%AE%BE%E8%AE%A1/</guid><description>&lt;img src="https://www.zata.cc/p/rag%E5%85%A5%E9%97%A8%E4%BB%8E%E9%97%AE%E9%A2%98%E5%AE%9A%E4%B9%89%E5%88%B0%E7%B3%BB%E7%BB%9F%E8%AE%BE%E8%AE%A1/images/index/index.png" alt="Featured image of post RAG入门：从问题定义到系统设计" />&lt;p>RAG，全称 Retrieval-Augmented Generation，核心思想其实很朴素：不要把所有知识都塞进模型参数里，而是在回答问题前，先从外部知识库里把最相关的信息找出来，再交给模型生成答案。&lt;/p>
&lt;p>这套方法之所以重要，是因为纯大模型回答经常会遇到三类问题：&lt;/p>
&lt;ol>
&lt;li>&lt;strong>知识过期&lt;/strong> — GPT-4 的训练数据截止到 2023 年底，你问它 2024 年 3 月的新闻，它要么说不知道，要么编一个。&lt;/li>
&lt;li>&lt;strong>专有知识缺失&lt;/strong> — 你们公司的内部文档、API 规范、业务规则，模型从来没见过。&lt;/li>
&lt;li>&lt;strong>幻觉问题&lt;/strong> — 模型会&amp;quot;自信地胡说八道&amp;quot;，尤其是当它不确定的时候。&lt;/li>
&lt;/ol>
&lt;p>RAG 的目标不是让模型&amp;quot;更聪明&amp;quot;，而是让系统&amp;quot;更可控&amp;quot;。你把知识更新、答案依据、召回范围、调试手段都从模型参数里解耦了出来。&lt;/p>
&lt;h2 id="一rag-解决的本质问题">一、RAG 解决的本质问题
&lt;/h2>&lt;p>先想清楚一个问题：&lt;strong>为什么不让模型直接回答，而要先检索？&lt;/strong>&lt;/p>
&lt;p>答案是：&lt;strong>模型的参数空间是有限的，但知识是无限的。&lt;/strong>&lt;/p>
&lt;p>GPT-4 有 1.8 万亿参数，看起来很多，但这些参数要编码：语言规律、世界知识、推理能力、常识……真正留给&amp;quot;事实知识&amp;quot;的空间并不多。而且参数一旦训练完成，就固定了。你想让它知道你们公司最新的报销流程？只能重新训练，成本几十万起步。&lt;/p>
&lt;p>RAG 的思路是：&lt;strong>把&amp;quot;事实知识&amp;quot;从参数里剥离出来，放到外部存储里。&lt;/strong>&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">传统模型：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">知识 → 训练 → 参数 → 推理 → 答案
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">（知识固化在参数里，更新成本高）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">RAG：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">知识 → 索引 → 向量库 → 检索 → 推理 → 答案
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">（知识和模型解耦，更新成本低）
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>代价是：每次回答前，需要先检索。检索的质量直接决定了答案的质量。&lt;/p>
&lt;h2 id="二embedding把文本变成向量">二、Embedding：把文本变成向量
&lt;/h2>&lt;p>RAG 的第一步是让机器&amp;quot;理解&amp;quot;文本。方法是 Embedding——把文本映射到一个高维向量空间。&lt;/p>
&lt;h3 id="embedding-的直觉">Embedding 的直觉
&lt;/h3>&lt;p>假设我们有一个 3 维空间，每个词可以表示为一个点：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">&amp;#34;猫&amp;#34; → (0.2, 0.8, 0.1)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&amp;#34;狗&amp;#34; → (0.3, 0.7, 0.2) ← 和&amp;#34;猫&amp;#34;很近，都是宠物
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&amp;#34;汽车&amp;#34; → (0.9, 0.1, 0.3) ← 和&amp;#34;猫&amp;#34;很远，不同类别
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>在这个空间里，语义相似的词距离近，语义不同的词距离远。&lt;/p>
&lt;p>实际的 Embedding 维度是几百到几千维，但原理一样：&lt;strong>让语义相似的文本在向量空间里距离近。&lt;/strong>&lt;/p>
&lt;h3 id="训练-embedding-模型">训练 Embedding 模型
&lt;/h3>&lt;p>Embedding 模型是怎么训练出来的？核心思想是：&lt;strong>让相似文本的向量靠近，不相似文本的向量远离。&lt;/strong>&lt;/p>
&lt;p>常用的训练方式有两种：&lt;/p>
&lt;p>&lt;strong>1. 对比学习（Contrastive Learning）&lt;/strong>&lt;/p>
&lt;p>准备大量&amp;quot;相似对&amp;quot;和&amp;quot;不相似对&amp;quot;：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">相似对：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">(&amp;#34;如何重置密码&amp;#34;, &amp;#34;密码重置方法&amp;#34;)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">(&amp;#34;API 超时设置&amp;#34;, &amp;#34;配置 API 超时时间&amp;#34;)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">不相似对：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">(&amp;#34;如何重置密码&amp;#34;, &amp;#34;API 超时设置&amp;#34;)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">(&amp;#34;密码重置方法&amp;#34;, &amp;#34;今天天气怎么样&amp;#34;)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>训练目标：让相似对的向量距离小，不相似对的向量距离大。&lt;/p>
&lt;p>损失函数（InfoNCE Loss）：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">L = -log( exp(sim(q, p+)/τ) / Σexp(sim(q, pi)/τ) )
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">其中：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- q 是 query 的向量
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- p+ 是正样本的向量
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- pi 是所有样本（包括正样本和负样本）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- sim 是相似度函数（如余弦相似度）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- τ 是温度参数
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>直觉解释：让正样本的相似度占主导，负样本的相似度被压制。&lt;/p>
&lt;p>&lt;strong>2. 自监督学习&lt;/strong>&lt;/p>
&lt;p>不用人工标注，利用文本本身的结构：&lt;/p>
&lt;ul>
&lt;li>同一个句子/段落的不同部分互为正样本&lt;/li>
&lt;li>不同句子/段落互为负样本&lt;/li>
&lt;/ul>
&lt;p>代表性方法：SimCSE，把同一个句子丢进模型两次（不同的 dropout mask），得到的两个向量应该相似。&lt;/p>
&lt;h3 id="主流-embedding-模型">主流 Embedding 模型
&lt;/h3>&lt;table>
&lt;thead>
&lt;tr>
&lt;th>模型&lt;/th>
&lt;th>维度&lt;/th>
&lt;th>特点&lt;/th>
&lt;th>适用场景&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>text-embedding-3-small&lt;/td>
&lt;td>1536&lt;/td>
&lt;td>OpenAI，多语言&lt;/td>
&lt;td>通用场景&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>text-embedding-3-large&lt;/td>
&lt;td>3072&lt;/td>
&lt;td>OpenAI，更高精度&lt;/td>
&lt;td>高质量要求&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>bge-large-zh&lt;/td>
&lt;td>1024&lt;/td>
&lt;td>开源，中文优化&lt;/td>
&lt;td>中文场景&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>bge-m3&lt;/td>
&lt;td>1024&lt;/td>
&lt;td>开源，多语言&lt;/td>
&lt;td>多语言混合&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>e5-large-v2&lt;/td>
&lt;td>1024&lt;/td>
&lt;td>开源，需要加前缀&lt;/td>
&lt;td>英文场景&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>选择原则：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>中文为主&lt;/strong>：选 bge-large-zh&lt;/li>
&lt;li>&lt;strong>多语言混合&lt;/strong>：选 text-embedding-3-small 或 bge-m3&lt;/li>
&lt;li>&lt;strong>追求高质量且有预算&lt;/strong>：选 text-embedding-3-large&lt;/li>
&lt;li>&lt;strong>私有化部署&lt;/strong>：选 bge 系列&lt;/li>
&lt;/ul>
&lt;h2 id="三向量检索在高维空间找邻居">三、向量检索：在高维空间找邻居
&lt;/h2>&lt;p>有了向量，接下来是检索：给定一个 query 向量，找到最相似的文档向量。&lt;/p>
&lt;h3 id="相似度计算">相似度计算
&lt;/h3>&lt;p>最常用的是&lt;strong>余弦相似度&lt;/strong>：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">cosine(a, b) = (a · b) / (||a|| × ||b||)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">其中：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- a · b 是向量点积
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- ||a|| 是向量的模长
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">结果范围：[-1, 1]，越大越相似
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>为什么用余弦相似度而不是欧氏距离？&lt;/p>
&lt;p>因为 Embedding 的方向比长度更重要。两个文本的语义相似，体现在向量方向相近，而不是长度相近。余弦相似度只看方向，不看长度。&lt;/p>
&lt;h3 id="暴力检索的问题">暴力检索的问题
&lt;/h3>&lt;p>最简单的检索方式是暴力计算：把 query 和所有文档向量都算一遍相似度，排序取 top-k。&lt;/p>
&lt;p>问题：慢。&lt;/p>
&lt;p>假设向量维度 1024，文档数量 100 万：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">单次检索计算量：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">100万 × 1024 = 10亿次浮点运算
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">耗时估算：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">单核 CPU ≈ 100-500ms
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">GPU ≈ 10-50ms
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>100 万文档还算小，到了 1000 万、1 亿，暴力检索就撑不住了。&lt;/p>
&lt;h3 id="近似最近邻搜索ann">近似最近邻搜索（ANN）
&lt;/h3>&lt;p>解决方案：&lt;strong>用精度换速度&lt;/strong>。不要求找到&amp;quot;最近的&amp;quot;，只要找到&amp;quot;足够近的&amp;quot;。&lt;/p>
&lt;p>主流算法：&lt;/p>
&lt;p>&lt;strong>1. HNSW（Hierarchical Navigable Small World）&lt;/strong>&lt;/p>
&lt;p>思路：构建一个多层的图结构，每层是一个小世界网络。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">第 0 层：所有节点
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">第 1 层：部分节点（概率选取）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">第 2 层：更少的节点
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">搜索时：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">从最高层开始，快速跳到目标区域附近
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">逐层下降，越来越精确
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">最后在第 0 层找到最近邻
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>类比：找北京的某个人。先在世界地图上定位到中国，再在中国地图上定位到北京，再在北京地图上定位到朝阳区……每一步都在缩小范围。&lt;/p>
&lt;p>&lt;strong>2. IVF（Inverted File Index）&lt;/strong>&lt;/p>
&lt;p>思路：把向量空间划分为多个区域（聚类中心），每个区域维护一个倒排表。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">离线阶段：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">1. 用 K-means 聚类，找到 K 个中心点
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">2. 把每个向量分配到最近的中心点
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">3. 每个中心点维护一个列表（倒排表）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">在线检索：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">1. 找到离 query 最近的几个中心点
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">2. 只在这几个中心点的倒排表里搜索
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">3. 不用遍历所有向量
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>参数选择：nprobe（探测几个中心点）。nprobe 越大，精度越高，速度越慢。&lt;/p>
&lt;p>&lt;strong>3. PQ（Product Quantization）&lt;/strong>&lt;/p>
&lt;p>思路：压缩向量，减少内存占用和计算量。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">原始向量：1024 维浮点数 = 4096 字节
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">压缩方法：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">1. 把 1024 维分成 8 组，每组 128 维
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">2. 每组用 K-means 聚类，生成 256 个中心点
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">3. 每个向量每组只需 1 字节（存储中心点编号）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">压缩后：8 字节
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">压缩比：512 倍
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>代价：精度损失。但换来的是内存占用大幅降低，可以索引更多文档。&lt;/p>
&lt;h3 id="向量库的选择">向量库的选择
&lt;/h3>&lt;table>
&lt;thead>
&lt;tr>
&lt;th>向量库&lt;/th>
&lt;th>算法&lt;/th>
&lt;th>特点&lt;/th>
&lt;th>适用场景&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>Chroma&lt;/td>
&lt;td>HNSW&lt;/td>
&lt;td>轻量，易上手&lt;/td>
&lt;td>开发/小规模&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Qdrant&lt;/td>
&lt;td>HNSW&lt;/td>
&lt;td>开源，功能全&lt;/td>
&lt;td>中等规模生产&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Milvus&lt;/td>
&lt;td>多种&lt;/td>
&lt;td>分布式，高性能&lt;/td>
&lt;td>大规模生产&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Pinecone&lt;/td>
&lt;td>专有&lt;/td>
&lt;td>全托管，免运维&lt;/td>
&lt;td>不想运维&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Weaviate&lt;/td>
&lt;td>HNSW&lt;/td>
&lt;td>原生支持多模态&lt;/td>
&lt;td>多模态场景&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;h2 id="四为什么单靠向量检索不够">四、为什么单靠向量检索不够
&lt;/h2>&lt;p>向量检索很强大，但有天生的局限。&lt;/p>
&lt;h3 id="局限-1对精确匹配不敏感">局限 1：对精确匹配不敏感
&lt;/h3>&lt;p>例子：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">文档：&amp;#34;错误码 401 表示认证失败&amp;#34;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">用户问：&amp;#34;401 是什么意思&amp;#34;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">向量检索可能召回：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&amp;#34;常见错误码及解决方案&amp;#34;（语义相似）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&amp;#34;权限问题排查&amp;#34;（语义相似）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">而不是精确包含 &amp;#34;401&amp;#34; 的那段。
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>原因：Embedding 模型学习的是语义相似性，不是字面匹配。&amp;ldquo;401&amp;rdquo; 这个数字在向量空间里没有特殊意义，它只是一个 token。&lt;/p>
&lt;h3 id="局限-2对专业术语不稳定">局限 2：对专业术语不稳定
&lt;/h3>&lt;p>例子：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">文档：&amp;#34;JSON Web Token 的有效期默认为 1 小时&amp;#34;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">用户问：&amp;#34;JWT 的有效期&amp;#34;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">向量检索：可能召回，也可能不召回，取决于模型是否学过 JWT = JSON Web Token
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>如果模型训练数据里没见过这个缩写，就找不到关联。&lt;/p>
&lt;h3 id="局限-3对编号版本号不稳定">局限 3：对编号/版本号不稳定
&lt;/h3>&lt;p>例子：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">文档：&amp;#34;v2.3.1 版本新增了批量导出功能&amp;#34;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">用户问：&amp;#34;v2.3.0 有什么新功能&amp;#34;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">向量检索：可能召回 v2.3.1 或 v2.3.2 的内容，因为&amp;#34;版本更新&amp;#34;语义相似
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>版本号之间的微小差异，在向量空间里可能被&amp;quot;版本&amp;quot;这个大概念淹没。&lt;/p>
&lt;h3 id="解决方案混合检索">解决方案：混合检索
&lt;/h3>&lt;p>这就是为什么生产环境几乎都用 &lt;strong>Hybrid Retrieval&lt;/strong>：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">候选集 = 向量检索（语义） + 关键词检索（字面） + 元数据过滤（业务规则）
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>下一篇文章会详细讲混合检索和 Rerank 的算法。&lt;/p>
&lt;h2 id="五rag-的三阶段架构">五、RAG 的三阶段架构
&lt;/h2>&lt;p>理解了 Embedding 和检索，我们来看 RAG 的整体架构。一个成熟的 RAG 系统分为三个阶段：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">┌─────────────────────────────────────────────────────────────────┐
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ 阶段 1：索引（Indexing） │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ 文档 → 解析 → 切分 → Embedding → 写入向量库 │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ 核心问题：怎么切分才能让每个 chunk 成为独立的证据？ │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">└────────────────────────┬────────────────────────────────────────┘
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ▼
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">┌─────────────────────────────────────────────────────────────────┐
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ 阶段 2：检索（Retrieval） │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ Query → Embedding → 向量检索 → 关键词检索 → 融合 → Rerank │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ 核心问题：怎么召回相关内容，同时减少噪声？ │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">└────────────────────────┬────────────────────────────────────────┘
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ▼
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">┌─────────────────────────────────────────────────────────────────┐
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ 阶段 3：生成（Generation） │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ Query + Chunks → Prompt 组装 → LLM → 答案 + 引用 │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ 核心问题：怎么让模型严格依据证据回答，不自由发挥？ │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">└─────────────────────────────────────────────────────────────────┘
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>每个阶段都有特定的优化空间，而且&lt;strong>问题往往出现在你忽视的地方&lt;/strong>。&lt;/p>
&lt;h3 id="索引阶段的关键决策">索引阶段的关键决策
&lt;/h3>&lt;p>&lt;strong>1. 切分粒度&lt;/strong>&lt;/p>
&lt;p>太大：噪声多，模型注意力分散
太小：信息碎片化，上下文丢失&lt;/p>
&lt;p>经验值：300-500 tokens（中文约 200-350 字）&lt;/p>
&lt;p>但比长度更重要的是&lt;strong>语义完整性&lt;/strong>。一个 chunk 应该能独立成为一个&amp;quot;证据&amp;quot;。&lt;/p>
&lt;p>&lt;strong>2. 上下文补充&lt;/strong>&lt;/p>
&lt;p>一个常见的坑：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">原始文档：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">### 2.3 校准流程
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">将旋钮调至 3 档，等待指示灯变为绿色。
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">按段落切分后，chunk 变成：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&amp;#34;将旋钮调至 3 档，等待指示灯变为绿色。&amp;#34;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">问题：用户问&amp;#34;ZK-200 怎么校准&amp;#34;，这个 chunk 不会召回，因为：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 没有&amp;#34;ZK-200&amp;#34;（在文档标题里）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 没有&amp;#34;校准&amp;#34;（在章节标题里）
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>解决方案：把父级标题拼到 chunk 前面。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">补充上下文后：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&amp;#34;【ZK-200 操作手册 &amp;gt; 2.3 校准流程】
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">将旋钮调至 3 档，等待指示灯变为绿色。&amp;#34;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>这个技巧叫 &lt;strong>Contextual Chunking&lt;/strong>，成本低，效果好。&lt;/p>
&lt;p>&lt;strong>3. 元数据设计&lt;/strong>&lt;/p>
&lt;p>每个 chunk 至少要存储：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">{
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &amp;#34;id&amp;#34;: &amp;#34;唯一标识&amp;#34;,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &amp;#34;content&amp;#34;: &amp;#34;文本内容&amp;#34;,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &amp;#34;document_id&amp;#34;: &amp;#34;所属文档&amp;#34;,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &amp;#34;source&amp;#34;: &amp;#34;知识库来源&amp;#34;,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &amp;#34;title&amp;#34;: &amp;#34;文档标题&amp;#34;,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &amp;#34;section&amp;#34;: &amp;#34;章节路径&amp;#34;,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &amp;#34;chunk_index&amp;#34;: &amp;#34;在文档中的位置&amp;#34;,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &amp;#34;created_at&amp;#34;: &amp;#34;创建时间&amp;#34;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>这些元数据在检索时可以做过滤（只搜特定来源）、在回答时可以做引用（答案来自哪个文档）。&lt;/p>
&lt;h3 id="检索阶段的关键决策">检索阶段的关键决策
&lt;/h3>&lt;p>&lt;strong>1. 召回数量&lt;/strong>&lt;/p>
&lt;p>召回太少：容易漏掉正确答案
召回太多：噪声多，成本高&lt;/p>
&lt;p>经验值：&lt;/p>
&lt;ul>
&lt;li>召回阶段：50-100 个候选&lt;/li>
&lt;li>Rerank 后：保留 10-20 个&lt;/li>
&lt;li>最终进 prompt：3-8 个&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>2. 检索策略&lt;/strong>&lt;/p>
&lt;p>简单场景：纯向量检索够用
复杂场景：向量 + BM25 混合
高精度场景：混合 + Rerank&lt;/p>
&lt;p>&lt;strong>3. 过滤策略&lt;/strong>&lt;/p>
&lt;p>利用元数据缩小检索范围：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">用户问：&amp;#34;销售合同模板在哪下载？&amp;#34;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">不过滤：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">搜索范围 = 所有文档（产品、研发、销售、HR...）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">结果可能混入研发部门的合同管理规范
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">按 department=&amp;#34;sales&amp;#34; 过滤：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">搜索范围 = 销售部门文档
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">结果更精准
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="生成阶段的关键决策">生成阶段的关键决策
&lt;/h3>&lt;p>&lt;strong>1. Prompt 设计&lt;/strong>&lt;/p>
&lt;p>核心原则：&lt;strong>让模型&amp;quot;不得不&amp;quot;依据证据回答。&lt;/strong>&lt;/p>
&lt;p>差的 Prompt：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">参考资料：{chunks}
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">问题：{query}
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">请回答：
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>模型可能无视参考资料，自由发挥。&lt;/p>
&lt;p>好的 Prompt：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">你是一个文档助手。请根据以下参考资料回答问题。
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">要求：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">1. 答案必须基于参考资料，不要编造
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">2. 如果参考资料中没有答案，请直接说&amp;#34;根据现有资料无法回答&amp;#34;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">3. 回答时标注引用来源，格式：[来源: 文档名 &amp;gt; 章节]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">参考资料：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">{chunks}
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">问题：{query}
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">请回答：
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>2. 上下文预算&lt;/strong>&lt;/p>
&lt;p>模型有上下文长度限制，不能无限塞内容。更重要的是：&lt;strong>内容越多，模型注意力越分散。&lt;/strong>&lt;/p>
&lt;p>经验值：检索内容不超过 4000 tokens（约 3000 字中文）&lt;/p>
&lt;p>分配建议：&lt;/p>
&lt;ul>
&lt;li>关键证据：2000-3000 tokens&lt;/li>
&lt;li>Prompt 模板：200-300 tokens&lt;/li>
&lt;li>用户问题和输出：预留 1000+ tokens&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>3. 引用机制&lt;/strong>&lt;/p>
&lt;p>让模型标注答案的来源：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">答案：&amp;#34;API 的超时时间默认为 30 秒 [来源: API 规范 &amp;gt; 3.2 超时配置]&amp;#34;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>好处：&lt;/p>
&lt;ul>
&lt;li>用户可以验证答案&lt;/li>
&lt;li>系统可以追溯错误&lt;/li>
&lt;li>增加答案的可信度&lt;/li>
&lt;/ul>
&lt;h2 id="六rag-vs-微调怎么选">六、RAG vs 微调：怎么选
&lt;/h2>&lt;p>很多人问：应该用 RAG 还是微调？&lt;/p>
&lt;p>答案：&lt;strong>不冲突，解决的问题不同。&lt;/strong>&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>问题类型&lt;/th>
&lt;th>解决方案&lt;/th>
&lt;th>原因&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>知识过时&lt;/td>
&lt;td>RAG&lt;/td>
&lt;td>更新知识库即可&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>专有知识缺失&lt;/td>
&lt;td>RAG&lt;/td>
&lt;td>模型没见过这些知识&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>输出格式不稳定&lt;/td>
&lt;td>微调&lt;/td>
&lt;td>让模型学会特定格式&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>输出风格不对&lt;/td>
&lt;td>微调&lt;/td>
&lt;td>让模型掌握特定风格&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>工具使用不规范&lt;/td>
&lt;td>微调&lt;/td>
&lt;td>让模型学会调用模式&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>&lt;strong>RAG 擅长：解决&amp;quot;知道什么&amp;quot;&lt;/strong>
&lt;strong>微调擅长：解决&amp;quot;怎么做&amp;quot;&lt;/strong>&lt;/p>
&lt;p>实际项目中，往往是两者结合：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">微调：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 让模型学会输出 JSON 格式
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 让模型学会引用来源
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 让模型学会说&amp;#34;不知道&amp;#34;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">RAG：
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 提供最新的业务知识
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 提供可追溯的证据
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">- 提供领域专业信息
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="七第一版-rag-应该怎么搭">七、第一版 RAG 应该怎么搭
&lt;/h2>&lt;p>如果你从零开始，我的建议是：&lt;/p>
&lt;p>&lt;strong>不要一上来就追求复杂架构。第一版的目标是&amp;quot;可解释&amp;quot;，不是&amp;quot;最优&amp;quot;。&lt;/strong>&lt;/p>
&lt;h3 id="推荐的第一版配置">推荐的第一版配置
&lt;/h3>&lt;table>
&lt;thead>
&lt;tr>
&lt;th>模块&lt;/th>
&lt;th>配置&lt;/th>
&lt;th>原因&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>文档导入&lt;/td>
&lt;td>手动，按需导入&lt;/td>
&lt;td>先保证质量，再追求自动化&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>切分策略&lt;/td>
&lt;td>按段落，500 字左右&lt;/td>
&lt;td>简单可控，效果有保障&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Embedding&lt;/td>
&lt;td>text-embedding-3-small 或 bge-large-zh&lt;/td>
&lt;td>成熟稳定，效果好&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>检索&lt;/td>
&lt;td>纯向量检索，top_k=5&lt;/td>
&lt;td>先跑通，再优化&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Prompt&lt;/td>
&lt;td>明确约束 + 引用要求&lt;/td>
&lt;td>确保答案有依据&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Inspect&lt;/td>
&lt;td>保留每次检索的候选列表&lt;/td>
&lt;td>方便定位问题&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;h3 id="第一版容易犯的错误">第一版容易犯的错误
&lt;/h3>&lt;p>&lt;strong>错误 1：上来就全量导入&lt;/strong>&lt;/p>
&lt;p>把公司所有文档都丢进去，结果检索噪声巨大，答案质量很差。&lt;/p>
&lt;p>正确做法：先导入核心文档（100-200 篇），验证效果，再逐步扩展。&lt;/p>
&lt;p>&lt;strong>错误 2：忽视元数据&lt;/strong>&lt;/p>
&lt;p>只存内容和向量，其他什么都不留。后面想做过滤、引用、增量更新，发现数据不够。&lt;/p>
&lt;p>正确做法：一开始就设计好元数据结构，chunk 要保留来源、章节、时间等信息。&lt;/p>
&lt;p>&lt;strong>错误 3：不做评测&lt;/strong>&lt;/p>
&lt;p>优化全靠&amp;quot;感觉&amp;quot;，没有客观标准。&lt;/p>
&lt;p>正确做法：准备 20-50 个测试问题，固定评测，每次改动都要看指标变化。&lt;/p>
&lt;p>&lt;strong>错误 4：没有 Inspect 能力&lt;/strong>&lt;/p>
&lt;p>答案错了，不知道是检索错还是生成错。&lt;/p>
&lt;p>正确做法：保留每次请求的中间结果（候选列表、分数、prompt），方便排查。&lt;/p>
&lt;h2 id="八如何判断系统准备好了">八、如何判断系统准备好了
&lt;/h2>&lt;p>在上线前，问自己这些问题：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">□ 能回答核心问题吗？
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> └── 准备 20 个关键问题，正确率 &amp;gt; 70%
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">□ 能解释答案来源吗？
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> └── 每个答案都有引用，用户能追溯
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">□ 答案错了能定位吗？
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> └── 有 Inspect 页面，能看到检索结果
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">□ 知道下一步优化什么吗？
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> └── 有评测指标，知道哪类问题表现差
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">□ 文档更新后能同步吗？
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> └── 有增量更新机制，不是全量重建
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>如果这些问题都回答不了，说明系统还不成熟，需要继续打磨。&lt;/p>
&lt;h2 id="九下一篇文章讲什么">九、下一篇文章讲什么
&lt;/h2>&lt;p>这篇文章讲了 RAG 的基础：&lt;/p>
&lt;ul>
&lt;li>Embedding 的原理和选择&lt;/li>
&lt;li>向量检索的算法和局限&lt;/li>
&lt;li>三阶段架构的关键决策&lt;/li>
&lt;/ul>
&lt;p>下一篇会深入讲：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Chunking 的策略&lt;/strong>：怎么切才能保留语义完整性&lt;/li>
&lt;li>&lt;strong>混合检索&lt;/strong>：BM25 算法原理、RRF 融合公式&lt;/li>
&lt;li>&lt;strong>Rerank&lt;/strong>：Cross-Encoder 的工作原理、为什么比向量检索更准&lt;/li>
&lt;/ul>
&lt;p>这些是 RAG 质量提升的关键，也是最容易踩坑的地方。&lt;/p></description></item><item><title>vector-database</title><link>https://www.zata.cc/p/vector-database/</link><pubDate>Thu, 20 Nov 2025 16:41:12 +0800</pubDate><guid>https://www.zata.cc/p/vector-database/</guid><description>&lt;img src="https://www.zata.cc/p/vector-database/images/index/index.png" alt="Featured image of post vector-database" />&lt;h1 id="向量数据库的教程">向量数据库的教程
&lt;/h1>&lt;p>&lt;a class="link" href="https://www.pinecone.io/learn/vector-database/" target="_blank" rel="noopener"
>松果&lt;/a>&lt;/p>
&lt;h1 id="ai-的海马体选型指南2026-年主流向量数据库全景对比">AI 的“海马体”选型指南：2026 年主流向量数据库全景对比
&lt;/h1>&lt;blockquote>
&lt;p>&lt;strong>摘要&lt;/strong>：在构建 RAG（检索增强生成）和 AI Agent 系统时，向量数据库（Vector Database）充当着“长期记忆”的关键角色。面对市场上琳琅满目的选项——Milvus、Qdrant、Weaviate、pgvector……架构师该如何抉择？本文将从架构轻重、运维成本和适用场景三个维度，为您深度解析主流向量数据库的优劣势。&lt;/p>
&lt;/blockquote>
&lt;p>在构建 RAG（检索增强生成）和 AI Agent 系统时，向量数据库（Vector Database）充当着“长期记忆”的关键角色。通过将文本、图片等非结构化数据转化为高维向量（Embeddings），向量数据库允许我们进行语义搜索。当用户问“还是老规矩”时，系统能从数据库中检索出该用户过去的行为偏好，而不是仅仅匹配关键词。但在 2026 年的今天，选型变得异常困难：是追求极致性能？还是追求开发效率？&lt;/p>
&lt;p>我们来聊聊主流的向量数据库，它们各有特色，也有不同的适用场景。以下是第一梯队的原生向量数据库：&lt;/p>
&lt;p>Milvus 是全球最流行的开源向量数据库之一，由 Zilliz 主导。&lt;/p>
&lt;ul>
&lt;li>
&lt;p>&lt;strong>核心定位&lt;/strong>：企业级、云原生、追求极致扩展性。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>✅ 核心优势&lt;/strong>：&lt;/p>
&lt;/li>
&lt;li>
&lt;p>十亿级吞吐：分布式架构极其成熟，轻松支撑 Billion-scale（十亿级）向量存储。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>生态完善：国内社区最活跃，文档支持最好，索引类型（HNSW, IVF 等）丰富。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>❌ 潜在短板&lt;/strong>：&lt;/p>
&lt;/li>
&lt;li>
&lt;p>架构极重：标准版部署依赖 Etcd（元数据）、MinIO（存储）和 Pulsar/Kafka（消息流）。在 &lt;code>docker-compose&lt;/code> 中启动它需要拉起 4-5 个容器，运维成本高。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>资源门槛：起步资源要求较高，不适合 MVP（最小可行性产品）阶段的小微项目。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>适用场景&lt;/strong>：大型互联网应用、核心推荐系统、海量知识库。&lt;/p>
&lt;/li>
&lt;/ul>
&lt;p>Qdrant 是用 Rust 编写的新一代向量数据库，也是许多 Agent 框架的首选。&lt;/p>
&lt;ul>
&lt;li>
&lt;p>&lt;strong>核心定位&lt;/strong>：兼顾高性能与易用性，开发者友好。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>✅ 核心优势&lt;/strong>：&lt;/p>
&lt;/li>
&lt;li>
&lt;p>架构轻量：Rust 编写，内存安全且高效。部署只需&lt;strong>一个 Docker 容器&lt;/strong>，无外部依赖。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>元数据过滤：它的 Payload Filtering 机制非常强大，支持像 MongoDB 一样灵活地过滤 JSON 字段（例如：“查找昨天情绪为‘开心’的对话记忆”）。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>本地模式：支持 Python 本地内存模式，开发阶段甚至无需启动服务。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>❌ 潜在短板&lt;/strong>：&lt;/p>
&lt;/li>
&lt;li>
&lt;p>超大规模验证：虽然支持分布式，但在百亿级规模的公开案例略少于 Milvus。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>适用场景&lt;/strong>：AI Agent 记忆系统、中大型 RAG 应用、快速迭代的初创项目。&lt;/p>
&lt;/li>
&lt;/ul>
&lt;p>Weaviate 是一款 AI 原生的向量数据库，具有模块化特性。&lt;/p>
&lt;ul>
&lt;li>
&lt;p>&lt;strong>核心定位&lt;/strong>：模块化、对象存储风格。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>✅ 核心优势&lt;/strong>：&lt;/p>
&lt;/li>
&lt;li>
&lt;p>内置模块：可直接集成 OpenAI/HuggingFace 模型，自动处理 Embedding，无需应用层操心。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>混合搜索：原生支持 BM25（关键词）+ 向量的混合检索，搜索效果通常优于纯向量。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>❌ 潜在短板&lt;/strong>：&lt;/p>
&lt;/li>
&lt;li>
&lt;p>学习曲线：使用 GraphQL 接口查询，Schema 定义方式独特，需要一定的上手时间。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>适用场景&lt;/strong>：Notebook 演示、Hackathon、个人知识库。&lt;/p>
&lt;/li>
&lt;/ul>
&lt;p>Chroma 是一款面向 Python 全栈开发者的向量数据库，设计极简。&lt;/p>
&lt;ul>
&lt;li>
&lt;p>&lt;strong>核心定位&lt;/strong>：面向 Python 全栈开发者，极简。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>✅ 核心优势&lt;/strong>：&lt;/p>
&lt;/li>
&lt;li>
&lt;p>上手最快：&lt;code>pip install chromadb&lt;/code> 即可，API 设计极其简单。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>❌ 潜在短板&lt;/strong>：&lt;/p>
&lt;/li>
&lt;li>
&lt;p>性能瓶颈：在大规模并发和分布式能力上弱于上述三位“老大哥”。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>适用场景&lt;/strong>：Notebook 演示、Hackathon、个人知识库。&lt;/p>
&lt;/li>
&lt;/ul>
&lt;p>如果你的技术栈已经很成熟，不想引入新的组件，传统数据库的扩展方案可能是更好的选择。&lt;/p>
&lt;p>PostgreSQL (pgvector) 是全球最强开源关系型数据库的插件。&lt;/p>
&lt;ul>
&lt;li>&lt;strong>定位&lt;/strong>：全球最强开源关系型数据库的插件。&lt;/li>
&lt;li>&lt;strong>优点&lt;/strong>：&lt;strong>单一数据源&lt;/strong>。无需在 SQL 数据库和向量库之间同步数据，完美支持 ACID 事务。如果你的数据量在百万级以内，直接开插件是最省事的选择。&lt;/li>
&lt;li>&lt;strong>缺点&lt;/strong>：在高并发 QPS 或超高维向量场景下，索引构建速度和查询延迟不如原生向量库。&lt;/li>
&lt;/ul>
&lt;p>Elasticsearch (ES) / OpenSearch 是全文检索的王者。&lt;/p>
&lt;ul>
&lt;li>&lt;strong>定位&lt;/strong>：全文检索的王者。&lt;/li>
&lt;li>&lt;strong>优点&lt;/strong>：如果你需要极强的&lt;strong>关键词搜索&lt;/strong>（BM25）能力，向量检索只是辅助，ES 是不二之选。&lt;/li>
&lt;li>&lt;strong>缺点&lt;/strong>：Java 编写，资源消耗大（内存吞噬者），纯向量检索性能并非顶尖。&lt;/li>
&lt;/ul>
&lt;p>Redis (RediSearch) 是一款基于内存的数据库。&lt;/p>
&lt;ul>
&lt;li>&lt;strong>定位&lt;/strong>：基于内存的极致速度。&lt;/li>
&lt;li>&lt;strong>优点&lt;/strong>：亚毫秒级延迟，适合高频读写的&lt;strong>短期记忆&lt;/strong>缓存。&lt;/li>
&lt;li>&lt;strong>缺点&lt;/strong>：内存昂贵，存储海量历史数据成本太高。&lt;/li>
&lt;/ul>
&lt;p>为了方便大家决策，我整理了这份决策矩阵：&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>你的需求场景&lt;/th>
&lt;th>推荐选型&lt;/th>
&lt;th>核心理由&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;strong>MVP 原型 / 个人开发&lt;/strong>&lt;/td>
&lt;td>&lt;strong>Chroma&lt;/strong> / &lt;strong>Qdrant (Local)&lt;/strong>&lt;/td>
&lt;td>代码量最少，环境搭建最快。&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>中型生产环境 / 追求性价比&lt;/strong>&lt;/td>
&lt;td>&lt;strong>Qdrant&lt;/strong>&lt;/td>
&lt;td>&lt;strong>Rust 高效、Docker 单容器部署、Filter 语法极佳。&lt;/strong> (这也是我们 V1.2 架构的首选)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>超大规模 / 十亿级数据&lt;/strong>&lt;/td>
&lt;td>&lt;strong>Milvus&lt;/strong>&lt;/td>
&lt;td>经过大规模验证的分布式能力，Zilliz 企业级支持。&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>已有 Postgres 且数据量不大&lt;/strong>&lt;/td>
&lt;td>&lt;strong>pgvector&lt;/strong>&lt;/td>
&lt;td>架构最简单，不用维护新数据库。&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>强依赖关键词搜索&lt;/strong>&lt;/td>
&lt;td>&lt;strong>Elasticsearch&lt;/strong>&lt;/td>
&lt;td>混合检索生态最强。&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>没有“最好”的数据库，只有“最适合”的架构。&lt;/p>
&lt;p>在我们的**“变色龙”进化型对话系统&lt;strong>项目中，我们最终选择了 &lt;strong>Qdrant&lt;/strong>。原因很简单：在快速迭代阶段，我们需要一个&lt;/strong>部署足够轻量**（不拖累 Docker Compose）、&lt;strong>查询足够灵活&lt;/strong>（处理复杂的记忆元数据）且&lt;strong>性能足够强&lt;/strong>的组件。Qdrant 完美击中了这三个甜点。&lt;/p>
&lt;h1 id="tutorial">tutorial
&lt;/h1>&lt;h2 id="milvus">Milvus
&lt;/h2>&lt;p>Milvus 专为大规模向量数据的存储、索引和搜索而设计，常用于构建 RAG（检索增强生成）、图像检索、推荐系统等应用。&lt;/p>
&lt;p>以下教程将涵盖：&lt;strong>环境部署&lt;/strong>、&lt;strong>核心概念&lt;/strong>、以及&lt;strong>完整的 Python 代码实战&lt;/strong>。&lt;/p>
&lt;hr>
&lt;p>1. 什么是 Milvus？&lt;/p>
&lt;p>Milvus 是一款云原生的向量数据库。它的核心作用是存储“向量”（Embeddings，即由 AI 模型生成的浮点数数组），并利用算法快速找出与查询向量“最相似”的数据。&lt;/p>
&lt;ul>
&lt;li>&lt;strong>非结构化数据处理：&lt;/strong> 它可以让图片、视频、文本等非结构化数据通过向量化后进行语义搜索。&lt;/li>
&lt;li>&lt;strong>高性能：&lt;/strong> 支持十亿级向量规模的毫秒级搜索。&lt;/li>
&lt;/ul>
&lt;hr>
&lt;p>2. 环境部署 (基于 Docker)&lt;/p>
&lt;p>使用 Docker 是运行 Milvus 最简单、最标准的方式。&lt;/p>
&lt;p>第一步：下载 Docker Compose 文件&lt;/p>
&lt;p>在你的终端中执行以下命令，下载官方的单机版配置文件：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">curl -L https://github.com/milvus-io/milvus/releases/download/v2.4.0/milvus-standalone-docker-compose.yml -o docker-compose.yml
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>第二步：启动 Milvus&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">docker-compose up -d
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>启动后，Milvus 会暴露以下端口：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>19530&lt;/strong>: gRPC 端口（主要用于代码连接）。&lt;/li>
&lt;li>&lt;strong>9091&lt;/strong>: 管理端口。&lt;/li>
&lt;/ul>
&lt;blockquote>
&lt;p>&lt;strong>推荐工具：&lt;/strong> 建议安装 &lt;strong>Attu&lt;/strong> (Milvus 的官方可视化管理界面)，可以通过 Docker 一并安装，方便查看数据。&lt;/p>
&lt;/blockquote>
&lt;p>docker run -p 8000:3000 -e MILVUS_URL=localhost:19530 zilliz/attu:v2.6&lt;/p>
&lt;p>&lt;a class="link" href="https://milvus.io/docs/zh/quickstart_with_attu.md" target="_blank" rel="noopener"
>桌面端安装&lt;/a>&lt;/p>
&lt;p>如果你是使用docker 容器安装在mac或者windows的docker desktop里面，你可以使用host.docker.internal:19530连接&lt;/p>
&lt;p>&lt;img src="https://www.zata.cc/p/vector-database/images/index/image.png"
width="1912"
height="1004"
srcset="https://www.zata.cc/p/vector-database/images/index/image_hu17960773405178075392.png 480w, https://www.zata.cc/p/vector-database/images/index/image_hu14563518848814044501.png 1024w"
loading="lazy"
alt="端口地址"
class="gallery-image"
data-flex-grow="190"
data-flex-basis="457px"
>&lt;/p>
&lt;hr>
&lt;p>3. Python 代码实战&lt;/p>
&lt;p>我们将使用 Python SDK (&lt;code>pymilvus&lt;/code>) 来完成从连接、建表、插入数据到相似度搜索的全过程。&lt;/p>
&lt;p>0. 安装 SDK&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">uv add pymilvus
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>1. 完整代码示例&lt;/p>
&lt;p>这个脚本模拟了一个场景：我们有 10,000 条文本数据（已转化为向量），我们要找出与某条查询最相似的 3 条数据。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">random&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">time&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">numpy&lt;/span> &lt;span class="k">as&lt;/span> &lt;span class="nn">np&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">pymilvus&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">connections&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">utility&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">FieldSchema&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">CollectionSchema&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">DataType&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">Collection&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># ==========================================&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 1. 连接 Milvus&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># ==========================================&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;&amp;gt;&amp;gt;&amp;gt; 正在连接 Milvus...&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">connections&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">connect&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;default&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">host&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;host.docker.internal&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">port&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;19530&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;&amp;gt;&amp;gt;&amp;gt; 连接成功！&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># ==========================================&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 2. 定义集合 (Schema)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># ==========================================&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 类似于关系型数据库中的“表”&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">collection_name&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;tutorial_demo&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">dim&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">128&lt;/span> &lt;span class="c1"># 向量维度 (根据你的 Embedding 模型决定，如 OpenAI text-embedding-3 是 1536)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 如果集合已存在，先删除（为了演示方便）&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">if&lt;/span> &lt;span class="n">utility&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">has_collection&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">collection_name&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">utility&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">drop_collection&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">collection_name&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 定义字段&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">fields&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># 主键 ID (自动增长)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">FieldSchema&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">name&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;id&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">dtype&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">DataType&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">INT64&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">is_primary&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="kc">True&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">auto_id&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="kc">True&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># 向量字段 (FLOAT_VECTOR)，必须指定维度&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">FieldSchema&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">name&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;embeddings&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">dtype&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">DataType&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">FLOAT_VECTOR&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">dim&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">dim&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># 元数据字段 (例如文章标题、日期等)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">FieldSchema&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">name&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;title&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">dtype&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">DataType&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">VARCHAR&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">max_length&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="mi">200&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">FieldSchema&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">name&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;count&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">dtype&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">DataType&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">INT64&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">schema&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">CollectionSchema&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">fields&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">description&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;Milvus 基础教程演示&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">collection&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">Collection&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">name&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">collection_name&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">schema&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">schema&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;&amp;gt;&amp;gt;&amp;gt; 集合 &amp;#39;&lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">collection_name&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#39; 创建成功&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># ==========================================&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 3. 插入数据 (Insert)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># ==========================================&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">num_entities&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">3000&lt;/span> &lt;span class="c1"># 插入 3000 条数据&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 生成随机向量数据模拟 Embeddings&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">rng&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">np&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">random&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">default_rng&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">seed&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="mi">19530&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">vectors&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">rng&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">random&lt;/span>&lt;span class="p">((&lt;/span>&lt;span class="n">num_entities&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">dim&lt;/span>&lt;span class="p">),&lt;/span> &lt;span class="n">dtype&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">np&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">float32&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="c1"># 生成随机浮点数&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 生成一些元数据&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">titles&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;Article_&lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">i&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span> &lt;span class="k">for&lt;/span> &lt;span class="n">i&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="nb">range&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">num_entities&lt;/span>&lt;span class="p">)]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">counts&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="n">i&lt;/span> &lt;span class="k">for&lt;/span> &lt;span class="n">i&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="nb">range&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">num_entities&lt;/span>&lt;span class="p">)]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 组织数据 (列表的顺序必须与 Schema 定义的顺序一致，排除 auto_id 的主键)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 注意：Milvus 插入数据是列式存储格式 [Column1_List, Column2_List, ...]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">data&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">vectors&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="c1"># 对应 embeddings&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">titles&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="c1"># 对应 title&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">counts&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="c1"># 对应 count&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">insert_result&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">collection&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">insert&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">data&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 即使插入了，数据还在内存缓冲区，需要 flush 到磁盘才能确保立即可见（生产环境通常不需要手动频繁 flush）&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">collection&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">flush&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;&amp;gt;&amp;gt;&amp;gt; 已插入 &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">insert_result&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">insert_count&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2"> 条数据&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># ==========================================&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 4. 创建索引 (Indexing)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># ==========================================&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 向量搜索如果是暴力搜索 (Flat) 会很慢，必须建立索引。&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># IVF_FLAT 是一种基于倒排文件的常见索引。&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">index_params&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;metric_type&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;L2&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="c1"># 距离度量：L2 (欧氏距离) 或 IP (内积/余弦相似度)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;index_type&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;IVF_FLAT&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="c1"># 索引类型&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;params&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">{&lt;/span>&lt;span class="s2">&amp;#34;nlist&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">128&lt;/span>&lt;span class="p">},&lt;/span> &lt;span class="c1"># nlist 是聚类中心的数量&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;&amp;gt;&amp;gt;&amp;gt; 正在构建索引...&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">collection&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">create_index&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">field_name&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;embeddings&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">index_params&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">index_params&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;&amp;gt;&amp;gt;&amp;gt; 索引构建完成&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># ==========================================&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 5. 加载集合 (Load)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># ==========================================&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># *关键步骤*：Milvus 必须将集合加载到内存中才能进行搜索&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">collection&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">load&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># ==========================================&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 6. 向量搜索 (Search)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># ==========================================&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 模拟一个查询向量&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">search_vectors&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">rng&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">random&lt;/span>&lt;span class="p">((&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">dim&lt;/span>&lt;span class="p">),&lt;/span> &lt;span class="n">dtype&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">np&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">float32&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 搜索参数&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">search_params&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;metric_type&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;L2&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;params&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">{&lt;/span>&lt;span class="s2">&amp;#34;nprobe&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">10&lt;/span>&lt;span class="p">},&lt;/span> &lt;span class="c1"># nprobe: 在多少个聚类中心里搜索，值越大越准但越慢&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;&amp;gt;&amp;gt;&amp;gt; 开始搜索...&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">start_time&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">time&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">time&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">results&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">collection&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">search&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">data&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">search_vectors&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="c1"># 查询向量&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">anns_field&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;embeddings&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="c1"># 在哪个字段搜索&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">param&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">search_params&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="c1"># 搜索参数&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">limit&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="mi">3&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="c1"># Top K：返回最相似的 3 个&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">output_fields&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;title&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;count&amp;#34;&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="c1"># 同时返回这些元数据字段&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">end_time&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">time&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">time&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># ==========================================&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 7. 解析结果&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># ==========================================&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;&amp;gt;&amp;gt;&amp;gt; 搜索耗时: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">end_time&lt;/span> &lt;span class="o">-&lt;/span> &lt;span class="n">start_time&lt;/span>&lt;span class="si">:&lt;/span>&lt;span class="s2">.4f&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2"> 秒&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;-&amp;#34;&lt;/span> &lt;span class="o">*&lt;/span> &lt;span class="mi">20&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">for&lt;/span> &lt;span class="n">hits&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">results&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="n">hit&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">hits&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># hit.id 是主键, hit.distance 是距离, hit.entity.get() 获取元数据&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;ID: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">hit&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">id&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">, Distance: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">hit&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">distance&lt;/span>&lt;span class="si">:&lt;/span>&lt;span class="s2">.4f&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">, Title: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">hit&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">entity&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">get&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;title&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># ==========================================&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 8. 清理 (可选)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># ==========================================&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># collection.drop()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># connections.disconnect(&amp;#34;default&amp;#34;)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;img src="https://www.zata.cc/p/vector-database/images/index/image-1.png"
width="529"
height="217"
srcset="https://www.zata.cc/p/vector-database/images/index/image-1_hu9517740438945300126.png 480w, https://www.zata.cc/p/vector-database/images/index/image-1_hu9772564493831020652.png 1024w"
loading="lazy"
alt="代码运行结果"
class="gallery-image"
data-flex-grow="243"
data-flex-basis="585px"
>&lt;/p>
&lt;p>&lt;img src="https://www.zata.cc/p/vector-database/images/index/image-2.png"
width="1912"
height="1003"
srcset="https://www.zata.cc/p/vector-database/images/index/image-2_hu9726437583620320690.png 480w, https://www.zata.cc/p/vector-database/images/index/image-2_hu7528629460368888339.png 1024w"
loading="lazy"
alt="数据库内容"
class="gallery-image"
data-flex-grow="190"
data-flex-basis="457px"
>&lt;/p>
&lt;hr>
&lt;p>4. 关键概念详解&lt;/p>
&lt;p>为了用好 Milvus，你需要理解以下几个核心参数：&lt;/p>
&lt;p>A. Metric Type (距离度量)&lt;/p>
&lt;p>这是衡量两个向量“相似度”的标准，必须在建立索引和搜索时保持一致。&lt;/p>
&lt;ul>
&lt;li>&lt;strong>L2 (欧氏距离):&lt;/strong> 也就是几何距离。值&lt;strong>越小&lt;/strong>表示越相似。适用于大多数场景。&lt;/li>
&lt;li>&lt;strong>IP (内积):&lt;/strong> 如果向量已归一化，它等同于余弦相似度。值&lt;strong>越大&lt;/strong>表示越相似。适用于文本语义搜索。&lt;/li>
&lt;/ul>
&lt;p>B. Index Type (索引类型)&lt;/p>
&lt;ul>
&lt;li>&lt;strong>FLAT:&lt;/strong> 暴力搜索，准确率 100%，但速度最慢。仅适用于少量数据 (&amp;lt;1万)。&lt;/li>
&lt;li>&lt;strong>IVF_FLAT / IVF_SQ8:&lt;/strong> 基于倒排聚类。速度快，但在召回率上有一点点损失。最常用。&lt;/li>
&lt;li>&lt;strong>HNSW:&lt;/strong> 基于图的索引。性能极高，是目前最流行的索引算法，但内存占用比 IVF 大。&lt;/li>
&lt;/ul>
&lt;p>C. Consistency Level (一致性级别)&lt;/p>
&lt;p>Milvus 是分布式的，搜索时可以指定一致性：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Strong:&lt;/strong> 保证刚插入的数据立马能搜到（性能最慢）。&lt;/li>
&lt;li>&lt;strong>Bounded:&lt;/strong> 允许几秒钟的延迟（性能最好，默认推荐）。&lt;/li>
&lt;/ul>
&lt;hr>
&lt;p>5. 进阶：结合 RAG 的应用流程&lt;/p>
&lt;p>如果你在做 AI 应用，Milvus 的工作流通常是这样的：&lt;/p>
&lt;ol>
&lt;li>&lt;strong>Embedding:&lt;/strong> 用户输入问题 -&amp;gt; 调用 OpenAI/HuggingFace API -&amp;gt; 获得向量 &lt;code>[0.1, 0.5, ...]&lt;/code>.&lt;/li>
&lt;li>&lt;strong>Search:&lt;/strong> 将这个向量传给 Milvus (&lt;code>collection.search&lt;/code>)。&lt;/li>
&lt;li>&lt;strong>Context:&lt;/strong> Milvus 返回 Top 3 相似的文本片段 (通过 &lt;code>output_fields&lt;/code> 返回文本内容)。&lt;/li>
&lt;li>&lt;strong>Generation:&lt;/strong> 将 &amp;ldquo;用户问题 + Milvus 返回的文本片段&amp;rdquo; 拼成 Prompt，发给 ChatGPT。&lt;/li>
&lt;li>&lt;strong>Answer:&lt;/strong> ChatGPT 根据参考资料回答问题。&lt;/li>
&lt;/ol>
&lt;hr>
&lt;p>6. 常见问题 (FAQ)&lt;/p>
&lt;ol>
&lt;li>&lt;strong>为什么搜索报错 &lt;code>Collection not loaded&lt;/code>？&lt;/strong>
&lt;ul>
&lt;li>Milvus 为了性能，搜索前必须显式调用 &lt;code>collection.load()&lt;/code> 将数据加载到内存。&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;strong>向量维度不匹配？&lt;/strong>
&lt;ul>
&lt;li>创建集合时的 &lt;code>dim&lt;/code> 必须与你的 Embedding 模型输出维度完全一致（例如 BERT 是 768，OpenAI &lt;code>text-embedding-3-small&lt;/code> 是 1536）。&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;strong>如何删除数据？&lt;/strong>
&lt;ul>
&lt;li>使用 &lt;code>collection.delete(&amp;quot;id in [1, 2]&amp;quot;)&lt;/code>。注意，删除操作是软删除，物理空间不会立即释放。&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ol>
&lt;hr>
&lt;p>以下是一个非常实用的企业级开发场景。&lt;strong>阿里云百炼 (DashScope)&lt;/strong> 提供了强大的通义千问 (Qwen) 大模型和文本向量化服务，结合 &lt;strong>Milvus&lt;/strong> 的存储检索能力，可以快速搭建一个私有知识库问答系统。&lt;/p>
&lt;p>以下是完整的 &lt;strong>Python 代码示例&lt;/strong>，涵盖了从“文本向量化”到“存入 Milvus”，再到“检索并生成回答”的全流程。&lt;/p>
&lt;p>&lt;strong>前置准备&lt;/strong>&lt;/p>
&lt;ol>
&lt;li>&lt;strong>获取 API Key&lt;/strong>：你需要去 &lt;a class="link" href="https://bailian.console.aliyun.com/" target="_blank" rel="noopener"
>阿里云百炼控制台&lt;/a> 开通服务并获取 API Key。&lt;/li>
&lt;li>&lt;strong>安装依赖库&lt;/strong>：
你需要安装阿里云的官方 SDK &lt;code>dashscope&lt;/code> 和 Milvus SDK &lt;code>pymilvus&lt;/code>。&lt;/li>
&lt;/ol>
&lt;!-- end list -->
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">pip install dashscope pymilvus
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;hr>
&lt;p>&lt;strong>完整代码示例&lt;/strong>&lt;/p>
&lt;p>新建一个 Python 文件（例如 &lt;code>rag_bailian_milvus.py&lt;/code>），填入你的 API Key 即可运行。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">dashscope&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">dashscope&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">Generation&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">pymilvus&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">connections&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">utility&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">FieldSchema&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">CollectionSchema&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">DataType&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">Collection&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">http&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">HTTPStatus&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># ==========================================&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 0. 配置部分&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># ==========================================&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 【重要】请替换为你的阿里云百炼 API Key&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">dashscope&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">api_key&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxx&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Milvus 配置&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">MILVUS_HOST&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;localhost&amp;#34;&lt;/span> &lt;span class="c1"># 如果在 Docker 内运行且用 host 模式，或者是本地运行&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">MILVUS_PORT&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;19530&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 模型配置&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">EMBEDDING_MODEL&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;text-embedding-v2&amp;#34;&lt;/span> &lt;span class="c1"># 阿里云通用文本向量模型&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">LLM_MODEL&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;qwen-turbo&amp;#34;&lt;/span> &lt;span class="c1"># 通义千问-Turbo (性价比高)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">VECTOR_DIM&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">1536&lt;/span> &lt;span class="c1"># text-embedding-v2 的维度是 1536&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># ==========================================&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 1. 辅助函数：调用百炼 API&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># ==========================================&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">get_embedding&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">text&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;调用阿里云百炼 Embedding API 将文本转换为向量&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">resp&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">dashscope&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">TextEmbedding&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">call&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">model&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">EMBEDDING_MODEL&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">input&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">text&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">resp&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">status_code&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="n">HTTPStatus&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">OK&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># 获取向量数据&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">resp&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">output&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s1">&amp;#39;embeddings&amp;#39;&lt;/span>&lt;span class="p">][&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">][&lt;/span>&lt;span class="s1">&amp;#39;embedding&amp;#39;&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">else&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;Embedding API 报错: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">resp&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">raise&lt;/span> &lt;span class="ne">Exception&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;Failed to generate embedding&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">call_llm&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">prompt&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;调用通义千问生成回答&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">messages&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[{&lt;/span>&lt;span class="s1">&amp;#39;role&amp;#39;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s1">&amp;#39;user&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">&amp;#39;content&amp;#39;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">prompt&lt;/span>&lt;span class="p">}]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">resp&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">Generation&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">call&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">model&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">LLM_MODEL&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">messages&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">messages&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">result_format&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s1">&amp;#39;message&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="c1"># 设置返回格式为 message&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">resp&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">status_code&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="n">HTTPStatus&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">OK&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">resp&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">output&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">choices&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">][&lt;/span>&lt;span class="s1">&amp;#39;message&amp;#39;&lt;/span>&lt;span class="p">][&lt;/span>&lt;span class="s1">&amp;#39;content&amp;#39;&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">else&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;LLM API 报错: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">resp&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="s2">&amp;#34;抱歉，生成回答时出错了。&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># ==========================================&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 2. 初始化 Milvus 集合&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># ==========================================&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;&amp;gt;&amp;gt;&amp;gt; 连接 Milvus...&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">connections&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">connect&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;default&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">host&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">MILVUS_HOST&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">port&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">MILVUS_PORT&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">collection_name&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;bailian_rag_demo&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 如果存在旧集合则删除，保证每次运行都是干净的环境&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">if&lt;/span> &lt;span class="n">utility&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">has_collection&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">collection_name&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">utility&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">drop_collection&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">collection_name&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 定义 Schema&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">fields&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">FieldSchema&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">name&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;id&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">dtype&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">DataType&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">INT64&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">is_primary&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="kc">True&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">auto_id&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="kc">True&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">FieldSchema&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">name&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;vector&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">dtype&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">DataType&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">FLOAT_VECTOR&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">dim&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">VECTOR_DIM&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">FieldSchema&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">name&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;text&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">dtype&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">DataType&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">VARCHAR&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">max_length&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="mi">2048&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="c1"># 存储原始文本用于构建 Prompt&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">schema&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">CollectionSchema&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">fields&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">description&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;阿里云百炼 RAG 演示&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">collection&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">Collection&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">name&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">collection_name&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">schema&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">schema&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 创建索引 (IVF_FLAT 适合大多数场景)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">index_params&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;metric_type&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;L2&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="c1"># 欧氏距离 (阿里云 Embedding 推荐用 Cosine，但在归一化后 L2 效果也能接受，这里演示用 L2)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;index_type&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;IVF_FLAT&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;params&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">{&lt;/span>&lt;span class="s2">&amp;#34;nlist&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">1024&lt;/span>&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">collection&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">create_index&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">field_name&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;vector&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">index_params&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">index_params&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">collection&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">load&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="c1"># 加载到内存&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;&amp;gt;&amp;gt;&amp;gt; 集合 &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">collection_name&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2"> 准备就绪&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># ==========================================&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 3. 模拟私有数据并入库 (Ingestion)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># ==========================================&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 假设这是一些只有你知道，ChatGPT 此时此刻可能不知道的“私有知识”&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">knowledge_base&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;TransMaster 项目是一个由 Python 开发的高效翻译管理系统。&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;Zata 计划在 2025年9月 去温州旅行。&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;Milvus 是一款高性能的开源向量数据库，支持十亿级数据检索。&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;在 Python 中使用 Celery 可以轻松处理异步任务队列。&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;阿里云百炼是阿里巴巴推出的一站式大模型服务平台。&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;&amp;gt;&amp;gt;&amp;gt; 正在向量化并存入数据...&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">data_vectors&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">data_texts&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">for&lt;/span> &lt;span class="n">text&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">knowledge_base&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">vec&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">get_embedding&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">text&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">data_vectors&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">append&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">vec&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">data_texts&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">append&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">text&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 插入 Milvus&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">collection&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">insert&lt;/span>&lt;span class="p">([&lt;/span>&lt;span class="n">data_vectors&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">data_texts&lt;/span>&lt;span class="p">])&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">collection&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">flush&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;&amp;gt;&amp;gt;&amp;gt; 成功插入 &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="nb">len&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">knowledge_base&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2"> 条知识数据&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># ==========================================&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 4. RAG 核心流程：检索 + 生成&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># ==========================================&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">rag_chat&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">user_query&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="se">\n&lt;/span>&lt;span class="s2">[用户提问]: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">user_query&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># A. 检索 (Retrieval)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># 1. 把用户的问题变成向量&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">query_vector&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">get_embedding&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">user_query&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># 2. 在 Milvus 中搜最相似的 2 条&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">search_params&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span>&lt;span class="s2">&amp;#34;metric_type&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;L2&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;params&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">{&lt;/span>&lt;span class="s2">&amp;#34;nprobe&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">10&lt;/span>&lt;span class="p">}}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">results&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">collection&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">search&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">data&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">query_vector&lt;/span>&lt;span class="p">],&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">anns_field&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;vector&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">param&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">search_params&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">limit&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="mi">2&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="c1"># Top 2&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">output_fields&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;text&amp;#34;&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="c1"># 记得把原始文本拿回来&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># 3. 提取上下文&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">retrieved_texts&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="n">hits&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">results&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="n">hit&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">hits&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># print(f&amp;#34; (命中参考资料: {hit.entity.get(&amp;#39;text&amp;#39;)} - 距离: {hit.distance})&amp;#34;)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">retrieved_texts&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">append&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">hit&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">entity&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">get&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;text&amp;#34;&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">context_str&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="se">\n&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">join&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">retrieved_texts&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># B. 生成 (Generation)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># 4. 组装 Prompt&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">prompt&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"> 请根据以下参考资料回答用户的问题。如果参考资料中没有答案，请回答“我不知道”。
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"> 【参考资料】：
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"> &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">context_str&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"> 【用户问题】：
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"> &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">user_query&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"> &amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># 5. 调用大模型&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;&amp;gt;&amp;gt;&amp;gt; 正在思考...&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">answer&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">call_llm&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">prompt&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;[AI 回答]: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">answer&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># ==========================================&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 5. 测试运行&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># ==========================================&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 测试 1: 问一个库里有的私有知识&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">rag_chat&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;Zata 打算什么时候去温州？&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 测试 2: 问一个库里有的技术知识&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">rag_chat&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;TransMaster 是什么项目？&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 测试 3: 问一个库里没有的无关问题&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">rag_chat&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;西红柿炒鸡蛋怎么做？&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;img src="https://www.zata.cc/p/vector-database/images/index/image-3.png"
width="694"
height="256"
srcset="https://www.zata.cc/p/vector-database/images/index/image-3_hu11806571009225573423.png 480w, https://www.zata.cc/p/vector-database/images/index/image-3_hu7241470505063214243.png 1024w"
loading="lazy"
alt="运行示例"
class="gallery-image"
data-flex-grow="271"
data-flex-basis="650px"
>&lt;/p>
&lt;hr>
&lt;p>代码核心逻辑解析&lt;/p>
&lt;ol>
&lt;li>
&lt;p>&lt;strong>统一模型：&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>入库时：使用 &lt;code>text-embedding-v2&lt;/code> 将知识转为向量。&lt;/li>
&lt;li>搜索时：使用&lt;strong>同一个&lt;/strong> &lt;code>text-embedding-v2&lt;/code> 将用户问题转为向量。这是向量搜索准确的前提。&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>Schema 设计：&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>我们在 Milvus 中定义了 &lt;code>text&lt;/code> 字段。这是 RAG 的关键技巧：向量数据库不仅存向量，最好也存一份&lt;strong>原始文本&lt;/strong>。这样搜索到向量后，直接就能取出对应的文字喂给大模型，不需要再去查 MySQL。&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>Prompt 组装：&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>代码中的 &lt;code>prompt&lt;/code> 变量展示了经典的 RAG 提示词模板。强制要求 AI 基于【参考资料】回答，可以有效减少大模型的“幻觉”（胡说八道）。&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ol>
&lt;p>可能遇到的问题&lt;/p>
&lt;ol>
&lt;li>
&lt;p>&lt;strong>Dimension 错误：&lt;/strong>
如果你换了模型（比如换成了 &lt;code>text-embedding-v1&lt;/code>），请务必确认它的维度。&lt;code>v2&lt;/code> 是 1536 维，如果报错 &lt;code>dimension mismatch&lt;/code>，请检查 Milvus 建表时的 &lt;code>dim&lt;/code> 参数。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>DashScope API 报错：&lt;/strong>
如果提示 &lt;code>InvalidApiKey&lt;/code>，请检查环境变量或代码中 Key 是否复制完整。&lt;/p>
&lt;/li>
&lt;/ol>
&lt;p>下一步&lt;/p>
&lt;p>现在你已经跑通了最核心的流程。如果你想把这个应用到实际的 &lt;strong>TransMaster&lt;/strong> 项目中，下一步通常是**“文档切分”**：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>任务：&lt;/strong> 把长的 PDF 或 Markdown 文档切成 500 字左右的小块。&lt;/li>
&lt;li>&lt;strong>工具：&lt;/strong> 可以使用 &lt;code>LangChain&lt;/code> 的 &lt;code>RecursiveCharacterTextSplitter&lt;/code> 来做切分，然后再传给这个脚本入库。&lt;/li>
&lt;/ul>
&lt;hr>
&lt;p>演示如何用 Python 切分长文本&lt;/p>
&lt;p>在实际的 RAG 应用（比如你的 TransMaster 项目）中，直接把整篇几千字的文章扔给 Embedding 模型是不行的，因为：&lt;/p>
&lt;ol>
&lt;li>&lt;strong>模型限制&lt;/strong>：Embedding 模型通常有 Token 长度限制（比如 8192 token）。&lt;/li>
&lt;li>&lt;strong>搜索精度&lt;/strong>：如果一段文本太长，里面包含的信息太杂，搜索匹配的精准度会下降。&lt;/li>
&lt;/ol>
&lt;p>因此，我们需要&lt;strong>切分 (Chunking)&lt;/strong>。&lt;/p>
&lt;p>业界最常用的是 &lt;strong>LangChain&lt;/strong> 提供的 &lt;code>RecursiveCharacterTextSplitter&lt;/code>（递归字符文本分割器）。它很聪明，会优先在段落（&lt;code>\n\n&lt;/code>）处切分，如果不行再在换行（&lt;code>\n&lt;/code>）切，尽量保证句子的完整性。&lt;/p>
&lt;hr>
&lt;p>1. 安装必要的库&lt;/p>
&lt;p>为了轻量化，我们只安装 LangChain 的文本切分组件，不需要安装整个庞大的 LangChain。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">uv add langchain-text-splitters
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;hr>
&lt;p>2. 独立演示代码：长文本切分&lt;/p>
&lt;p>这个脚本演示了如何把一篇关于“TransMaster 项目开发规范”的长文档，切分成适合入库的小块。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">langchain_text_splitters&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">RecursiveCharacterTextSplitter&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 1. 模拟一个长文档 (假设这是你项目里的 README 或需求文档)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">long_text&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"># TransMaster 项目开发规范 V1.0
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2">## 1. 项目简介
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2">TransMaster 是一个基于 Python 的高效翻译管理系统，旨在解决多语言项目中的协同翻译痛点。
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2">它集成了 Redis 缓存、Celery 异步任务队列以及 Docker 容器化部署方案。
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2">后端主要使用 Flask/FastAPI 框架，数据库采用 PostgreSQL。
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2">## 2. 核心功能
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"> 2.1 自动翻译
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2">系统接入了百度翻译、DeepL 以及 Google Translate API。
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2">当用户上传文档时，后台会通过 Celery Worker 自动将文档解析并进行预翻译。
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2">为了防止 API 超时，所有网络请求都必须设置 timeout 参数，建议值为 10 秒。
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"> 2.2 术语库管理
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2">用户可以上传 TMX 格式的术语库。
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2">在翻译过程中，系统会优先匹配术语库中的词汇，确保专业名词的统一性。
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2">术语库的匹配算法采用了最长字符串匹配策略。
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2">## 3. 部署指南
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"> 3.1 Docker 环境
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2">请确保本地安装了 Docker 和 Docker Compose。
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2">运行 `docker-compose up -d` 即可启动所有服务，包括 Web、Redis 和 Postgres。
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2">数据库的默认端口映射为 5432，Redis 为 6379。
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"> 3.2 数据库迁移
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2">我们使用 Alembic 进行数据库版本管理。
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2">如果修改了 models.py，请务必执行 `alembic revision --autogenerate` 生成迁移脚本，
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2">然后执行 `alembic upgrade head` 应用变更。
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2">## 4. 常见问题
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2">Q: 为什么上传大文件会失败？
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2">A: 请检查 Nginx 配置中的 client_max_body_size 设置，默认限制为 1MB，建议调整为 50MB。
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 2. 初始化切分器&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">text_splitter&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">RecursiveCharacterTextSplitter&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># chunk_size: 每个块的目标大小（字符数）。&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># 设置为 100-500 之间通常比较适合做 Embedding。&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">chunk_size&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="mi">200&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># chunk_overlap: 重叠部分。&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># 这是一个关键技巧！让两个块之间有重叠，防止切分时把一句话切断，导致上下文丢失。&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">chunk_overlap&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="mi">50&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># separators: 切分优先级。先试着按双换行切，不行就按单换行，再不行按空格。&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">separators&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="se">\n\n&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="se">\n&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34; &amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;&amp;#34;&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 3. 执行切分&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">chunks&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">text_splitter&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">create_documents&lt;/span>&lt;span class="p">([&lt;/span>&lt;span class="n">long_text&lt;/span>&lt;span class="p">])&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 4. 打印结果&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;&amp;gt;&amp;gt;&amp;gt; 原始文本长度: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="nb">len&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">long_text&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;&amp;gt;&amp;gt;&amp;gt; 切分后的块数: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="nb">len&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">chunks&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="se">\n&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;--- 切分详情 ---&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">for&lt;/span> &lt;span class="n">i&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">chunk&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="nb">enumerate&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">chunks&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">content&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">chunk&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">page_content&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;[块 &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">i&lt;/span>&lt;span class="o">+&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">] (长度 &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="nb">len&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">content&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">):&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="se">\&amp;#34;&lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">content&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="se">\&amp;#34;&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="c1"># 打印内容&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;-&amp;#34;&lt;/span> &lt;span class="o">*&lt;/span> &lt;span class="mi">30&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>运行结果分析&lt;/p>
&lt;p>&lt;img src="https://www.zata.cc/p/vector-database/images/index/image-4.png"
width="702"
height="630"
srcset="https://www.zata.cc/p/vector-database/images/index/image-4_hu15591082956297286550.png 480w, https://www.zata.cc/p/vector-database/images/index/image-4_hu10616313382388845115.png 1024w"
loading="lazy"
alt="结果"
class="gallery-image"
data-flex-grow="111"
data-flex-basis="267px"
>&lt;/p>
&lt;p>你会发现，它不会在“TransMaster”这个词中间切开，而是尽量在段落结束的地方切开。同时，由于设置了 &lt;code>chunk_overlap=50&lt;/code>，你可以观察到&lt;strong>块1的结尾和块2的开头会有部分重复的内容&lt;/strong>。&lt;/p>
&lt;hr>
&lt;p>3. 如何集成到刚才的 RAG 代码中？&lt;/p>
&lt;p>把你刚才那个 &lt;code>rag_bailian_milvus.py&lt;/code> 的 &lt;strong>“3. 模拟私有数据并入库”&lt;/strong> 部分替换为下面的逻辑：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># ... (前面的代码保持不变) ...&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">langchain_text_splitters&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">RecursiveCharacterTextSplitter&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 假设这是从文件读取出来的超长内容&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">raw_content&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;......这里是很长的 TransMaster 项目文档......&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 1. 切分&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">splitter&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">RecursiveCharacterTextSplitter&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">chunk_size&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="mi">500&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">chunk_overlap&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="mi">100&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">split_docs&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">splitter&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">create_documents&lt;/span>&lt;span class="p">([&lt;/span>&lt;span class="n">raw_content&lt;/span>&lt;span class="p">])&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;&amp;gt;&amp;gt;&amp;gt; 文档已切分为 &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="nb">len&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">split_docs&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2"> 个片段，开始向量化...&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">data_vectors&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">data_texts&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 2. 遍历切分后的片段进行 Embedding&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">for&lt;/span> &lt;span class="n">doc&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">split_docs&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">chunk_text&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">doc&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">page_content&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># 调用百炼 API 获得向量&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">try&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">vec&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">get_embedding&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">chunk_text&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">data_vectors&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">append&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">vec&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">data_texts&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">append&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">chunk_text&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34; - 已处理片段: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">chunk_text&lt;/span>&lt;span class="p">[:&lt;/span>&lt;span class="mi">30&lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">...&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="c1"># 打印前30个字示意&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">except&lt;/span> &lt;span class="ne">Exception&lt;/span> &lt;span class="k">as&lt;/span> &lt;span class="n">e&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34; x 跳过出错片段: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">e&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 3. 批量插入 Milvus&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">if&lt;/span> &lt;span class="n">data_vectors&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">collection&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">insert&lt;/span>&lt;span class="p">([&lt;/span>&lt;span class="n">data_vectors&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">data_texts&lt;/span>&lt;span class="p">])&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">collection&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">flush&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;&amp;gt;&amp;gt;&amp;gt; 成功入库 &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="nb">len&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">data_vectors&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2"> 个知识片段！&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># ... (后面的搜索代码保持不变) ...&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;img src="https://www.zata.cc/p/vector-database/images/index/image-5.png"
width="708"
height="361"
srcset="https://www.zata.cc/p/vector-database/images/index/image-5_hu17783599375848062197.png 480w, https://www.zata.cc/p/vector-database/images/index/image-5_hu10161293550995157055.png 1024w"
loading="lazy"
alt="结果"
class="gallery-image"
data-flex-grow="196"
data-flex-basis="470px"
>&lt;/p>
&lt;p>总结：最佳实践参数&lt;/p>
&lt;p>对于中文技术文档（如你的 Python 后端开发文档），我推荐以下参数：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Model&lt;/strong>: 阿里云 &lt;code>text-embedding-v2&lt;/code>&lt;/li>
&lt;li>&lt;strong>Chunk Size&lt;/strong>: &lt;code>300 - 500&lt;/code> 字符 (中文含金量高，不需要太长)&lt;/li>
&lt;li>&lt;strong>Overlap&lt;/strong>: &lt;code>50 - 100&lt;/code> 字符 (保证上下文连续)&lt;/li>
&lt;li>&lt;strong>Separators&lt;/strong>: &lt;code>[&amp;quot;\n\n&amp;quot;, &amp;quot;\n&amp;quot;, &amp;quot;。&amp;quot;, &amp;quot;！&amp;quot;, &amp;quot;？&amp;quot;]&lt;/code> (优先按段落切，其次按句号切)&lt;/li>
&lt;/ul>
&lt;p>这样切分后存入 Milvus，当你问“TransMaster 用什么数据库？”时，RAG 就能精准定位到包含“PostgreSQL”的那一小段文字，而不是把整篇文档都拉出来。&lt;/p></description></item><item><title>LangSmith使用教程</title><link>https://www.zata.cc/p/langsmith%E4%BD%BF%E7%94%A8%E6%95%99%E7%A8%8B/</link><pubDate>Fri, 30 May 2025 16:15:40 +0800</pubDate><guid>https://www.zata.cc/p/langsmith%E4%BD%BF%E7%94%A8%E6%95%99%E7%A8%8B/</guid><description>&lt;img src="https://www.zata.cc/p/langsmith%E4%BD%BF%E7%94%A8%E6%95%99%E7%A8%8B/images/index/index.png" alt="Featured image of post LangSmith使用教程" />&lt;h1 id="langsmith使用教程">LangSmith使用教程
&lt;/h1>&lt;p>LangSmith 是 LangChain 官方的 LLM 应用观测平台：调试、追踪、评估、监控都在上面。LLM 应用最麻烦的一点是行为不透明——同一份提示，换个模型、换个参数、换个措辞，结果就可能完全不同，只靠 print 调试很快就会失控。我的判断是：追踪不是可选项，而是基础设施；在 LangChain 生态里，LangSmith 是接入成本最低的那个。&lt;/p>
&lt;p>本文是我 2025 年 5 月的上手记录（截图都是当时的实拍）；2026 年 9 月整合博客时修订了环境变量命名和评估 API，过时的旧写法在文中用注记标出。&lt;/p>
&lt;h2 id="核心概念">核心概念
&lt;/h2>&lt;table>
&lt;thead>
&lt;tr>
&lt;th>概念&lt;/th>
&lt;th>一句话说明&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>Tracing（追踪）&lt;/td>
&lt;td>记录应用每个组件的输入、输出、耗时、token，还原完整执行路径&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Debugging（调试）&lt;/td>
&lt;td>基于 trace 定位错误和非预期行为&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Evaluation（评估）&lt;/td>
&lt;td>用数据集 + 评估器量化应用表现，比较不同版本&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Datasets（数据集）&lt;/td>
&lt;td>评估用的输入/期望输出样本集合，可从生产 trace 提取&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Monitoring（监控）&lt;/td>
&lt;td>部署后持续追踪错误率、延迟、token 消耗&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Project（项目）&lt;/td>
&lt;td>trace 的归属单位，一个应用一个项目&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;h2 id="接入设置">接入设置
&lt;/h2>&lt;h3 id="账户与-api-key">账户与 API Key
&lt;/h3>&lt;ol>
&lt;li>到 &lt;a class="link" href="https://smith.langchain.com/" target="_blank" rel="noopener"
>smith.langchain.com&lt;/a> 注册，登录后创建组织（Organization）。&lt;/li>
&lt;/ol>
&lt;p>&lt;img src="https://www.zata.cc/p/langsmith%E4%BD%BF%E7%94%A8%E6%95%99%E7%A8%8B/images/index/image.png"
width="1912"
height="954"
srcset="https://www.zata.cc/p/langsmith%E4%BD%BF%E7%94%A8%E6%95%99%E7%A8%8B/images/index/image_hu16999795923421345222.png 480w, https://www.zata.cc/p/langsmith%E4%BD%BF%E7%94%A8%E6%95%99%E7%A8%8B/images/index/image_hu13664730940339820997.png 1024w"
loading="lazy"
alt="langsmit界面"
class="gallery-image"
data-flex-grow="200"
data-flex-basis="481px"
>&lt;/p>
&lt;ol start="2">
&lt;li>在组织的 Settings → API Keys 里创建密钥，妥善保管，不要公开分享。&lt;/li>
&lt;/ol>
&lt;p>&lt;img src="https://www.zata.cc/p/langsmith%E4%BD%BF%E7%94%A8%E6%95%99%E7%A8%8B/images/index/image-1.png"
width="1912"
height="954"
srcset="https://www.zata.cc/p/langsmith%E4%BD%BF%E7%94%A8%E6%95%99%E7%A8%8B/images/index/image-1_hu8868344347395063117.png 480w, https://www.zata.cc/p/langsmith%E4%BD%BF%E7%94%A8%E6%95%99%E7%A8%8B/images/index/image-1_hu18255957453875288326.png 1024w"
loading="lazy"
alt="settings"
class="gallery-image"
data-flex-grow="200"
data-flex-basis="481px"
>&lt;/p>
&lt;p>&lt;img src="https://www.zata.cc/p/langsmith%E4%BD%BF%E7%94%A8%E6%95%99%E7%A8%8B/images/index/image-2.png"
width="269"
height="954"
srcset="https://www.zata.cc/p/langsmith%E4%BD%BF%E7%94%A8%E6%95%99%E7%A8%8B/images/index/image-2_hu13006059943811164616.png 480w, https://www.zata.cc/p/langsmith%E4%BD%BF%E7%94%A8%E6%95%99%E7%A8%8B/images/index/image-2_hu588751695666657627.png 1024w"
loading="lazy"
alt="apikey"
class="gallery-image"
data-flex-grow="28"
data-flex-basis="67px"
>&lt;/p>
&lt;h3 id="安装-sdk">安装 SDK
&lt;/h3>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">pip install langsmith
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="环境变量">环境变量
&lt;/h3>&lt;blockquote>
&lt;p>2026-09 修订：官方现在的推荐前缀是 &lt;code>LANGSMITH_&lt;/code>。本文写作时用的还是 &lt;code>LANGCHAIN_&lt;/code> 旧前缀（下方注记），两者目前兼容，新项目直接用新写法。&lt;/p>
&lt;/blockquote>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">&lt;span class="nb">export&lt;/span> &lt;span class="nv">LANGSMITH_TRACING_ENABLED&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;true&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">export&lt;/span> &lt;span class="nv">LANGSMITH_ENDPOINT&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;https://api.smith.langchain.com&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">export&lt;/span> &lt;span class="nv">LANGSMITH_API_KEY&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;YOUR_LANGSMITH_API_KEY&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">export&lt;/span> &lt;span class="nv">LANGSMITH_PROJECT&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;YOUR_PROJECT_NAME&amp;#34;&lt;/span> &lt;span class="c1"># 可选，默认 &amp;#34;default&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;ul>
&lt;li>&lt;code>LANGSMITH_TRACING_ENABLED&lt;/code>：总开关，设为 true 后 LangChain / LangGraph 应用自动上报&lt;/li>
&lt;li>&lt;code>LANGSMITH_PROJECT&lt;/code>：trace 归属的项目名，可在界面上创建和管理&lt;/li>
&lt;/ul>
&lt;blockquote>
&lt;p>⚠️ 旧写法：&lt;code>LANGCHAIN_TRACING_V2=&amp;quot;true&amp;quot;&lt;/code> / &lt;code>LANGCHAIN_ENDPOINT&lt;/code> / &lt;code>LANGCHAIN_API_KEY&lt;/code> / &lt;code>LANGCHAIN_PROJECT&lt;/code>。仍兼容，但已弃用。&lt;/p>
&lt;/blockquote>
&lt;h2 id="追踪-langchain-应用">追踪 LangChain 应用
&lt;/h2>&lt;p>环境变量设好之后，LangChain 应用&lt;strong>零代码改动&lt;/strong>自动上报——这是它比手动埋点省事的核心原因。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">langchain_openai&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">ChatOpenAI&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">langchain_core.prompts&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">ChatPromptTemplate&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">langchain_core.output_parsers&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">StrOutputParser&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">os&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 模型：阿里云百炼的 OpenAI 兼容模式&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># （修订：原文这里误用了未导入的 Tongyi 类，统一改为博客其他文章一致的 ChatOpenAI 写法）&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">llm&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">ChatOpenAI&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">model&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;qwen-plus&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">api_key&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">os&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">getenv&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;DASHSCOPE_API_KEY&amp;#34;&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">base_url&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;https://dashscope.aliyuncs.com/compatible-mode/v1&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">prompt&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">ChatPromptTemplate&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">from_messages&lt;/span>&lt;span class="p">([&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;system&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;You are a helpful assistant that translates &lt;/span>&lt;span class="si">{input_language}&lt;/span>&lt;span class="s2"> to &lt;/span>&lt;span class="si">{output_language}&lt;/span>&lt;span class="s2">.&amp;#34;&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;human&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="si">{text}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">])&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">chain&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">prompt&lt;/span> &lt;span class="o">|&lt;/span> &lt;span class="n">llm&lt;/span> &lt;span class="o">|&lt;/span> &lt;span class="n">StrOutputParser&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">result&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">chain&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">invoke&lt;/span>&lt;span class="p">({&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;input_language&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;English&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;output_language&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;Chinese&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;text&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;Hello, how are you?&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">})&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">result&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>运行后登录 LangSmith，在对应项目下就能看到这次运行的追踪记录：链的每个步骤、输入、输出、耗时、token，以及可能发生的任何错误。&lt;/p>
&lt;p>&lt;img src="https://www.zata.cc/p/langsmith%E4%BD%BF%E7%94%A8%E6%95%99%E7%A8%8B/images/index/image-3.png"
width="1912"
height="954"
srcset="https://www.zata.cc/p/langsmith%E4%BD%BF%E7%94%A8%E6%95%99%E7%A8%8B/images/index/image-3_hu668023011181215320.png 480w, https://www.zata.cc/p/langsmith%E4%BD%BF%E7%94%A8%E6%95%99%E7%A8%8B/images/index/image-3_hu14328061733863575152.png 1024w"
loading="lazy"
alt="trace"
class="gallery-image"
data-flex-grow="200"
data-flex-basis="481px"
>&lt;img src="https://www.zata.cc/p/langsmith%E4%BD%BF%E7%94%A8%E6%95%99%E7%A8%8B/images/index/image-4.png"
width="1912"
height="954"
srcset="https://www.zata.cc/p/langsmith%E4%BD%BF%E7%94%A8%E6%95%99%E7%A8%8B/images/index/image-4_hu395046776489291782.png 480w, https://www.zata.cc/p/langsmith%E4%BD%BF%E7%94%A8%E6%95%99%E7%A8%8B/images/index/image-4_hu3300973140154413831.png 1024w"
loading="lazy"
alt="记录"
class="gallery-image"
data-flex-grow="200"
data-flex-basis="481px"
>&lt;/p>
&lt;h2 id="追踪任意函数traceable">追踪任意函数：@traceable
&lt;/h2>&lt;p>不在 LangChain 链里的函数，用 &lt;code>@traceable&lt;/code> 装饰器也能纳入追踪：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">langsmith&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">traceable&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nd">@traceable&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">name&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;My Custom Function&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="c1"># name 可选，用于在 UI 中显示&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">my_data_processing_function&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">data&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">data&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">upper&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nd">@traceable&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">my_llm_logic&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">user_input&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">str&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># 里面可以调 LLM、调其他被追踪函数，调用链会串在一条 trace 里&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">my_data_processing_function&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">user_input&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">output&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">my_llm_logic&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;This is some input text.&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">output&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>被装饰的函数每次调用都会生成一条 trace，和 LangChain 链的 trace 在同一个视图里查看。&lt;/p>
&lt;h2 id="从-callbacks-到-astream_events观测方式的变化">从 Callbacks 到 astream_events：观测方式的变化
&lt;/h2>&lt;blockquote>
&lt;p>本节为 2026-09 整合时补充。&lt;/p>
&lt;/blockquote>
&lt;p>LangChain 0.x 时代，想监听应用内部发生的事，要写 CallbackHandler——继承 &lt;code>BaseCallbackHandler&lt;/code>，实现 &lt;code>on_llm_start&lt;/code> / &lt;code>on_llm_new_token&lt;/code> 等一串回调。能用，但啰嗦，和异步、流式代码搅在一起还容易出错。&lt;/p>
&lt;p>1.x 时代的官方答案是 &lt;strong>astream_events&lt;/strong>：一条流式事件管道，把链内部每个组件的开始/结束/token 都作为事件吐出来，不用写 handler 类：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="k">async&lt;/span> &lt;span class="k">for&lt;/span> &lt;span class="n">event&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">chain&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">astream_events&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>&lt;span class="s2">&amp;#34;input_language&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;English&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;output_language&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;Chinese&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;text&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;Hello, how are you?&amp;#34;&lt;/span>&lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">version&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;v2&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">event&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;event&amp;#34;&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="s2">&amp;#34;on_chat_model_stream&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">event&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;data&amp;#34;&lt;/span>&lt;span class="p">][&lt;/span>&lt;span class="s2">&amp;#34;chunk&amp;#34;&lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">content&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">end&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">flush&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="kc">True&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>分工可以这样理解：给用户看中间步骤、做流式 UI，用 astream_events；沉淀全量记录、做评估和监控，交给 LangSmith。两者不冲突。&lt;/p>
&lt;h2 id="查看追踪数据">查看追踪数据
&lt;/h2>&lt;p>登录平台后：&lt;/p>
&lt;p>&lt;img src="https://www.zata.cc/p/langsmith%E4%BD%BF%E7%94%A8%E6%95%99%E7%A8%8B/images/index/image-5.png"
width="1912"
height="954"
srcset="https://www.zata.cc/p/langsmith%E4%BD%BF%E7%94%A8%E6%95%99%E7%A8%8B/images/index/image-5_hu587824023913815603.png 480w, https://www.zata.cc/p/langsmith%E4%BD%BF%E7%94%A8%E6%95%99%E7%A8%8B/images/index/image-5_hu7873635904407332508.png 1024w"
loading="lazy"
alt="首页"
class="gallery-image"
data-flex-grow="200"
data-flex-basis="481px"
>&lt;/p>
&lt;p>&lt;img src="https://www.zata.cc/p/langsmith%E4%BD%BF%E7%94%A8%E6%95%99%E7%A8%8B/images/index/image-6.png"
width="1912"
height="954"
srcset="https://www.zata.cc/p/langsmith%E4%BD%BF%E7%94%A8%E6%95%99%E7%A8%8B/images/index/image-6_hu9543388786062130606.png 480w, https://www.zata.cc/p/langsmith%E4%BD%BF%E7%94%A8%E6%95%99%E7%A8%8B/images/index/image-6_hu279340018157319073.png 1024w"
loading="lazy"
alt="详情"
class="gallery-image"
data-flex-grow="200"
data-flex-basis="481px"
>&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Projects 视图&lt;/strong>：所有项目的概览——运行次数、错误率&lt;/li>
&lt;li>&lt;strong>Traces 视图&lt;/strong>：项目下每条 trace 对应一次完整执行；点进去能看到 Chain / LLM / Tool / Retriever 的调用层级、输入输出、耗时&lt;/li>
&lt;li>出错时，错误信息和堆栈直接展示在 trace 里&lt;/li>
&lt;li>可以给 trace 打 metadata 和 tags，方便组织和筛选&lt;/li>
&lt;/ul>
&lt;h2 id="playground">Playground
&lt;/h2>&lt;p>Playground 是交互式的提示试验台：改提示、换模型、调参数（temperature、max_tokens），立即看输出；所有运行自动留 trace。compare 模式可以把两个版本的输出并排对比——调提示词时特别有用。&lt;/p>
&lt;p>&lt;img src="https://www.zata.cc/p/langsmith%E4%BD%BF%E7%94%A8%E6%95%99%E7%A8%8B/images/index/image-7.png"
width="5104"
height="2662"
srcset="https://www.zata.cc/p/langsmith%E4%BD%BF%E7%94%A8%E6%95%99%E7%A8%8B/images/index/image-7_hu5515896410810803528.png 480w, https://www.zata.cc/p/langsmith%E4%BD%BF%E7%94%A8%E6%95%99%E7%A8%8B/images/index/image-7_hu16710636975432683832.png 1024w"
loading="lazy"
alt="playground"
class="gallery-image"
data-flex-grow="191"
data-flex-basis="460px"
>&lt;/p>
&lt;p>&lt;img src="https://www.zata.cc/p/langsmith%E4%BD%BF%E7%94%A8%E6%95%99%E7%A8%8B/images/index/image-8.png"
width="5104"
height="2662"
srcset="https://www.zata.cc/p/langsmith%E4%BD%BF%E7%94%A8%E6%95%99%E7%A8%8B/images/index/image-8_hu17388606319755112034.png 480w, https://www.zata.cc/p/langsmith%E4%BD%BF%E7%94%A8%E6%95%99%E7%A8%8B/images/index/image-8_hu16570506362763736550.png 1024w"
loading="lazy"
alt="compare"
class="gallery-image"
data-flex-grow="191"
data-flex-basis="460px"
>&lt;/p>
&lt;h2 id="prompt-hub">Prompt Hub
&lt;/h2>&lt;p>Prompt Hub 用来存放、版本化、复用提示模板。界面上创建模板，&lt;code>{xxx}&lt;/code> 占位符会自动识别成 inputs；修改模板会留 commit 记录，可以回溯。（&lt;a class="link" href="https://docs.smith.langchain.com/prompt_engineering/how_to_guides#prompt-hub" target="_blank" rel="noopener"
>文档地址&lt;/a>）&lt;/p>
&lt;p>代码里直接拉取调用：&lt;/p>
&lt;p>&lt;img src="https://www.zata.cc/p/langsmith%E4%BD%BF%E7%94%A8%E6%95%99%E7%A8%8B/images/index/image-9.png"
width="3454"
height="2230"
srcset="https://www.zata.cc/p/langsmith%E4%BD%BF%E7%94%A8%E6%95%99%E7%A8%8B/images/index/image-9_hu14466145963846159392.png 480w, https://www.zata.cc/p/langsmith%E4%BD%BF%E7%94%A8%E6%95%99%E7%A8%8B/images/index/image-9_hu17314811126818976642.png 1024w"
loading="lazy"
alt="调用代码"
class="gallery-image"
data-flex-grow="154"
data-flex-basis="371px"
>&lt;/p>
&lt;p>创建模板，右边自动出现 inputs：&lt;/p>
&lt;p>&lt;img src="https://www.zata.cc/p/langsmith%E4%BD%BF%E7%94%A8%E6%95%99%E7%A8%8B/images/index/image-10.png"
width="5104"
height="2662"
srcset="https://www.zata.cc/p/langsmith%E4%BD%BF%E7%94%A8%E6%95%99%E7%A8%8B/images/index/image-10_hu15835326501385185382.png 480w, https://www.zata.cc/p/langsmith%E4%BD%BF%E7%94%A8%E6%95%99%E7%A8%8B/images/index/image-10_hu6906222228396075153.png 1024w"
loading="lazy"
alt="创建方式"
class="gallery-image"
data-flex-grow="191"
data-flex-basis="460px"
>&lt;/p>
&lt;p>修改有 commit 记录：&lt;/p>
&lt;p>&lt;img src="https://www.zata.cc/p/langsmith%E4%BD%BF%E7%94%A8%E6%95%99%E7%A8%8B/images/index/image-11.png"
width="3454"
height="2230"
srcset="https://www.zata.cc/p/langsmith%E4%BD%BF%E7%94%A8%E6%95%99%E7%A8%8B/images/index/image-11_hu5092816811983418757.png 480w, https://www.zata.cc/p/langsmith%E4%BD%BF%E7%94%A8%E6%95%99%E7%A8%8B/images/index/image-11_hu13980254746554264517.png 1024w"
loading="lazy"
alt="prompt commit"
class="gallery-image"
data-flex-grow="154"
data-flex-basis="371px"
>&lt;/p>
&lt;h2 id="数据集与评估">数据集与评估
&lt;/h2>&lt;p>评估是 LangSmith 对我来说价值最大的部分：&lt;strong>凭感觉调提示，和拿数据说话，是两回事。&lt;/strong>&lt;/p>
&lt;h3 id="创建数据集">创建数据集
&lt;/h3>&lt;p>三种方式：&lt;/p>
&lt;ul>
&lt;li>UI 里手动创建，添加输入和期望输出（Ground Truth）&lt;/li>
&lt;li>从生产 trace 里筛选样本，「Add to Dataset」存进数据集——基于真实用户交互做评估，这是最推荐的路子&lt;/li>
&lt;li>SDK 代码创建（见下）&lt;/li>
&lt;/ul>
&lt;p>&lt;img src="https://www.zata.cc/p/langsmith%E4%BD%BF%E7%94%A8%E6%95%99%E7%A8%8B/images/index/image-12.png"
width="1912"
height="954"
srcset="https://www.zata.cc/p/langsmith%E4%BD%BF%E7%94%A8%E6%95%99%E7%A8%8B/images/index/image-12_hu3027362747169187572.png 480w, https://www.zata.cc/p/langsmith%E4%BD%BF%E7%94%A8%E6%95%99%E7%A8%8B/images/index/image-12_hu9836541955965867834.png 1024w"
loading="lazy"
alt="手动上传数据集"
class="gallery-image"
data-flex-grow="200"
data-flex-basis="481px"
>&lt;/p>
&lt;h3 id="sdk-创建数据集并运行评估">SDK 创建数据集并运行评估
&lt;/h3>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">langsmith&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">Client&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">evaluate&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">client&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">Client&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">dataset_name&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;My Translation Evaluations&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">try&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">dataset&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">client&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">create_dataset&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">dataset_name&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">description&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;Dataset for evaluating translations.&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">client&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">create_example&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">inputs&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="s2">&amp;#34;input_language&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;English&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;output_language&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;French&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;text&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;Hello&amp;#34;&lt;/span>&lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">outputs&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="s2">&amp;#34;expected_translation&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;Bonjour&amp;#34;&lt;/span>&lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">dataset_id&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">dataset&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">id&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">except&lt;/span> &lt;span class="ne">Exception&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># 大概率是数据集已存在，取现成的&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">dataset&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">client&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">list_datasets&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">dataset_name_contains&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">dataset_name&lt;/span>&lt;span class="p">)[&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 被评估的目标：包装成「接收 inputs、返回 dict」的函数（chain 即上文的翻译链）&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">predict&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">inputs&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">dict&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="nb">dict&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">translation&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">chain&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">invoke&lt;/span>&lt;span class="p">({&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;input_language&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">inputs&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;input_language&amp;#34;&lt;/span>&lt;span class="p">],&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;output_language&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">inputs&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;output_language&amp;#34;&lt;/span>&lt;span class="p">],&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;text&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">inputs&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;text&amp;#34;&lt;/span>&lt;span class="p">],&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">})&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="p">{&lt;/span>&lt;span class="s2">&amp;#34;output&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">translation&lt;/span>&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 自定义评估器：精确匹配&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">exact_match&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">run&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">example&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="nb">dict&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">expected&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">example&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">outputs&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;expected_translation&amp;#34;&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">score&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nb">int&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">run&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">outputs&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;output&amp;#34;&lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">strip&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="n">expected&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="p">{&lt;/span>&lt;span class="s2">&amp;#34;key&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;exact_match&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;score&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">score&lt;/span>&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">results&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">evaluate&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">predict&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">data&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">dataset_name&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">evaluators&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">exact_match&lt;/span>&lt;span class="p">],&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">experiment_prefix&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;translation-eval&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;blockquote>
&lt;p>修订说明：原文这里原本是两行省略号占位（没有可运行代码），上面是按 langsmith SDK 的 &lt;code>evaluate()&lt;/code> 补全的最小示例，具体参数以&lt;a class="link" href="https://docs.smith.langchain.com/" target="_blank" rel="noopener"
>官方文档&lt;/a>为准。&lt;/p>
&lt;/blockquote>
&lt;p>评估器除了自定义函数，常用的几类：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>字符串评估器&lt;/strong>：精确匹配、正则、包含性判断&lt;/li>
&lt;li>&lt;strong>LLM-as-Judge&lt;/strong>：用另一个模型按标准打分（相关性、质量、有害性）&lt;/li>
&lt;li>&lt;strong>轨迹评估器&lt;/strong>：评 Agent 的完整执行轨迹（工具用得对不对），而不只是最终答案&lt;/li>
&lt;li>&lt;strong>比较评估器&lt;/strong>：两个版本在同一输入上并排比&lt;/li>
&lt;/ul>
&lt;blockquote>
&lt;p>⚠️ 版本注记：旧文档里的 &lt;code>RunCollector&lt;/code> / &lt;code>QAEvalChain&lt;/code>（langchain.evaluation 体系）已被 LangSmith 平台和 SDK 的 &lt;code>evaluate()&lt;/code> 取代，这里留个名字备查。&lt;/p>
&lt;/blockquote>
&lt;h3 id="我的建议">我的建议
&lt;/h3>&lt;ol>
&lt;li>先在 LangSmith UI 里熟悉评估流程，再上 SDK&lt;/li>
&lt;li>应用跑起来、有 trace 上报是评估的前提——评估的对象就是这些运行记录&lt;/li>
&lt;li>数据集从真实 trace 里攒，比凭空编造更接近真实分布&lt;/li>
&lt;/ol>
&lt;h2 id="监控与协作">监控与协作
&lt;/h2>&lt;p>应用部署后，LangSmith 可以持续监控：仪表盘看延迟、错误率、token 消耗、用户反馈；指标超阈值可设警报；用户点赞/点踩能关联到对应 trace。团队协作方面，组织内共享 trace、数据集和评估结果。&lt;/p>
&lt;h2 id="总结">总结
&lt;/h2>&lt;ul>
&lt;li>接入成本几乎为零：环境变量一设，LangChain 应用自动上报&lt;/li>
&lt;li>调试靠 trace，迭代靠 Playground + Prompt Hub，质量靠数据集 + 评估&lt;/li>
&lt;li>观测分工：给用户看的中间过程用 astream_events，记录和分析交给 LangSmith&lt;/li>
&lt;li>官方文档：&lt;a class="link" href="https://docs.smith.langchain.com/" target="_blank" rel="noopener"
>docs.smith.langchain.com&lt;/a>&lt;/li>
&lt;/ul></description></item><item><title>Langchain-RAG实战教程</title><link>https://www.zata.cc/p/langchain-rag%E5%AE%9E%E6%88%98%E6%95%99%E7%A8%8B/</link><pubDate>Tue, 06 May 2025 11:05:13 +0800</pubDate><guid>https://www.zata.cc/p/langchain-rag%E5%AE%9E%E6%88%98%E6%95%99%E7%A8%8B/</guid><description>&lt;img src="https://www.zata.cc/p/langchain-rag%E5%AE%9E%E6%88%98%E6%95%99%E7%A8%8B/images/index/index.png" alt="Featured image of post Langchain-RAG实战教程" />&lt;p>LLM 有两个先天限制：知识停在训练截止日期，你的私有文档、内部资料也不在它的训练语料里。直接问，轻则答&amp;quot;不知道&amp;quot;，重则一本正经地编。RAG（Retrieval Augmented Generation）就是针对这两个问题的标准解法：先从你自己的文档集合里检索出与问题最相关的几段文本，连同问题一起交给模型，让它基于给定材料作答。&lt;/p>
&lt;p>这篇文章以我实际跑通的一套为例：阿里云百炼提供的 Embedding 模型和 qwen 系模型，LangChain 做编排，Chroma 做向量库。完整代码在同目录 &lt;code>main.py&lt;/code>，知识库是 &lt;code>example_doc.txt&lt;/code>（一篇约一万字符的 LangChain 教程文本），运行截图见下文。正文以现在还能跑的写法为准；当年 0.1 时代的老 API（RetrievalQA、&lt;code>get_relevant_documents()&lt;/code> 等）单独开一节做注记，不占主线。&lt;/p>
&lt;p>动手之前先回答选型问题：什么时候用 RAG，什么时候微调。&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>维度&lt;/th>
&lt;th>RAG&lt;/th>
&lt;th>微调&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>知识更新&lt;/td>
&lt;td>换文档立即生效&lt;/td>
&lt;td>需重新训练&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>擅长&lt;/td>
&lt;td>私有知识问答、事实性回答&lt;/td>
&lt;td>固定的风格、格式、领域行为&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>成本&lt;/td>
&lt;td>主要是检索和推理开销&lt;/td>
&lt;td>算力 + 数据准备 + 训练周期&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>可解释性&lt;/td>
&lt;td>回答可溯源到具体文档&lt;/td>
&lt;td>难以溯源&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>我的判断：知识层面的问题优先 RAG，微调解决的是&amp;quot;怎么说话&amp;quot;而不是&amp;quot;知道什么&amp;quot;。两者不冲突，可以叠加，但大多数场景先把 RAG 做好就够用了。&lt;/p>
&lt;h2 id="rag-的核心组件一条数据流">RAG 的核心组件：一条数据流
&lt;/h2>&lt;p>一个 LangChain RAG 应用的组件可以串成一条数据流看：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">原始文档
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │ Document Loaders（加载）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ▼
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Document（整篇 page_content + metadata）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │ Text Splitters（分割）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ▼
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">chunks（小块文本，继承 metadata）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │ Embedding 模型（向量化）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ▼
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">向量 → Vector Store（落库）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │ 用户提问同样转向量，相似度检索 top-k（Retriever）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ▼
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">问题 + 相关 chunks（拼进 Prompt）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> │ LLM（生成）
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ▼
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">基于文档的回答
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>对应到具体组件：&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>组件&lt;/th>
&lt;th>职责&lt;/th>
&lt;th>本文用的实现&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>Document Loaders&lt;/td>
&lt;td>从 txt/PDF/网页等来源读入文档&lt;/td>
&lt;td>&lt;code>TextLoader&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Text Splitters&lt;/td>
&lt;td>把长文档切成语义连贯的小块&lt;/td>
&lt;td>&lt;code>RecursiveCharacterTextSplitter&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Embedding 模型&lt;/td>
&lt;td>文本转向量&lt;/td>
&lt;td>百炼 &lt;code>text-embedding-v2&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Vector Store&lt;/td>
&lt;td>存向量，提供相似度检索&lt;/td>
&lt;td>Chroma&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Retriever&lt;/td>
&lt;td>统一检索接口，取 top-k&lt;/td>
&lt;td>&lt;code>as_retriever()&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>LLM&lt;/td>
&lt;td>基于上下文生成回答&lt;/td>
&lt;td>qwen-plus（OpenAI 兼容模式）&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>链（编排）&lt;/td>
&lt;td>把上面串成完整流程&lt;/td>
&lt;td>LCEL&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>前四类组件是&amp;quot;建索引&amp;quot;阶段，Retriever 和链是&amp;quot;查询&amp;quot;阶段。索引建一次可以服务无数次查询，知识更新只需重建索引——这是 RAG 相对微调灵活的根源。&lt;/p>
&lt;h2 id="实战百炼--langchain--chroma-端到端">实战：百炼 + LangChain + Chroma 端到端
&lt;/h2>&lt;p>阿里云百炼（Model Studio）是阿里云的大模型服务平台，qwen 系模型和 text-embedding 系向量模型都能通过 DashScope API 调用，并且提供 OpenAI 兼容接口——chat 侧可以直接用 &lt;code>langchain-openai&lt;/code> 的 &lt;code>ChatOpenAI&lt;/code> 接入，不需要专门的 SDK 封装。&lt;/p>
&lt;h3 id="源文件与运行结果">源文件与运行结果
&lt;/h3>&lt;blockquote>
&lt;p>说明：同目录的 &lt;code>main.py&lt;/code> 是当时跑通的原始代码，里面还保留着 0.1 时代的写法（&lt;code>from langchain.text_splitter import ...&lt;/code>、&lt;code>get_relevant_documents()&lt;/code>、&lt;code>vectorstore.persist()&lt;/code>、RetrievalQA）。本文正文按当前版本的写法整理过，对照源码阅读时注意区分。&lt;/p>
&lt;/blockquote>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>源文件&lt;/th>
&lt;th>说明&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;a class="link" href="./02-Langchain-Rag%e5%ae%9e%e6%88%98/example_doc.txt" >example_doc.txt&lt;/a>&lt;/td>
&lt;td>知识库文本，一篇约一万字符的 LangChain 教程&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;a class="link" href="./02-Langchain-Rag%e5%ae%9e%e6%88%98/main.py" >main.py&lt;/a>&lt;/td>
&lt;td>完整可运行代码&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>&lt;img src="https://www.zata.cc/p/langchain-rag%E5%AE%9E%E6%88%98%E6%95%99%E7%A8%8B/images/index/image.png"
width="996"
height="2516"
srcset="https://www.zata.cc/p/langchain-rag%E5%AE%9E%E6%88%98%E6%95%99%E7%A8%8B/images/index/image_hu16657321128811883448.png 480w, https://www.zata.cc/p/langchain-rag%E5%AE%9E%E6%88%98%E6%95%99%E7%A8%8B/images/index/image_hu6304295275968818289.png 1024w"
loading="lazy"
alt="运行示例"
class="gallery-image"
data-flex-grow="39"
data-flex-basis="95px"
>&lt;/p>
&lt;h3 id="准备依赖与-api-key">准备：依赖与 API Key
&lt;/h3>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">pip install langchain langchain-community langchain-openai langchain-text-splitters chromadb dashscope python-dotenv
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;code>dashscope&lt;/code> 是 &lt;code>DashScopeEmbeddings&lt;/code> 的依赖；如果嵌入走 OpenAI 兼容接口（见下文），这个包也可以不装。去百炼平台申请 API Key，写入脚本同目录的 &lt;code>.env&lt;/code>：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-env" data-lang="env">&lt;span class="line">&lt;span class="cl">&lt;span class="nv">DASHSCOPE_API_KEY&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;sk-xxxxxxxxxxxxxxxx&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">os&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">dotenv&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">load_dotenv&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">load_dotenv&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>模型名以百炼的模型列表为准：https://help.aliyun.com/zh/model-studio/getting-started/models&lt;/p>
&lt;h3 id="加载与分割">加载与分割
&lt;/h3>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">langchain_community.document_loaders&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">TextLoader&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">langchain_text_splitters&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">RecursiveCharacterTextSplitter&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">loader&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">TextLoader&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;./example_doc.txt&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">encoding&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;utf-8&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">documents&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">loader&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">load&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">text_splitter&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">RecursiveCharacterTextSplitter&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">chunk_size&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="mi">1000&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">chunk_overlap&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="mi">200&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">texts&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">text_splitter&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">split_documents&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">documents&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;文档被分割成 &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="nb">len&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">texts&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2"> 个文本块&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>两个参数直接决定检索质量：&lt;code>chunk_size&lt;/code> 是每块的最大字符数，太大则一块里混进多个主题、检索不精准，太小则上下文碎、嵌入的语义信息不够；&lt;code>chunk_overlap&lt;/code> 是相邻块的重叠字符数，用来避免关键句恰好被切断。1000/200 是常见的起步值，值得针对自己的文档实测调整。&lt;/p>
&lt;p>读 PDF 把 &lt;code>TextLoader&lt;/code> 换成 &lt;code>PyPDFLoader&lt;/code> 即可，分割逻辑完全复用。&lt;/p>
&lt;p>版本差异：0.1 时代的教程写 &lt;code>from langchain.text_splitter import ...&lt;/code>，现在分割器拆到了独立的 &lt;code>langchain_text_splitters&lt;/code> 包，老路径已废弃。&lt;/p>
&lt;h3 id="嵌入">嵌入
&lt;/h3>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">langchain_community.embeddings&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">DashScopeEmbeddings&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">embeddings_model&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">DashScopeEmbeddings&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">model&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;text-embedding-v2&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 快速自检：随便嵌一句话，能出向量就说明模型和 Key 都通了&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">example_embedding&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">embeddings_model&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">embed_query&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;这是一个示例文本，用于测试嵌入模型。&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;向量维度: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="nb">len&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">example_embedding&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>⚠️ &lt;code>DashScopeEmbeddings&lt;/code> 来自 &lt;code>langchain_community&lt;/code>（依赖 dashscope SDK），属于 legacy 集成，不保证长期维护。新代码可以直接走百炼的 OpenAI 兼容接口，与本站其他文章的做法一致：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">langchain_openai&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">OpenAIEmbeddings&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">embeddings_model&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">OpenAIEmbeddings&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">model&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;text-embedding-v2&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">api_key&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">os&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">getenv&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;DASHSCOPE_API_KEY&amp;#34;&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">base_url&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;https://dashscope.aliyuncs.com/compatible-mode/v1&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">check_embedding_ctx_length&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="kc">False&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="c1"># 关掉 OpenAI 特有的长度检查，否则会报错&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="向量库chroma">向量库：Chroma
&lt;/h3>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">langchain_community.vectorstores&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">Chroma&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">vectorstore&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">Chroma&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">from_documents&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">documents&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">texts&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">embedding&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">embeddings_model&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">persist_directory&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;./chroma_db&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>指定 &lt;code>persist_directory&lt;/code> 之后，数据在写入时自动落盘，下次运行用 &lt;code>Chroma(persist_directory=&amp;quot;./chroma_db&amp;quot;, embedding_function=embeddings_model)&lt;/code> 直接加载，不必重建索引。&lt;/p>
&lt;p>几个容易踩坑的点：&lt;/p>
&lt;ul>
&lt;li>0.1 时代教程里常见的 &lt;code>vectorstore.persist()&lt;/code> 不要再写了。Chroma 早就自动持久化，这个方法在新版集成里已被移除，留着只会报错。&lt;/li>
&lt;li>&lt;code>langchain_community.vectorstores&lt;/code> 里的 Chroma 是老路径，官方现在推荐独立包：&lt;code>pip install langchain-chroma&lt;/code>，然后 &lt;code>from langchain_chroma import Chroma&lt;/code>，参数一致。&lt;/li>
&lt;li>FAISS 也是常用选择：&lt;code>FAISS.from_documents(texts, embeddings_model)&lt;/code> 一行建库，纯内存、速度极快，适合原型验证；需要持久化时自己调 &lt;code>save_local()&lt;/code> / &lt;code>load_local()&lt;/code>。&lt;/li>
&lt;/ul>
&lt;h3 id="检索器">检索器
&lt;/h3>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="n">retriever&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">vectorstore&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">as_retriever&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">search_kwargs&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="s2">&amp;#34;k&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">3&lt;/span>&lt;span class="p">})&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">relevant_docs&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">retriever&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">invoke&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;什么是 Langchain?&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;检索到 &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="nb">len&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">relevant_docs&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2"> 个相关文档块&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">for&lt;/span> &lt;span class="n">i&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">doc&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="nb">enumerate&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">relevant_docs&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;文档 &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">i&lt;/span>&lt;span class="o">+&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">doc&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">page_content&lt;/span>&lt;span class="p">[:&lt;/span>&lt;span class="mi">150&lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">...&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;code>k=3&lt;/code> 即取相似度最高的 3 个块。Retriever 是 LangChain 的统一检索接口，向量库、多路召回、压缩检索都实现同一个接口，后面换策略不用改下游代码。&lt;/p>
&lt;p>版本差异：老教程里的 &lt;code>retriever.get_relevant_documents(query)&lt;/code> 已废弃，统一用 &lt;code>retriever.invoke(query)&lt;/code>。&lt;/p>
&lt;h3 id="生成模型qwen-plus">生成模型：qwen-plus
&lt;/h3>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">langchain_openai&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">ChatOpenAI&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">llm&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">ChatOpenAI&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">api_key&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">os&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">getenv&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;DASHSCOPE_API_KEY&amp;#34;&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">base_url&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;https://dashscope.aliyuncs.com/compatible-mode/v1&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">model&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;qwen-plus&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="c1"># 模型列表: https://help.aliyun.com/zh/model-studio/getting-started/models&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>这是百炼的 OpenAI 兼容模式：&lt;code>langchain-openai&lt;/code> 一个包通吃 qwen 系模型（qwen-plus、qwen-max 等），换模型只改 &lt;code>model&lt;/code> 字符串，我的 &lt;code>main.py&lt;/code> 走的就是这条路。另一条路是 &lt;code>langchain_community&lt;/code> 的 &lt;code>ChatTongyi&lt;/code>（或更新包里的 &lt;code>langchain-alibaba&lt;/code>），同样属于 legacy 集成，除非依赖 DashScope SDK 的特有能力，否则兼容模式这条最省事。&lt;/p>
&lt;h3 id="用-lcel-拼生成链">用 LCEL 拼生成链
&lt;/h3>&lt;p>基础链：检索结果拼成上下文，连同问题一起过 Prompt、LLM、输出解析。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">langchain_core.prompts&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">ChatPromptTemplate&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">langchain_core.runnables&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">RunnablePassthrough&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">langchain_core.output_parsers&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">StrOutputParser&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">template&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;请根据以下上下文信息来回答问题。如果你不知道答案，就说你不知道，不要试图编造答案。
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2">用最多三句话来回答，并保持答案简洁。
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2">上下文:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2">&lt;/span>&lt;span class="si">{context}&lt;/span>&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2">问题: &lt;/span>&lt;span class="si">{question}&lt;/span>&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2">有用的回答:&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">prompt&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">ChatPromptTemplate&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">from_template&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">template&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">format_docs&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">docs&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="se">\n\n&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">join&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">doc&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">page_content&lt;/span> &lt;span class="k">for&lt;/span> &lt;span class="n">doc&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">docs&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">rag_chain&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>&lt;span class="s2">&amp;#34;context&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">retriever&lt;/span> &lt;span class="o">|&lt;/span> &lt;span class="n">format_docs&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;question&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">RunnablePassthrough&lt;/span>&lt;span class="p">()}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">|&lt;/span> &lt;span class="n">prompt&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">|&lt;/span> &lt;span class="n">llm&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">|&lt;/span> &lt;span class="n">StrOutputParser&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">answer&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">rag_chain&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">invoke&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;Langchain 的核心组件有哪些？&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">answer&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>拆开看这条链：输入是问题字符串，&lt;code>RunnablePassthrough()&lt;/code> 把它原样传给 &lt;code>question&lt;/code>；&lt;code>retriever | format_docs&lt;/code> 表示先用检索器取文档、再拼成字符串赋给 &lt;code>context&lt;/code>；然后依次过 Prompt、LLM，&lt;code>StrOutputParser()&lt;/code> 把模型输出解析成纯字符串。整条链由 &lt;code>|&lt;/code> 组合，每一段都是 Runnable，天然支持 &lt;code>.invoke()&lt;/code>、流式输出和异步。&lt;/p>
&lt;p>想同时拿到源文档做溯源，用 &lt;code>RunnablePassthrough.assign&lt;/code> 把中间结果挂在字典里：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">operator&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">itemgetter&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">rag_chain_with_source&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">RunnablePassthrough&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">assign&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">context&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">itemgetter&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;question&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">|&lt;/span> &lt;span class="n">retriever&lt;/span> &lt;span class="o">|&lt;/span> &lt;span class="n">format_docs&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">)&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">assign&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">answer&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>&lt;span class="s2">&amp;#34;context&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">itemgetter&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;context&amp;#34;&lt;/span>&lt;span class="p">),&lt;/span> &lt;span class="s2">&amp;#34;question&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">itemgetter&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;question&amp;#34;&lt;/span>&lt;span class="p">)}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">|&lt;/span> &lt;span class="n">prompt&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">|&lt;/span> &lt;span class="n">llm&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">|&lt;/span> &lt;span class="n">StrOutputParser&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">result&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">rag_chain_with_source&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">invoke&lt;/span>&lt;span class="p">({&lt;/span>&lt;span class="s2">&amp;#34;question&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;Langchain 的核心组件有哪些？&amp;#34;&lt;/span>&lt;span class="p">})&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">result&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;answer&amp;#34;&lt;/span>&lt;span class="p">])&lt;/span> &lt;span class="c1"># 生成的回答&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">result&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;context&amp;#34;&lt;/span>&lt;span class="p">])&lt;/span> &lt;span class="c1"># 参与生成的检索原文，可用来核对出处&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Prompt 里&amp;quot;不知道就说不知道&amp;quot;这句话值得保留——它是压制幻觉的第一道闸门，尤其在检索没命中、上下文里根本没有答案的时候。&lt;/p>
&lt;h2 id="历史写法注记retrievalqa">历史写法注记：RetrievalQA
&lt;/h2>&lt;p>这份教程最初是用 &lt;code>RetrievalQA&lt;/code> 写的，0.1 时代的标准做法长这样：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># ⚠️ LangChain 1.0 已移除，仅供对照历史代码&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">langchain.chains&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">RetrievalQA&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">qa_chain&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">RetrievalQA&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">from_chain_type&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">llm&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">llm&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">chain_type&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;stuff&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="c1"># 或 &amp;#34;map_reduce&amp;#34; / &amp;#34;refine&amp;#34; / &amp;#34;map_rerank&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">retriever&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">retriever&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">return_source_documents&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="kc">True&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">result&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">qa_chain&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">invoke&lt;/span>&lt;span class="p">({&lt;/span>&lt;span class="s2">&amp;#34;query&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;请介绍一下 Langchain 的主要功能。&amp;#34;&lt;/span>&lt;span class="p">})&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">result&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;result&amp;#34;&lt;/span>&lt;span class="p">])&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>它把&amp;quot;检索 + 拼 Prompt + 生成&amp;quot;封成一个现成的链，&lt;code>chain_type&lt;/code> 决定文档的塞法：&lt;code>stuff&lt;/code> 全部塞进一个 Prompt；&lt;code>map_reduce&lt;/code> 分块各自作答再合并；&lt;code>refine&lt;/code> 逐块迭代修正；&lt;code>map_rerank&lt;/code> 分块作答后按置信度挑。当年确实好用，我的 &lt;code>main.py&lt;/code> 里跑通的也是它。&lt;/p>
&lt;p>LangChain 1.0 把 &lt;code>langchain.chains&lt;/code> 里这批 legacy 链整体移除了，&lt;code>RetrievalQA&lt;/code> 已不存在。现代等价物就是上文那几行 LCEL——十行不到，换来的是每一步可插拔可调试；要接 Agent 的话，用 &lt;code>create_retrieval_tool&lt;/code> 把检索器包成工具交给模型自主调用。老代码迁移时，遇到 &lt;code>from langchain.chains import RetrievalQA&lt;/code> 直接按上文重写即可。&lt;/p>
&lt;h2 id="进阶检索策略">进阶检索策略
&lt;/h2>&lt;p>基础 RAG 跑通后，瓶颈多半出在检索而不是生成。三个常用升级：&lt;/p>
&lt;h3 id="mmr解决检索回来的块都长一个样">MMR：解决&amp;quot;检索回来的块都长一个样&amp;quot;
&lt;/h3>&lt;p>普通 top-k 检索常返回几个彼此高度相似的块，占满上下文却没带来新信息。MMR（Maximal Marginal Relevance）在&amp;quot;与查询相似&amp;quot;和&amp;quot;彼此不相似&amp;quot;之间做平衡，迭代选点。启用只需换参数：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="n">retriever&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">vectorstore&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">as_retriever&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">search_type&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;mmr&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">search_kwargs&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="s2">&amp;#34;k&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">4&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;fetch_k&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">20&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;lambda_mult&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mf">0.5&lt;/span>&lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;code>fetch_k&lt;/code> 是参与 MMR 挑选的候选数（应大于 k），&lt;code>lambda_mult&lt;/code> 越接近 1 越偏多样性、越接近 0 越偏相似度。&lt;/p>
&lt;h3 id="multiqueryretriever用-llm-改写查询">MultiQueryRetriever：用 LLM 改写查询
&lt;/h3>&lt;p>用户的提问措辞常常和文档措辞对不上，一路查询召回有限。MultiQueryRetriever 让 LLM 把一个问题从不同角度改写成多个查询，分别检索后合并去重，提升召回：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="n">retriever_multiquery&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">MultiQueryRetriever&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">from_llm&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">retriever&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">vectorstore&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">as_retriever&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">search_kwargs&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="s2">&amp;#34;k&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">}),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">llm&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">llm&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>代价是每次检索多一次 LLM 调用，适合召回率明显不足时再上。&lt;/p>
&lt;h3 id="selfqueryretriever语义检索--元数据过滤">SelfQueryRetriever：语义检索 + 元数据过滤
&lt;/h3>&lt;p>文档带结构化元数据（来源、类别、日期）时，SelfQueryRetriever 用 LLM 把自然语言拆成&amp;quot;语义查询 + 元数据过滤条件&amp;quot;两部分，向量库里同时做相似度和过滤。&amp;ldquo;找水果相关的文档，只要 doc3 来源的&amp;quot;这类需求，普通向量检索表达不出来，它能。&lt;/p>
&lt;p>用法上需要用 &lt;code>AttributeInfo&lt;/code> 把元数据字段的名称、类型、描述告诉 LLM，再 &lt;code>SelfQueryRetriever.from_llm(llm=..., vectorstore=..., metadata_field_info=[...])&lt;/code> 构建，且向量库必须支持元数据过滤（Chroma 支持）。&lt;/p>
&lt;p>同一方向上还有几个值得知道的名字：&lt;code>ContextualCompressionRetriever&lt;/code>（检索后用 LLM 过滤或抽取相关片段，给上下文瘦身）、&lt;code>EnsembleRetriever&lt;/code>（多路召回 + RRF 融合，比如 BM25 和向量各一路）、&lt;code>ParentDocumentRetriever&lt;/code>（小块检索保精度、返回父块保上下文完整）。检索质量还有两个进阶手段：混合检索（关键词 + 向量）和对 top-k 结果做 Rerank 重排。不用一次全上，哪个指标差修哪个。&lt;/p>
&lt;h2 id="怎么知道-rag-好不好评估指标">怎么知道 RAG 好不好：评估指标
&lt;/h2>&lt;p>调优之前先有度量，否则改 chunk_size、换嵌入模型全是凭感觉。RAG 的质量要拆成两层看：检索层（该找到的找到了吗）和生成层（找到的材料用对了吗）。两层修法完全不同——检索差该动分割策略和嵌入模型，生成差该动 Prompt 和模型——混在一起调是常见错误。&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>层&lt;/th>
&lt;th>指标&lt;/th>
&lt;th>衡量什么&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>检索&lt;/td>
&lt;td>Hit Rate&lt;/td>
&lt;td>top-k 结果里包含正确文档的比例&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>检索&lt;/td>
&lt;td>MRR&lt;/td>
&lt;td>正确文档排名倒数的平均（第 1 名得 1，第 3 名得 1/3）&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>检索&lt;/td>
&lt;td>Precision@k / Recall@k&lt;/td>
&lt;td>返回的 k 条里有多少相关 / 该找到的找到了多少&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>检索&lt;/td>
&lt;td>nDCG@k&lt;/td>
&lt;td>带排名位置权重的列表质量&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>生成&lt;/td>
&lt;td>EM（Exact Match）&lt;/td>
&lt;td>答案与标准答案完全一致的比例&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>生成&lt;/td>
&lt;td>F1&lt;/td>
&lt;td>答案词级精确率/召回率的调和平均，适合抽取式问答&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>生成&lt;/td>
&lt;td>BLEU / ROUGE / METEOR&lt;/td>
&lt;td>生成文本与参考答案的重叠度，适合开放式生成&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>生成&lt;/td>
&lt;td>语义相似度&lt;/td>
&lt;td>用嵌入模型算答案与参考答案的向量相似度&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>评估方式从贵到便宜排：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>人工评估&lt;/strong>：质量的金标准，准但不可规模化。&lt;/li>
&lt;li>&lt;strong>LLM-as-judge&lt;/strong>：让一个强模型按准确性、相关性、忠实度等维度给答案打分，便宜可规模化，但有自己的偏差（比如偏好长答案），结论要抽检校准。&lt;/li>
&lt;li>&lt;strong>框架&lt;/strong>：Ragas 专做 RAG 评估，把忠实度（faithfulness）、答案相关性、上下文精确率/召回率等指标现成化；LangSmith 提供数据集管理、运行评估和版本对比。&lt;/li>
&lt;/ul>
&lt;p>这些指标都依赖一个带标准答案的测试集（问题 + 正确出处 + 参考答案）。没有测试集，任何指标都算不出来——这也是多数团队跳过评估的原因。哪怕手工整理二十个有代表性的问题，也够支撑第一轮迭代。&lt;/p>
&lt;h2 id="总结">总结
&lt;/h2>&lt;p>一条 RAG 主线至此完整：加载 → 分割 → 嵌入 → Chroma 落库 → 检索 → LCEL 生成，百炼的模型用 OpenAI 兼容模式接入，一套 &lt;code>langchain-openai&lt;/code> 通吃。下一步可以按顺序做这几件事：&lt;/p>
&lt;ol>
&lt;li>把 &lt;code>main.py&lt;/code> 里的老写法按本文修正（&lt;code>langchain_text_splitters&lt;/code> 导入、&lt;code>invoke()&lt;/code>、去掉 &lt;code>persist()&lt;/code>、RetrievalQA 换 LCEL），在当前版本的 LangChain 上重跑一遍。&lt;/li>
&lt;li>用同一组问题对比不同 &lt;code>chunk_size&lt;/code> / &lt;code>chunk_overlap&lt;/code> 下的检索质量，别沿用默认值。&lt;/li>
&lt;li>检索结果重复单一时，先把 &lt;code>search_type&lt;/code> 换成 &lt;code>&amp;quot;mmr&amp;quot;&lt;/code>，这是成本最低的升级。&lt;/li>
&lt;li>整理一批带标准答案的测试问题，用 Hit Rate / MRR 或直接上 Ragas 建立基线，之后的每次改动都对着基线说话。&lt;/li>
&lt;li>需要更精细的召回再考虑 MultiQuery / SelfQuery，需要接 Agent 用 &lt;code>create_retrieval_tool&lt;/code>。&lt;/li>
&lt;/ol>
&lt;p>进一步阅读：LangChain 官方文档 &lt;a class="link" href="https://python.langchain.com/" target="_blank" rel="noopener"
>https://python.langchain.com/&lt;/a>&lt;/p></description></item></channel></rss>