<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:atom="http://www.w3.org/2005/Atom" version="2.0">
    <channel>
      <title>bigfish的网络博客 - Pi</title>
      <link>https://blog.bigfisharc.cn</link>
      <description>记录技术和生活</description>
      <generator>Zola</generator>
      <language>zh</language>
      <atom:link href="https://blog.bigfisharc.cn/tags/pi/rss.xml" rel="self" type="application/rss+xml"/>
      <lastBuildDate>Sun, 04 Oct 2026 00:00:00 +0000</lastBuildDate>
      <item>
          <title>从 0 到 1 构建 Agent：为什么需要 Agent Loop，核心代码怎么写</title>
          <pubDate>Sun, 04 Oct 2026 00:00:00 +0000</pubDate>
          <author>bigfish</author>
          <link>https://blog.bigfisharc.cn/blog/20261004-rpi-rust-agent-loop-draft/</link>
          <guid>https://blog.bigfisharc.cn/blog/20261004-rpi-rust-agent-loop-draft/</guid>
          <description xml:base="https://blog.bigfisharc.cn/blog/20261004-rpi-rust-agent-loop-draft/">&lt;p&gt;如果你已经接通了大模型 API，做出了一个能聊天的小应用，下一步可能就想让它帮你干点事：读一下项目文件，改一处配置，再跑个命令看看结果。&lt;/p&gt;
