<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>LangChain 进阶 on 扎塔-Zata</title><link>https://www.zata.cc/tags/langchain-%E8%BF%9B%E9%98%B6/</link><description>Recent content in LangChain 进阶 on 扎塔-Zata</description><generator>Hugo -- gohugo.io</generator><language>zh-cn</language><copyright>扎塔-Zata</copyright><lastBuildDate>Thu, 24 Sep 2026 17:09:06 +0800</lastBuildDate><atom:link href="https://www.zata.cc/tags/langchain-%E8%BF%9B%E9%98%B6/index.xml" rel="self" type="application/rss+xml"/><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>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-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>