<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>Tool on On My Way</title>
    <link>https://tuanzi-wow.cn/tags/tool/</link>
    <description>Recent content in Tool on On My Way</description>
    <generator>Hugo</generator>
    <language>zh-CN</language>
    <atom:link href="https://tuanzi-wow.cn/tags/tool/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>9 月 20 日学习报告：LangChain Agent 的 Tool、Memory 与 Streaming</title>
      <link>https://tuanzi-wow.cn/frameworks/langchain/core_components/langchain-agent-study-report-2026-09-20/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>https://tuanzi-wow.cn/frameworks/langchain/core_components/langchain-agent-study-report-2026-09-20/</guid>
      <description>按修改时间与源文件结构整理 Tool、short-term memory、Event Streaming 和 Streaming。</description>
      <content:encoded><![CDATA[<h1 id="9-月-20-日学习报告langchain-agent-的-toolmemory-与-streaming">9 月 20 日学习报告：LangChain Agent 的 Tool、Memory 与 Streaming</h1>
<p>仅整理 2026-09-20 修改的 4 个文件。文件按最后修改时间排列；各文件内部保持原文标题顺序与层级，只压缩重复表述和示例，不省略知识点。</p>
<h2 id="140852--toolsmd">14:08:52 — Tools.md</h2>
<h3 id="tools">Tools</h3>
<p>Tools extend what agents can do。</p>
<h3 id="create-tools">Create tools</h3>
<h4 id="basic-tool-definition">Basic tool definition</h4>
<p>定义 Tool 最简单的方式是使用 <code>@tool</code> decorator。函数 docstring 会成为 Tool description；参数 type hints 是必须的，因为它们定义 Tool 的输入 Schema。docstring 应内容丰富且简明扼要。</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">langchain.tools</span> <span class="kn">import</span> <span class="n">tool</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nd">@tool</span>
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">search_database</span><span class="p">(</span><span class="n">query</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">limit</span><span class="p">:</span> <span class="nb">int</span> <span class="o">=</span> <span class="mi">10</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">str</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;&#34;&#34;Search the customer database for records matching the query.
</span></span></span><span class="line"><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="cl"><span class="s2">    Args:
</span></span></span><span class="line"><span class="cl"><span class="s2">        query: Search terms to look for
</span></span></span><span class="line"><span class="cl"><span class="s2">        limit: Maximum number of results to return
</span></span></span><span class="line"><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="sa">f</span><span class="s2">&#34;Found </span><span class="si">{</span><span class="n">limit</span><span class="si">}</span><span class="s2"> results for &#39;</span><span class="si">{</span><span class="n">query</span><span class="si">}</span><span class="s2">&#39;&#34;</span>
</span></span></code></pre></td></tr></table>
</div>
</div><h4 id="customize-tool-properties">Customize tool properties</h4>
<h5 id="custom-tool-name">Custom tool name</h5>
<p>Tool name 默认等于函数名，可通过 <code>@tool(&quot;web_search&quot;)</code> 覆盖。</p>
<h5 id="custom-tool-description">Custom tool description</h5>
<p>默认 description 来自 docstring。需要更明确地指导 model 时，使用 <code>description=</code> 覆盖自动生成的 description。</p>
<h4 id="advanced-schema-definition">Advanced schema definition</h4>
<p>复杂输入使用 Pydantic model 或 JSON Schema。<code>Field(description=...)</code> 描述字段，<code>Literal</code> 限定可选值，<code>@tool(args_schema=...)</code> 绑定输入 Schema。</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">Literal</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">pydantic</span> <span class="kn">import</span> <span class="n">BaseModel</span><span class="p">,</span> <span class="n">Field</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">WeatherInput</span><span class="p">(</span><span class="n">BaseModel</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="n">location</span><span class="p">:</span> <span class="nb">str</span> <span class="o">=</span> <span class="n">Field</span><span class="p">(</span><span class="n">description</span><span class="o">=</span><span class="s2">&#34;City name or coordinates&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">units</span><span class="p">:</span> <span class="n">Literal</span><span class="p">[</span><span class="s2">&#34;celsius&#34;</span><span class="p">,</span> <span class="s2">&#34;fahrenheit&#34;</span><span class="p">]</span> <span class="o">=</span> <span class="n">Field</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="n">default</span><span class="o">=</span><span class="s2">&#34;celsius&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">description</span><span class="o">=</span><span class="s2">&#34;Temperature unit preference&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">include_forecast</span><span class="p">:</span> <span class="nb">bool</span> <span class="o">=</span> <span class="n">Field</span><span class="p">(</span><span class="n">default</span><span class="o">=</span><span class="kc">False</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nd">@tool</span><span class="p">(</span><span class="n">args_schema</span><span class="o">=</span><span class="n">WeatherInput</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">get_weather</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">location</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">units</span><span class="p">:</span> <span class="nb">str</span> <span class="o">=</span> <span class="s2">&#34;celsius&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">include_forecast</span><span class="p">:</span> <span class="nb">bool</span> <span class="o">=</span> <span class="kc">False</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">str</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;&#34;&#34;Get current weather and optional forecast.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="o">...</span>
</span></span></code></pre></td></tr></table>
</div>
</div><h4 id="reserved-argument-names">Reserved argument names</h4>
<ul>
<li><code>config</code> 保留给 <code>RunnableConfig</code>；</li>
<li><code>runtime</code> 保留给 <code>ToolRuntime</code>。</li>
</ul>
<p>二者不能用作普通业务参数，否则会产生 runtime error。</p>
<h3 id="access-context">Access context</h3>
<p>Tool 通过 <code>ToolRuntime</code> 访问 conversation history、user data、persistent memory 和执行信息。</p>
<table>
	<thead>
			<tr>
					<th style="text-align: left">Component</th>
					<th style="text-align: left">访问方式</th>
					<th style="text-align: left">含义与用途</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td style="text-align: left">State</td>
					<td style="text-align: left"><code>runtime.state</code></td>
					<td style="text-align: left">当前 conversation 的可变 short-term memory；messages、计数器、自定义字段</td>
			</tr>
			<tr>
					<td style="text-align: left">Context</td>
					<td style="text-align: left"><code>runtime.context</code></td>
					<td style="text-align: left">invocation 时传入的不可变配置；user ID、session info</td>
			</tr>
			<tr>
					<td style="text-align: left">Store</td>
					<td style="text-align: left"><code>runtime.store</code></td>
					<td style="text-align: left">跨 conversation 持久化的 long-term memory</td>
			</tr>
			<tr>
					<td style="text-align: left">Stream Writer</td>
					<td style="text-align: left"><code>runtime.stream_writer</code></td>
					<td style="text-align: left">Tool 执行时发送实时 custom updates</td>
			</tr>
			<tr>
					<td style="text-align: left">Execution Info</td>
					<td style="text-align: left"><code>runtime.execution_info</code></td>
					<td style="text-align: left">thread ID、run ID、attempt number</td>
			</tr>
			<tr>
					<td style="text-align: left">Server Info</td>
					<td style="text-align: left"><code>runtime.server_info</code></td>
					<td style="text-align: left">assistant ID、graph ID、authenticated user</td>
			</tr>
			<tr>
					<td style="text-align: left">Config</td>
					<td style="text-align: left"><code>runtime.config</code></td>
					<td style="text-align: left"><code>RunnableConfig</code> 的 callbacks、tags、metadata</td>
			</tr>
			<tr>
					<td style="text-align: left">Tool Call ID</td>
					<td style="text-align: left"><code>runtime.tool_call_id</code></td>
					<td style="text-align: left">当前 Tool invocation ID，用于匹配 <code>ToolMessage</code></td>
			</tr>
	</tbody>
</table>
<h4 id="short-term-memorystate">Short-term memory(State)</h4>
<p>State 包含 message history 和 graph state 中定义的自定义字段。</p>
<h5 id="access-state">Access state</h5>
<p>在 Tool 签名中声明 <code>runtime: ToolRuntime</code>。<code>ToolNode</code> 会自动注入它，但它不会出现在发送给 model 的 Tool Schema 中。Tool 通过 <code>runtime.state</code> 读取 State。</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">langchain.messages</span> <span class="kn">import</span> <span class="n">HumanMessage</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">langchain.tools</span> <span class="kn">import</span> <span class="n">ToolRuntime</span><span class="p">,</span> <span class="n">tool</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nd">@tool</span>
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">get_last_user_message</span><span class="p">(</span><span class="n">runtime</span><span class="p">:</span> <span class="n">ToolRuntime</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">str</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;&#34;&#34;Get the most recent message from the user.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="k">for</span> <span class="n">message</span> <span class="ow">in</span> <span class="nb">reversed</span><span class="p">(</span><span class="n">runtime</span><span class="o">.</span><span class="n">state</span><span class="p">[</span><span class="s2">&#34;messages&#34;</span><span class="p">]):</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">message</span><span class="p">,</span> <span class="n">HumanMessage</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">            <span class="k">return</span> <span class="n">message</span><span class="o">.</span><span class="n">content</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="s2">&#34;No user messages found&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nd">@tool</span>
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">get_user_preference</span><span class="p">(</span><span class="n">pref_name</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">runtime</span><span class="p">:</span> <span class="n">ToolRuntime</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">str</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;&#34;&#34;Get a user preference value.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">runtime</span><span class="o">.</span><span class="n">state</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&#34;user_preferences&#34;</span><span class="p">,</span> <span class="p">{})</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">pref_name</span><span class="p">,</span> <span class="s2">&#34;Not set&#34;</span><span class="p">)</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>第二个 Tool 的 model-visible Schema 只有 <code>pref_name</code>，没有 <code>runtime</code>。</p>
<h5 id="update-state">Update state</h5>
<p>Tool 更新 State 时返回 <code>Command</code>。当目标是当前 graph 时：</p>
<ol>
<li>在 <code>Command.update</code> 写入 State 字段；</li>
<li>在 <code>Command.update[&quot;messages&quot;]</code> 中加入 <code>ToolMessage</code>；</li>
<li>结果写入 <code>ToolMessage.content</code>；</li>
<li><code>ToolMessage.tool_call_id</code> 必须等于 <code>runtime.tool_call_id</code>。</li>
</ol>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">langchain.agents</span> <span class="kn">import</span> <span class="n">AgentState</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">langchain.messages</span> <span class="kn">import</span> <span class="n">ToolMessage</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">langchain.tools</span> <span class="kn">import</span> <span class="n">ToolRuntime</span><span class="p">,</span> <span class="n">tool</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">langgraph.types</span> <span class="kn">import</span> <span class="n">Command</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">CustomState</span><span class="p">(</span><span class="n">AgentState</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="n">user_name</span><span class="p">:</span> <span class="nb">str</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nd">@tool</span>
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">set_user_name</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">new_name</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">runtime</span><span class="p">:</span> <span class="n">ToolRuntime</span><span class="p">[</span><span class="kc">None</span><span class="p">,</span> <span class="n">CustomState</span><span class="p">],</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Command</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;&#34;&#34;Set the user&#39;s name in the conversation state.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">Command</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="n">update</span><span class="o">=</span><span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="s2">&#34;user_name&#34;</span><span class="p">:</span> <span class="n">new_name</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="s2">&#34;messages&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">                <span class="n">ToolMessage</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">                    <span class="n">content</span><span class="o">=</span><span class="sa">f</span><span class="s2">&#34;User name set to </span><span class="si">{</span><span class="n">new_name</span><span class="si">}</span><span class="s2">.&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                    <span class="n">tool_call_id</span><span class="o">=</span><span class="n">runtime</span><span class="o">.</span><span class="n">tool_call_id</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                <span class="p">)</span>
</span></span><span class="line"><span class="cl">            <span class="p">],</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>每个 <code>AIMessage</code> tool call 都必须有对应的 <code>ToolMessage</code>。消息缺失或 ID 不匹配时，<code>ToolNode</code> 抛出 <code>ValueError</code>。多个 Tool 并行更新同一 State 字段时，应定义 reducer，明确覆盖、追加或其他合并规则。</p>
<h4 id="context">Context</h4>
<p>Context 是 invocation 时传入的不可变配置，包括 user ID、session details 和 application settings。使用 <code>context_schema</code> 声明结构，通过 <code>context=</code> 传入，Tool 从 <code>runtime.context</code> 读取。</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">dataclasses</span> <span class="kn">import</span> <span class="n">dataclass</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nd">@dataclass</span>
</span></span><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">UserContext</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="n">user_id</span><span class="p">:</span> <span class="nb">str</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nd">@tool</span>
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">get_account_info</span><span class="p">(</span><span class="n">runtime</span><span class="p">:</span> <span class="n">ToolRuntime</span><span class="p">[</span><span class="n">UserContext</span><span class="p">])</span> <span class="o">-&gt;</span> <span class="nb">str</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;&#34;&#34;Get the current user&#39;s account information.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="nb">str</span><span class="p">(</span><span class="n">USER_DATABASE</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">runtime</span><span class="o">.</span><span class="n">context</span><span class="o">.</span><span class="n">user_id</span><span class="p">,</span> <span class="s2">&#34;User not found&#34;</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">agent</span> <span class="o">=</span> <span class="n">create_agent</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">model</span><span class="o">=</span><span class="s2">&#34;openai:gpt-5.5&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">tools</span><span class="o">=</span><span class="p">[</span><span class="n">get_account_info</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">    <span class="n">context_schema</span><span class="o">=</span><span class="n">UserContext</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">result</span> <span class="o">=</span> <span class="n">agent</span><span class="o">.</span><span class="n">invoke</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span><span class="s2">&#34;messages&#34;</span><span class="p">:</span> <span class="p">[{</span><span class="s2">&#34;role&#34;</span><span class="p">:</span> <span class="s2">&#34;user&#34;</span><span class="p">,</span> <span class="s2">&#34;content&#34;</span><span class="p">:</span> <span class="s2">&#34;What&#39;s my balance?&#34;</span><span class="p">}]},</span>
</span></span><span class="line"><span class="cl">    <span class="n">config</span><span class="o">=</span><span class="p">{</span><span class="s2">&#34;configurable&#34;</span><span class="p">:</span> <span class="p">{</span><span class="s2">&#34;thread_id&#34;</span><span class="p">:</span> <span class="s2">&#34;thread-1&#34;</span><span class="p">}},</span>
</span></span><span class="line"><span class="cl">    <span class="n">context</span><span class="o">=</span><span class="n">UserContext</span><span class="p">(</span><span class="n">user_id</span><span class="o">=</span><span class="s2">&#34;user123&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p><code>thread_id</code> 标识 conversation；<code>user_id</code> 标识业务用户，二者不同。</p>
<h4 id="long-term-memorystore">Long-term memory(Store)</h4>
<p><code>BaseStore</code> 提供跨 conversation 持久化的数据。使用 <code>runtime.store</code> 访问，以 namespace/key 组织记录。</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="nd">@tool</span>
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">get_user_info</span><span class="p">(</span><span class="n">user_id</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">runtime</span><span class="p">:</span> <span class="n">ToolRuntime</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">str</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;&#34;&#34;Look up user info.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="n">item</span> <span class="o">=</span> <span class="n">runtime</span><span class="o">.</span><span class="n">store</span><span class="o">.</span><span class="n">get</span><span class="p">((</span><span class="s2">&#34;users&#34;</span><span class="p">,),</span> <span class="n">user_id</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="nb">str</span><span class="p">(</span><span class="n">item</span><span class="o">.</span><span class="n">value</span><span class="p">)</span> <span class="k">if</span> <span class="n">item</span> <span class="k">else</span> <span class="s2">&#34;Unknown user&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nd">@tool</span>
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">save_user_info</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">user_id</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">name</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">age</span><span class="p">:</span> <span class="nb">int</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">email</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">runtime</span><span class="p">:</span> <span class="n">ToolRuntime</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">str</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;&#34;&#34;Save user info.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="n">runtime</span><span class="o">.</span><span class="n">store</span><span class="o">.</span><span class="n">put</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="p">(</span><span class="s2">&#34;users&#34;</span><span class="p">,),</span> <span class="n">user_id</span><span class="p">,</span> <span class="p">{</span><span class="s2">&#34;name&#34;</span><span class="p">:</span> <span class="n">name</span><span class="p">,</span> <span class="s2">&#34;age&#34;</span><span class="p">:</span> <span class="n">age</span><span class="p">,</span> <span class="s2">&#34;email&#34;</span><span class="p">:</span> <span class="n">email</span><span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="s2">&#34;Successfully saved user info.&#34;</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>把同一 Store 传给 <code>create_agent(store=store)</code> 后，可在一个 session 保存、另一个 session 读取。</p>
<h4 id="streaming-writer">Streaming writer</h4>
<p><code>runtime.stream_writer</code> 可在 Tool 执行中发送实时 custom updates，适合长任务进度；Tool 最终仍用 <code>return</code> 返回完成结果。</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="nd">@tool</span>
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">get_weather</span><span class="p">(</span><span class="n">city</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">runtime</span><span class="p">:</span> <span class="n">ToolRuntime</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">str</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;&#34;&#34;Get weather for a given city.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="n">runtime</span><span class="o">.</span><span class="n">stream_writer</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Looking up data for city: </span><span class="si">{</span><span class="n">city</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">runtime</span><span class="o">.</span><span class="n">stream_writer</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Acquired data for city: </span><span class="si">{</span><span class="n">city</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="sa">f</span><span class="s2">&#34;It&#39;s always sunny in </span><span class="si">{</span><span class="n">city</span><span class="si">}</span><span class="s2">!&#34;</span>
</span></span></code></pre></td></tr></table>
</div>
</div><h4 id="execution-info">Execution info</h4>
<p><code>runtime.execution_info</code> 提供 <code>thread_id</code>、<code>run_id</code> 和 <code>node_attempt</code>，用于日志关联和重试判断。</p>
<h4 id="server-info">Server info</h4>
<p>在 LangGraph Server 上，<code>runtime.server_info</code> 提供 <code>assistant_id</code>、<code>graph_id</code> 和 authenticated user；非 Server 环境下为 <code>None</code>。</p>
<h3 id="tool-execution">Tool execution</h3>
<p>Tool 由 Agent 使用；Tool error handling 由 middleware 配置。</p>
<h4 id="tool-return-values">Tool return values</h4>
<h5 id="return-a-string">Return a string</h5>
<p>返回 human-readable <code>str</code>，供 model 下一步读取；框架自动将其转换为 <code>ToolMessage</code>。</p>
<h5 id="return-an-object">Return an object</h5>
<p>返回结构化 <code>object</code>，供 model 读取字段并推理。对象会序列化为 Tool output，但不会直接更新 graph State。</p>
<h5 id="return-multimodal-content">Return multimodal content</h5>
<p>多模态结果使用 standard content blocks，例如 text block 和 image block。结果转换成带 multimodal <code>content</code> 的 <code>ToolMessage</code>；可用 <code>message.content_blocks</code> 读取归一化 blocks。model 必须支持所返回的 image、audio 或 video modality。</p>
<h5 id="return-a-command">Return a Command</h5>
<p>需要更新 graph State 时返回 <code>Command</code>。若目标是当前 graph，必须显式提供与当前 tool call ID 匹配的 <code>ToolMessage</code>。普通 <code>str</code>/<code>object</code> 会自动包装为 <code>ToolMessage</code>，<code>Command</code> 不会。</p>
<h5 id="return-directly-from-a-tool">Return directly from a tool</h5>
<p><code>@tool(return_direct=True)</code> 使 agent loop 短路：Tool 正常执行，输出包装成 <code>ToolMessage</code> 后直接返回 caller，不再追加 model call。</p>
<p>适用于：Tool output 已是完整答案；希望避免额外 model call；需要 deterministic、unmodified output。</p>
<p>同一步包含多个并行 tool calls 时，会先执行整个批次；仅当每个 Tool 都设置 <code>return_direct=True</code> 时才路由到 <code>END</code>，最终响应包含全部 Tool outputs。</p>
<h5 id="return-a-command-with-return_direct">Return a Command with return_direct</h5>
<p><code>return_direct=True</code> 的 Tool 可返回 <code>Command</code>，在退出前更新 State。<code>Command</code> 仍不会自动生成 <code>ToolMessage</code>，必须手动加入 <code>tool_call_id=runtime.tool_call_id</code> 的消息。</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="nd">@tool</span><span class="p">(</span><span class="n">return_direct</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">fetch_and_store_order</span><span class="p">(</span><span class="n">order_id</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">runtime</span><span class="p">:</span> <span class="n">ToolRuntime</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Command</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;&#34;&#34;Fetch order status and store it in state.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="n">status</span> <span class="o">=</span> <span class="sa">f</span><span class="s2">&#34;Order </span><span class="si">{</span><span class="n">order_id</span><span class="si">}</span><span class="s2"> is shipped.&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">Command</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="n">update</span><span class="o">=</span><span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="s2">&#34;last_order_status&#34;</span><span class="p">:</span> <span class="n">status</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="s2">&#34;messages&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">                <span class="n">ToolMessage</span><span class="p">(</span><span class="n">content</span><span class="o">=</span><span class="n">status</span><span class="p">,</span> <span class="n">tool_call_id</span><span class="o">=</span><span class="n">runtime</span><span class="o">.</span><span class="n">tool_call_id</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">            <span class="p">],</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span></code></pre></td></tr></table>
</div>
</div><h4 id="error-handing">Error handing</h4>
<p>使用 middleware 重试失败 Tool，或把异常转换为 model 可处理的自定义 <code>ToolMessage</code>。异常结果的 <code>tool_call_id</code> 必须取原调用的 <code>request.tool_call[&quot;id&quot;]</code>。</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">langchain.agents.middleware</span> <span class="kn">import</span> <span class="n">wrap_tool_call</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">langchain.messages</span> <span class="kn">import</span> <span class="n">ToolMessage</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nd">@wrap_tool_call</span>
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">handle_tool_errors</span><span class="p">(</span><span class="n">request</span><span class="p">,</span> <span class="n">handler</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">ToolMessage</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="n">handler</span><span class="p">(</span><span class="n">request</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">except</span> <span class="ne">Exception</span> <span class="k">as</span> <span class="n">error</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="n">ToolMessage</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">            <span class="n">content</span><span class="o">=</span><span class="sa">f</span><span class="s2">&#34;Tool error: Please check your input. (</span><span class="si">{</span><span class="n">error</span><span class="si">}</span><span class="s2">)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="n">tool_call_id</span><span class="o">=</span><span class="n">request</span><span class="o">.</span><span class="n">tool_call</span><span class="p">[</span><span class="s2">&#34;id&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">        <span class="p">)</span>
</span></span></code></pre></td></tr></table>
</div>
</div><h4 id="state-injection">State injection</h4>
<p>Tool 通过框架注入且对 model 隐藏的 <code>ToolRuntime</code> 访问 State：读取用 <code>runtime.state</code>，更新用带匹配 <code>ToolMessage</code> 的 <code>Command</code>。</p>
<h3 id="dynamic-tool-section">Dynamic tool section</h3>
<p>Tool 太多会占用 context，太少会限制能力。动态 Tool 根据启动时是否已知分为两类。</p>
<h5 id="filtering-pre-registered-tools">Filtering pre-registered tools</h5>
<p>全部 Tool 在创建 Agent 时已知：先预注册，再依据 State、permissions 或 Context 动态过滤暴露给 model 的集合。适合 Tool 静态、availability 动态的场景。</p>
<h5 id="runtime-tool-register">Runtime tool register</h5>
<p>Tool 在 runtime 才发现或创建时，需要同时处理：</p>
<ol>
<li><code>wrap_model_call</code>：用 <code>request.override(tools=[...])</code> 加入本次 model request；</li>
<li><code>wrap_tool_call</code>：识别对应 name，用 <code>request.override(tool=...)</code> 提供执行对象。</li>
</ol>
<p>只让 model 看见而不提供执行处理器，runtime 无法完成调用。此方式适合 MCP server、database、user configuration 和 external registry。</p>
<h3 id="headless-tools">Headless tools</h3>
<p>服务端只注册 name、description 和 parameter Schema，实现只在客户端。model 可正常调用，但执行通过 interrupt/resume 在进程外完成。</p>
<h4 id="when-to-use">when to use</h4>
<p>用于依赖客户端 environment、device 或 UI 的工作：Browser APIs、privacy/locality、latency、structured safe effects。</p>
<h4 id="how-it-work">How it work</h4>
<ol>
<li>定义 Tool Schema，不提供 in-process implementation；</li>
<li>将定义注册给 <code>create_agent</code>；</li>
<li>调用时 graph 暂停，返回 <code>{&quot;type&quot;:&quot;tool&quot;,&quot;tool_call&quot;:{&quot;id&quot;,&quot;name&quot;,&quot;args&quot;}}</code>；</li>
<li>客户端执行，再 resume graph。</li>
</ol>
<p><code>onTool</code> callback 可观察 <code>start</code>、<code>success</code>、<code>error</code>，用于 spinner 或 toast。</p>
<h3 id="prebuilt-tools">Prebuilt tools</h3>
<p>LangChain 提供 web search、code interpretation、database access 等 prebuilt tools/toolkits，可直接集成，无需自行实现。</p>
<h3 id="tools-from-mcp">Tools from MCP</h3>
<p>MCP 统一应用向 model 暴露 Tools 的方式。<code>MCPAdapter</code> 发现 MCP server 的 Tools，将 <code>list_tools()</code> 结果转换为 LangChain Tools，再传给 <code>create_agent</code>。</p>
<h3 id="server-side-tool-use">Server-side tool use</h3>
<p>部分 chat models 提供由 provider 执行的 built-in Tools，例如 web search 和 code interpreter，无需自行定义或托管 Tool logic；具体配置取决于 model integration。</p>
<h2 id="153837--short-term_memorymd">15:38:37 — Short-term_memory.md</h2>
<p>Conversation history 是最常见的 short-term memory；Message 包含 instructions 和 inputs。</p>
<h3 id="usage">Usage</h3>
<p>为 Agent 添加 short-term memory，需要在 <code>create_agent</code> 时指定 <code>checkpointer</code>。Short-term memory 是 Agent State 的一部分；checkpointer 按 thread 持久化 State，使 conversation 可恢复。</p>
<p>State 在 Agent invocation 或 tool call 等 step 完成时更新，在每个 step 开始时读取。相同 <code>thread_id</code> 恢复相同 conversation；不同 <code>thread_id</code> 使用独立 State。</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">langchain.agents</span> <span class="kn">import</span> <span class="n">create_agent</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">langgraph.checkpoint.memory</span> <span class="kn">import</span> <span class="n">InMemorySaver</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">agent</span> <span class="o">=</span> <span class="n">create_agent</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">model</span><span class="o">=</span><span class="s2">&#34;openai:gpt-5.5&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">tools</span><span class="o">=</span><span class="p">[</span><span class="n">get_user_info</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">    <span class="n">checkpointer</span><span class="o">=</span><span class="n">InMemorySaver</span><span class="p">(),</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">config</span> <span class="o">=</span> <span class="p">{</span><span class="s2">&#34;configurable&#34;</span><span class="p">:</span> <span class="p">{</span><span class="s2">&#34;thread_id&#34;</span><span class="p">:</span> <span class="s2">&#34;1&#34;</span><span class="p">}}</span>
</span></span><span class="line"><span class="cl"><span class="n">agent</span><span class="o">.</span><span class="n">invoke</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span><span class="s2">&#34;messages&#34;</span><span class="p">:</span> <span class="p">[{</span><span class="s2">&#34;role&#34;</span><span class="p">:</span> <span class="s2">&#34;user&#34;</span><span class="p">,</span> <span class="s2">&#34;content&#34;</span><span class="p">:</span> <span class="s2">&#34;Hi! My name is Bob.&#34;</span><span class="p">}]},</span>
</span></span><span class="line"><span class="cl">    <span class="n">config</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">result</span> <span class="o">=</span> <span class="n">agent</span><span class="o">.</span><span class="n">invoke</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span><span class="s2">&#34;messages&#34;</span><span class="p">:</span> <span class="p">[{</span><span class="s2">&#34;role&#34;</span><span class="p">:</span> <span class="s2">&#34;user&#34;</span><span class="p">,</span> <span class="s2">&#34;content&#34;</span><span class="p">:</span> <span class="s2">&#34;What&#39;s my name?&#34;</span><span class="p">}]},</span>
</span></span><span class="line"><span class="cl">    <span class="n">config</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span></code></pre></td></tr></table>
</div>
</div><h4 id="in-production">In production</h4>
<p>生产环境使用 database-backed checkpointer；<code>InMemorySaver</code> 只适合本地示例和测试。</p>
<h3 id="customizing-agent-memory">Customizing agent memory</h3>
<p>Agent 使用 <code>AgentState</code> 管理 short-term memory，其中 <code>messages</code> key 保存 conversation history。需要额外字段时继承 <code>AgentState</code>，通过 <code>state_schema=</code> 传给 <code>create_agent</code>。</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">langchain.agents</span> <span class="kn">import</span> <span class="n">AgentState</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">CustomAgentState</span><span class="p">(</span><span class="n">AgentState</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="n">user_id</span><span class="p">:</span> <span class="nb">str</span>
</span></span><span class="line"><span class="cl">    <span class="n">preferences</span><span class="p">:</span> <span class="nb">dict</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">agent</span> <span class="o">=</span> <span class="n">create_agent</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">model</span><span class="o">=</span><span class="s2">&#34;openai:gpt-5.5&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">tools</span><span class="o">=</span><span class="p">[</span><span class="n">get_user_info</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">    <span class="n">state_schema</span><span class="o">=</span><span class="n">CustomAgentState</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">checkpointer</span><span class="o">=</span><span class="n">InMemorySaver</span><span class="p">(),</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">result</span> <span class="o">=</span> <span class="n">agent</span><span class="o">.</span><span class="n">invoke</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;messages&#34;</span><span class="p">:</span> <span class="p">[{</span><span class="s2">&#34;role&#34;</span><span class="p">:</span> <span class="s2">&#34;user&#34;</span><span class="p">,</span> <span class="s2">&#34;content&#34;</span><span class="p">:</span> <span class="s2">&#34;Hello&#34;</span><span class="p">}],</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;user_id&#34;</span><span class="p">:</span> <span class="s2">&#34;user_123&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;preferences&#34;</span><span class="p">:</span> <span class="p">{</span><span class="s2">&#34;theme&#34;</span><span class="p">:</span> <span class="s2">&#34;dark&#34;</span><span class="p">},</span>
</span></span><span class="line"><span class="cl">    <span class="p">},</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span><span class="s2">&#34;configurable&#34;</span><span class="p">:</span> <span class="p">{</span><span class="s2">&#34;thread_id&#34;</span><span class="p">:</span> <span class="s2">&#34;1&#34;</span><span class="p">}},</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p><code>state_schema</code> 定义 State 结构；<code>checkpointer</code> 负责按 thread 保存它。</p>
<h3 id="common-patterns">Common patterns</h3>
<p>以下模式用于处理 conversation history 超过 LLM context window。</p>
<h4 id="trim-message截断消息">Trim message(截断消息)</h4>
<p>接近 token limit 时，根据 token count 截断，并指定 boundary strategy。也可在 <code>@before_model</code> 中清空旧 messages，再写回首条与最近 messages。</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">langchain.agents.middleware</span> <span class="kn">import</span> <span class="n">before_model</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">langchain.messages</span> <span class="kn">import</span> <span class="n">RemoveMessage</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">langgraph.graph.message</span> <span class="kn">import</span> <span class="n">REMOVE_ALL_MESSAGES</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nd">@before_model</span>
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">trim_messages</span><span class="p">(</span><span class="n">state</span><span class="p">,</span> <span class="n">runtime</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">dict</span> <span class="o">|</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;&#34;&#34;Keep only the first and most recent messages.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="n">messages</span> <span class="o">=</span> <span class="n">state</span><span class="p">[</span><span class="s2">&#34;messages&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="n">messages</span><span class="p">)</span> <span class="o">&lt;=</span> <span class="mi">3</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="kc">None</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">first_message</span> <span class="o">=</span> <span class="n">messages</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">    <span class="n">recent_messages</span> <span class="o">=</span> <span class="n">messages</span><span class="p">[</span><span class="o">-</span><span class="mi">3</span><span class="p">:]</span> <span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="n">messages</span><span class="p">)</span> <span class="o">%</span> <span class="mi">2</span> <span class="o">==</span> <span class="mi">0</span> <span class="k">else</span> <span class="n">messages</span><span class="p">[</span><span class="o">-</span><span class="mi">4</span><span class="p">:]</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;messages&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">            <span class="n">RemoveMessage</span><span class="p">(</span><span class="nb">id</span><span class="o">=</span><span class="n">REMOVE_ALL_MESSAGES</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">            <span class="n">first_message</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="o">*</span><span class="n">recent_messages</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="p">]</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p><code>None</code> 表示不更新 State。选择最近 3 或 4 条是为了尽量维持有效的 request/response 边界。</p>
<h4 id="delete-message">Delete message</h4>
<p>使用 <code>RemoveMessage</code> 删除指定 message 或清空全部 history。Messages State key 必须使用 <code>add_messages</code> reducer；默认 <code>AgentState</code> 已提供。</p>
<h6 id="to-remove-specific-messages">To remove specific messages:</h6>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span><span class="lnt">8
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">langchain.messages</span> <span class="kn">import</span> <span class="n">RemoveMessage</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">delete_messages</span><span class="p">(</span><span class="n">state</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="n">messages</span> <span class="o">=</span> <span class="n">state</span><span class="p">[</span><span class="s2">&#34;messages&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="n">messages</span><span class="p">)</span> <span class="o">&gt;</span> <span class="mi">2</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="s2">&#34;messages&#34;</span><span class="p">:</span> <span class="p">[</span><span class="n">RemoveMessage</span><span class="p">(</span><span class="nb">id</span><span class="o">=</span><span class="n">message</span><span class="o">.</span><span class="n">id</span><span class="p">)</span> <span class="k">for</span> <span class="n">message</span> <span class="ow">in</span> <span class="n">messages</span><span class="p">[:</span><span class="mi">2</span><span class="p">]]</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><h6 id="to-remove-all-messages">To remove all messages:</h6>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">langchain.messages</span> <span class="kn">import</span> <span class="n">RemoveMessage</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">langgraph.graph.message</span> <span class="kn">import</span> <span class="n">REMOVE_ALL_MESSAGES</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">delete_messages</span><span class="p">(</span><span class="n">state</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="p">{</span><span class="s2">&#34;messages&#34;</span><span class="p">:</span> <span class="p">[</span><span class="n">RemoveMessage</span><span class="p">(</span><span class="nb">id</span><span class="o">=</span><span class="n">REMOVE_ALL_MESSAGES</span><span class="p">)]}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>删除后必须保持有效消息历史：部分 provider 要求以 <code>user</code> message 开始；多数 provider 要求带 tool call 的 <code>assistant</code> message 后紧跟对应的 <code>tool</code> result，不能只删除配对的一侧。</p>
<h4 id="summarize-messages">Summarize messages</h4>
<p>Trim 和 Delete 会丢失信息。需要保留旧语义时，使用内置 <code>SummarizationMiddleware</code>。</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">langchain.agents.middleware</span> <span class="kn">import</span> <span class="n">SummarizationMiddleware</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">agent</span> <span class="o">=</span> <span class="n">create_agent</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">model</span><span class="o">=</span><span class="s2">&#34;openai:gpt-5.5&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">tools</span><span class="o">=</span><span class="p">[</span><span class="o">...</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">    <span class="n">middleware</span><span class="o">=</span><span class="p">[</span>
</span></span><span class="line"><span class="cl">        <span class="n">SummarizationMiddleware</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">            <span class="n">model</span><span class="o">=</span><span class="s2">&#34;openai:gpt-5.4-mini&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="n">trigger</span><span class="o">=</span><span class="p">(</span><span class="s2">&#34;tokens&#34;</span><span class="p">,</span> <span class="mi">4000</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">            <span class="n">keep</span><span class="o">=</span><span class="p">(</span><span class="s2">&#34;messages&#34;</span><span class="p">,</span> <span class="mi">20</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">],</span>
</span></span><span class="line"><span class="cl">    <span class="n">checkpointer</span><span class="o">=</span><span class="n">checkpointer</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>示例在 4000 tokens 时触发摘要，保留最近 20 条 messages。</p>
<h3 id="access-memory">Access memory</h3>
<h4 id="tools-1">tools</h4>
<h5 id="read-short-term-memory-in-a-tool">Read short-term memory in a tool</h5>
<p>Tool 使用对 model 隐藏的 <code>runtime: ToolRuntime</code>，通过 <code>runtime.state</code> 读取 short-term memory。</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="nd">@tool</span>
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">get_user_info</span><span class="p">(</span><span class="n">runtime</span><span class="p">:</span> <span class="n">ToolRuntime</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">str</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;&#34;&#34;Look up user info.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="n">user_id</span> <span class="o">=</span> <span class="n">runtime</span><span class="o">.</span><span class="n">state</span><span class="p">[</span><span class="s2">&#34;user_id&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="s2">&#34;User is John Smith&#34;</span> <span class="k">if</span> <span class="n">user_id</span> <span class="o">==</span> <span class="s2">&#34;user_123&#34;</span> <span class="k">else</span> <span class="s2">&#34;Unknown user&#34;</span>
</span></span></code></pre></td></tr></table>
</div>
</div><h5 id="write-short-term-memory-form-tools">write short-term memory form tools</h5>
<p>Tool 写 short-term memory 时返回 <code>Command</code>，并在 <code>Command.update[&quot;messages&quot;]</code> 中加入 <code>tool_call_id=runtime.tool_call_id</code> 的 <code>ToolMessage</code>。</p>
<h4 id="prompt">Prompt</h4>
<p>使用 middleware 根据 conversation history 或 custom State fields 创建 dynamic prompt。<code>@dynamic_prompt</code> 可通过 <code>request.runtime.context</code> 读取 invocation Context。</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">TypedDict</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">langchain.agents.middleware</span> <span class="kn">import</span> <span class="n">ModelRequest</span><span class="p">,</span> <span class="n">dynamic_prompt</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">CustomContext</span><span class="p">(</span><span class="n">TypedDict</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="n">user_name</span><span class="p">:</span> <span class="nb">str</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nd">@dynamic_prompt</span>
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">dynamic_system_prompt</span><span class="p">(</span><span class="n">request</span><span class="p">:</span> <span class="n">ModelRequest</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">str</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="n">user_name</span> <span class="o">=</span> <span class="n">request</span><span class="o">.</span><span class="n">runtime</span><span class="o">.</span><span class="n">context</span><span class="p">[</span><span class="s2">&#34;user_name&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="sa">f</span><span class="s2">&#34;You are a helpful assistant. Address the user as </span><span class="si">{</span><span class="n">user_name</span><span class="si">}</span><span class="s2">.&#34;</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>创建 Agent 时传入 <code>context_schema=CustomContext</code>，调用时通过 <code>context=CustomContext(...)</code> 提供值。</p>
<h4 id="before-model">Before model</h4>
<p><code>@before_model</code> 在每次 model call 前处理 State，适合 trim、删除、注入或重排 messages。返回 <code>None</code> 表示无修改，返回 <code>dict</code> 表示要合并进 State 的更新。</p>
<p>After model</p>
<p><code>@after_model</code> 在 model call 后检查 State。可读取最后一条 response，用 <code>RemoveMessage(id=last_message.id)</code> 删除不符合要求的输出。</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">langchain.agents.middleware</span> <span class="kn">import</span> <span class="n">after_model</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">langchain.messages</span> <span class="kn">import</span> <span class="n">RemoveMessage</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nd">@after_model</span>
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">validate_response</span><span class="p">(</span><span class="n">state</span><span class="p">,</span> <span class="n">runtime</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">dict</span> <span class="o">|</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;&#34;&#34;Remove messages containing sensitive words.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="n">stop_words</span> <span class="o">=</span> <span class="p">[</span><span class="s2">&#34;password&#34;</span><span class="p">,</span> <span class="s2">&#34;secret&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">    <span class="n">last_message</span> <span class="o">=</span> <span class="n">state</span><span class="p">[</span><span class="s2">&#34;messages&#34;</span><span class="p">][</span><span class="o">-</span><span class="mi">1</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="nb">any</span><span class="p">(</span><span class="n">word</span> <span class="ow">in</span> <span class="n">last_message</span><span class="o">.</span><span class="n">content</span> <span class="k">for</span> <span class="n">word</span> <span class="ow">in</span> <span class="n">stop_words</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="p">{</span><span class="s2">&#34;messages&#34;</span><span class="p">:</span> <span class="p">[</span><span class="n">RemoveMessage</span><span class="p">(</span><span class="nb">id</span><span class="o">=</span><span class="n">last_message</span><span class="o">.</span><span class="n">id</span><span class="p">)]}</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="kc">None</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>该 hook 修改最终 State；对于实时 Stream，只在 <code>after_model</code> 删除可能太晚，流式输出需在离开 run 前过滤。</p>
<h2 id="162701--event_streamingmd">16:27:01 — Event_Streaming.md</h2>
<p>LangChain Agents 构建在 LangGraph 上，因此支持相同的 streaming stack，并提供 messages、tool calls、State 和 custom updates 等 Agent-focused projections。</p>
<p>多数场景使用 <code>stream_events(..., version=&quot;v3&quot;)</code>。它返回带 typed projections 的 run object，各 projection 可独立消费，无需解析 stream-mode tuples。</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">stream</span> <span class="o">=</span> <span class="n">agent</span><span class="o">.</span><span class="n">stream_events</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span><span class="s2">&#34;messages&#34;</span><span class="p">:</span> <span class="p">[{</span><span class="s2">&#34;role&#34;</span><span class="p">:</span> <span class="s2">&#34;user&#34;</span><span class="p">,</span> <span class="s2">&#34;content&#34;</span><span class="p">:</span> <span class="s2">&#34;What is the weather in SF?&#34;</span><span class="p">}]},</span>
</span></span><span class="line"><span class="cl">    <span class="n">version</span><span class="o">=</span><span class="s2">&#34;v3&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">for</span> <span class="n">message</span> <span class="ow">in</span> <span class="n">stream</span><span class="o">.</span><span class="n">messages</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="k">for</span> <span class="n">delta</span> <span class="ow">in</span> <span class="n">message</span><span class="o">.</span><span class="n">text</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="n">delta</span><span class="p">,</span> <span class="n">end</span><span class="o">=</span><span class="s2">&#34;&#34;</span><span class="p">,</span> <span class="n">flush</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">final_state</span> <span class="o">=</span> <span class="n">stream</span><span class="o">.</span><span class="n">output</span>
</span></span></code></pre></td></tr></table>
</div>
</div><h3 id="what-you-can-stream">What you can stream</h3>
<table>
	<thead>
			<tr>
					<th style="text-align: left">Projection</th>
					<th style="text-align: left">内容</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td style="text-align: left"><code>for event in stream</code></td>
					<td style="text-align: left">带完整 envelope、可访问全部 channel 的 raw protocol events</td>
			</tr>
			<tr>
					<td style="text-align: left"><code>stream.messages</code></td>
					<td style="text-align: left">每次 LLM call 的 model message streams</td>
			</tr>
			<tr>
					<td style="text-align: left"><code>message.text</code></td>
					<td style="text-align: left">Text deltas 与 final text</td>
			</tr>
			<tr>
					<td style="text-align: left"><code>message.reasoning</code></td>
					<td style="text-align: left">Model 支持时的 reasoning deltas</td>
			</tr>
			<tr>
					<td style="text-align: left"><code>message.tool_calls</code></td>
					<td style="text-align: left">Tool-call argument chunks 与 finalized tool calls</td>
			</tr>
			<tr>
					<td style="text-align: left"><code>message.output</code></td>
					<td style="text-align: left">本次 model call 的 final message object</td>
			</tr>
			<tr>
					<td style="text-align: left"><code>stream.values</code></td>
					<td style="text-align: left">Agent State snapshots</td>
			</tr>
			<tr>
					<td style="text-align: left"><code>stream.output</code></td>
					<td style="text-align: left">Final Agent State</td>
			</tr>
			<tr>
					<td style="text-align: left"><code>stream.subgraphs</code></td>
					<td style="text-align: left">Nested graph runs，包括 sub-agents 和普通 subgraphs</td>
			</tr>
			<tr>
					<td style="text-align: left"><code>stream.extensions</code></td>
					<td style="text-align: left">Custom transformer projections</td>
			</tr>
			<tr>
					<td style="text-align: left"><code>stream.tool_calls</code></td>
					<td style="text-align: left">Tool lifecycle、inputs、output deltas、final output、errors</td>
			</tr>
	</tbody>
</table>
<p><code>stream.messages</code> 产生 <code>ChatModelStream</code>。每个 message stream 暴露 <code>.text</code>、<code>.reasoning</code>、<code>.tool_calls</code>、<code>.output</code>。同步 projection 可实时迭代，也可排空后读取最终值：<code>str(message.text)</code> 得到最终文本，<code>message.tool_calls.get()</code> 得到完整 Tool calls。</p>
<h3 id="agent-messages">Agent messages</h3>
<p>需要每次 LLM call 的 model output 时使用 <code>stream.messages</code>。<code>message.output</code> 是 finalized AI message，包含 provider-specific content blocks；token usage 位于 <code>message.output.usage_metadata</code>。</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="k">for</span> <span class="n">message</span> <span class="ow">in</span> <span class="n">stream</span><span class="o">.</span><span class="n">messages</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;[</span><span class="si">{</span><span class="n">message</span><span class="o">.</span><span class="n">node</span><span class="si">}</span><span class="s2">] &#34;</span><span class="p">,</span> <span class="n">end</span><span class="o">=</span><span class="s2">&#34;&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">for</span> <span class="n">delta</span> <span class="ow">in</span> <span class="n">message</span><span class="o">.</span><span class="n">text</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="n">delta</span><span class="p">,</span> <span class="n">end</span><span class="o">=</span><span class="s2">&#34;&#34;</span><span class="p">,</span> <span class="n">flush</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="n">message</span><span class="o">.</span><span class="n">output</span><span class="o">.</span><span class="n">usage_metadata</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="n">message</span><span class="o">.</span><span class="n">output</span><span class="o">.</span><span class="n">usage_metadata</span><span class="p">)</span>
</span></span></code></pre></td></tr></table>
</div>
</div><h3 id="reasoning-content">Reasoning content</h3>
<p><code>message.reasoning</code> 与 <code>.text</code> 用法相同，但只有 model 发出 reasoning blocks 时才有内容。</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="k">for</span> <span class="n">message</span> <span class="ow">in</span> <span class="n">stream</span><span class="o">.</span><span class="n">messages</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="k">for</span> <span class="n">delta</span> <span class="ow">in</span> <span class="n">message</span><span class="o">.</span><span class="n">reasoning</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;[thinking] </span><span class="si">{</span><span class="n">delta</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">,</span> <span class="n">end</span><span class="o">=</span><span class="s2">&#34;&#34;</span><span class="p">,</span> <span class="n">flush</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">for</span> <span class="n">delta</span> <span class="ow">in</span> <span class="n">message</span><span class="o">.</span><span class="n">text</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="n">delta</span><span class="p">,</span> <span class="n">end</span><span class="o">=</span><span class="s2">&#34;&#34;</span><span class="p">,</span> <span class="n">flush</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
</span></span></code></pre></td></tr></table>
</div>
</div><h3 id="tool-calls">Tool calls</h3>
<p>Tool calls 有两种 projection：</p>
<ul>
<li><code>message.tool_calls</code>：model 生成 Tool call 时，流式输出参数 chunks 和 finalized call；</li>
<li><code>stream.tool_calls</code>：Tool 开始执行后，流式输出 lifecycle、input、output deltas、final output、error。</li>
</ul>
<p>前者观察 model 正在构造什么调用，后者观察 Tool 实际如何执行。</p>
<h3 id="streaming-sub-agent">Streaming sub-agent</h3>
<p>当一个 <code>create_agent</code> 调用另一个已命名的 <code>create_agent</code> 时，内部 Agent 事件位于嵌套 namespace。传给 <code>create_agent</code> 的 <code>name=</code> 用于标识内部 Agent。</p>
<p>命名 sub-agent 出现在 <code>stream.subagents</code> projection；每个 handle 暴露 <code>.messages</code>、<code>.values</code>、<code>.tool_calls</code>、<code>.output</code>，以及 <code>.name</code>、<code>.cause</code>。</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="k">for</span> <span class="n">subagent</span> <span class="ow">in</span> <span class="n">stream</span><span class="o">.</span><span class="n">subagents</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="n">subagent</span><span class="o">.</span><span class="n">name</span><span class="si">}</span><span class="s2">: &#34;</span><span class="p">,</span> <span class="n">end</span><span class="o">=</span><span class="s2">&#34;&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">for</span> <span class="n">message</span> <span class="ow">in</span> <span class="n">subagent</span><span class="o">.</span><span class="n">messages</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="k">for</span> <span class="n">token</span> <span class="ow">in</span> <span class="n">message</span><span class="o">.</span><span class="n">text</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="nb">print</span><span class="p">(</span><span class="n">token</span><span class="p">,</span> <span class="n">end</span><span class="o">=</span><span class="s2">&#34;&#34;</span><span class="p">,</span> <span class="n">flush</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
</span></span></code></pre></td></tr></table>
</div>
</div><h3 id="state-and-final-output">state and final output</h3>
<p><code>stream.values</code> 获取运行过程中的 State snapshots；<code>stream.output</code> 获取最终 Agent State。</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="k">for</span> <span class="n">snapshot</span> <span class="ow">in</span> <span class="n">stream</span><span class="o">.</span><span class="n">values</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="n">snapshot</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">final_state</span> <span class="o">=</span> <span class="n">stream</span><span class="o">.</span><span class="n">output</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p><code>message.output</code> 是单次 model call 的最终 AI message；<code>stream.output</code> 是整个 Agent run 的最终 State。</p>
<h3 id="multiple-projections">Multiple projections</h3>
<p>Async code 使用 <code>astream_events</code> 与 <code>asyncio.gather</code> 并发消费多个 projections。</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">asyncio</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">stream</span> <span class="o">=</span> <span class="k">await</span> <span class="n">agent</span><span class="o">.</span><span class="n">astream_events</span><span class="p">(</span><span class="nb">input</span><span class="p">,</span> <span class="n">version</span><span class="o">=</span><span class="s2">&#34;v3&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">consume_messages</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">    <span class="k">async</span> <span class="k">for</span> <span class="n">message</span> <span class="ow">in</span> <span class="n">stream</span><span class="o">.</span><span class="n">messages</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="k">await</span> <span class="n">message</span><span class="o">.</span><span class="n">text</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">consume_tool_calls</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">    <span class="k">async</span> <span class="k">for</span> <span class="n">call</span> <span class="ow">in</span> <span class="n">stream</span><span class="o">.</span><span class="n">tool_calls</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="n">call</span><span class="o">.</span><span class="n">tool_name</span><span class="p">,</span> <span class="n">call</span><span class="o">.</span><span class="n">input</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">await</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">gather</span><span class="p">(</span><span class="n">consume_messages</span><span class="p">(),</span> <span class="n">consume_tool_calls</span><span class="p">())</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>Synchronous code 使用 <code>stream.interleave(...)</code>，返回 <code>(name, item)</code> 区分 projection。</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="k">for</span> <span class="n">name</span><span class="p">,</span> <span class="n">item</span> <span class="ow">in</span> <span class="n">stream</span><span class="o">.</span><span class="n">interleave</span><span class="p">(</span><span class="s2">&#34;messages&#34;</span><span class="p">,</span> <span class="s2">&#34;tool_calls&#34;</span><span class="p">,</span> <span class="s2">&#34;values&#34;</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="n">name</span> <span class="o">==</span> <span class="s2">&#34;messages&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="n">item</span><span class="o">.</span><span class="n">text</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">elif</span> <span class="n">name</span> <span class="o">==</span> <span class="s2">&#34;tool_calls&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="n">item</span><span class="o">.</span><span class="n">tool_name</span><span class="p">,</span> <span class="n">item</span><span class="o">.</span><span class="n">input</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">elif</span> <span class="n">name</span> <span class="o">==</span> <span class="s2">&#34;values&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="n">item</span><span class="p">)</span>
</span></span></code></pre></td></tr></table>
</div>
</div><h3 id="custom-updates">Custom updates</h3>
<p>内置 projection 不够时，使用 custom stream transformer，并从 <code>stream.extensions</code> 读取扩展 projection。</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span><span class="lnt">8
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">stream</span> <span class="o">=</span> <span class="n">agent</span><span class="o">.</span><span class="n">stream_events</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="nb">input</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">version</span><span class="o">=</span><span class="s2">&#34;v3&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">transformers</span><span class="o">=</span><span class="p">[</span><span class="n">ToolActivityTransformer</span><span class="p">],</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">for</span> <span class="n">activity</span> <span class="ow">in</span> <span class="n">stream</span><span class="o">.</span><span class="n">extensions</span><span class="p">[</span><span class="s2">&#34;tool_activity&#34;</span><span class="p">]:</span>
</span></span><span class="line"><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="n">activity</span><span class="p">)</span>
</span></span></code></pre></td></tr></table>
</div>
</div><h4 id="register-transformers-on-middleware">Register transformers on middleware</h4>
<p>Middleware 可在 hooks 和 Tools 旁声明 transformer factories。将 <code>AgentMiddleware.transformers</code> 设置为 factories 序列；每个 factory 形如 <code>Callable[[tuple[str, ...]], StreamTransformer]</code>，框架调用 <code>factory(scope)</code>。</p>
<p>Root mux 的 <code>scope</code> 为 <code>()</code>，subgraph 为非空 tuple。每次返回新的 transformer，使各 subgraph 隔离。</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">langchain.agents.middleware</span> <span class="kn">import</span> <span class="n">AgentMiddleware</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">ToolActivityMiddleware</span><span class="p">(</span><span class="n">AgentMiddleware</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="n">transformers</span> <span class="o">=</span> <span class="p">(</span><span class="n">ToolActivityTransformer</span><span class="p">,)</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p><code>create_agent</code> 编译时合并 middleware factories 与自身 <code>transformers=</code>，顺序为：</p>
<ol>
<li>内置 <code>ToolCallTransformer</code>；</li>
<li>Middleware factories，按 middleware 顺序；</li>
<li>调用方传给 <code>create_agent(transformers=...)</code> 的 factories。</li>
</ol>
<p>这让内置 Tool call projection 位于消费者 transformer 之前，并让调用方条目拥有最终处理权。</p>
<p><code>PIIMiddleware(..., apply_to_output=True)</code> 会在 text deltas、tool call arguments、tool outputs、State snapshots 离开 run 前清除 PII。</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">agent</span> <span class="o">=</span> <span class="n">create_agent</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">model</span><span class="o">=</span><span class="s2">&#34;openai:gpt-5-nano&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">tools</span><span class="o">=</span><span class="p">[],</span>
</span></span><span class="line"><span class="cl">    <span class="n">middleware</span><span class="o">=</span><span class="p">[</span>
</span></span><span class="line"><span class="cl">        <span class="n">PIIMiddleware</span><span class="p">(</span><span class="s2">&#34;email&#34;</span><span class="p">,</span> <span class="n">strategy</span><span class="o">=</span><span class="s2">&#34;redact&#34;</span><span class="p">,</span> <span class="n">apply_to_output</span><span class="o">=</span><span class="kc">True</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">    <span class="p">],</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>若只依赖 <code>after_model</code> 做 State-level filtering，原始 PII 可能已通过 <code>stream_events(version=&quot;v3&quot;)</code> 实时发送给 reader。</p>
<h2 id="165227--streamingmd">16:52:27 — Streaming.md</h2>
<h3 id="overview">Overview</h3>
<p>Streaming 支持：</p>
<ul>
<li>Stream Agent progress；</li>
<li>Stream LLM tokens；</li>
<li>Streaming thinking/reasoning tokens；</li>
<li>Stream custom updates；</li>
<li>从 <code>updates</code>、<code>messages</code>、<code>custom</code> 中选择一个或多个 modes。</li>
</ul>
<h3 id="supported-stream-modes">Supported stream modes</h3>
<p>将一个 mode 或多个 modes 的 list 传给 <code>stream</code> / <code>astream</code>。</p>
<table>
	<thead>
			<tr>
					<th style="text-align: left">Mode</th>
					<th style="text-align: left">Description</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td style="text-align: left"><code>updates</code></td>
					<td style="text-align: left">每个 Agent step 后发送 State updates；同一步运行多个 nodes 时分别发送</td>
			</tr>
			<tr>
					<td style="text-align: left"><code>messages</code></td>
					<td style="text-align: left">从所有调用 LLM 的 graph nodes 发送 <code>(token, metadata)</code> tuples</td>
			</tr>
			<tr>
					<td style="text-align: left"><code>custom</code></td>
					<td style="text-align: left">发送 graph nodes 内通过 stream writer 写出的 custom data</td>
			</tr>
	</tbody>
</table>
<h3 id="agent-progress">Agent progress</h3>
<p>使用 <code>stream_mode=&quot;updates&quot;</code> 的 <code>stream</code> 或 <code>astream</code>，在每个 Agent step 后接收事件。一次 Tool call 的典型顺序是：</p>
<ol>
<li><strong>LLM node</strong>：输出带 Tool call requests 的 <code>AIMessage</code>；</li>
<li><strong>Tool node</strong>：输出带 execution result 的 <code>ToolMessage</code>；</li>
<li><strong>LLM node</strong>：输出 final AI response。</li>
</ol>
]]></content:encoded>
    </item>
  </channel>
</rss>