&lt;p&gt;但从“能聊天”走到“能执行”，中间那段代码该怎么写？模型已经返回了工具名称和参数，是直接调用函数就行，还是要再请求一次模型？网上讲 Agent 的文章不少，真要自己动手时，往往还是卡在这里。&lt;/p&gt;
&lt;p&gt;上一篇聊了用 Rust 重写 Pi，以及启动时间的测试。这篇就顺着这个问题看实现。我们选一个能亲手检查的任务：让 Agent 把文字写进文件，再告诉我们执行结果。&lt;/p&gt;
&lt;h2 id=&quot;cong-diao-yong-yi-ci-mo-xing-kai-shi&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#cong-diao-yong-yi-ci-mo-xing-kai-shi&quot; aria-label=&quot;Anchor link for: cong-diao-yong-yi-ci-mo-xing-kai-shi&quot;&gt;从调用一次模型开始&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;如果已经接过大模型 API，下面这个流程应该很熟悉：&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;用户输入 → 请求模型 → 显示回答&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;让模型解释代码、总结文章，这样就能工作。&lt;/p&gt;
&lt;p&gt;但把请求换成“把 hello 写进 out.txt”，问题就来了。模型可以生成写文件的代码，也可以返回一个结构化的工具调用，但文件不会因为返回了这段内容就自动出现。&lt;/p&gt;
&lt;p&gt;你可以打开目录检查一下：模型回复里说了“怎么写”，文件却还没有出现。执行这一步，要由我们的程序接上。&lt;/p&gt;
&lt;p&gt;假设给模型提供了一个 &lt;code&gt;write&lt;/code&gt; 工具，告诉它需要 &lt;code&gt;path&lt;/code&gt; 和 &lt;code&gt;content&lt;/code&gt; 两个参数。它可能返回这样的调用。这里是便于阅读的示意，不是某家服务的完整 API 格式：&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;json&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-3&quot;&gt;  &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-3&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-3&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-12 z-d-5&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-12 z-d-5&quot;&gt;call_001&lt;/span&gt;&lt;span class=&quot;z-l-12 z-d-5&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-3&quot;&gt;  &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-3&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-3&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-12 z-d-5&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-12 z-d-5&quot;&gt;write&lt;/span&gt;&lt;span class=&quot;z-l-12 z-d-5&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-3&quot;&gt;  &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-3&quot;&gt;arguments&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-3&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-3&quot;&gt;    &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-3&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-3&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-12 z-d-5&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-12 z-d-5&quot;&gt;out.txt&lt;/span&gt;&lt;span class=&quot;z-l-12 z-d-5&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-3&quot;&gt;    &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-3&quot;&gt;content&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-3&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-12 z-d-5&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-12 z-d-5&quot;&gt;hello&lt;/span&gt;&lt;span class=&quot;z-l-12 z-d-5&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;拿到这个 JSON，你可能会觉得接下来很简单：按名称找到函数，传入参数，写文件就好了。&lt;/p&gt;
&lt;p&gt;文件写完后，模型还不知道结果。要把执行结果回传给它，成功时才能回复完成；权限不足或磁盘写入失败时，也能根据错误继续处理。这里的 write 会自动创建父目录，所以父目录不存在本身不一定导致失败。&lt;/p&gt;
&lt;p&gt;一次模型调用，开始变成了几次调用之间的往返。&lt;/p&gt;
&lt;h2 id=&quot;yi-ge-agent-zhi-shao-yao-kao-lu-shen-me&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#yi-ge-agent-zhi-shao-yao-kao-lu-shen-me&quot; aria-label=&quot;Anchor link for: yi-ge-agent-zhi-shao-yao-kao-lu-shen-me&quot;&gt;一个 Agent 至少要考虑什么&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;开始写的时候，很容易只盯着“调用模型”和“执行函数”这两段。等第一次遇到工具报错、消息丢失或者任务停不下来，才发现还有一些状态没人管。我们先用这张表检查一下，写文件这个任务需要谁负责什么。&lt;/p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;要考虑的事&lt;/th&gt;&lt;th&gt;在写文件任务里是什么&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;模型接口&lt;/td&gt;&lt;td&gt;发出请求，接收文本或工具调用&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;上下文&lt;/td&gt;&lt;td&gt;保留用户请求、模型调用和工具结果&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;工具定义&lt;/td&gt;&lt;td&gt;告诉模型 write 是什么，参数怎么填&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;工具执行&lt;/td&gt;&lt;td&gt;查找实现、验证参数、实际写入文件&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;继续与结束&lt;/td&gt;&lt;td&gt;工具执行后再问模型，得到最终回答后结束&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;错误和取消&lt;/td&gt;&lt;td&gt;参数错了、模型断流了、用户要求停止，分别怎么办&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;如果要把它做成可用的交互工具，还需要进度事件。否则命令跑了半分钟，界面上什么都没有，用户很难判断它是在工作还是卡住了。&lt;/p&gt;
&lt;p&gt;看到这些名词，可能会觉得一个小 Agent 也太麻烦了。这里可以先收住范围：第一版只要求任务跑完、结果能对上、失败时知道停在哪里。会话持久化和上下文压缩，等这条流程通了再加。&lt;/p&gt;
&lt;h2 id=&quot;wei-shen-me-xu-yao-agent-loop&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#wei-shen-me-xu-yao-agent-loop&quot; aria-label=&quot;Anchor link for: wei-shen-me-xu-yao-agent-loop&quot;&gt;为什么需要 Agent Loop&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;把写文件的完整过程画出来：&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;mermaid&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-13 z-d-8&quot;&gt;sequenceDiagram&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-13 z-d-8&quot;&gt;    actor&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-10&quot;&gt; U&lt;/span&gt;&lt;span class=&quot;z-l-13 z-d-8&quot;&gt; as&lt;/span&gt;&lt;span class=&quot;z-l-12 z-d-5&quot;&gt; 用户&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-13 z-d-8&quot;&gt;    participant&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-10&quot;&gt; A&lt;/span&gt;&lt;span class=&quot;z-l-13 z-d-8&quot;&gt; as&lt;/span&gt;&lt;span class=&quot;z-l-12 z-d-5&quot;&gt; Agent 运行时&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-13 z-d-8&quot;&gt;    participant&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-10&quot;&gt; M&lt;/span&gt;&lt;span class=&quot;z-l-13 z-d-8&quot;&gt; as&lt;/span&gt;&lt;span class=&quot;z-l-12 z-d-5&quot;&gt; 模型&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-13 z-d-8&quot;&gt;    participant&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-10&quot;&gt; T&lt;/span&gt;&lt;span class=&quot;z-l-13 z-d-8&quot;&gt; as&lt;/span&gt;&lt;span class=&quot;z-l-12 z-d-5&quot;&gt; write 工具&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-d-10&quot;&gt;    U&lt;/span&gt;&lt;span class=&quot;z-l-13 z-d-8&quot;&gt;-&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-10&quot;&gt;A&lt;/span&gt;&lt;span class=&quot;z-l-13 z-d-8&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-12 z-d-5&quot;&gt; 把 hello 写进 out.txt&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-d-10&quot;&gt;    A&lt;/span&gt;&lt;span class=&quot;z-l-13 z-d-8&quot;&gt;-&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-10&quot;&gt;M&lt;/span&gt;&lt;span class=&quot;z-l-13 z-d-8&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-12 z-d-5&quot;&gt; 用户请求 + 历史 + 工具 schema&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-d-10&quot;&gt;    M&lt;/span&gt;&lt;span class=&quot;z-l-13 z-d-8&quot;&gt;--&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-10&quot;&gt;A&lt;/span&gt;&lt;span class=&quot;z-l-13 z-d-8&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-12 z-d-5&quot;&gt; ToolCall：write，ID 为 call_001&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-d-10&quot;&gt;    A&lt;/span&gt;&lt;span class=&quot;z-l-13 z-d-8&quot;&gt;-&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-10&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;z-l-13 z-d-8&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-12 z-d-5&quot;&gt; 检查参数后执行&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-d-10&quot;&gt;    T&lt;/span&gt;&lt;span class=&quot;z-l-13 z-d-8&quot;&gt;--&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-10&quot;&gt;A&lt;/span&gt;&lt;span class=&quot;z-l-13 z-d-8&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-12 z-d-5&quot;&gt; 写入结果或错误&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-13 z-d-8&quot;&gt;    Note&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-6&quot;&gt; over&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-10&quot;&gt; A&lt;/span&gt;&lt;span class=&quot;z-l-13 z-d-8&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-12 z-d-5&quot;&gt; 保存调用与结果，并用 ID 配对&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-d-10&quot;&gt;    A&lt;/span&gt;&lt;span class=&quot;z-l-13 z-d-8&quot;&gt;-&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-10&quot;&gt;M&lt;/span&gt;&lt;span class=&quot;z-l-13 z-d-8&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-12 z-d-5&quot;&gt; 带 ToolResult 的新上下文&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-d-10&quot;&gt;    M&lt;/span&gt;&lt;span class=&quot;z-l-13 z-d-8&quot;&gt;--&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-10&quot;&gt;A&lt;/span&gt;&lt;span class=&quot;z-l-13 z-d-8&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-12 z-d-5&quot;&gt; 最终回复，无新工具调用&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-d-10&quot;&gt;    A&lt;/span&gt;&lt;span class=&quot;z-l-13 z-d-8&quot;&gt;--&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-10&quot;&gt;U&lt;/span&gt;&lt;span class=&quot;z-l-13 z-d-8&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-12 z-d-5&quot;&gt; 显示结果，本次运行结束&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果任务永远只有“写一次文件”，把这些步骤顺着写下来就够了。但你大概很快会想让它“先读配置，再修改配置，最后跑检查”。这时检查可能失败，模型还得根据错误再改一次，往返次数就不固定了。&lt;/p&gt;
&lt;p&gt;因此需要一个循环，反复做三件事：请求模型，执行工具，把结果交回去。每一轮都要判断还能不能继续。&lt;/p&gt;
&lt;p&gt;先看一个简化版。下面是教学伪代码，省略了流式事件、取消和钩子，不能直接编译：&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-13 z-d-8&quot;&gt;loop&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-2 z-d-8&quot;&gt;    let&lt;/span&gt;&lt;span class=&quot;z-l-3&quot;&gt; reply&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-6&quot;&gt; call_model&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;z-l-3&quot;&gt;messages&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt; &amp;amp;&lt;/span&gt;&lt;span class=&quot;z-l-3&quot;&gt;tools&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-13 z-d-8&quot;&gt;await&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt;?&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-2 z-d-8&quot;&gt;    let&lt;/span&gt;&lt;span class=&quot;z-l-3&quot;&gt; calls&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-6&quot;&gt; collect_tool_calls&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;z-l-3&quot;&gt;reply&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3&quot;&gt;    messages&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-6&quot;&gt;push&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-3&quot;&gt;reply&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-13 z-d-8&quot;&gt;    if&lt;/span&gt;&lt;span class=&quot;z-l-3&quot;&gt; calls&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-6&quot;&gt;is_empty&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-13 z-d-8&quot;&gt;        break&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-13 z-d-8&quot;&gt;    for&lt;/span&gt;&lt;span class=&quot;z-l-3&quot;&gt; call&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-8&quot;&gt; in&lt;/span&gt;&lt;span class=&quot;z-l-3&quot;&gt; calls&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-2 z-d-8&quot;&gt;        let&lt;/span&gt;&lt;span class=&quot;z-l-3&quot;&gt; result&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-6&quot;&gt; execute_tool&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-3&quot;&gt;call&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-13 z-d-8&quot;&gt;await&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3&quot;&gt;        messages&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-6&quot;&gt;push&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-3&quot;&gt;result&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这里可以停一下，看 &lt;code&gt;messages.push(reply)&lt;/code&gt;。第一次写很容易只保存用户输入和工具结果，觉得模型的调用已经执行过，就不用留了。但下一次请求需要知道“谁要求执行了什么”，结果里的调用 ID 也要能对应到前面的请求。所以这条 assistant 消息同样要保留。&lt;/p&gt;
&lt;p&gt;这个循环也没有替我们解决无限重试。模型可能反复调用失败的工具，或者一直认为任务没有结束。实际应用还需要轮次、时间和费用预算，以及能从外部取消的入口。&lt;/p&gt;
&lt;p&gt;接下来把这段伪代码，对照到 pi-rust 的实现。&lt;/p&gt;
&lt;h2 id=&quot;xian-pao-tong-yi-ge-neng-xie-wen-jian-de-li-zi&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#xian-pao-tong-yi-ge-neng-xie-wen-jian-de-li-zi&quot; aria-label=&quot;Anchor link for: xian-pao-tong-yi-ge-neng-xie-wen-jian-de-li-zi&quot;&gt;先跑通一个能写文件的例子&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;光看循环，可能还没有“它真的执行了”的感觉。可以先跑仓库里的 &lt;code&gt;examples/tools&lt;/code&gt;，看一次文件写入的结果，再回头读源码：&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;powershell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;git clone https:&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-2&quot;&gt;github.com&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt;/&lt;/span&gt;&lt;span&gt;bigfish1913&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt;/&lt;/span&gt;&lt;span&gt;pi&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt;-&lt;/span&gt;&lt;span&gt;rust.git&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;cd pi&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt;-&lt;/span&gt;&lt;span&gt;rust&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;cargo run &lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt;-&lt;/span&gt;&lt;span&gt;locked &lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt;-&lt;/span&gt;&lt;span&gt;q &lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt;-&lt;/span&gt;&lt;span&gt;p tools&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt;-&lt;/span&gt;&lt;span&gt;example&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;本次运行得到的业务输出如下，编译警告没有放进来：&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;tools example: 4 messages after run&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;wrote out.txt (28 bytes): &amp;quot;hello from the tools example&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;AgentEnd observed; tool ran against OsExecutionEnv.&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这里用的是 &lt;code&gt;FauxProvider&lt;/code&gt;，也就是预先写好响应的模拟模型，不需要 API key。工具是真执行的，文件写在临时目录，示例读取并检查内容后会清理目录。首次构建可能需要下载依赖。&lt;/p&gt;
&lt;p&gt;四条消息对应：&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;User：写入文件&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Assistant：发出 write 工具调用&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;ToolResult：工具执行结果&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Assistant：最终回复&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这四条消息可以当成后面的阅读地图。如果源码里类型太多，一时看乱了，就回到这里：用户输入之后，是模型调用、工具结果，最后才是回复。这个例子使用模拟模型，验证的是运行时把这些步骤接起来了。&lt;/p&gt;
&lt;p&gt;示例创建工具时，先给出执行环境。&lt;a rel=&quot;noopener noreferrer external&quot; target=&quot;_blank&quot; href=&quot;https://github.com/bigfish1913/pi-rust/blob/1138ab1007f41cc9d24c494845e6019019e91d43/examples/tools/src/main.rs#L29&quot;&gt;工具装配代码&lt;/a&gt;负责把对象接起来；真正写文件的是 &lt;a rel=&quot;noopener noreferrer external&quot; target=&quot;_blank&quot; href=&quot;https://github.com/bigfish1913/pi-rust/blob/1138ab1007f41cc9d24c494845e6019019e91d43/crates/rpi-tools/src/tools/write.rs#L84&quot;&gt;WriteTool&lt;/a&gt;。先看装配：&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-2 z-d-8&quot;&gt;let&lt;/span&gt;&lt;span class=&quot;z-l-3&quot;&gt; env&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; Arc&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-6&quot;&gt;new&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;OsExecutionEnv&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-6&quot;&gt;with_cwd&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-3&quot;&gt;tmp&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-6&quot;&gt;clone&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-2 z-d-8&quot;&gt;let&lt;/span&gt;&lt;span class=&quot;z-l-3&quot;&gt; env_dyn&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; Arc&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-8&quot;&gt;dyn&lt;/span&gt;&lt;span class=&quot;z-l-3&quot;&gt; rpi_tools&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;ExecutionEnv&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-3&quot;&gt; env&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-6&quot;&gt;clone&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-2 z-d-8&quot;&gt;let&lt;/span&gt;&lt;span class=&quot;z-l-3&quot;&gt; mut_env&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; Arc&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-8&quot;&gt;dyn&lt;/span&gt;&lt;span class=&quot;z-l-3&quot;&gt; rpi_tools&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;MutatingEnv&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-3&quot;&gt; env&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-6&quot;&gt;clone&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-2 z-d-8&quot;&gt;let&lt;/span&gt;&lt;span class=&quot;z-l-3&quot;&gt; ctx&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; ExecutionToolContext&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-6&quot;&gt;new&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-3&quot;&gt;env_dyn&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; Some&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-3&quot;&gt;mut_env&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-2 z-d-8&quot;&gt;let&lt;/span&gt;&lt;span class=&quot;z-l-3&quot;&gt; write_tool&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-6&quot;&gt; create_write_tool&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;z-l-3&quot;&gt;ctx&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-2 z-d-8&quot;&gt;let&lt;/span&gt;&lt;span class=&quot;z-l-3&quot;&gt; bash_tool&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-6&quot;&gt; create_bash_tool&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;z-l-3&quot;&gt;ctx&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; None&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;OsExecutionEnv&lt;/code&gt; 使用真实操作系统，&lt;code&gt;tmp&lt;/code&gt; 是示例前面创建的临时目录。&lt;code&gt;Arc&lt;/code&gt; 让这些对象可以共享；&lt;code&gt;dyn ExecutionEnv&lt;/code&gt; 表示调用方通过接口使用它。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;env_dyn&lt;/code&gt; 和 &lt;code&gt;mut_env&lt;/code&gt; 来自同一个 env，只是提供不同的接口：前者提供文件和命令等执行能力，后者提供可变更环境及文件修改队列所需的能力。&lt;code&gt;create_write_tool&lt;/code&gt; 要求 context 包含 &lt;code&gt;mutating_env&lt;/code&gt;，取得环境，再把它与工具 schema 一起放进 WriteTool。这里完成的是对象装配，还没有写文件。&lt;/p&gt;
&lt;p&gt;这里还创建了 bash 工具，但本次脚本只调用 write。注册了一个工具，不等于这一轮会执行它。&lt;/p&gt;
&lt;p&gt;然后安排模型的两次响应：&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-2 z-d-8&quot;&gt;let&lt;/span&gt;&lt;span class=&quot;z-l-3&quot;&gt; script&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; FauxScript&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-6&quot;&gt;new&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-d-8&quot;&gt;    .&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-6&quot;&gt;with_tool_call&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-12 z-d-5&quot;&gt;        &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-12 z-d-5&quot;&gt;write&lt;/span&gt;&lt;span class=&quot;z-l-12 z-d-5&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;        serde_json&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-6&quot;&gt;json!&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-12 z-d-5&quot;&gt;            &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-12 z-d-5&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;z-l-12 z-d-5&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-12 z-d-5&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-12 z-d-5&quot;&gt;out.txt&lt;/span&gt;&lt;span class=&quot;z-l-12 z-d-5&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-12 z-d-5&quot;&gt;            &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-12 z-d-5&quot;&gt;content&lt;/span&gt;&lt;span class=&quot;z-l-12 z-d-5&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-12 z-d-5&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-12 z-d-5&quot;&gt;hello from the tools example&lt;/span&gt;&lt;span class=&quot;z-l-12 z-d-5&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        }&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    )&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-d-8&quot;&gt;    .&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-6&quot;&gt;with_text&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-12 z-d-5&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-12 z-d-5&quot;&gt;Done — wrote the file.&lt;/span&gt;&lt;span class=&quot;z-l-12 z-d-5&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-2 z-d-8&quot;&gt;let&lt;/span&gt;&lt;span class=&quot;z-l-3&quot;&gt; provider&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; FauxProvider&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-6&quot;&gt;new&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-3&quot;&gt;script&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-2 z-d-8&quot;&gt;let&lt;/span&gt;&lt;span class=&quot;z-l-3&quot;&gt; model&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-3&quot;&gt; provider&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-6&quot;&gt;default_model&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-6&quot;&gt;clone&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;最后把模型入口和工具放进 Agent：&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-2 z-d-8&quot;&gt;let&lt;/span&gt;&lt;span class=&quot;z-l-3&quot;&gt; agent&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; AgentBuilder&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-6&quot;&gt;new&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-d-8&quot;&gt;    .&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-6&quot;&gt;model&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-3&quot;&gt;model&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-d-8&quot;&gt;    .&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-6&quot;&gt;stream_fn&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-6&quot;&gt;make_stream_fn&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-3&quot;&gt;provider&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-d-8&quot;&gt;    .&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-6&quot;&gt;tools&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-6&quot;&gt;vec!&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span class=&quot;z-l-3&quot;&gt;write_tool&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-3&quot;&gt; bash_tool&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-d-8&quot;&gt;    .&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-6&quot;&gt;build&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-d-8&quot;&gt;    .&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-6&quot;&gt;expect&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-12 z-d-5&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-12 z-d-5&quot;&gt;agent builds&lt;/span&gt;&lt;span class=&quot;z-l-12 z-d-5&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这些是&lt;a rel=&quot;noopener noreferrer external&quot; target=&quot;_blank&quot; href=&quot;https://github.com/bigfish1913/pi-rust/blob/1138ab1007f41cc9d24c494845e6019019e91d43/examples/tools/src/main.rs&quot;&gt;工具示例&lt;/a&gt;中的片段。完整文件还包含 import、事件订阅、prompt、文件断言，以及 &lt;code&gt;make_stream_fn&lt;/code&gt;，运行时请使用完整示例。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;make_stream_fn&lt;/code&gt; 在这个例子里负责桥接 Provider 的异步接口和同步返回流的 &lt;code&gt;StreamFn&lt;/code&gt;。它使用 Tokio 的 &lt;code&gt;block_in_place&lt;/code&gt; 和 &lt;code&gt;block_on&lt;/code&gt;；先把它当成示例适配器即可，不要把它照搬到所有异步应用中。&lt;/p&gt;
&lt;h2 id=&quot;di-yi-bu-ba-xin-qing-qiu-fang-jin-shang-xia-wen&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#di-yi-bu-ba-xin-qing-qiu-fang-jin-shang-xia-wen&quot; aria-label=&quot;Anchor link for: di-yi-bu-ba-xin-qing-qiu-fang-jin-shang-xia-wen&quot;&gt;第一步，把新请求放进上下文&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;主入口在 &lt;a rel=&quot;noopener noreferrer external&quot; target=&quot;_blank&quot; href=&quot;https://github.com/bigfish1913/pi-rust/blob/1138ab1007f41cc9d24c494845e6019019e91d43/crates/rpi-agent/src/agent_loop.rs#L72&quot;&gt;&lt;code&gt;run_agent_loop&lt;/code&gt;&lt;/a&gt;。函数签名如下：&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-2 z-d-8&quot;&gt;pub&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-8&quot;&gt; async&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-8&quot;&gt; fn&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-6&quot;&gt; run_agent_loop&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3&quot;&gt;    prompts&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; Vec&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;AgentMessage&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3&quot;&gt;    context&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; AgentContext&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3&quot;&gt;    config&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; AgentLoopConfig&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3&quot;&gt;    emit&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; Arc&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-8&quot;&gt;dyn&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; AgentEmitter&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3&quot;&gt;    stream_fn&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; StreamFn&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; Result&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;NewMessages&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; AgentError&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;prompts&lt;/code&gt; 是这次输入，&lt;code&gt;context&lt;/code&gt; 带着已有历史和工具，&lt;code&gt;config&lt;/code&gt; 管模型及运行选项。&lt;code&gt;emit&lt;/code&gt; 用来发布进度事件，&lt;code&gt;stream_fn&lt;/code&gt; 是模型调用入口。&lt;/p&gt;
&lt;p&gt;进入函数后，先做这件事：&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-2 z-d-8&quot;&gt;let&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-8&quot;&gt; mut&lt;/span&gt;&lt;span class=&quot;z-l-3&quot;&gt; new_messages&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; Vec&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;AgentMessage&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-3&quot;&gt; prompts&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-6&quot;&gt;clone&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-2 z-d-8&quot;&gt;let&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-8&quot;&gt; mut&lt;/span&gt;&lt;span class=&quot;z-l-3&quot;&gt; current_context&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; AgentContext&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3&quot;&gt;    system_prompt&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-3&quot;&gt; context&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt;.&lt;/span&gt;&lt;span&gt;system_prompt&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-6&quot;&gt;clone&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3&quot;&gt;    messages&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt;:&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-2 z-d-8&quot;&gt;        let&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-8&quot;&gt; mut&lt;/span&gt;&lt;span class=&quot;z-l-3&quot;&gt; v&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-3&quot;&gt; context&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt;.&lt;/span&gt;&lt;span&gt;messages&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-6&quot;&gt;clone&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3&quot;&gt;        v&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-6&quot;&gt;extend&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-3&quot;&gt;prompts&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3&quot;&gt;        v&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    }&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3&quot;&gt;    tools&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-3&quot;&gt; context&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt;.&lt;/span&gt;&lt;span&gt;tools&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-6&quot;&gt;clone&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这里有两份消息集合，作用不同。&lt;/p&gt;
&lt;p&gt;如果你在这里疑惑“为什么要放两份消息”，区别在于范围：&lt;code&gt;current_context.messages&lt;/code&gt; 是已有历史加上本次输入，后面的模型请求要用它；&lt;code&gt;new_messages&lt;/code&gt; 只记这次新增的消息，最后返回给调用方。把这两个用途分开，后面看到 push 时就不容易混了。&lt;/p&gt;
&lt;p&gt;接着发布 &lt;code&gt;AgentStart&lt;/code&gt;、&lt;code&gt;TurnStart&lt;/code&gt; 和用户消息事件，进入 &lt;code&gt;run_loop&lt;/code&gt;。到这里还没有请求模型，只是准备好了运行状态。&lt;/p&gt;
&lt;h2 id=&quot;di-er-bu-ba-shang-xia-wen-jiao-gei-mo-xing-xiao-fei-liu-shi-xiang-ying&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#di-er-bu-ba-shang-xia-wen-jiao-gei-mo-xing-xiao-fei-liu-shi-xiang-ying&quot; aria-label=&quot;Anchor link for: di-er-bu-ba-shang-xia-wen-jiao-gei-mo-xing-xiao-fei-liu-shi-xiang-ying&quot;&gt;第二步，把上下文交给模型，消费流式响应&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;真正发起这一轮请求的是 &lt;a rel=&quot;noopener noreferrer external&quot; target=&quot;_blank&quot; href=&quot;https://github.com/bigfish1913/pi-rust/blob/1138ab1007f41cc9d24c494845e6019019e91d43/crates/rpi-agent/src/agent_loop.rs#L441&quot;&gt;&lt;code&gt;stream_assistant_response&lt;/code&gt;&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;Agent 内部消息不一定能原样发给模型，所以这里会先做可选的上下文转换，再调用 &lt;code&gt;convert_to_llm&lt;/code&gt;。之后构造模型使用的上下文，核心片段如下：&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-2 z-d-8&quot;&gt;let&lt;/span&gt;&lt;span class=&quot;z-l-3&quot;&gt; llm_context&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; rpi_ai&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;types&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;Context&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3&quot;&gt;    system_prompt&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-13 z-d-8&quot;&gt; if&lt;/span&gt;&lt;span class=&quot;z-l-3&quot;&gt; context&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt;.&lt;/span&gt;&lt;span&gt;system_prompt&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-6&quot;&gt;is_empty&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;        None&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    }&lt;/span&gt;&lt;span class=&quot;z-l-13 z-d-8&quot;&gt; else&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;        Some&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-3&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt;.&lt;/span&gt;&lt;span&gt;system_prompt&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-6&quot;&gt;clone&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    }&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3&quot;&gt;    messages&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-3&quot;&gt; llm_messages&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3&quot;&gt;    tools&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-3&quot;&gt; context&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt;.&lt;/span&gt;&lt;span&gt;tools&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-6&quot;&gt;iter&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-6&quot;&gt;map&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;z-l-3&quot;&gt;t&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;z-l-3&quot;&gt; t&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-6&quot;&gt;schema&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-6&quot;&gt;clone&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-6&quot;&gt;collect&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;可以重点看最后一行。我们没有把 Rust 函数发给模型，只发工具 schema，也就是名称、描述和参数约束。模型据此选择工具、填写参数，函数实现仍留在自己的程序里。这也是为什么仅仅“告诉模型有个 write 工具”，还不能让文件落盘。&lt;/p&gt;
&lt;p&gt;解析凭据和请求选项后，模型调用收敛成这一行：&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-2 z-d-8&quot;&gt;let&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-8&quot;&gt; mut&lt;/span&gt;&lt;span class=&quot;z-l-3&quot;&gt; response&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-6&quot;&gt; stream_fn&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;z-l-3&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt;.&lt;/span&gt;&lt;span&gt;model&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt; &amp;amp;&lt;/span&gt;&lt;span class=&quot;z-l-3&quot;&gt;llm_context&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt; &amp;amp;&lt;/span&gt;&lt;span class=&quot;z-l-3&quot;&gt;opts&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;返回的是事件流，要继续消费：&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-13 z-d-8&quot;&gt;while&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-8&quot;&gt; let&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; Some&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-3&quot;&gt;event&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-3&quot;&gt; response&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-6&quot;&gt;next&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-13 z-d-8&quot;&gt;await&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-4&quot;&gt;    //&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-4&quot;&gt; 按事件类型处理，下面解释各分支&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;循环体有几个分支，作用可以这样理解：&lt;/p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;模型事件&lt;/th&gt;&lt;th&gt;运行时怎么处理&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;Start&lt;/code&gt;&lt;/td&gt;&lt;td&gt;给上下文加入当前 assistant 消息的占位，发布 MessageStart&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;文本、思考或工具调用的增量&lt;/td&gt;&lt;td&gt;发布 MessageUpdate，供界面显示&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;Done&lt;/code&gt; 或 &lt;code&gt;Error&lt;/code&gt;&lt;/td&gt;&lt;td&gt;取得最终消息，替换占位，发布 MessageEnd&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;源码没有在每个 delta 到来时都深拷贝整条增长中的消息到上下文，而是在终止事件时放入最终版本。界面则从更新事件里拿进度。&lt;/p&gt;
&lt;p&gt;这也说明“看见工具调用的一部分”和“可以执行工具”之间有差别。pi-rust 的主循环先取得这一轮最终 assistant 消息，再从中提取调用。半截 JSON 不会在这里直接拿去写文件。&lt;/p&gt;
&lt;h2 id=&quot;di-san-bu-cong-hui-da-li-zhao-chu-gong-ju-diao-yong&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#di-san-bu-cong-hui-da-li-zhao-chu-gong-ju-diao-yong&quot; aria-label=&quot;Anchor link for: di-san-bu-cong-hui-da-li-zhao-chu-gong-ju-diao-yong&quot;&gt;第三步，从回答里找出工具调用&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;回到 &lt;a rel=&quot;noopener noreferrer external&quot; target=&quot;_blank&quot; href=&quot;https://github.com/bigfish1913/pi-rust/blob/1138ab1007f41cc9d24c494845e6019019e91d43/crates/rpi-agent/src/agent_loop.rs#L220&quot;&gt;&lt;code&gt;run_loop&lt;/code&gt;&lt;/a&gt;。拿到 assistant 消息后，先检查模型是否错误或中止；再从内容块里收集调用：&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-2 z-d-8&quot;&gt;let&lt;/span&gt;&lt;span class=&quot;z-l-3&quot;&gt; tool_calls&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; Vec&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;ToolCall&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-3&quot;&gt; message&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-d-8&quot;&gt;    .&lt;/span&gt;&lt;span&gt;content&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-d-8&quot;&gt;    .&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-6&quot;&gt;iter&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-d-8&quot;&gt;    .&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-6&quot;&gt;filter_map&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;z-l-3&quot;&gt;c&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;z-l-13 z-d-8&quot;&gt; match&lt;/span&gt;&lt;span class=&quot;z-l-3&quot;&gt; c&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;        Content&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-6&quot;&gt;ToolCall&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-3&quot;&gt;tc&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt; =&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; Some&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-3&quot;&gt;tc&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-6&quot;&gt;clone&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3&quot;&gt;        _&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt; =&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; None&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    }&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-d-8&quot;&gt;    .&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-6&quot;&gt;collect&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;一条 assistant 消息可以包含文本，也可以包含多个工具调用。这里保留所有 &lt;code&gt;ToolCall&lt;/code&gt;，顺序和原内容一致。&lt;/p&gt;
&lt;p&gt;随后决定这一批怎么处理：&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3&quot;&gt;has_more_tool_calls&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt; false&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-13 z-d-8&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt; !&lt;/span&gt;&lt;span class=&quot;z-l-3&quot;&gt;tool_calls&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-6&quot;&gt;is_empty&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-2 z-d-8&quot;&gt;    let&lt;/span&gt;&lt;span class=&quot;z-l-3&quot;&gt; batch&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-13 z-d-8&quot;&gt; if&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-6&quot;&gt; matches!&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-3&quot;&gt;message&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt;.&lt;/span&gt;&lt;span&gt;stop_reason&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; StopReason&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;Length&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-9 z-d-6&quot;&gt;        fail_tool_calls_from_truncated_message&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;z-l-3&quot;&gt;tool_calls&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-3&quot;&gt; emit&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-13 z-d-8&quot;&gt;await&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt;?&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    }&lt;/span&gt;&lt;span class=&quot;z-l-13 z-d-8&quot;&gt; else&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-9 z-d-6&quot;&gt;        execute_tool_calls&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-3&quot;&gt;current_context&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt; &amp;amp;&lt;/span&gt;&lt;span class=&quot;z-l-3&quot;&gt;message&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt; &amp;amp;&lt;/span&gt;&lt;span class=&quot;z-l-3&quot;&gt;tool_calls&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-3&quot;&gt; config&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-3&quot;&gt; emit&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-13 z-d-8&quot;&gt;await&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt;?&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    }&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3&quot;&gt;    tool_results&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-6&quot;&gt;extend&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-3&quot;&gt;batch&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt;.&lt;/span&gt;&lt;span&gt;messages&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3&quot;&gt;    has_more_tool_calls&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt; !&lt;/span&gt;&lt;span class=&quot;z-l-3&quot;&gt;batch&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt;.&lt;/span&gt;&lt;span&gt;terminate&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-4&quot;&gt;    //&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-4&quot;&gt; 后面把结果加入上下文&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这个片段省略了结果入库的循环，下一节再看。&lt;/p&gt;
&lt;p&gt;如果模型因长度限制结束，当前实现会让这一轮的工具调用全部失败，不执行其中任何一个。即使某个调用看上去完整，它也采用同一条规则。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;has_more_tool_calls&lt;/code&gt; 这个名字容易让人误读成“还有工具没执行”。这里它控制是否继续下一次模型请求：刚执行了一批工具，通常需要让模型看结果，因此继续；如果这一批明确要求终止，则不再因此继续。&lt;/p&gt;
&lt;h2 id=&quot;di-si-bu-zhao-dao-gong-ju-jian-cha-can-shu-zai-zhi-xing&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#di-si-bu-zhao-dao-gong-ju-jian-cha-can-shu-zai-zhi-xing&quot; aria-label=&quot;Anchor link for: di-si-bu-zhao-dao-gong-ju-jian-cha-can-shu-zai-zhi-xing&quot;&gt;第四步，找到工具，检查参数，再执行&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;a rel=&quot;noopener noreferrer external&quot; target=&quot;_blank&quot; href=&quot;https://github.com/bigfish1913/pi-rust/blob/1138ab1007f41cc9d24c494845e6019019e91d43/crates/rpi-agent/src/agent_loop.rs#L925&quot;&gt;&lt;code&gt;prepare_tool_call&lt;/code&gt;&lt;/a&gt; 按调用名称找工具：&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-2 z-d-8&quot;&gt;let&lt;/span&gt;&lt;span class=&quot;z-l-3&quot;&gt; tool&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-3&quot;&gt; current_context&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-d-8&quot;&gt;    .&lt;/span&gt;&lt;span&gt;tools&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-d-8&quot;&gt;    .&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-6&quot;&gt;iter&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-d-8&quot;&gt;    .&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-6&quot;&gt;find&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;z-l-3&quot;&gt;t&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;z-l-3&quot;&gt; t&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-6&quot;&gt;schema&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt;.&lt;/span&gt;&lt;span&gt;name &lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt;==&lt;/span&gt;&lt;span class=&quot;z-l-3&quot;&gt; tool_call&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt;.&lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-d-8&quot;&gt;    .&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-6&quot;&gt;cloned&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;没找到时，生成一个错误结果，不会随便找个命令替代执行。&lt;/p&gt;
&lt;p&gt;找到后，先运行工具的 &lt;code&gt;prepare_arguments&lt;/code&gt;，再按 schema 验证参数。源码中的验证调用是：&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-2 z-d-8&quot;&gt;let&lt;/span&gt;&lt;span class=&quot;z-l-3&quot;&gt; validated_args&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-13 z-d-8&quot;&gt; match&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-6&quot;&gt; validate_tool_arguments&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-3&quot;&gt;tool&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-6&quot;&gt;schema&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt; &amp;amp;&lt;/span&gt;&lt;span class=&quot;z-l-3&quot;&gt;prepared_tool_call&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;    Ok&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-3&quot;&gt;v&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt; =&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-3&quot;&gt; v&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;    Err&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-3&quot;&gt;e&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt; =&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-13 z-d-8&quot;&gt;        return&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; Prepared&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;Immediate&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3&quot;&gt;            result&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-6&quot;&gt; create_error_tool_result&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;z-l-3&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-6&quot;&gt;to_string&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3&quot;&gt;            is_error&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt; true&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        }&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;只有名称和参数都符合要求，才进入执行阶段。中间还可以通过 &lt;code&gt;before_tool_call&lt;/code&gt; 阻止调用，并检查取消状态。&lt;/p&gt;
&lt;p&gt;有个实现细节要注意：这个钩子如果替换了参数，当前代码不会再做一次 schema 验证，因此钩子提供的是受信任的替换。参数验证也不等于执行权限检查，schema 正确的文件路径仍然可能越过应用允许的范围。&lt;/p&gt;
&lt;p&gt;工具最终通过 &lt;code&gt;AgentTool&lt;/code&gt; 接口执行。实际执行调用位于 &lt;a rel=&quot;noopener noreferrer external&quot; target=&quot;_blank&quot; href=&quot;https://github.com/bigfish1913/pi-rust/blob/1138ab1007f41cc9d24c494845e6019019e91d43/crates/rpi-agent/src/agent_loop.rs#L1025&quot;&gt;&lt;code&gt;execute_prepared_tool_call&lt;/code&gt;&lt;/a&gt;：&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-2 z-d-8&quot;&gt;let&lt;/span&gt;&lt;span class=&quot;z-l-3&quot;&gt; child_token&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-3&quot;&gt; config&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt;.&lt;/span&gt;&lt;span&gt;signal&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-6&quot;&gt;child_token&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-13 z-d-8&quot;&gt;match&lt;/span&gt;&lt;span class=&quot;z-l-3&quot;&gt; tool&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-d-8&quot;&gt;    .&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-6&quot;&gt;execute&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;z-l-3&quot;&gt;tool_call&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt;.&lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-3&quot;&gt; args&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-6&quot;&gt;clone&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-3&quot;&gt; child_token&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-3&quot;&gt; on_update&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-d-8&quot;&gt;    .&lt;/span&gt;&lt;span class=&quot;z-l-13 z-d-8&quot;&gt;await&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-4&quot;&gt;    //&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-4&quot;&gt; Ok 转成成功结果，Err 转成带 is_error 的错误结果&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这里省略了 match 分支。&lt;code&gt;on_update&lt;/code&gt; 用来回报工具进度，取消 token 传给工具，让它有机会响应停止请求。工具仍然需要主动处理取消，传了 token 并不意味着外部副作用会自动回滚。&lt;/p&gt;
&lt;p&gt;普通执行错误会被转换成工具结果交回模型。例如文件写入失败，模型下一轮能看到错误，而不是整个程序直接丢掉上下文。&lt;/p&gt;
&lt;h3 id=&quot;write-gong-ju-li-jiu-jing-fa-sheng-liao-shen-me&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#write-gong-ju-li-jiu-jing-fa-sheng-liao-shen-me&quot; aria-label=&quot;Anchor link for: write-gong-ju-li-jiu-jing-fa-sheng-liao-shen-me&quot;&gt;write 工具里究竟发生了什么&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;上面的 &lt;code&gt;tool.execute(...)&lt;/code&gt; 是接口调用，继续点进 &lt;a rel=&quot;noopener noreferrer external&quot; target=&quot;_blank&quot; href=&quot;https://github.com/bigfish1913/pi-rust/blob/1138ab1007f41cc9d24c494845e6019019e91d43/crates/rpi-tools/src/tools/write.rs#L84&quot;&gt;WriteTool::execute&lt;/a&gt;，才能看到文件为什么会出现。&lt;/p&gt;
&lt;p&gt;工具输入是两个字符串，类型定义也用来生成 schema：&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;derive&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;Debug&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; Clone&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; JsonSchema&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; Deserialize&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-2 z-d-8&quot;&gt;pub&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-8&quot;&gt; struct&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; WriteInput&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-2 z-d-8&quot;&gt;    pub&lt;/span&gt;&lt;span class=&quot;z-l-3&quot;&gt; path&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; String&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-2 z-d-8&quot;&gt;    pub&lt;/span&gt;&lt;span class=&quot;z-l-3&quot;&gt; content&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; String&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Loop 做过 schema 验证后，工具还会把 JSON 反序列化成 &lt;code&gt;WriteInput&lt;/code&gt;，这样内部可以按 Rust 类型使用字段。解析失败会返回 Validation 错误：&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-2 z-d-8&quot;&gt;let&lt;/span&gt;&lt;span class=&quot;z-l-3&quot;&gt; input&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; WriteInput&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; serde_json&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-6&quot;&gt;from_value&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-3&quot;&gt;params&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-d-8&quot;&gt;    .&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-6&quot;&gt;map_err&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;z-l-3&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; AgentError&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-6&quot;&gt;Validation&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-6&quot;&gt;format!&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-12 z-d-5&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-12 z-d-5&quot;&gt;write input invalid: &lt;/span&gt;&lt;span class=&quot;z-l-12 z-d-5&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;z-l-12 z-d-5&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;z-l-12 z-d-5&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;z-l-12 z-d-5&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt;?&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-2 z-d-8&quot;&gt;let&lt;/span&gt;&lt;span class=&quot;z-l-3&quot;&gt; abs&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-6&quot;&gt; resolve_tool_path&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt;.&lt;/span&gt;&lt;span&gt;env&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-6&quot;&gt;as_env&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt; &amp;amp;&lt;/span&gt;&lt;span class=&quot;z-l-3&quot;&gt;input&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt;.&lt;/span&gt;&lt;span&gt;path&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; Some&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;z-l-3&quot;&gt;signal&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-d-8&quot;&gt;    .&lt;/span&gt;&lt;span class=&quot;z-l-13 z-d-8&quot;&gt;await&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-d-8&quot;&gt;    .&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-6&quot;&gt;map_err&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-3&quot;&gt;file_err_to_agent&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt;?&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;相对路径以执行环境为基础解析。示例环境指向临时目录，所以 &lt;code&gt;out.txt&lt;/code&gt; 写在那里。设置工作目录不等于限制只能访问这个目录，路径边界仍需要应用额外约束。&lt;/p&gt;
&lt;p&gt;接下来进入按路径组织的文件修改队列，同一个队列键的修改需要等待前一次完成。下面是队列闭包中的节选，外层的锁与变量准备省略了：&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-9 z-d-6&quot;&gt;check_abort&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-3&quot;&gt;cancel&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt;?&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-6&quot;&gt;as_env&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-d-8&quot;&gt;    .&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-6&quot;&gt;write_file&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;z-l-3&quot;&gt;abs_str&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; FileContent&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-6&quot;&gt;Text&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-3&quot;&gt;content&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; Some&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-3&quot;&gt;cancel&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-d-8&quot;&gt;    .&lt;/span&gt;&lt;span class=&quot;z-l-13 z-d-8&quot;&gt;await&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt;?&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-9 z-d-6&quot;&gt;check_abort&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-3&quot;&gt;cancel&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt;?&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;write_file&lt;/code&gt; 继续进入 &lt;a rel=&quot;noopener noreferrer external&quot; target=&quot;_blank&quot; href=&quot;https://github.com/bigfish1913/pi-rust/blob/1138ab1007f41cc9d24c494845e6019019e91d43/crates/rpi-tools/src/os_env.rs#L466&quot;&gt;OsExecutionEnv 的实现&lt;/a&gt;：解析路径、创建父目录、检查取消，再执行可取消的写入。write 会覆盖已有文件，并不是追加。&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;mermaid&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-13 z-d-8&quot;&gt;flowchart&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-6&quot;&gt; TD&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-d-10&quot;&gt;    A&lt;/span&gt;&lt;span class=&quot;z-l-13 z-d-8&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;z-l-12 z-d-5&quot;&gt;Loop 已校验的 JSON 参数&lt;/span&gt;&lt;span class=&quot;z-l-13 z-d-8&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;z-l-13 z-d-8&quot;&gt; --&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-10&quot;&gt; B&lt;/span&gt;&lt;span class=&quot;z-l-13 z-d-8&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;z-l-12 z-d-5&quot;&gt;反序列化为 WriteInput&lt;/span&gt;&lt;span class=&quot;z-l-13 z-d-8&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-d-10&quot;&gt;    B &lt;/span&gt;&lt;span class=&quot;z-l-13 z-d-8&quot;&gt;--&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-10&quot;&gt; C&lt;/span&gt;&lt;span class=&quot;z-l-13 z-d-8&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;z-l-12 z-d-5&quot;&gt;解析目标路径&lt;/span&gt;&lt;span class=&quot;z-l-13 z-d-8&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-d-10&quot;&gt;    C &lt;/span&gt;&lt;span class=&quot;z-l-13 z-d-8&quot;&gt;--&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-10&quot;&gt; D&lt;/span&gt;&lt;span class=&quot;z-l-13 z-d-8&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;z-l-12 z-d-5&quot;&gt;进入该路径的修改队列&lt;/span&gt;&lt;span class=&quot;z-l-13 z-d-8&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-d-10&quot;&gt;    D &lt;/span&gt;&lt;span class=&quot;z-l-13 z-d-8&quot;&gt;--&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-10&quot;&gt; E&lt;/span&gt;&lt;span class=&quot;z-l-13 z-d-8&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;z-l-12 z-d-5&quot;&gt;检查取消状态&lt;/span&gt;&lt;span class=&quot;z-l-13 z-d-8&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-d-10&quot;&gt;    E &lt;/span&gt;&lt;span class=&quot;z-l-13 z-d-8&quot;&gt;--&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-10&quot;&gt; F&lt;/span&gt;&lt;span class=&quot;z-l-13 z-d-8&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;z-l-12 z-d-5&quot;&gt;创建父目录并写入文件&lt;/span&gt;&lt;span class=&quot;z-l-13 z-d-8&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-d-10&quot;&gt;    F &lt;/span&gt;&lt;span class=&quot;z-l-13 z-d-8&quot;&gt;--&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-10&quot;&gt; G&lt;/span&gt;&lt;span class=&quot;z-l-13 z-d-8&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;z-l-12 z-d-5&quot;&gt;再次检查取消状态&lt;/span&gt;&lt;span class=&quot;z-l-13 z-d-8&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-d-10&quot;&gt;    G &lt;/span&gt;&lt;span class=&quot;z-l-13 z-d-8&quot;&gt;--&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-10&quot;&gt; H&lt;/span&gt;&lt;span class=&quot;z-l-13 z-d-8&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;z-l-12 z-d-5&quot;&gt;返回 AgentToolResult&lt;/span&gt;&lt;span class=&quot;z-l-13 z-d-8&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-d-10&quot;&gt;    B &lt;/span&gt;&lt;span class=&quot;z-l-13 z-d-8&quot;&gt;-.&lt;/span&gt;&lt;span class=&quot;z-l-12 z-d-5&quot;&gt; 解析错误 &lt;/span&gt;&lt;span class=&quot;z-l-13 z-d-8&quot;&gt;.-&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-10&quot;&gt; X&lt;/span&gt;&lt;span class=&quot;z-l-13 z-d-8&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;z-l-12 z-d-5&quot;&gt;返回错误给 Loop&lt;/span&gt;&lt;span class=&quot;z-l-13 z-d-8&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-d-10&quot;&gt;    C &lt;/span&gt;&lt;span class=&quot;z-l-13 z-d-8&quot;&gt;-.&lt;/span&gt;&lt;span class=&quot;z-l-12 z-d-5&quot;&gt; 路径错误 &lt;/span&gt;&lt;span class=&quot;z-l-13 z-d-8&quot;&gt;.-&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-10&quot;&gt; X&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-d-10&quot;&gt;    E &lt;/span&gt;&lt;span class=&quot;z-l-13 z-d-8&quot;&gt;-.&lt;/span&gt;&lt;span class=&quot;z-l-12 z-d-5&quot;&gt; 已取消 &lt;/span&gt;&lt;span class=&quot;z-l-13 z-d-8&quot;&gt;.-&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-10&quot;&gt; X&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-d-10&quot;&gt;    F &lt;/span&gt;&lt;span class=&quot;z-l-13 z-d-8&quot;&gt;-.&lt;/span&gt;&lt;span class=&quot;z-l-12 z-d-5&quot;&gt; 写入失败或取消 &lt;/span&gt;&lt;span class=&quot;z-l-13 z-d-8&quot;&gt;.-&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-10&quot;&gt; X&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-d-10&quot;&gt;    G &lt;/span&gt;&lt;span class=&quot;z-l-13 z-d-8&quot;&gt;-.&lt;/span&gt;&lt;span class=&quot;z-l-12 z-d-5&quot;&gt; 已取消 &lt;/span&gt;&lt;span class=&quot;z-l-13 z-d-8&quot;&gt;.-&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-10&quot;&gt; X&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-d-10&quot;&gt;    X &lt;/span&gt;&lt;span class=&quot;z-l-13 z-d-8&quot;&gt;--&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-10&quot;&gt; Y&lt;/span&gt;&lt;span class=&quot;z-l-13 z-d-8&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;z-l-12 z-d-5&quot;&gt;Loop 生成错误 ToolResult&lt;/span&gt;&lt;span class=&quot;z-l-13 z-d-8&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-d-10&quot;&gt;    H &lt;/span&gt;&lt;span class=&quot;z-l-13 z-d-8&quot;&gt;--&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-10&quot;&gt; Z&lt;/span&gt;&lt;span class=&quot;z-l-13 z-d-8&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;z-l-12 z-d-5&quot;&gt;Loop 生成成功 ToolResult&lt;/span&gt;&lt;span class=&quot;z-l-13 z-d-8&quot;&gt;]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;成功时，WriteTool 返回的文本是 &lt;code&gt;Successfully wrote to out.txt&lt;/code&gt;。Loop 再给它加上调用 ID、工具名称和 &lt;code&gt;is_error&lt;/code&gt; 等信息，形成下一节的 &lt;code&gt;ToolResultMessage&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;写前、写后都有取消检查，但不提供事务回滚。如果文件已写入，紧接着发现取消，工具可能返回中止错误，文件变更仍然存在。不能把“工具返回错误”理解成“外部环境一定没变化”。&lt;/p&gt;
&lt;h2 id=&quot;di-wu-bu-jie-guo-hui-dao-shang-xia-wen-xun-huan-cai-lian-qi-lai&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#di-wu-bu-jie-guo-hui-dao-shang-xia-wen-xun-huan-cai-lian-qi-lai&quot; aria-label=&quot;Anchor link for: di-wu-bu-jie-guo-hui-dao-shang-xia-wen-xun-huan-cai-lian-qi-lai&quot;&gt;第五步，结果回到上下文，循环才连起来&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;工具执行完，结果需要对应到原调用。&lt;a rel=&quot;noopener noreferrer external&quot; target=&quot;_blank&quot; href=&quot;https://github.com/bigfish1913/pi-rust/blob/1138ab1007f41cc9d24c494845e6019019e91d43/crates/rpi-agent/src/agent_loop.rs#L1160&quot;&gt;create_tool_result_message&lt;/a&gt; 构造结果消息时有这些字段，以下是节选：&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3&quot;&gt;tool_call_id&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-3&quot;&gt; tool_call&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt;.&lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-6&quot;&gt;clone&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3&quot;&gt;tool_name&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-3&quot;&gt; tool_call&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt;.&lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-6&quot;&gt;clone&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3&quot;&gt;content&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-3&quot;&gt; result&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-6&quot;&gt;clone&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-6&quot;&gt;into_content&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3&quot;&gt;is_error&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;tool_call_id&lt;/code&gt; 把结果和调用连起来。它比一句“写入成功”多提供了一个关键的信息：这是哪次调用的结果。&lt;/p&gt;
&lt;p&gt;主循环再把消息放回去：&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-13 z-d-8&quot;&gt;for&lt;/span&gt;&lt;span class=&quot;z-l-3&quot;&gt; result&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-8&quot;&gt; in&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt; &amp;amp;&lt;/span&gt;&lt;span class=&quot;z-l-3&quot;&gt;tool_results&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-2 z-d-8&quot;&gt;    let&lt;/span&gt;&lt;span class=&quot;z-l-3&quot;&gt; am&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; AgentMessage&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-6&quot;&gt;ToolResult&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;Box&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-6&quot;&gt;new&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-3&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-6&quot;&gt;clone&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3&quot;&gt;    current_context&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt;.&lt;/span&gt;&lt;span&gt;messages&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-6&quot;&gt;push&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-3&quot;&gt;am&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-6&quot;&gt;clone&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3&quot;&gt;    new_messages&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-6&quot;&gt;push&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-3&quot;&gt;am&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;现在再回看开头那个疑问：文件写完后，模型怎么知道成功了？答案就在这两次 push 里。下一次调用模型，它看到的历史已经包含用户请求、assistant 发出的调用和执行结果。&lt;/p&gt;
&lt;p&gt;在写文件例子中，模拟 Provider 的下一步返回完成消息。没有新的工具调用，也没有待处理的用户消息，这次运行就结束。这样才得到前面看到的四条消息。&lt;/p&gt;
&lt;h2 id=&quot;yuan-ma-wei-shen-me-huan-you-liang-ceng-xun-huan&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#yuan-ma-wei-shen-me-huan-you-liang-ceng-xun-huan&quot; aria-label=&quot;Anchor link for: yuan-ma-wei-shen-me-huan-you-liang-ceng-xun-huan&quot;&gt;源码为什么还有两层循环&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;简化伪代码只有一个 loop，实际实现的结构是这样的节选：&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-13 z-d-8&quot;&gt;loop&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-2 z-d-8&quot;&gt;    let&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-8&quot;&gt; mut&lt;/span&gt;&lt;span class=&quot;z-l-3&quot;&gt; has_more_tool_calls&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-2&quot;&gt; true&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-13 z-d-8&quot;&gt;    while&lt;/span&gt;&lt;span class=&quot;z-l-3&quot;&gt; has_more_tool_calls&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt; |&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt; !&lt;/span&gt;&lt;span class=&quot;z-l-3&quot;&gt;pending_messages&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-6&quot;&gt;is_empty&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-4&quot;&gt;        //&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-4&quot;&gt; 模型请求、工具执行，以及新的 steering 消息&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-2 z-d-8&quot;&gt;    let&lt;/span&gt;&lt;span class=&quot;z-l-3&quot;&gt; follow_ups&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-6&quot;&gt; drain_follow_up&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-3&quot;&gt;config&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-13 z-d-8&quot;&gt;await&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-13 z-d-8&quot;&gt;    if&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt; !&lt;/span&gt;&lt;span class=&quot;z-l-3&quot;&gt;follow_ups&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-6&quot;&gt;is_empty&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3&quot;&gt;        pending_messages&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-3&quot;&gt; follow_ups&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-13 z-d-8&quot;&gt;        continue&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-13 z-d-8&quot;&gt;    break&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;内层处理当前任务的工具往返，也处理用户运行中发来的 steering 消息。比如 Agent 正在工作，用户补充“先不要改那个文件”，这条消息在代码规定的轮次边界进入上下文。&lt;/p&gt;
&lt;p&gt;外层检查 follow-up，也就是当前任务结束后排队要做的事。没有后续任务，就结束整个运行。&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;mermaid&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-13 z-d-8&quot;&gt;flowchart&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-6&quot;&gt; TD&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-d-10&quot;&gt;    A&lt;/span&gt;&lt;span class=&quot;z-l-13 z-d-8&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;z-l-12 z-d-5&quot;&gt;进入当前任务&lt;/span&gt;&lt;span class=&quot;z-l-13 z-d-8&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;z-l-13 z-d-8&quot;&gt; --&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-10&quot;&gt; B&lt;/span&gt;&lt;span class=&quot;z-l-13 z-d-8&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;z-l-12 z-d-5&quot;&gt;注入已排队的 steering 消息&lt;/span&gt;&lt;span class=&quot;z-l-13 z-d-8&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-d-10&quot;&gt;    B &lt;/span&gt;&lt;span class=&quot;z-l-13 z-d-8&quot;&gt;--&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-10&quot;&gt; C&lt;/span&gt;&lt;span class=&quot;z-l-13 z-d-8&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;z-l-12 z-d-5&quot;&gt;请求模型并取得完整响应&lt;/span&gt;&lt;span class=&quot;z-l-13 z-d-8&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-d-10&quot;&gt;    C &lt;/span&gt;&lt;span class=&quot;z-l-13 z-d-8&quot;&gt;--&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-10&quot;&gt; D&lt;/span&gt;&lt;span class=&quot;z-l-13 z-d-8&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;z-l-12 z-d-5&quot;&gt;模型错误或中止&lt;/span&gt;&lt;span class=&quot;z-l-13 z-d-8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-d-10&quot;&gt;    D &lt;/span&gt;&lt;span class=&quot;z-l-13 z-d-8&quot;&gt;--&lt;/span&gt;&lt;span class=&quot;z-l-12 z-d-5&quot;&gt; 是 &lt;/span&gt;&lt;span class=&quot;z-l-13 z-d-8&quot;&gt;--&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-10&quot;&gt; Z&lt;/span&gt;&lt;span class=&quot;z-l-13 z-d-8&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;z-l-12 z-d-5&quot;&gt;结束本次运行&lt;/span&gt;&lt;span class=&quot;z-l-13 z-d-8&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-d-10&quot;&gt;    D &lt;/span&gt;&lt;span class=&quot;z-l-13 z-d-8&quot;&gt;--&lt;/span&gt;&lt;span class=&quot;z-l-12 z-d-5&quot;&gt; 否 &lt;/span&gt;&lt;span class=&quot;z-l-13 z-d-8&quot;&gt;--&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-10&quot;&gt; E&lt;/span&gt;&lt;span class=&quot;z-l-13 z-d-8&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;z-l-12 z-d-5&quot;&gt;有工具调用&lt;/span&gt;&lt;span class=&quot;z-l-13 z-d-8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-d-10&quot;&gt;    E &lt;/span&gt;&lt;span class=&quot;z-l-13 z-d-8&quot;&gt;--&lt;/span&gt;&lt;span class=&quot;z-l-12 z-d-5&quot;&gt; 是 &lt;/span&gt;&lt;span class=&quot;z-l-13 z-d-8&quot;&gt;--&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-10&quot;&gt; F&lt;/span&gt;&lt;span class=&quot;z-l-13 z-d-8&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;z-l-12 z-d-5&quot;&gt;执行工具或生成截断错误结果&lt;/span&gt;&lt;span class=&quot;z-l-13 z-d-8&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-d-10&quot;&gt;    F &lt;/span&gt;&lt;span class=&quot;z-l-13 z-d-8&quot;&gt;--&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-10&quot;&gt; G&lt;/span&gt;&lt;span class=&quot;z-l-13 z-d-8&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;z-l-12 z-d-5&quot;&gt;将工具结果加入上下文&lt;/span&gt;&lt;span class=&quot;z-l-13 z-d-8&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-d-10&quot;&gt;    E &lt;/span&gt;&lt;span class=&quot;z-l-13 z-d-8&quot;&gt;--&lt;/span&gt;&lt;span class=&quot;z-l-12 z-d-5&quot;&gt; 否 &lt;/span&gt;&lt;span class=&quot;z-l-13 z-d-8&quot;&gt;--&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-10&quot;&gt; H&lt;/span&gt;&lt;span class=&quot;z-l-13 z-d-8&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;z-l-12 z-d-5&quot;&gt;回合结束与钩子检查&lt;/span&gt;&lt;span class=&quot;z-l-13 z-d-8&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-d-10&quot;&gt;    G &lt;/span&gt;&lt;span class=&quot;z-l-13 z-d-8&quot;&gt;--&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-10&quot;&gt; H&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-d-10&quot;&gt;    H &lt;/span&gt;&lt;span class=&quot;z-l-13 z-d-8&quot;&gt;--&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-10&quot;&gt; I&lt;/span&gt;&lt;span class=&quot;z-l-13 z-d-8&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;z-l-12 z-d-5&quot;&gt;钩子要求停止或任务被取消&lt;/span&gt;&lt;span class=&quot;z-l-13 z-d-8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-d-10&quot;&gt;    I &lt;/span&gt;&lt;span class=&quot;z-l-13 z-d-8&quot;&gt;--&lt;/span&gt;&lt;span class=&quot;z-l-12 z-d-5&quot;&gt; 是 &lt;/span&gt;&lt;span class=&quot;z-l-13 z-d-8&quot;&gt;--&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-10&quot;&gt; Z&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-d-10&quot;&gt;    I &lt;/span&gt;&lt;span class=&quot;z-l-13 z-d-8&quot;&gt;--&lt;/span&gt;&lt;span class=&quot;z-l-12 z-d-5&quot;&gt; 否 &lt;/span&gt;&lt;span class=&quot;z-l-13 z-d-8&quot;&gt;--&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-10&quot;&gt; J&lt;/span&gt;&lt;span class=&quot;z-l-13 z-d-8&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;z-l-12 z-d-5&quot;&gt;读取新的 steering 消息&lt;/span&gt;&lt;span class=&quot;z-l-13 z-d-8&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-d-10&quot;&gt;    J &lt;/span&gt;&lt;span class=&quot;z-l-13 z-d-8&quot;&gt;--&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-10&quot;&gt; K&lt;/span&gt;&lt;span class=&quot;z-l-13 z-d-8&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;z-l-12 z-d-5&quot;&gt;需要工具往返或有 steering 消息&lt;/span&gt;&lt;span class=&quot;z-l-13 z-d-8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-d-10&quot;&gt;    K &lt;/span&gt;&lt;span class=&quot;z-l-13 z-d-8&quot;&gt;--&lt;/span&gt;&lt;span class=&quot;z-l-12 z-d-5&quot;&gt; 是 &lt;/span&gt;&lt;span class=&quot;z-l-13 z-d-8&quot;&gt;--&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-10&quot;&gt; B&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-d-10&quot;&gt;    K &lt;/span&gt;&lt;span class=&quot;z-l-13 z-d-8&quot;&gt;--&lt;/span&gt;&lt;span class=&quot;z-l-12 z-d-5&quot;&gt; 否 &lt;/span&gt;&lt;span class=&quot;z-l-13 z-d-8&quot;&gt;--&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-10&quot;&gt; L&lt;/span&gt;&lt;span class=&quot;z-l-13 z-d-8&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;z-l-12 z-d-5&quot;&gt;有排队的 follow&lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span class=&quot;z-l-12 z-d-5&quot;&gt;up&lt;/span&gt;&lt;span class=&quot;z-l-13 z-d-8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-d-10&quot;&gt;    L &lt;/span&gt;&lt;span class=&quot;z-l-13 z-d-8&quot;&gt;--&lt;/span&gt;&lt;span class=&quot;z-l-12 z-d-5&quot;&gt; 是 &lt;/span&gt;&lt;span class=&quot;z-l-13 z-d-8&quot;&gt;--&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-10&quot;&gt; M&lt;/span&gt;&lt;span class=&quot;z-l-13 z-d-8&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;z-l-12 z-d-5&quot;&gt;切换到后续任务&lt;/span&gt;&lt;span class=&quot;z-l-13 z-d-8&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-d-10&quot;&gt;    M &lt;/span&gt;&lt;span class=&quot;z-l-13 z-d-8&quot;&gt;--&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-10&quot;&gt; B&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-d-10&quot;&gt;    L &lt;/span&gt;&lt;span class=&quot;z-l-13 z-d-8&quot;&gt;--&lt;/span&gt;&lt;span class=&quot;z-l-12 z-d-5&quot;&gt; 否 &lt;/span&gt;&lt;span class=&quot;z-l-13 z-d-8&quot;&gt;--&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-10&quot;&gt; Z&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;图中展示普通请求路径，省略了接续已有 assistant 消息及取消时仍有排队消息等分支。steering 在工具批次结束等轮次边界读取，不能保证立刻阻止已经开始的写入；中止正在执行的任务还需要取消机制配合。&lt;/p&gt;
&lt;p&gt;如果两层循环一下子不好理解，可以先按本篇的小例子读：没有排队消息时，主要关注内层的模型与工具往返。等这条线清楚了，再加上“用户中途补充要求”和“做完后还有下一个任务”这两种情况。&lt;/p&gt;
&lt;h2 id=&quot;neng-xun-huan-yi-hou-huan-yao-bu-na-xie-yue-shu&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#neng-xun-huan-yi-hou-huan-yao-bu-na-xie-yue-shu&quot; aria-label=&quot;Anchor link for: neng-xun-huan-yi-hou-huan-yao-bu-na-xie-yue-shu&quot;&gt;能循环以后，还要补哪些约束&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;剩下几个问题，做实际工具时很快就会遇到。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;能不能并行执行？&lt;/strong&gt; 两个独立的读取可以并行，修改同一份文件就得考虑顺序。pi-rust 同时检查全局执行模式和每个工具的模式；这一批里只要有工具要求顺序执行，整批就走顺序路径。并行模式下，完成事件按完成顺序发出，结果消息按原调用顺序整理。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;什么时候必须停？&lt;/strong&gt; 错误、中止、工具批次终止和 &lt;code&gt;should_stop_after_turn&lt;/code&gt; 都是入口。接真实模型时，还应在应用或 Harness 配置预算，限制时间、轮次或费用。一个 while 本身提供不了这些限制。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;工具能碰到哪里？&lt;/strong&gt; 执行环境负责落地操作，应用还要决定文件范围、命令权限和审批规则。&lt;code&gt;OsExecutionEnv&lt;/code&gt; 使用真实系统，不能因为有这一层接口就把它称作安全沙箱。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;中断后能不能恢复？&lt;/strong&gt; 本篇的核心循环处理内存上下文。持久化和恢复在 Harness；外部操作已经发生但结果还没保存时，恢复逻辑不能随意重放有副作用的工具。&lt;/p&gt;
&lt;p&gt;如果你准备自己写一版，可以先沿用本文的写文件任务。跑完以后检查文件内容，再检查历史里有没有那四条消息。然后故意传错一个参数，看看错误能不能回到模型；再尝试取消一次执行。这样每加一项能力，你都知道它具体解决了哪个问题。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;本文源码链接固定到公开提交 &lt;code&gt;1138ab1007f41cc9d24c494845e6019019e91d43&lt;/code&gt;。片段按阅读顺序节选，伪代码和省略处已标注。完整入口见&lt;a rel=&quot;noopener noreferrer external&quot; target=&quot;_blank&quot; href=&quot;https://github.com/bigfish1913/pi-rust/blob/1138ab1007f41cc9d24c494845e6019019e91d43/examples/tools/src/main.rs&quot;&gt;工具示例&lt;/a&gt;，核心实现见 &lt;a rel=&quot;noopener noreferrer external&quot; target=&quot;_blank&quot; href=&quot;https://github.com/bigfish1913/pi-rust/blob/1138ab1007f41cc9d24c494845e6019019e91d43/crates/rpi-agent/src/agent_loop.rs&quot;&gt;agent_loop.rs&lt;/a&gt;。&lt;/p&gt;
&lt;/blockquote&gt;
</description>
      </item>
      <item>
          <title>Rust 版 Pi 工作基本完成了，记录下性能对比</title>
          <pubDate>Sat, 03 Oct 2026 00:00:00 +0000</pubDate>
          <author>bigfish</author>
          <link>https://blog.bigfisharc.cn/blog/20261003-rpi-performance/</link>
          <guid>https://blog.bigfisharc.cn/blog/20261003-rpi-performance/</guid>
          <description xml:base="https://blog.bigfisharc.cn/blog/20261003-rpi-performance/">&lt;p&gt;最近做了一个 Rust 项目：&lt;a rel=&quot;noopener noreferrer external&quot; target=&quot;_blank&quot; href=&quot;https://github.com/bigfish1913/pi-rust&quot;&gt;pi-rust&lt;/a&gt;。把 TypeScript 版的 Pi 编码助手用 Rust 重写，命令叫 &lt;code&gt;rpi&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;先把测试结果放出来。&lt;/p&gt;
&lt;h2 id=&quot;xian-kan-shu-ju-dui-bi&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#xian-kan-shu-ju-dui-bi&quot; aria-label=&quot;Anchor link for: xian-kan-shu-ju-dui-bi&quot;&gt;先看数据对比&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;对比版本是 rpi 0.3.6 和 Pi 0.87.1&lt;/p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;指标&lt;/th&gt;&lt;th style=&quot;text-align: right&quot;&gt;Rust 版 rpi&lt;/th&gt;&lt;th style=&quot;text-align: right&quot;&gt;TypeScript 版 Pi&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;打印版本号 &lt;code&gt;--version&lt;/code&gt;&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;17.5 ms&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;169.5 ms&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;启动到能够响应状态请求&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;17.7 ms&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;189.5 ms&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;刚就绪时的常驻内存 RSS&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;12.0 MiB&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;91.5 MiB&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;安装体积&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;21.8 MiB&lt;/td&gt;&lt;td style=&quot;text-align: right&quot;&gt;约 385 MiB&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;启动时间相差约 &lt;strong&gt;10.7 倍&lt;/strong&gt;，rpi 的就绪内存约为 Pi 的 &lt;strong&gt;13%&lt;/strong&gt;。安装体积这一项，Pi 还没有算上 Node 运行时。&lt;/p&gt;
&lt;h2 id=&quot;wei-shen-me-yao-yong-rust-zhong-xie&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#wei-shen-me-yao-yong-rust-zhong-xie&quot; aria-label=&quot;Anchor link for: wei-shen-me-yao-yong-rust-zhong-xie&quot;&gt;为什么要用 Rust 重写？&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id=&quot;1-xue-xi-rust&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#1-xue-xi-rust&quot; aria-label=&quot;Anchor link for: 1-xue-xi-rust&quot;&gt;1. 学习 Rust&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;首先就是想学 Rust。&lt;/p&gt;
&lt;p&gt;我想找一个能持续写下去的项目。Agent 涉及异步请求、流式输出、文件操作和状态管理，后面还有终端界面和插件。这些需求会逼着我把 Rust 用起来：共享状态怎么处理，错误怎么往上传，异步任务怎么取消，接口怎么拆。&lt;/p&gt;
&lt;p&gt;选 Pi 来重写，也有一个好处：有现成的项目可以参考。遇到一个功能，可以先看原版怎么做，再考虑在 Rust 里怎么实现。写完以后还有东西可以对照，不至于只凭自己的想象判断是否合理。&lt;/p&gt;
&lt;h3 id=&quot;2-xiang-nong-ming-bai-yi-ge-agent-shi-zen-me-gong-zuo-de&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#2-xiang-nong-ming-bai-yi-ge-agent-shi-zen-me-gong-zuo-de&quot; aria-label=&quot;Anchor link for: 2-xiang-nong-ming-bai-yi-ge-agent-shi-zen-me-gong-zuo-de&quot;&gt;2. 想弄明白一个  Agent 是怎么工作的&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;平时用 AI 编码工具，看到的是输入需求、等它改代码。自己实现一次，就得处理那些界面上看不见的细节。&lt;/p&gt;
&lt;p&gt;模型返回一个工具调用以后，谁去执行？工具出错了，要把什么结果交给模型？流式消息还没结束，用户取消了怎么办？程序中断后，下一次启动看到的历史应该是什么？&lt;/p&gt;
&lt;p&gt;这些问题在调一次聊天 API 时未必会遇到，做完整 Agent 就绕不过去。&lt;/p&gt;
&lt;p&gt;核心循环其实不长：请求模型，发现工具调用，执行工具，回传结果，再请求模型。周围的错误处理、取消、会话保存，才是需要花心思的地方。&lt;/p&gt;
&lt;h3 id=&quot;3-xiang-ba-ta-zuo-cheng-yi-ge-neng-fu-yong-de-ku&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#3-xiang-ba-ta-zuo-cheng-yi-ge-neng-fu-yong-de-ku&quot; aria-label=&quot;Anchor link for: 3-xiang-ba-ta-zuo-cheng-yi-ge-neng-fu-yong-de-ku&quot;&gt;3. 想把它做成一个能复用的库&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;我希望 rpi 除了能在终端里运行，也能直接嵌进自己的 Rust 程序。应用调用接口、订阅事件，就能使用 Agent 的能力。&lt;/p&gt;
&lt;p&gt;所以项目用了 Cargo workspace，把模型适配、Agent 循环、工具执行和会话管理拆开。CLI 再把它们组合起来。&lt;/p&gt;
&lt;p&gt;最小的 Agent 不需要终端界面，也不一定需要持久化。需要文件工具，就加工具层；需要保存会话和压缩上下文，再接 Harness。这样不同应用能各取所需。&lt;/p&gt;
&lt;h3 id=&quot;4-kan-kan-yuan-sheng-cheng-xu-neng-sheng-xia-duo-shao-kai-xiao&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#4-kan-kan-yuan-sheng-cheng-xu-neng-sheng-xia-duo-shao-kai-xiao&quot; aria-label=&quot;Anchor link for: 4-kan-kan-yuan-sheng-cheng-xu-neng-sheng-xia-duo-shao-kai-xiao&quot;&gt;4. 看看原生程序能省下多少开销&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;终端工具最后能交付一个可执行文件，安装时少管理一套 Node 和 npm 依赖，我觉得值得试。&lt;/p&gt;
&lt;p&gt;启动和内存上的收益，前面的测试已经给了一组结果。代价也有：Rust 的异步接口、共享状态和动态插件都需要仔细处理。把原版代码逐行翻译过来，很多地方并不好用，接口也得跟着重新整理。&lt;/p&gt;
&lt;h2 id=&quot;xian-zai-dao-na-yi-bu-liao&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#xian-zai-dao-na-yi-bu-liao&quot; aria-label=&quot;Anchor link for: xian-zai-dao-na-yi-bu-liao&quot;&gt;现在到哪一步了&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;已经有 CLI、TUI 和 SDK，不只是一个调用模型的 demo。当前工作区版本是 &lt;strong&gt;0.3.14&lt;/strong&gt;；发布状态以仓库 Releases 和 crates.io 为准。&lt;/p&gt;
&lt;p&gt;模型侧有 Anthropic 和 OpenAI 兼容接口，也支持通过配置接自定义网关。Agent 能处理流式响应、工具调用和取消，配有文件读写、编辑和命令执行等工具。&lt;/p&gt;
&lt;p&gt;会话用 JSONL 保存，支持分支、上下文压缩和中断恢复。这里也一直在修实际问题。比如 CHANGELOG 里记录过：会话中断时，工具调用已经保存，结果还没保存，恢复后的消息就可能被模型服务拒绝。现在会补上表示中断的错误结果，让上下文可以继续使用，而不会直接重跑那个工具。&lt;/p&gt;
&lt;p&gt;扩展方面已经有动态库插件 SDK 和加载器。远程模式也有了，可以在一台机器上跑 Agent，另一端用终端连接。&lt;/p&gt;
&lt;p&gt;这些功能对应九个产品 crate。主要几层如下：&lt;/p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;crate&lt;/th&gt;&lt;th&gt;做什么&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;rpi-ai&lt;/code&gt;&lt;/td&gt;&lt;td&gt;模型适配、消息类型和流式响应&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;rpi-agent&lt;/code&gt;&lt;/td&gt;&lt;td&gt;Agent 循环、工具调用、事件和取消&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;rpi-tools&lt;/code&gt;&lt;/td&gt;&lt;td&gt;内置工具和执行环境&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;rpi-harness&lt;/code&gt;&lt;/td&gt;&lt;td&gt;会话、持久化、恢复和上下文压缩&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;rpi-cli&lt;/code&gt;&lt;/td&gt;&lt;td&gt;配置、CLI、TUI 和远程模式&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;想从代码看起，可以先跑两个例子：&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;powershell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;cargo run &lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt;-&lt;/span&gt;&lt;span&gt;p minimal&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;cargo run &lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt;-&lt;/span&gt;&lt;span&gt;p tools&lt;/span&gt;&lt;span class=&quot;z-d-8&quot;&gt;-&lt;/span&gt;&lt;span&gt;example&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;它们用模拟 Provider，不需要模型密钥。第二个例子会驱动真实工具，在临时目录写文件，再读取并验证内容。这样可以先检查 Agent 的执行流程，不受真实模型输出影响。目前还有不少需要打磨的地方。&lt;/p&gt;
&lt;h2 id=&quot;you-mei-you-hou-xu-de-kuo-zhan-ji-hua&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#you-mei-you-hou-xu-de-kuo-zhan-ji-hua&quot; aria-label=&quot;Anchor link for: you-mei-you-hou-xu-de-kuo-zhan-ji-hua&quot;&gt;有没有后续的扩展计划&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;有，接下来想继续做的主要是这些方向。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;扩展更容易写和安装。&lt;/strong&gt; 插件接口和加载器已经有了，但还需要更顺手的模板、版本兼容说明，以及能直接拿来用的扩展。先把开发和使用的流程理顺，比单纯增加插件数量更有用。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;继续补模型适配。&lt;/strong&gt; 现有 Provider 接口可以复用，后面会根据使用需求考虑 Gemini、Bedrock 等适配器。每接一种协议，都要检查流式事件、工具调用和错误处理，不能只验证普通聊天能返回文字。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;把性能测试往长任务上补。&lt;/strong&gt; 下一步值得测的是长会话内存增长、工具循环吞吐、上下文压缩和 TUI 渲染。启动数据已经有了，我也想知道连续用上几个小时以后是什么表现。&lt;/p&gt;
&lt;p&gt;更往后的方向包括执行隔离、编辑器集成，以及是否把多 Agent 编排放进核心运行时。这些需要继续设计，暂时没有确定的交付时间。&lt;/p&gt;
&lt;p&gt;项目地址：&lt;a rel=&quot;noopener noreferrer external&quot; target=&quot;_blank&quot; href=&quot;https://github.com/bigfish1913/pi-rust&quot;&gt;bigfish1913/pi-rust&lt;/a&gt;。如果你也在学 Rust，或者想拆开看看  Agent 的实现，可以从两个离线示例开始。&lt;/p&gt;
</description>
      </item>
      <item>
          <title>Pi 的 Rust 实现来了：rpi，不止一个终端 Agent</title>
          <pubDate>Thu, 01 Oct 2026 23:11:04 +0800</pubDate>
          <author>bigfish</author>
          <link>https://blog.bigfisharc.cn/blog/20261006-2311-pide-rustshi-xian-lai-liao-rpi-bu-zhi-yi-ge-zhong-duan-agent/</link>
          <guid>https://blog.bigfisharc.cn/blog/20261006-2311-pide-rustshi-xian-lai-liao-rpi-bu-zhi-yi-ge-zhong-duan-agent/</guid>
          <description xml:base="https://blog.bigfisharc.cn/blog/20261006-2311-pide-rustshi-xian-lai-liao-rpi-bu-zhi-yi-ge-zhong-duan-agent/">&lt;p&gt;如果你熟悉 Pi，rpi 可以理解为它的 Rust 实现：同样能在终端里读文件、改代码、执行命令，也能作为库嵌入自己的程序。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;rpi 用 Rust 重新实现 Pi SDK，并在这套运行时之上提供终端 CLI 和原生扩展。&lt;/strong&gt; 想直接使用编码助手，可以安装后运行；想构建自己的 Agent，则可以选择需要的模型接入、工具循环和会话管理组件。&lt;/p&gt;</description>
      </item>
    </channel>
</rss>
