<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
   <title>tiankonguse blog</title>
   <link href="https://github.tiankonguse.com/atom.xml" rel="self" type="application/atom+xml"/>
   <link href="https://github.tiankonguse.com" rel="alternate" type="text/html" />
   <updated>2026-05-13T22:52:38+08:00</updated>
   <id>https://github.tiankonguse.com</id>
   <description>这里是一个程序员的生活缩影，记录的有： 1. 计算机技术与算法 2. 程序员生活的酸甜苦辣 3. 投资理财小知识 4. 健康知识：感冒、皮肤、健身、营养学等 5. 读书笔记 6. 电影观后感 7. 人生思考 这些都可以通过菜单快速找到。</description>
   <author>
     <name>tiankonguse</name>
     <email>i@tiankonguse.com</email>
   </author>
   
    
   <entry>
     <title>Agent 的万能插座：MCP 协议</title>
     <link href="https://github.tiankonguse.com/blog/2026/05/13/agent-mcp.html"/>
     <updated>2026-05-13T21:00:00+08:00</updated>
     <published>2026-05-13T21:00:00+08:00</published>
     <id>https://github.tiankonguse.com/blog/2026/05/13/agent-mcp</id>
     <content type="html">&lt;p&gt;&lt;img src=&quot;https://res2026.tiankonguse.com/images/2026/05/13/000.png&quot; alt=&quot;截图&quot; /&gt;&lt;/p&gt;

&lt;p&gt;前四篇文章分别讲了 Agent 的 &lt;a href=&quot;https://mp.weixin.qq.com/s/dkdrwVlwe3IkH2hzSzy53A&quot;&gt;Loop&lt;/a&gt;、&lt;a href=&quot;https://mp.weixin.qq.com/s/xyX4_CF5cveezEDuzFT13g&quot;&gt;Tools&lt;/a&gt;、&lt;a href=&quot;https://mp.weixin.qq.com/s/lguRAdxFoN22rqPyx3BIzw&quot;&gt;Prompts&lt;/a&gt; 和 &lt;a href=&quot;https://mp.weixin.qq.com/s/YRS29wRckEmFgNb0eJrxrQ&quot;&gt;Context Compact&lt;/a&gt;。&lt;/p&gt;

&lt;p&gt;这篇聊一个工程上绕不过的问题——&lt;strong&gt;工具&lt;/strong&gt;。&lt;/p&gt;

&lt;p&gt;Agent 的核心价值在于能调用工具。&lt;br /&gt;
工具越多，Agent 能干的事就越多。&lt;br /&gt;
但工具是谁来写的？&lt;br /&gt;
谁来维护？&lt;br /&gt;
怎么让 Agent 用上别人已经做好的工具？&lt;/p&gt;

&lt;p&gt;这就是今天要聊的东西，&lt;strong&gt;MCP&lt;/strong&gt;。&lt;/p&gt;

&lt;h2 id=&quot;一痛点每个工具都要单独对接&quot;&gt;一、痛点：每个工具都要单独对接&lt;/h2&gt;

&lt;p&gt;先回顾一下。&lt;/p&gt;

&lt;p&gt;在第二篇里，我们实现了一套工具注册机制：每个工具写一个 Go 文件，通过 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;init()&lt;/code&gt; 自动注册，Agent 就能调用它。&lt;/p&gt;

&lt;p&gt;这套机制对内置工具来说，挺好用的。&lt;/p&gt;

&lt;p&gt;但现实里，工具的来源不只是你自己写的代码。&lt;/p&gt;

&lt;p&gt;比如你公司有一个数据查询服务，你想用某个开源的文件搜索工具，或者你想调用某个第三方的 API 封装。&lt;/p&gt;

&lt;p&gt;如果每接入一个外部工具，都要专门写一段 Go 代码来适配，这个成本会越来越高。&lt;/p&gt;

&lt;p&gt;更麻烦的是，这些外部服务的维护方不是你。&lt;br /&gt;
服务升级了，你的适配代码也要跟着改。&lt;/p&gt;

&lt;p&gt;打个比方。&lt;/p&gt;

&lt;p&gt;这就好比你家的电器，每一种都需要专门定制的插头。&lt;br /&gt;
换一台新电视，就得重新改一次墙上的插座。&lt;/p&gt;

&lt;p&gt;你说这事烦不烦？&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://res2026.tiankonguse.com/images/2026/05/13/006.png&quot; alt=&quot;截图&quot; /&gt;&lt;/p&gt;

&lt;p&gt;能不能有一个&lt;strong&gt;统一的标准插座&lt;/strong&gt;，所有工具都按这个标准接入，Agent 就能直接用上？&lt;/p&gt;

&lt;p&gt;这就是 &lt;strong&gt;MCP&lt;/strong&gt; 的出发点。&lt;/p&gt;

&lt;h2 id=&quot;二mcp-是什么&quot;&gt;二、MCP 是什么&lt;/h2&gt;

&lt;p&gt;MCP，全称 &lt;strong&gt;Model Context Protocol&lt;/strong&gt;，是 Anthropic 在 2024 年底推出的一套开放协议。&lt;/p&gt;

&lt;p&gt;它的核心思路就一句话：&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;把”AI 调用外部能力”这件事，标准化成一套协议。&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;有了这套协议，工具的提供方只需要实现 MCP Server，遵守协议规范。&lt;br /&gt;
Agent 只需要实现 MCP Client，学会跟任何遵守协议的 Server 通信。&lt;/p&gt;

&lt;p&gt;双方都不需要知道对方的内部实现细节。&lt;/p&gt;

&lt;p&gt;就像网页浏览器和 Web 服务器的关系。&lt;br /&gt;
浏览器不需要知道服务器是用 Python 还是 Go 写的，只要服务器遵守 HTTP 协议，浏览器就能访问它。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://res2026.tiankonguse.com/images/2026/05/13/001.png&quot; alt=&quot;截图&quot; /&gt;&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-mermaid&quot;&gt;graph LR
    A[&quot;Agent\n(MCP Client)&quot;] -- &quot;标准 MCP 协议&quot; --&amp;gt; B[&quot;MCP Server A\n数据查询服务&quot;]
    A -- &quot;标准 MCP 协议&quot; --&amp;gt; C[&quot;MCP Server B\n文件搜索工具&quot;]
    A -- &quot;标准 MCP 协议&quot; --&amp;gt; D[&quot;MCP Server C\n第三方 API&quot;]

    style A fill:#c8e6c9
    style B fill:#bbdefb
    style C fill:#bbdefb
    style D fill:#bbdefb
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;有了 MCP，Agent 的工具生态就从”自己写、自己维护”，变成了”任何人都可以发布 MCP Server，Agent 接入即用”。&lt;/p&gt;

&lt;p&gt;这是一个质的变化。&lt;/p&gt;

&lt;h2 id=&quot;三底层通信json-rpc-20&quot;&gt;三、底层通信：JSON-RPC 2.0&lt;/h2&gt;

&lt;p&gt;聊完了”是什么”，来看看它底下是怎么跑的。&lt;/p&gt;

&lt;p&gt;MCP 的底层通信协议是 &lt;strong&gt;JSON-RPC 2.0&lt;/strong&gt;。&lt;/p&gt;

&lt;p&gt;这东西非常轻量。&lt;br /&gt;
每条消息就是一个 JSON 对象，通过 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;method&lt;/code&gt; 字段声明调用什么方法，通过 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;params&lt;/code&gt; 字段传参数，通过 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;result&lt;/code&gt; 字段返回结果。&lt;/p&gt;

&lt;p&gt;长什么样呢？&lt;br /&gt;
看一眼就明白了：&lt;/p&gt;

&lt;div class=&quot;language-json highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;err&quot;&gt;//&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;请求（Client&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;→&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;Server）&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;jsonrpc&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;2.0&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;id&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;method&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;tools/call&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;params&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;QueryUnionPlus&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;arguments&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;view_name&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;2003&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;key&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;mzc002009g0nh88&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;field_name&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;title&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;

&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;//&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;响应（Server&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;→&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;Client）&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;jsonrpc&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;2.0&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;id&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;result&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;content&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;text&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;text&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Union value: {&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;title&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;主角&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;}&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;就这么简单，一来一回。&lt;/p&gt;

&lt;p&gt;MCP 在这套基础上，定义了三个核心方法：&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;initialize&lt;/code&gt;&lt;/strong&gt;，握手。&lt;br /&gt;
Client 和 Server 互相确认协议版本和能力。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;tools/list&lt;/code&gt;&lt;/strong&gt;，工具发现。&lt;br /&gt;
Client 拉取 Server 提供的所有工具列表及参数格式。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;tools/call&lt;/code&gt;&lt;/strong&gt;，工具调用。&lt;br /&gt;
Client 发起具体的工具请求，Server 执行并返回结果。&lt;/p&gt;

&lt;p&gt;整个连接的生命周期，画出来是这样的：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://res2026.tiankonguse.com/images/2026/05/13/002.png&quot; alt=&quot;截图&quot; /&gt;&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-mermaid&quot;&gt;sequenceDiagram
    participant Client as MCP Client
    participant Server as MCP Server

    Client-&amp;gt;&amp;gt;Server: initialize（协议版本、能力声明）
    Server--&amp;gt;&amp;gt;Client: 确认握手
    Client-&amp;gt;&amp;gt;Server: notifications/initialized
    Client-&amp;gt;&amp;gt;Server: tools/list
    Server--&amp;gt;&amp;gt;Client: [{name, description, inputSchema}, ...]
    Note over Client,Server: 工具已发现，开始正常调用
    Client-&amp;gt;&amp;gt;Server: tools/call {name, arguments}
    Server--&amp;gt;&amp;gt;Client: {content: [{type, text}]}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;先握手，再拿工具列表，然后就可以按需调用了。&lt;/p&gt;

&lt;p&gt;逻辑非常清晰。&lt;/p&gt;

&lt;h2 id=&quot;四三种传输方式&quot;&gt;四、三种传输方式&lt;/h2&gt;

&lt;p&gt;JSON-RPC 消息需要一个”管道”来传输。&lt;/p&gt;

&lt;p&gt;MCP 规范定义了三种传输方式，适用于不同的部署场景。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;第一种，stdio（标准输入输出）。&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;最简单的方式。&lt;br /&gt;
Client 把 Server 当作一个子进程启动，通过进程的标准输入和标准输出传递 JSON-RPC 消息。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Client ──stdin──→ Server 子进程
Client ←─stdout── Server 子进程
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;适合本地工具，比如文件系统操作、代码执行等场景。&lt;br /&gt;
Server 就是一个可执行文件。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;第二种，SSE（Server-Sent Events）。&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;基于 HTTP 长连接。&lt;br /&gt;
Client 向 Server 发起一个 GET 请求，建立一条持久的 SSE 推送流；同时通过 POST 发送请求消息；Server 的响应通过 SSE 流推回来。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Client ──POST──→ Server（发请求）
Client ←─SSE─── Server（收响应，持久连接）
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;适合远程 Server，需要维护一条持久连接。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;第三种，streamableHttp（可流式 HTTP）。&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;更简单的远程方案。&lt;br /&gt;
每次调用都是一个独立的 HTTP POST 请求，Server 可以直接返回 JSON 响应，也可以用 SSE 格式返回流式内容。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Client ──POST──→ Server（每次独立请求）
Client ←─JSON── Server（或 SSE 流）
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;适合无状态的远程服务，对 Server 部署要求最低。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://res2026.tiankonguse.com/images/2026/05/13/003.png&quot; alt=&quot;截图&quot; /&gt;&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-mermaid&quot;&gt;graph TD
    A[&quot;MCP 传输方式&quot;] --&amp;gt; B[&quot;stdio\n本地子进程\n适合本地工具&quot;]
    A --&amp;gt; C[&quot;SSE\n持久长连接\n适合远程服务（有状态）&quot;]
    A --&amp;gt; D[&quot;streamableHttp\n独立 HTTP 请求\n适合远程服务（无状态）&quot;]

    style B fill:#c8e6c9
    style C fill:#bbdefb
    style D fill:#bbdefb
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;三种方式，覆盖了从本地到远程、从有状态到无状态的所有场景。&lt;/p&gt;

&lt;p&gt;按需选择就行。&lt;/p&gt;

&lt;h2 id=&quot;五evo-agent-怎么实现的&quot;&gt;五、evo-agent 怎么实现的&lt;/h2&gt;

&lt;p&gt;了解了协议本身，来看看 evo-agent 是怎么落地的。&lt;/p&gt;

&lt;p&gt;evo-agent 目前已经实现了五个核心模块：&lt;strong&gt;Loop&lt;/strong&gt;（Agent 主循环）、&lt;strong&gt;Tools&lt;/strong&gt;（工具注册与调用）、&lt;strong&gt;Prompts&lt;/strong&gt;（系统提示管理）、&lt;strong&gt;Context Compact&lt;/strong&gt;（上下文压缩）、&lt;strong&gt;MCP&lt;/strong&gt;（外部工具）。&lt;/p&gt;

&lt;p&gt;代码托管在 https://github.com/tiankonguse/evo-agent ，感兴趣可以配合代码一起看。&lt;/p&gt;

&lt;p&gt;先说配置。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;配置文件。&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;MCP Server 的配置统一放在 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.evo_agent/mcp.json&lt;/code&gt; 里：&lt;/p&gt;

&lt;div class=&quot;language-json highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;mcpServers&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;unionplus_mcp_normal&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;streamableHttp&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;url&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;https://example.com/mcp&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;headers&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;Authorization&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Bearer &amp;lt;token&amp;gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;other_header&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;xxx&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;disabled&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;每个 Server 有一个名字，配置里指定传输类型、连接地址和认证信息。&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;disabled: true&lt;/code&gt; 可以临时关闭某个 Server，不用删配置。&lt;/p&gt;

&lt;p&gt;对应的 Go 结构体长这样：&lt;/p&gt;

&lt;div class=&quot;language-go highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;MCPServerConfig&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;Type&lt;/span&gt;     &lt;span class=&quot;kt&quot;&gt;string&lt;/span&gt;            &lt;span class=&quot;s&quot;&gt;`json:&quot;type&quot;`&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;Disabled&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;bool&lt;/span&gt;              &lt;span class=&quot;s&quot;&gt;`json:&quot;disabled&quot;`&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;Timeout&lt;/span&gt;  &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt;               &lt;span class=&quot;s&quot;&gt;`json:&quot;timeout&quot;`&lt;/span&gt;

    &lt;span class=&quot;c&quot;&gt;// stdio 专用&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;Command&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;string&lt;/span&gt;            &lt;span class=&quot;s&quot;&gt;`json:&quot;command&quot;`&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;Args&lt;/span&gt;    &lt;span class=&quot;p&quot;&gt;[]&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;string&lt;/span&gt;          &lt;span class=&quot;s&quot;&gt;`json:&quot;args&quot;`&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;Env&lt;/span&gt;     &lt;span class=&quot;k&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;`json:&quot;env&quot;`&lt;/span&gt;

    &lt;span class=&quot;c&quot;&gt;// HTTP 传输专用&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;URL&lt;/span&gt;     &lt;span class=&quot;kt&quot;&gt;string&lt;/span&gt;            &lt;span class=&quot;s&quot;&gt;`json:&quot;url&quot;`&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;Headers&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;`json:&quot;headers&quot;`&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;统一接口。&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;三种传输方式的内部实现完全不同，但对外暴露的是同一套接口：&lt;/p&gt;

&lt;div class=&quot;language-go highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;mcpClient&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;getTools&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[]&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;mcpToolSpec&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;callTool&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;toolName&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;arguments&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;json&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;RawMessage&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;stop&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mcpProcess&lt;/code&gt;（stdio）、&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mcpHTTPClient&lt;/code&gt;（streamableHttp）、&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mcpSSEClient&lt;/code&gt;（SSE）分别实现这个接口。&lt;/p&gt;

&lt;p&gt;上层代码完全不感知传输细节，只需要调 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;callTool&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;这个设计其实就是经典的策略模式——底层实现随便换，上层调用完全透明。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;工具命名与路由。&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;这里有个小巧思。&lt;/p&gt;

&lt;p&gt;MCP 工具在 Agent 侧有一套固定的命名规则：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;mcp__{服务器名}__{工具名}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;比如 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mcp__unionplus_mcp_normal__QueryUnionPlus&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;这个前缀有两个用途。&lt;/p&gt;

&lt;p&gt;第一，让 Agent 知道这是一个 MCP 工具，跟内置工具区分开来。&lt;/p&gt;

&lt;p&gt;第二，携带了路由信息。&lt;br /&gt;
调用时只需要按 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;__&lt;/code&gt; 分割，就能知道该把请求转发给哪个 Server。&lt;/p&gt;

&lt;p&gt;路由代码写出来就这几行：&lt;/p&gt;

&lt;div class=&quot;language-go highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;DispatchMCP&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;name&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;input&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;json&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;RawMessage&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;rest&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;strings&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;TrimPrefix&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;mcp__&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;sep&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;strings&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Index&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;rest&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;__&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;serverName&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;rest&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sep&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;toolName&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;rest&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sep&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;client&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;mcpServers&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;serverName&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;client&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;callTool&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;toolName&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;input&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;信息藏在名字里，不需要额外的映射表。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;启动流程。&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;程序启动时，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;InitMCP()&lt;/code&gt; 负责加载配置、连接所有 Server、拉取工具列表：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://res2026.tiankonguse.com/images/2026/05/13/004.png&quot; alt=&quot;截图&quot; /&gt;&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-mermaid&quot;&gt;sequenceDiagram
    participant Main as main.go
    participant InitMCP as InitMCP()
    participant Server as MCP Server

    Main-&amp;gt;&amp;gt;InitMCP: tools.InitMCP()
    InitMCP-&amp;gt;&amp;gt;InitMCP: 读取 .evo_agent/mcp.json
    loop 每个启用的 Server
        InitMCP-&amp;gt;&amp;gt;Server: initialize 握手
        Server--&amp;gt;&amp;gt;InitMCP: 握手成功
        InitMCP-&amp;gt;&amp;gt;Server: tools/list
        Server--&amp;gt;&amp;gt;InitMCP: 工具列表
        InitMCP-&amp;gt;&amp;gt;InitMCP: 缓存工具到 mcpServers
    end
    InitMCP-&amp;gt;&amp;gt;Main: 打印已连接的 Server 和工具数
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;工具列表在启动时就全部拉取并缓存在内存里。&lt;/p&gt;

&lt;p&gt;每次 Agent Loop 调用 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;tools.Tools()&lt;/code&gt; 时，MCP 工具会和内置工具合并，一起打包进 LLM 的请求里。&lt;/p&gt;

&lt;p&gt;整个调用链路画出来是这样的：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://res2026.tiankonguse.com/images/2026/05/13/005.png&quot; alt=&quot;截图&quot; /&gt;&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-mermaid&quot;&gt;graph TD
    A[&quot;LLM 返回 tool_use\nmcp__unionplus__QueryUnionPlus&quot;] --&amp;gt; B[&quot;executor.go\nDispatch(name, input)&quot;]
    B --&amp;gt; C{&quot;名字是否以 mcp__ 开头？&quot;}
    C -- 是 --&amp;gt; D[&quot;DispatchMCP(name, input)&quot;]
    C -- 否 --&amp;gt; E[&quot;内置工具 Registry&quot;]
    D --&amp;gt; F[&quot;按服务器名查找 mcpServers&quot;]
    F --&amp;gt; G[&quot;mcpHTTPClient.callTool()&quot;]
    G --&amp;gt; H[&quot;POST /mcp\nJSON-RPC tools/call&quot;]
    H --&amp;gt; I[&quot;返回结果字符串&quot;]
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;从 LLM 的返回到最终拿到结果，整个链路一目了然。&lt;/p&gt;

&lt;h2 id=&quot;六跑一次真实的看看&quot;&gt;六、跑一次真实的看看&lt;/h2&gt;

&lt;p&gt;光看代码和架构图不够直观。&lt;br /&gt;
来看一次真实的运行记录。&lt;/p&gt;

&lt;p&gt;接入的是之前我开发的 UnionPlus 数据 MCP 查询服务，通过 streamableHttp 方式连接。&lt;br /&gt;
Server 提供了 9 个工具，涵盖视图查询、字段查询、枚举翻译等能力。&lt;/p&gt;

&lt;p&gt;启动时的输出：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;[MCP] Connected to &quot;unionplus_mcp_normal&quot; (9 tools)
Server: unionplus_mcp_normal
  - QueryUnionPlus: 查询Union数据，单key多字段，字段需要在appid上已授权
  - QueryViewList: 查询视图列表，支持按视图名搜索
  - QueryViewFields: 查询指定视图下的字段列表
  - QueryEnumValues: 按枚举库ID查询枚举值翻译列表
  ...（共9个工具）
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;9 个工具，全部自动发现，自动注册。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;然后我试了第一次查询。&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;查视图 2003，主键 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mzc002009g0nh88&lt;/code&gt;，字段 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;title&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;LLM 收到请求后，先判断”视图 ID 2003 对应的视图名是什么”，尝试调用 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;QueryViewList&lt;/code&gt; 搜索：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ mcp__unionplus_mcp_normal__QueryViewList({&quot;view_name&quot;:&quot;&quot;})
Error: bufio.Scanner: token too long
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;返回数据太多，报错了。&lt;/p&gt;

&lt;p&gt;但 LLM 并没有放弃。&lt;/p&gt;

&lt;p&gt;它调整了策略：直接把 “2003” 作为 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;view_name&lt;/code&gt; 传给 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;QueryUnionPlus&lt;/code&gt; 试一试：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ mcp__unionplus_mcp_normal__QueryUnionPlus({&quot;view_name&quot;:&quot;2003&quot;,&quot;field_name&quot;:&quot;title&quot;,&quot;key&quot;:&quot;mzc002009g0nh88&quot;})
Union value: {&quot;title&quot;:&quot;主角&quot;}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;成了。&lt;/p&gt;

&lt;p&gt;LLM 凭借对工具的理解和错误反馈，自己找到了正确的调用方式。&lt;/p&gt;

&lt;p&gt;这正是 Agent Loop 的价值所在——遇到错误，观察，调整，再试。&lt;br /&gt;
不需要人去干预，它自己就能摸索出路。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;第二次查询就更有意思了。&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;同一个 key，这次查多个字段。&lt;/p&gt;

&lt;p&gt;LLM 直接复用了上一次探索出来的调用方式，用逗号分隔多个字段名：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ mcp__unionplus_mcp_normal__QueryUnionPlus({
  &quot;view_name&quot;: &quot;2003&quot;,
  &quot;key&quot;: &quot;mzc002009g0nh88&quot;,
  &quot;field_name&quot;: &quot;title,type_name,publish_date,second_title,description,stars&quot;
})
Union value: {
  &quot;title&quot;: &quot;主角&quot;,
  &quot;type_name&quot;: &quot;电视剧&quot;,
  &quot;publish_date&quot;: &quot;2026-05-10&quot;,
  &quot;second_title&quot;: &quot;张艺谋监制！命运共振大剧&quot;,
  &quot;description&quot;: &quot;电视剧《主角》讲述了秦腔名伶忆秦娥...&quot;
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;整个过程，Agent 没有用任何专门为 UnionPlus 写的适配代码。&lt;/p&gt;

&lt;p&gt;它只是拿到了工具的描述和参数格式，自己摸索出了正确的调用方式。&lt;/p&gt;

&lt;p&gt;这就是 MCP 的价值所在——&lt;strong&gt;工具的接入和工具的使用，完全解耦了。&lt;/strong&gt;&lt;/p&gt;

&lt;h2 id=&quot;七回到最初的问题&quot;&gt;七、回到最初的问题&lt;/h2&gt;

&lt;p&gt;回到开头说的那个问题。&lt;/p&gt;

&lt;p&gt;工具越写越多，每个系统都要单独对接，怎么破？&lt;/p&gt;

&lt;p&gt;MCP 给出的答案很简单：&lt;strong&gt;标准化&lt;/strong&gt;。&lt;/p&gt;

&lt;p&gt;在 MCP 之前，每个 Agent 框架有自己的工具格式，每个外部系统需要专门的适配层。&lt;br /&gt;
工具越多，维护成本越高，生态越碎片化。&lt;/p&gt;

&lt;p&gt;MCP 之后，工具的提供方和消费方各自只需要遵守一套协议。&lt;br /&gt;
任何实现了 MCP Server 的服务，任何实现了 MCP Client 的 Agent，都能直接对接。&lt;/p&gt;

&lt;p&gt;evo-agent 的 MCP 实现，说到底就做了三件事：&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;统一接口。&lt;/strong&gt;&lt;br /&gt;
三种传输方式（stdio、SSE、streamableHttp）背后，对上层暴露同一套 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mcpClient&lt;/code&gt; 接口，调用方完全透明。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;统一命名。&lt;/strong&gt;&lt;br /&gt;
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mcp__{服务器名}__{工具名}&lt;/code&gt; 的命名规则，让工具路由信息直接藏在名字里，无需额外的映射表。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;无缝融合。&lt;/strong&gt;&lt;br /&gt;
MCP 工具和内置工具在 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;tools.Tools()&lt;/code&gt; 里合并，LLM 看不出区别，Agent Loop 也不需要做任何特殊处理。&lt;/p&gt;

&lt;p&gt;接入一个新的 MCP Server，只需要在 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.evo_agent/mcp.json&lt;/code&gt; 里加几行配置，重启就生效。&lt;/p&gt;

&lt;p&gt;对 Agent 来说，工具的边界消失了。&lt;/p&gt;

&lt;p&gt;就像有了标准插座之后，你再也不用为每台电器定制插头了。&lt;br /&gt;
买回来，插上，就能用。&lt;/p&gt;

&lt;p&gt;MCP 做的，就是这件事。&lt;/p&gt;

&lt;p&gt;《完》&lt;/p&gt;

&lt;p&gt;-EOF-&lt;/p&gt;

&lt;p&gt;本文公众号：天空的代码世界&lt;br /&gt;
个人微信号：tiankonguse&lt;br /&gt;
公众号ID：tiankonguse-code&lt;/p&gt;
</content>
     <summary>工具越写越多，每个系统都要单独对接，怎么破？本文介绍 MCP 协议的设计思路，再看 evo-agent 如何实现三种传输方式，最终让 Agent 直接查询内部数据服务。</summary>
     <category term="程序人生" scheme="https://github.tiankonguse.com/categories.html#程序人生-category-ref"/>
     <tag term="agent" scheme="https://github.tiankonguse.com/tags.html#agent-tag-ref"/><tag term="mcp" scheme="https://github.tiankonguse.com/tags.html#mcp-tag-ref"/><tag term="protocol" scheme="https://github.tiankonguse.com/tags.html#protocol-tag-ref"/><tag term="golang" scheme="https://github.tiankonguse.com/tags.html#golang-tag-ref"/>
   </entry>
   
    
   <entry>
     <title>Agent 的上下文压缩：3个策略</title>
     <link href="https://github.tiankonguse.com/blog/2026/05/12/agent-context-compact.html"/>
     <updated>2026-05-12T21:00:00+08:00</updated>
     <published>2026-05-12T21:00:00+08:00</published>
     <id>https://github.tiankonguse.com/blog/2026/05/12/agent-context-compact</id>
     <content type="html">&lt;p&gt;&lt;img src=&quot;https://res2026.tiankonguse.com/images/2026/05/12/007.png&quot; alt=&quot;截图&quot; /&gt;&lt;/p&gt;

&lt;p&gt;前三篇文章分别讲了 Agent 的 &lt;a href=&quot;https://mp.weixin.qq.com/s/dkdrwVlwe3IkH2hzSzy53A&quot;&gt;Loop&lt;/a&gt;、&lt;a href=&quot;https://mp.weixin.qq.com/s/xyX4_CF5cveezEDuzFT13g&quot;&gt;Tools&lt;/a&gt; 和 &lt;a href=&quot;https://mp.weixin.qq.com/s/lguRAdxFoN22rqPyx3BIzw&quot;&gt;Prompts&lt;/a&gt;。&lt;/p&gt;

&lt;p&gt;这篇聊一个迟早得正面面对的问题。&lt;/p&gt;

&lt;p&gt;Agent 跑着跑着，messages 越滚越大，context window 快撑不住了，怎么办？&lt;/p&gt;

&lt;h2 id=&quot;一项目进度回顾&quot;&gt;一、项目进度回顾&lt;/h2&gt;

&lt;p&gt;先简单回顾一下。&lt;/p&gt;

&lt;p&gt;evo-agent 是我从零构建 Agent 的学习项目。&lt;br /&gt;
https://github.com/tiankonguse/evo-agent&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;第一篇&lt;/strong&gt;，搭骨架：接入 Anthropic API，实现 ReAct Loop，第一个工具 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bash&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;第二篇&lt;/strong&gt;，扩展工具系统：新增 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;read_file&lt;/code&gt;、&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;write_file&lt;/code&gt;、&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;edit_file&lt;/code&gt;，重构工具注册机制。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;第三篇&lt;/strong&gt;，理解记忆层：System Prompt、Messages History、Tools Schema 三位一体。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;这一篇&lt;/strong&gt;，解决一个绕不过去的工程问题——上下文压缩。&lt;/p&gt;

&lt;p&gt;当前项目的目录结构如下：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;src/
├── main.go
├── internal/
│   ├── agent/
│   │   ├── loop.go          # Agent 主循环
│   │   ├── state.go         # 对话状态（含 CompactState）
│   │   ├── compact.go       # 压缩核心引擎（新增）
│   │   └── transcripts.go   # 对话存档（新增）
│   ├── tools/
│   │   ├── tool.go
│   │   ├── executor.go
│   │   ├── bash.go
│   │   ├── read_file.go
│   │   ├── write_file.go
│   │   ├── edit_file.go
│   │   └── compact.go       # compact 工具（新增）
│   ├── config/
│   │   └── config.go
│   └── ui/
│       └── terminal.go
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;二问题的根源messages-是一个只增不减的雪球&quot;&gt;二、问题的根源：messages 是一个只增不减的雪球&lt;/h2&gt;

&lt;p&gt;上一篇说过，LLM 没有记忆，它的”记忆”就是每次传进去的 messages 数组。&lt;/p&gt;

&lt;p&gt;这就带来了一个很现实的问题。&lt;/p&gt;

&lt;p&gt;每完成一步操作，messages 就会追加几条：助手的 tool_use 请求、执行工具的 tool_result 返回。&lt;br /&gt;
一个稍微复杂点的任务，跑个十几轮下来，messages 就能轻松膨胀到几万个字符。&lt;/p&gt;

&lt;p&gt;而 LLM 的上下文窗口是有限的。&lt;/p&gt;

&lt;p&gt;超出了，轻则截断历史导致 LLM “忘事”，重则 API 直接报错。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://res2026.tiankonguse.com/images/2026/05/12/001.png&quot; alt=&quot;截图&quot; /&gt;&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-mermaid&quot;&gt;graph LR
    A[&quot;第 1 轮\n2 条消息&quot;] --&amp;gt; B[&quot;第 5 轮\n10 条消息&quot;]
    B --&amp;gt; C[&quot;第 20 轮\n40+ 条消息&quot;]
    C --&amp;gt; D[&quot;第 N 轮\n💥 Context Overflow&quot;]

    style D fill:#ffcdd2
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;打个比方。&lt;/p&gt;

&lt;p&gt;你让助理帮你做一个调研报告，他查了 20 个网站，每查一个网站就在笔记本上记一页。&lt;br /&gt;
结果笔记本只有 30 页。&lt;br /&gt;
还没调研完，笔记本就写满了。&lt;/p&gt;

&lt;p&gt;前面记的东西要么被撕掉，要么再也找不到了。&lt;/p&gt;

&lt;p&gt;这不是假设，这是任何跑长任务的 Agent 都&lt;strong&gt;必然&lt;/strong&gt;会撞上的墙。&lt;/p&gt;

&lt;p&gt;解决这个问题的方案，就叫&lt;strong&gt;上下文压缩（Context Compact）&lt;/strong&gt;。&lt;/p&gt;

&lt;h2 id=&quot;三业界怎么做的&quot;&gt;三、业界怎么做的&lt;/h2&gt;

&lt;p&gt;上下文压缩没有银弹。&lt;/p&gt;

&lt;p&gt;不同的方案各有各的取舍，我大致梳理了四类。&lt;/p&gt;

&lt;h3 id=&quot;31-滑动窗口sliding-window&quot;&gt;3.1 滑动窗口（Sliding Window）&lt;/h3&gt;

&lt;p&gt;最简单粗暴的方案：直接丢弃最早的消息，保留最近的 N 条。&lt;/p&gt;

&lt;p&gt;实现成本极低，一行代码搞定。&lt;/p&gt;

&lt;p&gt;但它有个致命缺陷。&lt;/p&gt;

&lt;p&gt;你最开始交代的任务目标，很可能就在最早的消息里。&lt;br /&gt;
丢掉了开头，Agent 就忘了自己在干什么。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://res2026.tiankonguse.com/images/2026/05/12/002.png&quot; alt=&quot;截图&quot; /&gt;&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-mermaid&quot;&gt;graph LR
    A[&quot;消息 1: 任务目标 ❌丢弃&quot;] 
    B[&quot;消息 2: 工具调用 ❌丢弃&quot;]
    C[&quot;消息 3: 工具结果 ❌丢弃&quot;]
    D[&quot;消息 4: 工具调用 ✅保留&quot;]
    E[&quot;消息 5: 工具结果 ✅保留&quot;]
    F[&quot;消息 6: 最新回答 ✅保留&quot;]
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这就好比你让一个人帮你搬家，搬着搬着他忘了目的地在哪了。&lt;br /&gt;
东西还在手上，但不知道往哪送了。&lt;/p&gt;

&lt;p&gt;适合闲聊场景，不适合需要长期记住目标的 Agent。&lt;/p&gt;

&lt;h3 id=&quot;32-llm-摘要压缩summarization&quot;&gt;3.2 LLM 摘要压缩（Summarization）&lt;/h3&gt;

&lt;p&gt;让 LLM 把旧的对话历史”读”一遍，浓缩成一段摘要，再把摘要作为新的”起点”注入到 messages 里。&lt;/p&gt;

&lt;p&gt;这样既能大幅压缩 token 数量，又保留了关键信息。&lt;/p&gt;

&lt;p&gt;代价是需要额外调用一次 LLM，有几秒的延迟和少量 token 开销。&lt;/p&gt;

&lt;p&gt;但这个代价完全可以接受。&lt;/p&gt;

&lt;p&gt;这也是目前主流 Agent 框架，比如 LangChain、Claude Code 等，普遍采用的方案。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://res2026.tiankonguse.com/images/2026/05/12/003.png&quot; alt=&quot;截图&quot; /&gt;&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-mermaid&quot;&gt;sequenceDiagram
    participant AgentLoop as Agent Loop
    participant LLM as 摘要 LLM

    AgentLoop-&amp;gt;&amp;gt;AgentLoop: 检测到 messages 过大
    AgentLoop-&amp;gt;&amp;gt;LLM: 请帮我把这段对话压缩成摘要
    LLM--&amp;gt;&amp;gt;AgentLoop: [摘要文本]
    AgentLoop-&amp;gt;&amp;gt;AgentLoop: messages = [摘要] （只剩一条）
    AgentLoop-&amp;gt;&amp;gt;AgentLoop: 继续运行
&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id=&quot;33-工具结果占位符placeholder-compaction&quot;&gt;3.3 工具结果占位符（Placeholder Compaction）&lt;/h3&gt;

&lt;p&gt;工具调用的结果往往是最占空间的——读一个大文件返回了几千行，执行一个命令输出了几百行。&lt;/p&gt;

&lt;p&gt;但这些结果一旦被 LLM 处理过，后续几乎用不到了。&lt;/p&gt;

&lt;p&gt;一个很轻量的方案是：把旧的工具结果内容替换成一句话的占位符，只保留最近几条完整结果。&lt;/p&gt;

&lt;p&gt;几乎零成本，不需要调用 LLM，速度极快。&lt;/p&gt;

&lt;h3 id=&quot;34-rag-检索增强retrieval-augmented-generation&quot;&gt;3.4 RAG 检索增强（Retrieval-Augmented Generation）&lt;/h3&gt;

&lt;p&gt;更重量级的方案：把历史消息存进向量数据库，每次 LLM 调用前，根据当前问题检索最相关的历史片段注入进去。&lt;/p&gt;

&lt;p&gt;优点是灵活，理论上可以无限扩展历史。&lt;br /&gt;
缺点是架构复杂，需要维护向量库，检索结果也不一定精准。&lt;/p&gt;

&lt;p&gt;对一般的 Agent 任务来说，属于过度设计了。&lt;br /&gt;
适合那种对话轮次极多、需要跨会话记忆的场景。&lt;/p&gt;

&lt;h3 id=&quot;35-总结一下&quot;&gt;3.5 总结一下&lt;/h3&gt;

&lt;p&gt;&lt;img src=&quot;https://res2026.tiankonguse.com/images/2026/05/12/004.png&quot; alt=&quot;截图&quot; /&gt;&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-mermaid&quot;&gt;graph TD
    A[&quot;上下文压缩方案&quot;] --&amp;gt; B[&quot;滑动窗口\n✅ 零成本\n❌ 丢失目标&quot;]
    A --&amp;gt; C[&quot;LLM 摘要\n✅ 保留关键信息\n⚠️ 需额外 LLM 调用&quot;]
    A --&amp;gt; D[&quot;占位符替换\n✅ 极快无成本\n⚠️ 只处理工具结果&quot;]
    A --&amp;gt; E[&quot;RAG 检索\n✅ 无限历史\n❌ 架构复杂&quot;]

    style B fill:#fff9c4
    style C fill:#c8e6c9
    style D fill:#c8e6c9
    style E fill:#ffe0b2
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;没有哪个方案是万能的。&lt;/p&gt;

&lt;p&gt;滑动窗口零成本但会丢目标，LLM 摘要效果最好但需要额外调用，占位符替换极快但只处理工具结果，RAG 能力最强但架构太重。&lt;/p&gt;

&lt;p&gt;实际工程中，通常是几种策略&lt;strong&gt;分层组合&lt;/strong&gt;使用。&lt;/p&gt;

&lt;p&gt;先拿最便宜的手段挡一挡，挡不住了再上重武器。&lt;/p&gt;

&lt;p&gt;这个思路其实也是 evo-agent 的设计思路。&lt;/p&gt;

&lt;h2 id=&quot;四evo-agent-的压缩设计&quot;&gt;四、evo-agent 的压缩设计&lt;/h2&gt;

&lt;p&gt;evo-agent 采用了&lt;strong&gt;三层压缩策略&lt;/strong&gt;，按照成本从低到高依次触发。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://res2026.tiankonguse.com/images/2026/05/12/005.png&quot; alt=&quot;截图&quot; /&gt;&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-mermaid&quot;&gt;graph TD
    A[&quot;每次 LLM 调用前&quot;] --&amp;gt; B[&quot;第一层：MicroCompact\n替换旧工具结果为占位符\n成本：&amp;lt;1ms&quot;]
    B --&amp;gt; C{&quot;context size\n&amp;gt; 50,000 字符？&quot;}
    C -- 否 --&amp;gt; D[&quot;正常调用 LLM&quot;]
    C -- 是 --&amp;gt; E[&quot;第二层：CompactHistory\n调用 LLM 生成摘要\n成本：1~5s&quot;]
    E --&amp;gt; D

    F[&quot;LLM 返回后&quot;] --&amp;gt; G{&quot;模型主动调用\ncompact 工具？&quot;}
    G -- 是 --&amp;gt; H[&quot;手动 Compact\n附带 focus 提示\n成本：1~5s&quot;]
    G -- 否 --&amp;gt; I[&quot;继续下一轮&quot;]
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;就像三道防线。&lt;/p&gt;

&lt;p&gt;第一道，能在内存里解决的，绝不动网络。&lt;br /&gt;
第二道，内存搞不定了，调 LLM 做摘要。&lt;br /&gt;
第三道，LLM 自己觉得不行了，主动喊暂停。&lt;/p&gt;

&lt;p&gt;一层一层兜底。&lt;/p&gt;

&lt;h3 id=&quot;第一层microcompact微压缩&quot;&gt;第一层：MicroCompact（微压缩）&lt;/h3&gt;

&lt;p&gt;每次调用 LLM 之前，先扫一遍 messages，把较旧的工具结果替换成一行占位符：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;[Earlier tool result compacted. Re-run the tool if you need full detail.]
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;只保留最近 3 条工具结果的完整内容，更早的全部折叠。&lt;/p&gt;

&lt;p&gt;这个操作在内存里完成，耗时不到 1ms，没有任何网络请求。&lt;/p&gt;

&lt;p&gt;这里有一个关键的保护逻辑：&lt;strong&gt;最后一批工具结果永远不会被压缩&lt;/strong&gt;。&lt;/p&gt;

&lt;p&gt;为什么？&lt;/p&gt;

&lt;p&gt;因为 LLM 刚刚发起了工具调用，还没来得及”看”这批结果。&lt;br /&gt;
如果此时就把它们折叠掉，LLM 就会拿到空结果，行为会出错。&lt;/p&gt;

&lt;p&gt;这就好比你刚派人去查资料，人还没回来呢，你就把他的工位清了。&lt;br /&gt;
回来了没地方汇报，这活就白干了。&lt;/p&gt;

&lt;h3 id=&quot;第二层compacthistory全量压缩&quot;&gt;第二层：CompactHistory（全量压缩）&lt;/h3&gt;

&lt;p&gt;微压缩之后，再测量一次 messages 的总体积。&lt;br /&gt;
如果仍然超过 50,000 字符，就触发全量压缩。&lt;/p&gt;

&lt;p&gt;全量压缩的流程分三步。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;第一步，存档。&lt;/strong&gt;&lt;br /&gt;
先把当前完整的 messages 写入磁盘，存成 JSONL 格式的 transcript 文件。&lt;br /&gt;
这是压缩前的”存档”，日后可以用来调试和回溯。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;第二步，摘要。&lt;/strong&gt;&lt;br /&gt;
调用 LLM，让它把整段对话浓缩成一份结构化摘要。&lt;br /&gt;
要求保留五样东西：当前目标、重要发现与决策、读过和修改过的文件、剩余工作、用户约束。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;第三步，替换。&lt;/strong&gt;&lt;br /&gt;
用这条摘要替换掉所有 messages。&lt;br /&gt;
messages 从几十条，瞬间变成 1 条。&lt;/p&gt;

&lt;p&gt;就像你写了 30 页的调研笔记，最后浓缩成一页纸的摘要。&lt;br /&gt;
笔记本清空了，但关键信息都在那一页纸上。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://res2026.tiankonguse.com/images/2026/05/12/006.png&quot; alt=&quot;截图&quot; /&gt;&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-mermaid&quot;&gt;sequenceDiagram
    participant AgentLoop as loop.go
    participant Compact as compact.go
    participant Transcript as transcripts.go
    participant LLM as LLM API

    AgentLoop-&amp;gt;&amp;gt;Compact: CompactHistory(messages)
    Compact-&amp;gt;&amp;gt;Transcript: WriteTranscript(messages) → .evo_agent/transcripts/
    Compact-&amp;gt;&amp;gt;LLM: &quot;请压缩这段对话...&quot;
    LLM--&amp;gt;&amp;gt;Compact: [摘要文本]
    Compact-&amp;gt;&amp;gt;Compact: 追加 RecentFiles 到摘要
    Compact--&amp;gt;&amp;gt;AgentLoop: [新 messages：只含摘要]
&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id=&quot;第三层手动-compact-工具&quot;&gt;第三层：手动 Compact 工具&lt;/h3&gt;

&lt;p&gt;除了自动触发，LLM 自己也可以主动要求压缩。&lt;/p&gt;

&lt;p&gt;工具系统里注册了一个 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;compact&lt;/code&gt; 工具，LLM 可以调用它，还能传一个 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;focus&lt;/code&gt; 参数，告诉压缩引擎”这次特别要保留什么”：&lt;/p&gt;

&lt;div class=&quot;language-go highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;// tools/compact.go&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;CompactInput&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;Focus&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;`json:&quot;focus,omitempty&quot;`&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;比如 LLM 觉得自己快撑不住了，可以主动说：&lt;br /&gt;
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;compact(focus=&quot;当前正在重构的 loop.go 和相关接口&quot;)&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;摘要里会额外追加这段 focus 提示，确保下一轮不忘关键信息。&lt;/p&gt;

&lt;p&gt;这个设计挺有意思的。&lt;/p&gt;

&lt;p&gt;相当于 Agent 有了”自我管理记忆”的能力。&lt;br /&gt;
它不只是被动等着系统来压缩，它可以自己判断什么时候该”忘”一些东西，同时告诉系统”但这些别忘”。&lt;/p&gt;

&lt;h2 id=&quot;五compactstate压缩也需要记忆&quot;&gt;五、CompactState：压缩也需要记忆&lt;/h2&gt;

&lt;p&gt;压缩这件事本身，也需要状态追踪。&lt;/p&gt;

&lt;p&gt;不然你都不知道压缩发生过几次，上次摘要长什么样。&lt;/p&gt;

&lt;p&gt;所以我把压缩相关的状态单独抽了出来，存在 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CompactState&lt;/code&gt; 里：&lt;/p&gt;

&lt;div class=&quot;language-go highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;// state.go&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;CompactState&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;HasCompacted&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;bool&lt;/span&gt;     &lt;span class=&quot;c&quot;&gt;// 是否已发生过压缩&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;LastSummary&lt;/span&gt;  &lt;span class=&quot;kt&quot;&gt;string&lt;/span&gt;   &lt;span class=&quot;c&quot;&gt;// 最后一次生成的摘要&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;RecentFiles&lt;/span&gt;  &lt;span class=&quot;p&quot;&gt;[]&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;c&quot;&gt;// 最近访问的文件（FIFO，最多 5 个）&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;CompactCount&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt;      &lt;span class=&quot;c&quot;&gt;// 压缩次数计数&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;这里有一个细节设计，我觉得值得单独说一下——&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RecentFiles&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;每次 LLM 调用了 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;read_file&lt;/code&gt; 工具，loop.go 都会把这个文件路径记进去，保持最近 5 个，先进先出。&lt;/p&gt;

&lt;p&gt;压缩发生时，这份文件列表会附加到摘要末尾：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Recent files to reopen if needed:
- src/internal/agent/loop.go
- src/internal/agent/compact.go
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;为什么要这么做？&lt;/p&gt;

&lt;p&gt;因为压缩之后，messages 里的旧内容都没了。&lt;br /&gt;
LLM 读完摘要，知道”上次我在看这几个文件”，可以快速重新打开，不用重新摸索。&lt;/p&gt;

&lt;p&gt;就像你午睡醒了，桌上摆着你睡前打开的几份文件。&lt;br /&gt;
你一看就知道：”哦，我之前在做这个事”。&lt;br /&gt;
不用从头回忆。&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CompactState&lt;/code&gt; 不随每次用户提问重置，而是在整个 REPL 会话中持久保持：&lt;/p&gt;

&lt;div class=&quot;language-go highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;// loop.go - Run()&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;compactState&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;CompactState&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{}&lt;/span&gt; &lt;span class=&quot;c&quot;&gt;// 整个会话只初始化一次&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;c&quot;&gt;// REPL 循环&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;state&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;LoopState&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;Messages&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;     &lt;span class=&quot;n&quot;&gt;history&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;CompactState&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;compactState&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;c&quot;&gt;// 每次查询复用同一个 state&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Loop&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;state&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;compactState&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;state&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;CompactState&lt;/span&gt; &lt;span class=&quot;c&quot;&gt;// 同步回来&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;这样即使跨多次用户查询，压缩历史和文件追踪都不会丢失。&lt;/p&gt;

&lt;h2 id=&quot;六几个关键的数字&quot;&gt;六、几个关键的数字&lt;/h2&gt;

&lt;p&gt;聊完了设计，再看看具体的参数配置。&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CONTEXT_LIMIT = 50,000 字符&lt;/code&gt;，超过这个值就触发全量压缩。&lt;br /&gt;
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;KEEP_RECENT_RESULTS = 3&lt;/code&gt;，微压缩保留最近 3 条工具结果。&lt;br /&gt;
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;maxConversationBytes = 80,000&lt;/code&gt;，送给摘要 LLM 的最大对话长度。&lt;br /&gt;
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;max RecentFiles = 5&lt;/code&gt;，追踪最近 5 个文件。&lt;/p&gt;

&lt;p&gt;各层操作的时间成本：&lt;/p&gt;

&lt;p&gt;微压缩在内存里完成，不到 1ms。&lt;br /&gt;
全量压缩需要额外一次 LLM 调用，通常 1 到 5 秒。&lt;br /&gt;
Transcript 落盘是异步的，约 100ms。&lt;br /&gt;
文件追踪是纯内存操作，可以忽略不计。&lt;/p&gt;

&lt;p&gt;全量压缩的效果：messages 从几十条压缩到 1 条，token 节省通常超过 90%。&lt;/p&gt;

&lt;p&gt;这个数字还是很可观的。&lt;/p&gt;

&lt;h2 id=&quot;七总结&quot;&gt;七、总结&lt;/h2&gt;

&lt;p&gt;回过头来看，上下文压缩这件事，其实本质上就是在回答一个问题：&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;当一个只有短期记忆的 AI 要干长期的活，你怎么让它不失忆？&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;答案说起来也简单——该忘的忘，该记的记。&lt;/p&gt;

&lt;p&gt;但”什么该忘、什么该记”，这里面的分寸拿捏，才是真正的工程活。&lt;/p&gt;

&lt;p&gt;evo-agent 的策略是三层叠加：&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;占位符微压缩作为第一道防线&lt;/strong&gt;——成本几乎为零，先把最占空间的工具结果折叠掉。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;LLM 摘要作为第二道防线&lt;/strong&gt;——防线要破了，花几秒钟让 LLM 自己做个总结，浓缩成一页纸。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;手动 Compact 工具作为第三道防线&lt;/strong&gt;——让 LLM 自己决定什么时候压缩、重点保留什么。&lt;/p&gt;

&lt;p&gt;再加上 CompactState 在会话内持久追踪文件路径，让 Agent 在压缩之后也能快速找回上下文。&lt;/p&gt;

&lt;p&gt;有了这套机制，理论上一个 Agent 可以无限续航，不再受 context window 限制。&lt;/p&gt;

&lt;p&gt;这话说起来轻巧，但真正实现出来，需要的就是上面这些一层一层的工程细节。&lt;/p&gt;

&lt;p&gt;下一篇，我们来看 Agent 的另一个核心话题。&lt;/p&gt;

&lt;p&gt;《完》&lt;/p&gt;

&lt;p&gt;-EOF-&lt;/p&gt;

&lt;p&gt;本文公众号：天空的代码世界&lt;br /&gt;
个人微信号：tiankonguse&lt;br /&gt;
公众号ID：tiankonguse-code&lt;/p&gt;
</content>
     <summary>messages 越滚越大，context window 撑不住了怎么办？本文梳理业界主流的上下文压缩方案，再看 evo-agent 如何用三层策略让 Agent 无限续航。</summary>
     <category term="程序人生" scheme="https://github.tiankonguse.com/categories.html#程序人生-category-ref"/>
     <tag term="agent" scheme="https://github.tiankonguse.com/tags.html#agent-tag-ref"/><tag term="context" scheme="https://github.tiankonguse.com/tags.html#context-tag-ref"/><tag term="compact" scheme="https://github.tiankonguse.com/tags.html#compact-tag-ref"/><tag term="golang" scheme="https://github.tiankonguse.com/tags.html#golang-tag-ref"/>
   </entry>
   
    
   <entry>
     <title>leetcode 周赛 501</title>
     <link href="https://github.tiankonguse.com/blog/2026/05/10/leetcode-contest-501.html"/>
     <updated>2026-05-10T12:13:00+08:00</updated>
     <published>2026-05-10T12:13:00+08:00</published>
     <id>https://github.tiankonguse.com/blog/2026/05/10/leetcode-contest-501</id>
     <content type="html">&lt;h2 id=&quot;零背景&quot;&gt;零、背景&lt;/h2&gt;

&lt;p&gt;这次比赛最后一题是全源最短路，看错数据范围了，错了两次。&lt;/p&gt;

&lt;p&gt;本场题型概览如下。&lt;br /&gt;
A 题：翻转。&lt;br /&gt;
B 题：分割字符串。&lt;br /&gt;
C 题：因子分解。&lt;br /&gt;
D 题：全源最短路。&lt;/p&gt;

&lt;h2 id=&quot;一连接逆序数组&quot;&gt;一、连接逆序数组&lt;/h2&gt;

&lt;p&gt;题意：给一个数组，求原数组与翻转数组拼接后的数组。&lt;/p&gt;

&lt;p&gt;思路：按题意原数组与翻转数组相连即可。&lt;/p&gt;

&lt;h2 id=&quot;二有效单词计数&quot;&gt;二、有效单词计数&lt;/h2&gt;

&lt;p&gt;题意：给若干字符串，问这些字符串拼接到一起后，进行分词统计，问指定的单词分别出现几次。&lt;br /&gt;
单词定义：字母与连接符组成，连接符左右必须是字母。&lt;/p&gt;

&lt;p&gt;思路：分割字符串&lt;/p&gt;

&lt;p&gt;双层循环遍历字符串数组，把字符尝试追加到单词上。&lt;br /&gt;
如果得到新的单词，更新单词的频率。&lt;/p&gt;

&lt;p&gt;四种情况算单词结束。&lt;/p&gt;

&lt;p&gt;1）遇到空格
2）前缀单词为空，遇到连接符
3）前缀单词最后一个字符是连接符，再次遇到连接符
4) 输入结束&lt;/p&gt;

&lt;div class=&quot;language-cpp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;auto&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Add&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;](&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;char&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;c&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;c&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;sc&quot;&gt;&apos; &apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;Flush&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;c&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;sc&quot;&gt;&apos;-&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;empty&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;back&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;sc&quot;&gt;&apos;-&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;push_back&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;c&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;Flush&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;push_back&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;c&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;单词识别结束时，还需要判断有没有后缀连接符，有的话需要删除。&lt;/p&gt;

&lt;div class=&quot;language-cpp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;auto&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Flush&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;empty&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;back&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;sc&quot;&gt;&apos;-&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pop_back&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;empty&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;())&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;cnt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;clear&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;三可整除替换后的数组最小元素和&quot;&gt;三、可整除替换后的数组最小元素和&lt;/h2&gt;

&lt;p&gt;题意：给一个数组，如果数组中存在一个数字是自己的因子，则自己可以使用因子进行替换。&lt;br /&gt;
问不断替换，最终可以得到的最小数组和。&lt;/p&gt;

&lt;p&gt;思路：因数分解&lt;/p&gt;

&lt;p&gt;显然，对于一个数组，使用数组中出现过的最小因子进行替换，才能得到最小数组和。&lt;br /&gt;
故，问题转化为找到数字在数组中出现的最小因子。&lt;/p&gt;

&lt;p&gt;可以先对数组排序，从小到大处理，这样因子分解时，就知道因子是否出现过了。&lt;/p&gt;

&lt;p&gt;因子分解可以使用根号算法 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sqrt(n)&lt;/code&gt; 来做。&lt;br /&gt;
复杂度：&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;O(n sqrt(n))&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;扩展：数据范围不大，除了进行质因数分解，还可以使用筛法。&lt;br /&gt;
即如果一个数字不存在更小的因子，则把这个数字所有的倍数都标记。&lt;br /&gt;
复杂度保持不变。&lt;/p&gt;

&lt;h2 id=&quot;四购买苹果的最低成本-ii&quot;&gt;四、购买苹果的最低成本 II&lt;/h2&gt;

&lt;p&gt;题意：给两个完全一样的无向图 G1 和 G2，权值不一样。&lt;br /&gt;
问对于每个节点 a，通过图 G1 到达任意节点，再通过图 G2 返回到 a 的最小代价。&lt;br /&gt;
数据范围：点 1000 个，边 2000 条。&lt;/p&gt;

&lt;p&gt;思路：Dijkstra 单源最短路&lt;/p&gt;

&lt;p&gt;按题意构造两个图，分别求 n 次单源最短路，再枚举求最小答案即可。&lt;/p&gt;

&lt;p&gt;PS：一开始看错题意了，看成点是 100，直接 floyd 超时了。&lt;/p&gt;

&lt;h2 id=&quot;五最后&quot;&gt;五、最后&lt;/h2&gt;

&lt;p&gt;这次比赛很简单，第二题解析字符串、第三题因数分解、第四题最短路，都基础题。&lt;/p&gt;

&lt;p&gt;《完》&lt;/p&gt;

&lt;p&gt;-EOF-&lt;/p&gt;

&lt;p&gt;本文公众号：天空的代码世界&lt;br /&gt;
个人微信号：tiankonguse&lt;br /&gt;
公众号 ID：tiankonguse-code&lt;/p&gt;
</content>
     <summary>全源最短路</summary>
     <category term="算法" scheme="https://github.tiankonguse.com/categories.html#算法-category-ref"/>
     <tag term="算法" scheme="https://github.tiankonguse.com/tags.html#算法-tag-ref"/><tag term="leetcode" scheme="https://github.tiankonguse.com/tags.html#leetcode-tag-ref"/><tag term="算法比赛" scheme="https://github.tiankonguse.com/tags.html#算法比赛-tag-ref"/>
   </entry>
   
    
   <entry>
     <title>Agent 的记忆：系统提示词与对话历史</title>
     <link href="https://github.tiankonguse.com/blog/2026/05/09/agent-message.html"/>
     <updated>2026-05-09T12:00:00+08:00</updated>
     <published>2026-05-09T12:00:00+08:00</published>
     <id>https://github.tiankonguse.com/blog/2026/05/09/agent-message</id>
     <content type="html">&lt;p&gt;前两篇文章分别讲了 Agent 的 &lt;a href=&quot;https://mp.weixin.qq.com/s/dkdrwVlwe3IkH2hzSzy53A&quot;&gt;Loop&lt;/a&gt; 和 &lt;a href=&quot;https://mp.weixin.qq.com/s/xyX4_CF5cveezEDuzFT13g&quot;&gt;Tools&lt;/a&gt;。&lt;br /&gt;
这篇讲 Agent 的 Prompts。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://res2026.tiankonguse.com/images/2026/05/09/005.png&quot; alt=&quot;截图&quot; /&gt;&lt;/p&gt;

&lt;p&gt;注意，这篇的重点，其实不只是”怎么写提示词”。&lt;br /&gt;
而是要回答一个更根本的问题：&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;LLM 是怎么知道它在干什么的？&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;不知道你是否知道，LLM 每次被调用，其实跟一个失忆的人一样。&lt;br /&gt;
上一秒说了什么，它不知道。&lt;br /&gt;
你之前跟它聊了三个小时，它也不知道。&lt;/p&gt;

&lt;p&gt;那它是怎么”记住”对话的？&lt;/p&gt;

&lt;p&gt;答案比你想的简单得多，也残酷得多。&lt;/p&gt;

&lt;h2 id=&quot;一项目进度回顾&quot;&gt;一、项目进度回顾&lt;/h2&gt;

&lt;p&gt;&lt;img src=&quot;https://res2026.tiankonguse.com/images/2026/05/09/011.png&quot; alt=&quot;截图&quot; /&gt;&lt;/p&gt;

&lt;p&gt;先简单交代一下背景。&lt;/p&gt;

&lt;p&gt;evo-agent 是我从零开始构建 Agent 的学习项目。&lt;br /&gt;
https://github.com/tiankonguse/evo-agent&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;第一篇&lt;/strong&gt;，搭了骨架：接入 Anthropic API，实现 ReAct Loop（思考 → 行动 → 观察 → 循环），提供了第一个工具 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bash&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;第二篇&lt;/strong&gt;，扩展了工具系统：新增 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;read_file&lt;/code&gt;、&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;write_file&lt;/code&gt;、&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;edit_file&lt;/code&gt; 三个文件操作工具，重构了工具注册机制。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;这一篇&lt;/strong&gt;，来聊 Agent 最底层的”语言层”——Prompts 和 Messages History。&lt;/p&gt;

&lt;p&gt;当前项目的目录结构如下：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;src/
├── main.go                    # 入口：交互式 REPL
├── internal/
│   ├── agent/
│   │   ├── loop.go            # Agent 主循环 + REPL
│   │   └── state.go           # 对话状态（Messages + TurnCount）
│   ├── tools/
│   │   ├── tool.go            # 工具注册表 &amp;amp; Dispatch
│   │   ├── executor.go        # 工具执行器
│   │   ├── bash.go
│   │   ├── read_file.go
│   │   ├── write_file.go
│   │   └── edit_file.go
│   ├── config/
│   │   └── config.go          # 配置加载（SystemMsg 在这里生成）
│   └── ui/
│       └── terminal.go        # 终端彩色输出
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;二llm-没有记忆&quot;&gt;二、LLM 没有记忆&lt;/h2&gt;

&lt;p&gt;&lt;img src=&quot;https://res2026.tiankonguse.com/images/2026/05/09/006.png&quot; alt=&quot;截图&quot; /&gt;&lt;/p&gt;

&lt;p&gt;这是理解一切的前提。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;LLM 没有记忆。&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;每次你调用 API，对它来说都是全新的开始。&lt;br /&gt;
上一轮说了什么，它完全不知道。&lt;br /&gt;
你不告诉它，它就不记得。&lt;/p&gt;

&lt;p&gt;这和人完全不同。&lt;br /&gt;
你跟朋友聊天，昨天说的话今天还记得。&lt;br /&gt;
LLM 不行，每次调用，它的”记忆”都被清空了。&lt;/p&gt;

&lt;p&gt;那问题来了。&lt;/p&gt;

&lt;p&gt;ChatGPT 为什么看起来能”记住”上下文？&lt;/p&gt;

&lt;p&gt;答案很简单——ChatGPT 在每次请求里，把整个对话历史都塞给了 LLM。&lt;br /&gt;
LLM 读完这些历史，才知道”哦，我们之前聊到这里了”。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;本质上，LLM 的”记忆”，就是你塞给它的 messages 数组。&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;打个比方。&lt;br /&gt;
你跟一个彻底失忆的人聊天，每次开口之前，你都得把之前所有的聊天记录念给他听一遍。&lt;br /&gt;
他听完了，才知道”我们聊到哪了”。&lt;/p&gt;

&lt;p&gt;LLM 就是这个失忆的人。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://res2026.tiankonguse.com/images/2026/05/09/001.png&quot; alt=&quot;截图&quot; /&gt;&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-mermaid&quot;&gt;graph LR
    A[&quot;第 1 轮\nUser: 你好&quot;] --&amp;gt; B[&quot;LLM\n只看到第 1 条&quot;]
    B --&amp;gt; C[&quot;Assistant: 你好！&quot;]

    D[&quot;第 2 轮\nUser: 你叫什么&quot;] --&amp;gt; E[&quot;LLM\n看到第 1+2+3 条&quot;]
    E --&amp;gt; F[&quot;Assistant: 我是 Claude&quot;]

    style B fill:#ffe0b2
    style E fill:#ffe0b2
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;每一轮对话，messages 数组都在增长。&lt;br /&gt;
LLM 每次都要读完整个历史，才能知道”我们聊到哪了”。&lt;/p&gt;

&lt;p&gt;就像滚雪球，越滚越大。&lt;/p&gt;

&lt;h2 id=&quot;三messages-长什么样&quot;&gt;三、Messages 长什么样&lt;/h2&gt;

&lt;p&gt;&lt;img src=&quot;https://res2026.tiankonguse.com/images/2026/05/09/012.png&quot; alt=&quot;截图&quot; /&gt;&lt;/p&gt;

&lt;p&gt;在搞清楚 messages 的结构之前，先抛两个你可能有过的疑问。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;疑问一：思考模式。&lt;/strong&gt;&lt;br /&gt;
你用过 Claude 的扩展思维（Extended Thinking）吗？开启之后，LLM 会先输出一段”内心戏”，再给出最终答案。&lt;br /&gt;
那这段思考内容，是单独返回的，还是混在答案里的？它又是怎么存进 messages 的？&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;疑问二：工具调用。&lt;/strong&gt;&lt;br /&gt;
LLM 想用工具，它怎么”告诉”我们它要用哪个工具、传什么参数？&lt;br /&gt;
我们执行完工具，又怎么把结果”喂回”给 LLM？&lt;/p&gt;

&lt;p&gt;搞清楚 messages 的结构，这两个问题就迎刃而解了。&lt;/p&gt;

&lt;p&gt;好，那 messages 数组里到底装了些什么？&lt;/p&gt;

&lt;p&gt;Anthropic API 的 messages 是一个数组，每条消息有两个核心字段：&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;role&lt;/code&gt;&lt;/strong&gt;：这条消息是谁说的。&lt;br /&gt;
只有两个值——&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;user&lt;/code&gt;（用户）或 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;assistant&lt;/code&gt;（模型）。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;content&lt;/code&gt;&lt;/strong&gt;：消息的内容。&lt;br /&gt;
最简单就是一个字符串，但现代 LLM 支持了思考模式和工具调用之后，content 有时候是一个数组。&lt;/p&gt;

&lt;p&gt;具体来说：&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;用户消息&lt;/strong&gt;，普通对话直接用字符串就行。&lt;br /&gt;
只有在需要把工具执行结果返回给 LLM 时，content 才变成数组（&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;tool_result&lt;/code&gt; 类型）。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;助手消息&lt;/strong&gt;，由于 LLM 可能同时输出思考过程、工具调用请求和普通文本，content 通常是一个数组，包含三种 block 类型：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;thinking&lt;/code&gt; — 模型的内心戏（开启扩展思维时才有）&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;tool_use&lt;/code&gt; — “我要用这个工具”的请求，携带工具名、参数，以及一个唯一的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;id&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;text&lt;/code&gt; — 最终的文本回答&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;这里有个关键细节：&lt;strong&gt;LLM 每次发起工具调用，都会给它分配一个唯一的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;tool_use_id&lt;/code&gt;&lt;/strong&gt;。&lt;/p&gt;

&lt;p&gt;为什么需要这个 ID？&lt;/p&gt;

&lt;p&gt;因为 LLM 可以在一次响应里同时请求多个工具。&lt;br /&gt;
我们执行完之后，要把结果一一对应地还回去——用 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;tool_use_id&lt;/code&gt; 做锚点，LLM 才知道哪个结果对应哪个请求。&lt;br /&gt;
就像你同时派出三个人去办事，每人手里拿一张编号的任务单，回来汇报时按编号核对。&lt;/p&gt;

&lt;p&gt;用 JSON 来直观感受一下：&lt;/p&gt;

&lt;div class=&quot;language-json highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;role&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;user&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;content&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;帮我找出最大的文件&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;role&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;assistant&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;content&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;thinking&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;thinking&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;用户想找最大的文件，我应该用 du 命令...&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;tool_use&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;id&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;tool_abc123&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;bash&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;input&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;command&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;du -sh * | sort -rh | head -1&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;role&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;user&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;content&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;tool_result&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;tool_use_id&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;tool_abc123&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;content&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;128M  evo_agent&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;role&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;assistant&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;content&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;text&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;text&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;最大的文件是 evo_agent，占用 128M。&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;注意两个细节。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;第一，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;tool_use_id&lt;/code&gt; 贯穿请求和结果。&lt;/strong&gt;&lt;br /&gt;
LLM 发出 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;tool_use&lt;/code&gt; 时带上 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;id: &quot;tool_abc123&quot;&lt;/code&gt;，我们返回结果时用 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;tool_use_id: &quot;tool_abc123&quot;&lt;/code&gt; 对应回去。&lt;br /&gt;
ID 不匹配，LLM 就不知道这个结果是给谁的。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;第二，工具调用的结果是以 user 角色发回去的。&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;为什么？&lt;/p&gt;

&lt;p&gt;因为从 API 的视角看，工具结果就是我们的程序”说”给 LLM 的话。&lt;br /&gt;
执行工具的是我们的代码，不是 LLM 自己，所以自然是 user 角色。&lt;/p&gt;

&lt;p&gt;这个设计很合理。&lt;br /&gt;
LLM 说”我想用 bash 执行这个命令”，我们的代码执行完了告诉它”结果是这个”——就像你让秘书去查个数据，秘书查完了回来跟你汇报。&lt;/p&gt;

&lt;h2 id=&quot;四system-promptagent-的底色&quot;&gt;四、System Prompt：Agent 的”底色”&lt;/h2&gt;

&lt;p&gt;&lt;img src=&quot;https://res2026.tiankonguse.com/images/2026/05/09/013.png&quot; alt=&quot;截图&quot; /&gt;&lt;/p&gt;

&lt;p&gt;如果你用 Agent 写过代码，一定见过”上下文压缩”这个提示。&lt;br /&gt;
对话太长了，Token 快撑不住了，Agent 会自动把历史消息压缩一遍。&lt;/p&gt;

&lt;p&gt;你有没有想过一个问题：&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;上下文压缩，会不会把系统提示词也一起压掉？&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;如果压掉了，那些强加给 Agent 的规则——”不准删文件”、”只能操作这个目录”——是不是就悄悄失效了？&lt;/p&gt;

&lt;p&gt;答案是：&lt;strong&gt;不会。&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;因为系统提示词根本就不在 messages 里。&lt;br /&gt;
它是一个独立的字段，压缩的是 messages 历史，System Prompt 每次调用都原封不动地带着。&lt;/p&gt;

&lt;p&gt;这就是为什么它重要。&lt;/p&gt;

&lt;p&gt;除了 messages 数组，还有一个独立的字段：&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;system&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;这就是&lt;strong&gt;系统提示词（System Prompt）&lt;/strong&gt;。&lt;/p&gt;

&lt;p&gt;它不在 messages 里，独立于对话历史之外。&lt;br /&gt;
每次调用 API 都会带上它，但它不会随着对话增长。&lt;/p&gt;

&lt;p&gt;系统提示词的作用是什么？&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;给 Agent 设定身份和行为框架。&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;告诉它：”你是谁，你在哪，你应该怎么做事。”&lt;/p&gt;

&lt;p&gt;打个比方。&lt;br /&gt;
你去面试，面试官坐下来第一件事，不是直接问你问题，而是先告诉你”我是技术面试官，今天考察后端能力，时间 40 分钟”。&lt;/p&gt;

&lt;p&gt;这就是 System Prompt。&lt;br /&gt;
它定义了整个对话的基调。&lt;/p&gt;

&lt;p&gt;在 evo-agent 里，系统提示词是在 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;config.go&lt;/code&gt; 里自动生成的：&lt;/p&gt;

&lt;div class=&quot;language-go highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;// config.go&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;SystemMsg&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;fmt&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Sprintf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;&quot;You are a coding agent at %s.&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;cwd&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;非常简洁——你是一个代码助手，工作目录在这里。&lt;/p&gt;

&lt;p&gt;为什么要把工作目录注入进去？&lt;/p&gt;

&lt;p&gt;因为 LLM 在调用 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bash&lt;/code&gt;、&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;read_file&lt;/code&gt; 等工具时，需要知道相对路径从哪里算起。&lt;br /&gt;
告诉它”你在 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/Users/tiankonguse/project/evo-agent&lt;/code&gt;“，它构造文件路径时就不会迷失方向。&lt;/p&gt;

&lt;p&gt;在 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;loop.go&lt;/code&gt; 里，每次调用 API 时，系统提示词都会传进去：&lt;/p&gt;

&lt;div class=&quot;language-go highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;// loop.go&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;resp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;err&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;client&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Messages&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;New&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Background&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(),&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;anthropic&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;MessageNewParams&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;Model&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;anthropic&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Model&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cfg&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ModelID&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[]&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;anthropic&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;TextBlockParam&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Text&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cfg&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;SystemMsg&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;   &lt;span class=&quot;c&quot;&gt;// 系统提示词&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;Messages&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;  &lt;span class=&quot;n&quot;&gt;state&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Messages&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;    &lt;span class=&quot;c&quot;&gt;// 对话历史&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;Tools&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;     &lt;span class=&quot;n&quot;&gt;tools&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Tools&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(),&lt;/span&gt;     &lt;span class=&quot;c&quot;&gt;// 工具列表&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;MaxTokens&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;8000&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;五system-vs-user两种提示词的区别&quot;&gt;五、System vs User：两种提示词的区别&lt;/h2&gt;

&lt;p&gt;&lt;img src=&quot;https://res2026.tiankonguse.com/images/2026/05/09/007.png&quot; alt=&quot;截图&quot; /&gt;&lt;/p&gt;

&lt;p&gt;有了 System Prompt，为什么还要有 User Prompt？&lt;br /&gt;
它们各管什么？&lt;/p&gt;

&lt;p&gt;用一句话区分：&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;System Prompt 是角色设定，User Prompt 是任务指令。&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;还是用面试的比方。&lt;/p&gt;

&lt;p&gt;System Prompt 就是”我是技术面试官，考察后端，40 分钟”——整场面试不会变。&lt;/p&gt;

&lt;p&gt;User Prompt 就是每一道具体的面试题——”说说 Redis 的淘汰策略？”&lt;/p&gt;

&lt;p&gt;System Prompt 定义了 Agent 的”底色”：你是什么、能干什么、有什么约束。&lt;br /&gt;
User Prompt 驱动 Agent 在当前这一轮做什么。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://res2026.tiankonguse.com/images/2026/05/09/002.png&quot; alt=&quot;截图&quot; /&gt;&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-mermaid&quot;&gt;graph TD
    A[&quot;每次 API 调用&quot;] --&amp;gt; B[&quot;System Prompt\n(每次都发，固定不变)\n&apos;你是一个代码助手\n工作目录在 /path/to/project&apos;&quot;]
    A --&amp;gt; C[&quot;Messages\n(随对话增长)&quot;]
    C --&amp;gt; D[&quot;User: &apos;帮我找最大的文件&apos;&quot;]
    C --&amp;gt; E[&quot;Assistant: tool_use bash&quot;]
    C --&amp;gt; F[&quot;User: tool_result ...&quot;]
    C --&amp;gt; G[&quot;Assistant: &apos;最大的文件是...&apos;&quot;]

    style B fill:#c8e6c9
    style C fill:#bbdefb
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;两者合在一起，就是 LLM 每次”看到”的完整世界。&lt;/p&gt;

&lt;h2 id=&quot;六对话历史的管理两层循环&quot;&gt;六、对话历史的管理：两层循环&lt;/h2&gt;

&lt;p&gt;evo-agent 的对话历史管理有两层循环。&lt;br /&gt;
这个设计其实挺巧妙的。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;外层循环&lt;/strong&gt;：REPL 交互循环。&lt;/p&gt;

&lt;p&gt;负责读取用户输入、驱动一次完整的 Agent 任务、打印最终回答。&lt;br /&gt;
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;history&lt;/code&gt; 在这一层跨多次用户查询累积——你问了第一个问题，LLM 的全部回答都会留在 history 里；你问第二个问题时，LLM 能看到第一轮的完整上下文。&lt;/p&gt;

&lt;div class=&quot;language-go highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;// 外层：REPL 循环，每次读取一个用户问题&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;history&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[]&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;anthropic&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;MessageParam&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;c&quot;&gt;// 不断等待用户输入&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;query&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;readUserInput&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;history&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;append&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;history&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;anthropic&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;NewUserMessage&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;query&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;state&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;LoopState&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Messages&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;history&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Loop&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;state&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;          &lt;span class=&quot;c&quot;&gt;// 内层循环在这里运行&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;history&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;state&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Messages&lt;/span&gt;   &lt;span class=&quot;c&quot;&gt;// 把本轮所有对话（含工具调用）同步回来&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;printFinalAnswer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;history&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;内层循环&lt;/strong&gt;：ReAct 循环。&lt;/p&gt;

&lt;p&gt;负责驱动一次任务里的多轮 LLM 调用。&lt;br /&gt;
每次 LLM 返回工具调用请求，就执行工具、把结果追加进 messages，再发起下一轮调用，直到 LLM 不再需要工具为止。&lt;/p&gt;

&lt;div class=&quot;language-go highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;// 内层：ReAct 循环，每次调用一次 LLM&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;a&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Agent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;RunOneTurn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;state&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;LoopState&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;bool&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;resp&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;callLLM&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;state&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Messages&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;           &lt;span class=&quot;c&quot;&gt;// 调用 LLM&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;state&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Messages&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;append&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;state&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Messages&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;resp&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ToParam&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;())&lt;/span&gt;   &lt;span class=&quot;c&quot;&gt;// 追加助手响应&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;toolResults&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;tools&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Execute&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;resp&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Content&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;                &lt;span class=&quot;c&quot;&gt;// 执行工具&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;toolResults&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;false&lt;/span&gt;                          &lt;span class=&quot;c&quot;&gt;// 没有工具调用，结束循环&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;state&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Messages&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;append&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;state&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Messages&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;                   &lt;span class=&quot;c&quot;&gt;// 追加工具结果&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;anthropic&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;NewUserMessage&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;toolResults&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;true&lt;/span&gt;                               &lt;span class=&quot;c&quot;&gt;// 继续下一轮&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;a&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Agent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Loop&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;state&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;LoopState&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;RunOneTurn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;state&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;两层循环的关系怎么理解？&lt;/p&gt;

&lt;p&gt;外层循环是”会话”，内层循环是”一次任务的思考过程”。&lt;/p&gt;

&lt;p&gt;就像你跟助理说”帮我调研一下竞品”，这是外层的一个任务。&lt;br /&gt;
助理在执行这个任务时，可能要查好几个网站、比对好几份数据，这是内层的多次操作。&lt;/p&gt;

&lt;p&gt;内层每轮都在往 messages 里追加新内容，外层循环结束时把整个膨胀后的 history 保留下来，供下一次用户提问继续使用。&lt;/p&gt;

&lt;p&gt;整个过程就像滚雪球——messages 只会越来越长，LLM 每次能看到的上下文也越来越完整。&lt;/p&gt;

&lt;h2 id=&quot;七一次完整对话的消息流&quot;&gt;七、一次完整对话的消息流&lt;/h2&gt;

&lt;p&gt;&lt;img src=&quot;https://res2026.tiankonguse.com/images/2026/05/09/008.png&quot; alt=&quot;截图&quot; /&gt;&lt;/p&gt;

&lt;p&gt;从用户输入到最终回答，messages 数组是怎么演变的？&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://res2026.tiankonguse.com/images/2026/05/09/003.png&quot; alt=&quot;截图&quot; /&gt;&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-mermaid&quot;&gt;sequenceDiagram
    participant User as 用户
    participant REPL as REPL (Run)
    participant AgentLoop as Agent Loop
    participant LLM as LLM API

    User-&amp;gt;&amp;gt;REPL: &quot;帮我找最大的文件&quot;
    REPL-&amp;gt;&amp;gt;AgentLoop: history = [User: &quot;帮我找最大的文件&quot;]

    AgentLoop-&amp;gt;&amp;gt;LLM: messages=[User msg] + system + tools
    LLM--&amp;gt;&amp;gt;AgentLoop: tool_use: bash(&quot;du -sh *&quot;)
    AgentLoop-&amp;gt;&amp;gt;AgentLoop: history += [Assistant: tool_use]
    AgentLoop-&amp;gt;&amp;gt;AgentLoop: 执行 bash，得到结果
    AgentLoop-&amp;gt;&amp;gt;AgentLoop: history += [User: tool_result &quot;128M evo_agent&quot;]

    AgentLoop-&amp;gt;&amp;gt;LLM: messages=[User, Assistant, User(tool_result)] + ...
    LLM--&amp;gt;&amp;gt;AgentLoop: &quot;最大的文件是 evo_agent，128M&quot;
    AgentLoop-&amp;gt;&amp;gt;AgentLoop: history += [Assistant: &quot;最大的文件是...&quot;]

    AgentLoop-&amp;gt;&amp;gt;REPL: 返回最终 history
    REPL-&amp;gt;&amp;gt;User: 打印最终回答
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;看到了吧？&lt;/p&gt;

&lt;p&gt;每次 LLM 调用，它看到的 messages 都比上一次多几条。&lt;br /&gt;
这就是为什么 LLM 能”记住”工具调用的结果——不是因为它有记忆，是因为我们把结果又喂回去了。&lt;/p&gt;

&lt;h2 id=&quot;八loopstate状态快照&quot;&gt;八、LoopState：状态快照&lt;/h2&gt;

&lt;p&gt;对话历史被包装在 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;LoopState&lt;/code&gt; 里传递：&lt;/p&gt;

&lt;div class=&quot;language-go highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;// state.go&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;LoopState&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;Messages&lt;/span&gt;         &lt;span class=&quot;p&quot;&gt;[]&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;anthropic&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;MessageParam&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;TurnCount&lt;/span&gt;        &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;TransitionReason&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;string&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;三个字段，各有各的用处。&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Messages&lt;/code&gt; 是核心，就是前面说的 messages 数组。&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;TurnCount&lt;/code&gt; 记录当前 Loop 经历了多少轮 LLM 调用。&lt;br /&gt;
可以用来设置最大轮次限制——防止 Agent 陷入死循环，一直在那调工具停不下来。&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;TransitionReason&lt;/code&gt; 记录每轮结束的原因。&lt;br /&gt;
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&quot;tool_result&quot;&lt;/code&gt; 表示因为有工具调用而继续，空字符串表示 LLM 选择结束。&lt;/p&gt;

&lt;p&gt;这个字段对调试特别有用。&lt;br /&gt;
当 Agent 行为诡异的时候，你可以看看它到底是在哪一步”决定”停下来的。&lt;/p&gt;

&lt;h2 id=&quot;九prompts-是写给-llm-看的&quot;&gt;九、Prompts 是写给 LLM 看的&lt;/h2&gt;

&lt;p&gt;&lt;img src=&quot;https://res2026.tiankonguse.com/images/2026/05/09/010.png&quot; alt=&quot;截图&quot; /&gt;&lt;/p&gt;

&lt;p&gt;最后聊一个容易被忽视的事。&lt;/p&gt;

&lt;p&gt;evo-agent 目前的系统提示词很简单，只有一行：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;You are a coding agent at /path/to/project.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;现阶段够用。&lt;/p&gt;

&lt;p&gt;但随着项目功能越来越多，这东西也得跟着演进。&lt;br /&gt;
一个成熟的 Agent 的 System Prompt，通常还要告诉 LLM：遇到不确定的情况怎么办、文件操作有什么禁区、回答用什么语言和风格。&lt;/p&gt;

&lt;p&gt;这些不写进去，LLM 就只能靠猜。&lt;br /&gt;
猜错了，就是一次莫名其妙的行为。&lt;/p&gt;

&lt;p&gt;等 evo-agent 功能足够完善之后，我会单独写一篇来深入聊系统提示词的设计。&lt;br /&gt;
现阶段先把骨架搭好。&lt;/p&gt;

&lt;p&gt;另外还有一个经常被忽略的点——&lt;strong&gt;工具的描述也是 Prompt 的一部分&lt;/strong&gt;。&lt;/p&gt;

&lt;p&gt;每次调用 API 时，工具的 Schema 和描述都会发给 LLM。&lt;br /&gt;
LLM 靠这些描述来判断什么时候用哪个工具。&lt;/p&gt;

&lt;p&gt;描述写得清晰，LLM 选择就更准确。&lt;br /&gt;
描述含糊，LLM 就容易用错工具。&lt;/p&gt;

&lt;p&gt;所以写工具描述的时候，你的读者不是人类开发者，是 LLM 本身。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://res2026.tiankonguse.com/images/2026/05/09/004.png&quot; alt=&quot;截图&quot; /&gt;&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-mermaid&quot;&gt;graph TD
    A[&quot;LLM 的完整输入&quot;] --&amp;gt; B[&quot;System Prompt\n身份 + 行为约束 + 工作目录&quot;]
    A --&amp;gt; C[&quot;Messages History\n完整对话记录&quot;]
    A --&amp;gt; D[&quot;Tools Schema\n工具名称 + 描述 + 参数&quot;]

    B --&amp;gt; E[&quot;LLM 的&apos;世界观&apos;&quot;]
    C --&amp;gt; F[&quot;LLM 的&apos;记忆&apos;&quot;]
    D --&amp;gt; G[&quot;LLM 的&apos;工具箱&apos;&quot;]

    E &amp;amp; F &amp;amp; G --&amp;gt; H[&quot;LLM 的输出\n(思考 + 工具调用 + 文本)&quot;]

    style B fill:#c8e6c9
    style C fill:#bbdefb
    style D fill:#ffe0b2
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;System Prompt、Messages History、Tools Schema。&lt;br /&gt;
三者合一，才是 LLM 每次”看到的世界”。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;写 Prompt，本质上就是在构建 LLM 的世界观。&lt;/strong&gt;&lt;br /&gt;
这件事，比写代码更接近”沟通”，而不是”编程”。&lt;/p&gt;

&lt;h2 id=&quot;十总结&quot;&gt;十、总结&lt;/h2&gt;

&lt;p&gt;&lt;img src=&quot;https://res2026.tiankonguse.com/images/2026/05/09/014.png&quot; alt=&quot;截图&quot; /&gt;&lt;/p&gt;

&lt;p&gt;回顾一下这篇的核心内容。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;LLM 是无状态的。&lt;/strong&gt;&lt;br /&gt;
它所知道的一切，都来自你每次传进去的 messages 数组。&lt;br /&gt;
没有 messages，它就是一个失忆的人。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;System Prompt 是 Agent 的底色。&lt;/strong&gt;&lt;br /&gt;
告诉 LLM “你是谁、你在哪、你该怎么做”，每次调用都发，但不会随对话增长。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Messages 是 Agent 的短期记忆。&lt;/strong&gt;&lt;br /&gt;
对话历史随每轮 Loop 滚动累积，LLM 通过读这段历史来”记住”之前的事。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;工具描述也是 Prompt。&lt;/strong&gt;&lt;br /&gt;
写给 LLM 看的，不是写给人看的。&lt;br /&gt;
名字要直白，描述要具体。&lt;/p&gt;

&lt;p&gt;到这里，evo-agent 的三个核心要素都讲完了。&lt;/p&gt;

&lt;p&gt;Loop 驱动 Agent 不断行动。&lt;br /&gt;
Tools 让 Agent 能操作真实世界。&lt;br /&gt;
Prompts 和历史，则是 LLM 感知一切的唯一窗口。&lt;/p&gt;

&lt;p&gt;它只能通过你传进去的文字来理解世界。&lt;br /&gt;
你不说的，它就不知道。&lt;br /&gt;
你说错的，它就信以为真。&lt;/p&gt;

&lt;p&gt;下一篇，我们来看一个很实际的问题：当 messages 越滚越大，context window 撑不住了，怎么办？&lt;/p&gt;

&lt;p&gt;《完》&lt;/p&gt;

&lt;p&gt;-EOF-&lt;/p&gt;

&lt;p&gt;本文公众号：天空的代码世界&lt;br /&gt;
个人微信号：tiankonguse&lt;br /&gt;
公众号ID：tiankonguse-code&lt;/p&gt;
</content>
     <summary>LLM 本身是无状态的，它所知道的一切，全来自你每次传进去的那个 messages 数组。理解 Prompts 和对话历史，才能真正理解 Agent 是怎么&quot;记住&quot;事情的。</summary>
     <category term="程序人生" scheme="https://github.tiankonguse.com/categories.html#程序人生-category-ref"/>
     <tag term="agent" scheme="https://github.tiankonguse.com/tags.html#agent-tag-ref"/><tag term="prompts" scheme="https://github.tiankonguse.com/tags.html#prompts-tag-ref"/><tag term="golang" scheme="https://github.tiankonguse.com/tags.html#golang-tag-ref"/>
   </entry>
   
    
   <entry>
     <title>Agent 的手脚：接入更多的工具</title>
     <link href="https://github.tiankonguse.com/blog/2026/05/08/agent-tools.html"/>
     <updated>2026-05-07T12:13:00+08:00</updated>
     <published>2026-05-07T12:13:00+08:00</published>
     <id>https://github.tiankonguse.com/blog/2026/05/08/agent-tools</id>
     <content type="html">&lt;blockquote&gt;
  &lt;p&gt;Agent = LLM（大脑）+ Tools（手脚）+ Loop（神经反射弧）&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;上一篇&lt;a href=&quot;https://mp.weixin.qq.com/s/dkdrwVlwe3IkH2hzSzy53A&quot;&gt;Agent 的本质：一个 Loop 循环&lt;/a&gt;讲了 Loop。&lt;br /&gt;
这篇讲 Tools。&lt;/p&gt;

&lt;p&gt;如果 Loop 是 Agent 的神经反射弧，那工具就是它的手脚。&lt;br /&gt;
没有工具，LLM 再聪明，也只是个嘴上功夫的大脑。&lt;br /&gt;
什么都说得出来，什么都做不了。&lt;/p&gt;

&lt;h2 id=&quot;一项目进度回顾&quot;&gt;一、项目进度回顾&lt;/h2&gt;

&lt;p&gt;evo-agent 是一个从零开始构建 Agent 的学习项目。&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://mp.weixin.qq.com/s/dkdrwVlwe3IkH2hzSzy53A&quot;&gt;之前&lt;/a&gt;，我们实现了 Agent 最核心的骨架：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;接入 Anthropic API&lt;/li&gt;
  &lt;li&gt;实现 ReAct Loop（思考 → 行动 → 观察 → 循环）&lt;/li&gt;
  &lt;li&gt;提供第一个工具：&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bash&lt;/code&gt;（执行 Shell 命令）&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;这篇文章&lt;/strong&gt;，我们扩展了工具系统：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;新增 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;read_file&lt;/code&gt;：读取文件内容&lt;/li&gt;
  &lt;li&gt;新增 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;write_file&lt;/code&gt;：写入文件（自动创建目录）&lt;/li&gt;
  &lt;li&gt;新增 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;edit_file&lt;/code&gt;：精准替换文件片段&lt;/li&gt;
  &lt;li&gt;重构工具注册机制，支持工具自注册，方便后续扩展&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;当前项目目录结构如下：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;src/
├── main.go                    # 入口：交互式 REPL
├── internal/
│   ├── agent/
│   │   ├── loop.go            # Agent 主循环
│   │   └── state.go           # 对话状态
│   ├── tools/
│   │   ├── tool.go            # 工具注册表 &amp;amp; Dispatch
│   │   ├── executor.go        # 工具执行器（解析 LLM 响应）
│   │   ├── bash.go            # bash 工具
│   │   ├── read_file.go       # read_file 工具
│   │   ├── write_file.go      # write_file 工具
│   │   └── edit_file.go       # edit_file 工具
│   ├── config/
│   │   └── config.go          # 配置加载（.env）
│   └── ui/
│       └── terminal.go        # 终端彩色输出
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;二工具是什么&quot;&gt;二、工具是什么？&lt;/h2&gt;

&lt;p&gt;在 Agent 的语境里，工具（Tool）就是一个 LLM 可以”主动调用”的函数。&lt;br /&gt;
注意这里的关键词是”主动”。&lt;br /&gt;
不是我们程序员硬编码去调，而是 LLM 自己判断”我现在需要用这个工具”，然后发起调用。&lt;/p&gt;

&lt;p&gt;LLM 本身只能生成文本。&lt;br /&gt;
但如果我们事先告诉它：”你有这些工具，每个工具叫什么名字、接收什么参数、有什么用”。&lt;br /&gt;
那 LLM 在需要的时候，就会输出一段结构化的”工具调用请求”。&lt;/p&gt;

&lt;p&gt;Agent 系统拦截这个请求，真正去执行工具，再把结果还给 LLM。&lt;br /&gt;
这个过程叫 &lt;strong&gt;Tool Use&lt;/strong&gt;（工具调用）。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://res2026.tiankonguse.com/images/2026/05/08/009.png&quot; alt=&quot;截图&quot; /&gt;&lt;/p&gt;

&lt;p&gt;举个例子。&lt;br /&gt;
用户说：”帮我读取 config.json 的内容”。&lt;br /&gt;
LLM 不会傻乎乎地编一段文件内容出来，而是会说：”我要调用 read_file 工具，参数是 config.json”。&lt;br /&gt;
系统执行完，把真实的文件内容返回给 LLM，LLM 再基于真实内容回答用户。&lt;/p&gt;

&lt;p&gt;整个过程长这样：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://res2026.tiankonguse.com/images/2026/05/08/001.png&quot; alt=&quot;截图&quot; /&gt;&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-mermaid&quot;&gt;sequenceDiagram
    participant User as 用户
    participant Agent as Agent
    participant LLM as Claude (LLM)
    participant Tool as 具体工具

    User-&amp;gt;&amp;gt;Agent: &quot;帮我读取 config.json 的内容&quot;
    Agent-&amp;gt;&amp;gt;LLM: Messages + Tools Schema
    LLM-&amp;gt;&amp;gt;Agent: tool_use: read_file({path: &quot;config.json&quot;})
    Agent-&amp;gt;&amp;gt;Tool: 调用 read_file 处理函数
    Tool-&amp;gt;&amp;gt;Agent: 返回文件内容
    Agent-&amp;gt;&amp;gt;LLM: tool_result: &quot;{...}&quot;
    LLM-&amp;gt;&amp;gt;User: &quot;config.json 的内容是...&quot;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;而且工具调用不是一次性的。&lt;br /&gt;
LLM 可以连续调用多个工具，直到它认为信息足够了，才输出最终答案。&lt;br /&gt;
这就是&lt;a href=&quot;https://mp.weixin.qq.com/s/dkdrwVlwe3IkH2hzSzy53A&quot;&gt;上一篇&lt;/a&gt;讲的 Loop 的作用。&lt;/p&gt;

&lt;h2 id=&quot;三工具的数据结构&quot;&gt;三、工具的数据结构&lt;/h2&gt;

&lt;p&gt;每一个工具，本质上就是两件东西的组合：&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;strong&gt;Schema（接口描述）&lt;/strong&gt;：告诉 LLM 这个工具叫什么、接收什么参数。这是给 LLM 看的”说明书”。&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Handler（执行逻辑）&lt;/strong&gt;：真正的函数实现，这是给 Go 程序执行的。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;一个给 LLM 看，一个给程序跑。&lt;/p&gt;

&lt;p&gt;在代码里，我们用 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ToolDef&lt;/code&gt; 把这两件事绑在一起：&lt;/p&gt;

&lt;div class=&quot;language-go highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;// tool.go&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;// Handler 是每个工具必须实现的函数签名&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Handler&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;func&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;input&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;json&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;RawMessage&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;// ToolDef 把工具的 API schema 和 handler 绑定在一起&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ToolDef&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;Schema&lt;/span&gt;  &lt;span class=&quot;n&quot;&gt;anthropic&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ToolParam&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;Handler&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Handler&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Schema&lt;/code&gt; 里包含工具名称、描述、参数的 JSON Schema，发给 Anthropic API&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Handler&lt;/code&gt; 接收 LLM 传来的 JSON 参数，返回字符串结果（或错误）&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;四自注册模式让工具自己报到&quot;&gt;四、自注册模式：让工具”自己报到”&lt;/h2&gt;

&lt;p&gt;&lt;img src=&quot;https://res2026.tiankonguse.com/images/2026/05/08/006.png&quot; alt=&quot;截图&quot; /&gt;&lt;/p&gt;

&lt;p&gt;一个常见的工程问题：&lt;strong&gt;工具越来越多，怎么管？&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;最朴素的方式是维护一个全局列表，每次新增工具都去改那个列表。&lt;br /&gt;
但这很容易出错，耦合度也高。&lt;br /&gt;
改着改着就乱了。&lt;/p&gt;

&lt;p&gt;evo-agent 采用了 Go 的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;init()&lt;/code&gt; 自注册模式。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;核心思路&lt;/strong&gt;：每个工具文件自己负责把自己注册到全局注册表。&lt;br /&gt;
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;main&lt;/code&gt; 包只需要 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;import&lt;/code&gt; 这些文件，注册就自动完成了。&lt;br /&gt;
谁也不用管谁。&lt;/p&gt;

&lt;div class=&quot;language-go highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;// tool.go —— 全局注册表&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;registry&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ToolDef&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{}&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;// Register 向注册表添加一个工具&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Register&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ToolDef&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;registry&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;def&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Schema&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;def&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;// Tools 返回所有注册的工具 schema，用于发给 Anthropic API&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Tools&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[]&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;anthropic&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ToolUnionParam&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;...&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;// Dispatch 根据工具名找到 handler 并执行&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Dispatch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;name&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;input&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;json&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;RawMessage&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;...&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;每个工具文件里，在 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;init()&lt;/code&gt; 里调用 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Register&lt;/code&gt;：&lt;/p&gt;

&lt;div class=&quot;language-go highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;// bash.go&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;init&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;Register&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ToolDef&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;Schema&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;anthropic&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ToolParam&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;Name&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;        &lt;span class=&quot;s&quot;&gt;&quot;bash&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;Description&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;anthropic&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Run a shell command in the current workspace.&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;InputSchema&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;GenerateSchema&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;BashInput&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;](),&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;Handler&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;func&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;input&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;json&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;RawMessage&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;BashInput&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;json&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Unmarshal&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;input&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;in&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;runBash&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;in&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Command&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;nil&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;这样，&lt;strong&gt;添加一个新工具，只需要新建一个 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.go&lt;/code&gt; 文件&lt;/strong&gt;。&lt;br /&gt;
不需要修改任何已有代码。&lt;br /&gt;
完美的开闭原则。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://res2026.tiankonguse.com/images/2026/05/08/002.png&quot; alt=&quot;截图&quot; /&gt;&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-mermaid&quot;&gt;graph TD
    A[程序启动] --&amp;gt; B[Go runtime 执行所有 init]
    B --&amp;gt; C[bash.go init 注册 bash]
    B --&amp;gt; D[read_file.go init 注册 read_file]
    B --&amp;gt; E[write_file.go init 注册 write_file]
    B --&amp;gt; F[edit_file.go init 注册 edit_file]
    C --&amp;gt; G[registry map]
    D --&amp;gt; G
    E --&amp;gt; G
    F --&amp;gt; G
    G --&amp;gt; H[&quot;Tools() 返回所有 schema&quot;]
    G --&amp;gt; I[Dispatch 按名字路由]
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id=&quot;五schema-自动生成&quot;&gt;五、Schema 自动生成&lt;/h2&gt;

&lt;p&gt;上面说了，每个工具都需要一份 JSON Schema，告诉 LLM 这个工具接收什么参数。&lt;br /&gt;
但这个 Schema 怎么来？&lt;/p&gt;

&lt;p&gt;如果手写，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;read_file&lt;/code&gt; 的 Schema 大概长这样：&lt;/p&gt;

&lt;div class=&quot;language-go highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;anthropic&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ToolInputSchemaParam&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;Properties&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;interface&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{}{&lt;/span&gt;
        &lt;span class=&quot;s&quot;&gt;&quot;path&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;interface&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{}{&lt;/span&gt;
            &lt;span class=&quot;s&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;        &lt;span class=&quot;s&quot;&gt;&quot;string&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;s&quot;&gt;&quot;description&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;The relative path of a file in the working directory.&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
        &lt;span class=&quot;s&quot;&gt;&quot;limit&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;interface&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{}{&lt;/span&gt;
            &lt;span class=&quot;s&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;        &lt;span class=&quot;s&quot;&gt;&quot;integer&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;s&quot;&gt;&quot;description&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;Maximum number of lines to return (0 = no limit).&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;这还只是两个字段。&lt;br /&gt;
如果工具有五六个参数，而且项目里有十几个工具，每个都这样手写……&lt;br /&gt;
不仅代码量爆炸，参数描述和实际的 Go 结构体还是两套东西。&lt;br /&gt;
改一个忘了改另一个，迟早出问题。&lt;/p&gt;

&lt;p&gt;所以 evo-agent 用反射来自动生成 Schema：&lt;/p&gt;

&lt;div class=&quot;language-go highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;// GenerateSchema 用反射从 Go 结构体生成工具的 InputSchema&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;GenerateSchema&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;T&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;any&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]()&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;anthropic&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ToolInputSchemaParam&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;reflector&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;jsonschema&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Reflector&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;AllowAdditionalProperties&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;DoNotReference&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;            &lt;span class=&quot;no&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;v&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;T&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;schema&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;reflector&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Reflect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;v&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;anthropic&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ToolInputSchemaParam&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;Properties&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;schema&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Properties&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;只需要定义一个 Go 结构体，用 tag 写上字段描述，Schema 就自动生成了：&lt;/p&gt;

&lt;div class=&quot;language-go highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;BashInput&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;Command&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;`json:&quot;command&quot; jsonschema_description:&quot;The shell command to run.&quot;`&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ReadFileInput&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;Path&lt;/span&gt;  &lt;span class=&quot;kt&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;`json:&quot;path&quot;            jsonschema_description:&quot;The relative path of a file.&quot;`&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;Limit&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt;    &lt;span class=&quot;s&quot;&gt;`json:&quot;limit,omitempty&quot; jsonschema_description:&quot;Maximum lines to return (0 = no limit).&quot;`&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;img src=&quot;https://res2026.tiankonguse.com/images/2026/05/08/007.png&quot; alt=&quot;截图&quot; /&gt;&lt;/p&gt;

&lt;p&gt;一石二鸟。&lt;br /&gt;
LLM 会读这份 Schema，知道该传什么参数。&lt;br /&gt;
Go 程序收到 LLM 的 JSON 后，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;json.Unmarshal&lt;/code&gt; 进对应的结构体，完成类型安全的参数解析。&lt;br /&gt;
定义一次，两边都用。&lt;/p&gt;

&lt;h2 id=&quot;六四个工具详解&quot;&gt;六、四个工具详解&lt;/h2&gt;

&lt;h3 id=&quot;61-bash万能瑞士军刀&quot;&gt;6.1 bash：万能瑞士军刀&lt;/h3&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bash&lt;/code&gt; 是最灵活的工具。&lt;br /&gt;
只要是 Shell 能做的事，它都能做。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;bash(command=&quot;ls -lh&quot;)
bash(command=&quot;git log --oneline -5&quot;)
bash(command=&quot;grep -r &apos;TODO&apos; ./src&quot;)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;但灵活也意味着需要更多防护。&lt;br /&gt;
实现上有几个关键设计：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;用 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;exec.CommandContext&lt;/code&gt; 设置 &lt;strong&gt;120 秒超时&lt;/strong&gt;，防止命令卡死&lt;/li&gt;
  &lt;li&gt;合并 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;stdout&lt;/code&gt; 和 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;stderr&lt;/code&gt;（&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CombinedOutput&lt;/code&gt;），让 LLM 能看到完整输出（包括错误）&lt;/li&gt;
  &lt;li&gt;输出截断到 &lt;strong&gt;50000 字符&lt;/strong&gt;，避免 token 爆炸&lt;/li&gt;
  &lt;li&gt;空输出返回 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&quot;(no output)&quot;&lt;/code&gt;，而不是空字符串，LLM 更容易理解&lt;/li&gt;
&lt;/ul&gt;

&lt;div class=&quot;language-go highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;runBash&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;command&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;ctx&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cancel&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;WithTimeout&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Background&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(),&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;120&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;time&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Second&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;defer&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cancel&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;cmd&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;exec&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;CommandContext&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ctx&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;bash&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;-c&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;command&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;out&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;err&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cmd&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;CombinedOutput&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ctx&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Err&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;DeadlineExceeded&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;Error: Timeout (120s)&quot;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;c&quot;&gt;// ...&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;62-read_file精准读取&quot;&gt;6.2 read_file：精准读取&lt;/h3&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bash&lt;/code&gt; 当然也能 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cat&lt;/code&gt; 文件。&lt;br /&gt;
但专门的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;read_file&lt;/code&gt; 工具更语义化，也更可控。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;read_file(path=&quot;src/main.go&quot;)
read_file(path=&quot;large_log.txt&quot;, limit=50)   // 只读前 50 行
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;实现要点：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;支持 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;limit&lt;/code&gt; 参数，读大文件时只取前 N 行，后面追加 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&quot;... (N more lines)&quot;&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;同样做 50000 字符截断&lt;/li&gt;
  &lt;li&gt;不支持读目录（目录用 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bash&lt;/code&gt; + &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ls&lt;/code&gt; 处理）&lt;/li&gt;
&lt;/ul&gt;

&lt;div class=&quot;language-go highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;runReadFile&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;path&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;limit&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;os&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ReadFile&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;text&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;limit&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;lines&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;strings&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Split&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;text&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;limit&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;lines&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;text&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;strings&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Join&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;lines&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;limit&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt;
                &lt;span class=&quot;n&quot;&gt;fmt&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Sprintf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;... (%d more lines)&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;lines&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;limit&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;text&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;nil&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;63-write_file全量写入&quot;&gt;6.3 write_file：全量写入&lt;/h3&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;write_file&lt;/code&gt; 用于创建新文件，或者完全覆盖一个文件。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;write_file(path=&quot;output/result.txt&quot;, content=&quot;Hello, World!&quot;)
write_file(path=&quot;src/new_feature.go&quot;, content=&quot;package main\n...&quot;)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;实现要点：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;自动创建父目录（&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;os.MkdirAll&lt;/code&gt;），不用先 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mkdir -p&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;返回写入字节数，方便 LLM 确认写入成功&lt;/li&gt;
&lt;/ul&gt;

&lt;div class=&quot;language-go highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;runWriteFile&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;content&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;os&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;MkdirAll&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;filepath&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Dir&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;o755&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;os&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;WriteFile&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[]&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;byte&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;content&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;o644&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;fmt&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Sprintf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Wrote %d bytes to %s&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;content&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;nil&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;64-edit_file精准替换&quot;&gt;6.4 edit_file：精准替换&lt;/h3&gt;

&lt;p&gt;这是最精妙的一个工具。&lt;/p&gt;

&lt;p&gt;为什么需要它？&lt;br /&gt;
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;write_file&lt;/code&gt; 每次都要写整个文件。&lt;br /&gt;
如果只需要改一行代码，让 LLM 重写整个文件，效率很低，而且它容易”改着改着把其他内容搞丢了”。&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;edit_file&lt;/code&gt; 只替换文件中的一个片段。&lt;br /&gt;
传入旧内容 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;old_str&lt;/code&gt; 和新内容 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;new_str&lt;/code&gt;，只动这一处，其他地方纹丝不动。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;edit_file(
  path=&quot;src/main.go&quot;,
  old_str=&quot;fmt.Println(\&quot;hello\&quot;)&quot;,
  new_str=&quot;fmt.Println(\&quot;hello, agent!\&quot;)&quot;
)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;实现要点：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;old_str&lt;/code&gt; 必须在文件中&lt;strong&gt;唯一存在&lt;/strong&gt;，避免误改&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;old_str&lt;/code&gt; 为空且文件不存在时，等价于创建新文件（复用 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;write_file&lt;/code&gt; 逻辑）&lt;/li&gt;
  &lt;li&gt;用 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;strings.Replace(..., 1)&lt;/code&gt; 只替换第一处匹配，作为额外的安全兜底&lt;/li&gt;
&lt;/ul&gt;

&lt;div class=&quot;language-go highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;runEditFile&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;oldStr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;newStr&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;err&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;os&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ReadFile&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;os&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;IsNotExist&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;oldStr&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;&quot;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;runWriteFile&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;newStr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;  &lt;span class=&quot;c&quot;&gt;// 文件不存在 + 空 old_str = 创建&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;content&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;strings&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Contains&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;content&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;oldStr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;fmt&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Errorf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;edit_file: old_str not found in %s&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;newContent&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;strings&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Replace&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;content&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;oldStr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;newStr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;os&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;WriteFile&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[]&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;byte&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;newContent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;o644&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;fmt&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Sprintf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Edited %s&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;nil&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;七工具执行器连接-llm-和工具&quot;&gt;七、工具执行器：连接 LLM 和工具&lt;/h2&gt;

&lt;p&gt;LLM 返回的响应是一个内容块列表（&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[]ContentBlockUnion&lt;/code&gt;），里面可能包含三种东西：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ThinkingBlock&lt;/code&gt;：模型的思考过程（扩展思维功能）&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;TextBlock&lt;/code&gt;：普通文本输出&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ToolUseBlock&lt;/code&gt;：工具调用请求&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;executor.go&lt;/code&gt; 负责遍历这个列表，碰到 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ToolUseBlock&lt;/code&gt; 就去执行：&lt;/p&gt;

&lt;div class=&quot;language-go highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Execute&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;content&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[]&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;anthropic&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ContentBlockUnion&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[]&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;anthropic&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ContentBlockParamUnion&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;results&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[]&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;anthropic&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ContentBlockParamUnion&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;block&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;range&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;content&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;switch&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;v&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;block&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;AsAny&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;anthropic&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ThinkingBlock&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;ui&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;PrintThinking&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;v&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Thinking&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

        &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;anthropic&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;TextBlock&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;ui&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;PrintText&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;v&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Text&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

        &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;anthropic&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ToolUseBlock&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;ui&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;PrintToolCall&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;v&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;                                         &lt;span class=&quot;c&quot;&gt;// 打印工具名&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;ui&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;PrintCommand&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;fmt&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Sprintf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;%s(%s)&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;v&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;v&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Input&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Raw&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()))&lt;/span&gt;   &lt;span class=&quot;c&quot;&gt;// 打印参数&lt;/span&gt;

            &lt;span class=&quot;n&quot;&gt;inputBytes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;json&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Marshal&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;v&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Input&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;output&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;err&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Dispatch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;v&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;inputBytes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;                      &lt;span class=&quot;c&quot;&gt;// 执行工具&lt;/span&gt;

            &lt;span class=&quot;n&quot;&gt;isError&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;err&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;nil&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;results&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;append&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;results&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;anthropic&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;NewToolResultBlock&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;v&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ID&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;output&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;isError&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;results&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;执行结果会被打包成 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ToolResultBlock&lt;/code&gt;，下一轮发给 LLM，让它继续思考。&lt;/p&gt;

&lt;p&gt;整个数据流如下：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://res2026.tiankonguse.com/images/2026/05/08/003.png&quot; alt=&quot;截图&quot; /&gt;&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-mermaid&quot;&gt;flowchart LR
    A[&quot;LLM 响应\n[]ContentBlockUnion&quot;] --&amp;gt; B{块类型}
    B --&amp;gt;|ThinkingBlock| C[打印思考过程]
    B --&amp;gt;|TextBlock| D[打印文本]
    B --&amp;gt;|ToolUseBlock| E[Dispatch 工具]
    E --&amp;gt; F{工具注册表}
    F --&amp;gt;|bash| G[执行 Shell]
    F --&amp;gt;|read_file| H[读取文件]
    F --&amp;gt;|write_file| I[写入文件]
    F --&amp;gt;|edit_file| J[编辑文件]
    G &amp;amp; H &amp;amp; I &amp;amp; J --&amp;gt; K[&quot;ToolResultBlock\n(结果 or 错误)&quot;]
    K --&amp;gt; L[追加到 Messages]
    L --&amp;gt; M[下一轮 LLM 调用]
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id=&quot;八agent-主循环的完整视角&quot;&gt;八、Agent 主循环的完整视角&lt;/h2&gt;

&lt;p&gt;前面分别讲了 Loop 和 Tools。&lt;br /&gt;
现在把它们合在一起看，整个 Agent 的运作过程就一目了然了。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://res2026.tiankonguse.com/images/2026/05/08/004.png&quot; alt=&quot;截图&quot; /&gt;&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-mermaid&quot;&gt;sequenceDiagram
    participant User as 用户
    participant AgentLoop as Agent Loop
    participant LLM as LLM API
    participant Exec as 工具执行器
    participant Tool as 工具 (bash/file...)

    User-&amp;gt;&amp;gt;AgentLoop: 输入任务
    loop 直到 stop_reason = end_turn
        AgentLoop-&amp;gt;&amp;gt;LLM: Messages + Tools Schema
        LLM--&amp;gt;&amp;gt;AgentLoop: &quot;响应 (可能含 tool_use)&quot;
        AgentLoop-&amp;gt;&amp;gt;Exec: &quot;Execute(response.Content)&quot;
        Exec-&amp;gt;&amp;gt;Tool: &quot;Dispatch(name, input)&quot;
        Tool--&amp;gt;&amp;gt;Exec: 执行结果
        Exec--&amp;gt;&amp;gt;AgentLoop: []ToolResultBlock
        AgentLoop-&amp;gt;&amp;gt;AgentLoop: 追加 tool_result 到 Messages
    end
    AgentLoop-&amp;gt;&amp;gt;User: 最终回答
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;核心代码（&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;loop.go&lt;/code&gt;）非常简洁：&lt;/p&gt;

&lt;div class=&quot;language-go highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;a&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Agent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;RunOneTurn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;state&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;LoopState&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;bool&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;c&quot;&gt;// 1. 调用 LLM&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;resp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;client&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Messages&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;New&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ctx&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;anthropic&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;MessageNewParams&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;Messages&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;state&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Messages&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;Tools&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;    &lt;span class=&quot;n&quot;&gt;tools&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Tools&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(),&lt;/span&gt;  &lt;span class=&quot;c&quot;&gt;// 所有注册的工具&lt;/span&gt;
        &lt;span class=&quot;c&quot;&gt;// ...&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;

    &lt;span class=&quot;c&quot;&gt;// 2. 追加助手响应&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;state&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Messages&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;append&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;state&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Messages&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;resp&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ToParam&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;())&lt;/span&gt;

    &lt;span class=&quot;c&quot;&gt;// 3. 执行工具&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;toolResults&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;tools&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Execute&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;resp&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Content&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;c&quot;&gt;// 4. 没有工具调用 → 结束&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;toolResults&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;false&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;c&quot;&gt;// 5. 追加工具结果 → 继续下一轮&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;state&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Messages&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;append&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;state&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Messages&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;anthropic&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;NewUserMessage&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;toolResults&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;true&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;a&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Agent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Loop&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;state&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;LoopState&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;RunOneTurn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;state&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;你看，整个 Loop 的逻辑其实就一行：&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;for a.RunOneTurn(state) {}&lt;/code&gt;。&lt;br /&gt;
简洁到有点过分。&lt;br /&gt;
但就是这一行，驱动了 Agent 的全部行为。&lt;/p&gt;

&lt;h2 id=&quot;九为什么要有多种文件工具&quot;&gt;九、为什么要有多种文件工具？&lt;/h2&gt;

&lt;p&gt;有人可能会问：有了 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bash&lt;/code&gt;，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cat&lt;/code&gt;、&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;echo&lt;/code&gt;、&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sed&lt;/code&gt; 什么都能干。&lt;br /&gt;
那为什么还要单独实现 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;read_file&lt;/code&gt;、&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;write_file&lt;/code&gt;、&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;edit_file&lt;/code&gt;？&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://res2026.tiankonguse.com/images/2026/05/08/008.png&quot; alt=&quot;截图&quot; /&gt;&lt;/p&gt;

&lt;p&gt;原因有三。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;第一，语义更清晰，LLM 选择更准确。&lt;/strong&gt;&lt;br /&gt;
LLM 在选工具时，是根据工具描述来判断的。&lt;br /&gt;
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;read_file&lt;/code&gt; 的描述明确说”读文件内容”，比让 LLM 自己去猜该用什么 shell 命令更可靠，出错率更低。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;第二，安全性和可控性更好。&lt;/strong&gt;&lt;br /&gt;
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bash&lt;/code&gt; 是万能的，但万能也意味着危险。&lt;br /&gt;
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rm -rf /&lt;/code&gt; 也是合法的 bash 命令。&lt;br /&gt;
专用文件工具只做文件操作，边界清晰，日后加权限控制也方便。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;第三，输出更结构化，便于处理。&lt;/strong&gt;&lt;br /&gt;
用 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bash&lt;/code&gt; 执行 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cat&lt;/code&gt; 的输出可能有细微差别（比如某些情况下多了换行符）。&lt;br /&gt;
专用工具的输出就是干净的文件内容，LLM 处理起来更省心。&lt;/p&gt;

&lt;p&gt;简单说就是：&lt;strong&gt;能用专用工具就别用万能工具。&lt;/strong&gt;&lt;br /&gt;
能用确定性的方案，就别引入不确定性。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://res2026.tiankonguse.com/images/2026/05/08/005.png&quot; alt=&quot;截图&quot; /&gt;&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-mermaid&quot;&gt;graph TD
    A[LLM 需要操作文件] --&amp;gt; B{选择工具}
    B --&amp;gt; C[&quot;bash\n万能但危险\n适合复杂操作&quot;]
    B --&amp;gt; D[&quot;read_file\n安全只读\n适合查看内容&quot;]
    B --&amp;gt; E[&quot;write_file\n全量覆盖\n适合创建新文件&quot;]
    B --&amp;gt; F[&quot;edit_file\n精准替换\n适合修改已有文件&quot;]

    style C fill:#ff9999
    style D fill:#99ff99
    style E fill:#99ccff
    style F fill:#ffcc99
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id=&quot;十总结&quot;&gt;十、总结&lt;/h2&gt;

&lt;p&gt;回顾一下这篇文章的几个要点：&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. 工具决定了 Agent 的能力边界。&lt;/strong&gt;&lt;br /&gt;
你给它什么工具，它就能干什么活。&lt;br /&gt;
只有 bash 和有一整套文件操作工具，体验完全不一样。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;init()&lt;/code&gt; 自注册，加工具不用改老代码。&lt;/strong&gt;&lt;br /&gt;
新建一个文件就搞定，干净利落。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. 能用专用工具就别用 bash。&lt;/strong&gt;&lt;br /&gt;
语义更清晰，更安全，LLM 选错的概率也更低。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. 工具描述写给 LLM 看，不是写给自己看。&lt;/strong&gt;&lt;br /&gt;
名字要直白，描述要具体，参数要明确。&lt;br /&gt;
LLM 读不懂你的工具描述，它就不会用。&lt;/p&gt;

&lt;p&gt;到这里，我们的 Agent 已经有了大脑（LLM）、神经反射弧（Loop）、和手脚（Tools）。&lt;br /&gt;
下一篇，我们继续完善这个 Agent，看看还能给它装上什么新能力。&lt;/p&gt;

&lt;p&gt;《完》&lt;/p&gt;

&lt;p&gt;-EOF-&lt;/p&gt;

&lt;p&gt;本文公众号：天空的代码世界&lt;br /&gt;
个人微信号：tiankonguse&lt;br /&gt;
公众号ID：tiankonguse-code&lt;/p&gt;
</content>
     <summary>Agent 能做什么，取决于它有哪些工具。本文介绍如何从零设计一个可扩展的工具系统，并实现文件读写、编辑等核心工具。</summary>
     <category term="程序人生" scheme="https://github.tiankonguse.com/categories.html#程序人生-category-ref"/>
     <tag term="agent" scheme="https://github.tiankonguse.com/tags.html#agent-tag-ref"/><tag term="tools" scheme="https://github.tiankonguse.com/tags.html#tools-tag-ref"/><tag term="golang" scheme="https://github.tiankonguse.com/tags.html#golang-tag-ref"/>
   </entry>
   
    
   <entry>
     <title>Agent 的本质：一个简单的循环 (Loop)</title>
     <link href="https://github.tiankonguse.com/blog/2026/05/07/agent-is-a-loop.html"/>
     <updated>2026-05-07T12:13:00+08:00</updated>
     <published>2026-05-07T12:13:00+08:00</published>
     <id>https://github.tiankonguse.com/blog/2026/05/07/agent-is-a-loop</id>
     <content type="html">&lt;p&gt;最近跟不少朋友聊 Agent，发现大家对这个词的理解差异巨大。&lt;br /&gt;
有人觉得 Agent 就是更高级的 ChatGPT，有人觉得 Agent 是某种很玄乎的 AI 黑科技。&lt;/p&gt;

&lt;p&gt;但其实，当你真正去拆解它的时候，你会发现一个事实：&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Agent 的本质，就是一个 Loop（循环）。&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;没了。&lt;br /&gt;
就这么简单。&lt;/p&gt;

&lt;h2 id=&quot;一从-chatbot-到-agent&quot;&gt;一、从 Chatbot 到 Agent&lt;/h2&gt;

&lt;p&gt;先说一个很多人搞混的事。&lt;/p&gt;

&lt;p&gt;LLM 和 Agent，不是一个东西。&lt;/p&gt;

&lt;p&gt;打个比方：&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Chatbot（聊天机器人）&lt;/strong&gt;，就像一个坐在椅子上的教授。&lt;br /&gt;
你问他问题，他凭记忆给你答案。&lt;br /&gt;
但他不会站起来，不会帮你查资料，不会动手帮你干活。&lt;br /&gt;
他只负责”说”。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Agent（智能体）&lt;/strong&gt;，是给这个教授装上了手脚，还给他打了一针肾上腺素。&lt;br /&gt;
他不光能说，他还能动。&lt;br /&gt;
能查文件，能执行命令，能调接口，能自己验证自己的答案对不对。&lt;/p&gt;

&lt;p&gt;那问题来了。&lt;/p&gt;

&lt;p&gt;一个有手有脚的 AI，它怎么知道什么时候该动手，什么时候该停下来？&lt;/p&gt;

&lt;p&gt;答案就是 Loop。&lt;/p&gt;

&lt;p&gt;如果 LLM 是大脑，那 &lt;strong&gt;Loop 就是它的神经反射弧&lt;/strong&gt;。&lt;br /&gt;
没有 Loop，LLM 就是一个瘫在轮椅上的天才。&lt;br /&gt;
有了 Loop，它才能站起来走路。&lt;/p&gt;

&lt;h2 id=&quot;二为什么-agent-需要-loop&quot;&gt;二、为什么 Agent 需要 Loop？&lt;/h2&gt;

&lt;p&gt;这事得从 LLM 的一个致命缺陷说起。&lt;/p&gt;

&lt;p&gt;LLM 是一个&lt;strong&gt;一次性生成&lt;/strong&gt;过程。&lt;br /&gt;
一旦它开始输出，就没办法在中途停下来，看看现实世界发生了什么，然后修正方向。&lt;/p&gt;

&lt;p&gt;什么意思？&lt;/p&gt;

&lt;p&gt;就好比你让一个人蒙着眼睛走迷宫。&lt;br /&gt;
他可能很聪明，能猜出大概的路线。&lt;br /&gt;
但他看不见墙在哪，看不见岔路口在哪，看不见自己有没有走偏。&lt;/p&gt;

&lt;p&gt;而 Loop 的作用，就是每走一步，给他摘一下蒙眼布。&lt;br /&gt;
让他看一眼环境，调整方向，再继续走。&lt;/p&gt;

&lt;p&gt;这个”走一步看一步”的模式，在学术界有个正式的名字，叫 &lt;strong&gt;ReAct（Reason + Act）&lt;/strong&gt;。&lt;/p&gt;

&lt;p&gt;核心流程长这样：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://res2026.tiankonguse.com/images/2026/05/07/001.png&quot; alt=&quot;截图&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;三循环内部发生了什么&quot;&gt;三、循环内部发生了什么？&lt;/h2&gt;

&lt;p&gt;每一次循环，其实就四件事。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;第一步，Thought（思考）。&lt;/strong&gt;&lt;br /&gt;
LLM 看看当前的情况，想一想接下来该干嘛。&lt;br /&gt;
比如：”用户让我找最大的文件，那我得先看看目录里有哪些文件。”&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;第二步，Action（行动）。&lt;/strong&gt;&lt;br /&gt;
想好了就动手。&lt;br /&gt;
LLM 不再是吐出最终答案，而是发出一条”指令”，去调用一个外部工具。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;第三步，Observation（观察）。&lt;/strong&gt;&lt;br /&gt;
工具执行完了，把结果返回来。&lt;br /&gt;
LLM 拿到这个结果，看看发生了什么。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;第四步，Loop（回到第一步）。&lt;/strong&gt;&lt;br /&gt;
LLM 把观察到的结果塞进上下文，再次进入思考。&lt;br /&gt;
如此往复，直到它觉得信息够了，可以给出最终答案为止。&lt;/p&gt;

&lt;p&gt;就这四步，不断循环。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;没有 Loop，LLM 只能靠猜。&lt;/strong&gt;&lt;br /&gt;
&lt;strong&gt;有了 Loop，LLM 能靠真实反馈来验证。&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;这就是本质区别。&lt;/p&gt;

&lt;h2 id=&quot;四来个具体例子&quot;&gt;四、来个具体例子&lt;/h2&gt;

&lt;p&gt;说个最简单的场景：让 Agent 找出目录下最大的文件。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;第一轮循环：&lt;/strong&gt;&lt;br /&gt;
Thought：我需要列出文件和大小。&lt;br /&gt;
Action：执行 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ls -S -l&lt;/code&gt;。&lt;br /&gt;
Observation：拿到了文件列表。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;第二轮循环：&lt;/strong&gt;&lt;br /&gt;
Thought：看到了，最大的文件是 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;file_a&lt;/code&gt;。&lt;br /&gt;
Action：不需要再调工具了，直接出答案。&lt;br /&gt;
Final Answer：最大的文件是 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;file_a&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;两轮就搞定了。&lt;/p&gt;

&lt;p&gt;注意，Agent 不是一步到位”猜”出来的。&lt;br /&gt;
它是走了一步，看了一眼结果，确认没问题，才给出的答案。&lt;/p&gt;

&lt;p&gt;这就是 Loop 的力量。&lt;br /&gt;
不靠猜测，靠验证。&lt;/p&gt;

&lt;h2 id=&quot;五loop-是-agent-的灵魂&quot;&gt;五、Loop 是 Agent 的灵魂&lt;/h2&gt;

&lt;p&gt;为什么我说 Loop 是灵魂？&lt;/p&gt;

&lt;p&gt;因为一个 Agent “聪明”不”聪明”，很多时候根本不取决于它用了多强的模型。&lt;br /&gt;
而是取决于它的 Loop 设计得好不好。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;第一，容错。&lt;/strong&gt;&lt;br /&gt;
命令报错了怎么办？&lt;br /&gt;
没有 Loop 的系统，直接趴窝。&lt;br /&gt;
有 Loop 的 Agent，看到报错，想想为什么错了，换个方式再来一遍。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;第二，分解复杂任务。&lt;/strong&gt;&lt;br /&gt;
一个大任务拆成一百个小步。&lt;br /&gt;
每一步都简单到不容易出错，串起来就能完成非常复杂的工作。&lt;br /&gt;
这就是 Loop 天然的分治能力。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;第三，自主决策。&lt;/strong&gt;&lt;br /&gt;
有了 Loop，Agent 不需要你手把手教每一步该干嘛。&lt;br /&gt;
它能根据环境反馈，自己判断下一步做什么。&lt;br /&gt;
这才是真正的”智能体”。&lt;/p&gt;

&lt;h2 id=&quot;六总结&quot;&gt;六、总结&lt;/h2&gt;

&lt;p&gt;学 Agent，第一天最重要的认知就一条：&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;不要想着写一个完美的 Prompt 一步到位。&lt;/strong&gt;&lt;br /&gt;
&lt;strong&gt;而是要构建一个 Loop，让 AI 在跟环境的交互中，一步一步逼近正确答案。&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;一句话：&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Agent = LLM（大脑）+ Tools（手脚）+ Loop（神经反射弧）。&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;有了大脑，它能想。&lt;br /&gt;
有了手脚，它能动。&lt;br /&gt;
有了 Loop，它才能像一个真正的人一样，边做边看，边看边调。&lt;/p&gt;

&lt;p&gt;这就是 Agent。&lt;br /&gt;
没有什么玄乎的。&lt;/p&gt;

&lt;p&gt;就是一个循环。&lt;/p&gt;

&lt;p&gt;《完》&lt;/p&gt;

&lt;p&gt;-EOF-&lt;/p&gt;

&lt;p&gt;本文公众号：天空的代码世界&lt;br /&gt;
个人微信号：tiankonguse&lt;br /&gt;
公众号ID：tiankonguse-code&lt;/p&gt;
</content>
     <summary>Agent = LLM (大脑) + Tools (手脚) + Loop (神经反射弧)。</summary>
     <category term="程序人生" scheme="https://github.tiankonguse.com/categories.html#程序人生-category-ref"/>
     <tag term="项目实践" scheme="https://github.tiankonguse.com/tags.html#项目实践-tag-ref"/>
   </entry>
   
    
   <entry>
     <title>leetcode 周赛 500</title>
     <link href="https://github.tiankonguse.com/blog/2026/05/05/leetcode-contest-500.html"/>
     <updated>2026-05-05T12:13:00+08:00</updated>
     <published>2026-05-05T12:13:00+08:00</published>
     <id>https://github.tiankonguse.com/blog/2026/05/05/leetcode-contest-500</id>
     <content type="html">&lt;h2 id=&quot;零背景&quot;&gt;零、背景&lt;/h2&gt;

&lt;p&gt;这次比赛时间在 5 月 3 号，我还在五一休假，故没参加比赛。&lt;br /&gt;
5 号有时间了，补做了一下比赛。&lt;/p&gt;

&lt;p&gt;本场题型概览如下。&lt;br /&gt;
A 题：统计。&lt;br /&gt;
B 题：素数。&lt;br /&gt;
C 题：前缀和。&lt;br /&gt;
D 题：二维 LIS。&lt;/p&gt;

&lt;h2 id=&quot;一统计下标的相反奇偶性得分&quot;&gt;一、统计下标的相反奇偶性得分&lt;/h2&gt;

&lt;p&gt;题意：给一个数组，统计每个下标对应的值与后缀中值的奇偶性不同的个数。&lt;/p&gt;

&lt;p&gt;思路：统计。&lt;/p&gt;

&lt;p&gt;从后到前统计奇偶值的个数，即可得到每个下标的答案。&lt;/p&gt;

&lt;h2 id=&quot;二区间内的质数和&quot;&gt;二、区间内的质数和&lt;/h2&gt;

&lt;p&gt;题意：给一个数字 a，反转得到数字 b，问 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[a, b]&lt;/code&gt; 之间素数的个数。&lt;/p&gt;

&lt;p&gt;思路：筛素数。&lt;/p&gt;

&lt;p&gt;方法1：数据范围不大，逐个判断是否是素数即可。&lt;br /&gt;
方法2：前缀和预处理每个数字前缀素数的个数，两个前缀和求差即可。&lt;/p&gt;

&lt;h2 id=&quot;三在下标间移动的最小代价&quot;&gt;三、在下标间移动的最小代价&lt;/h2&gt;

&lt;p&gt;题意：给一个严格递增的数组，有两类移动操作。&lt;br /&gt;
操作1：从下标 x 跳到下标 y，代价为 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;abs(nums[x] - nums[y])&lt;/code&gt;。&lt;br /&gt;
操作2：从下标 x 移动到最近的相邻位置，代价为 1。&lt;br /&gt;
有 q 个询问，问从下标 x 移动到 y 的最小代价。&lt;/p&gt;

&lt;p&gt;最近相邻位置定义：左右最近的位置，如果距离相等，则下标小的为最近的位置。&lt;/p&gt;

&lt;p&gt;思路：前缀和。&lt;/p&gt;

&lt;p&gt;分析：由于数组严格递增，从下标 x 直接跳到下标 y，等价于沿这个方向逐个跳过去。&lt;br /&gt;
因此，如果某一跳刚好是最近的相邻位置，则可以贪心地使用操作2 来降低代价。&lt;/p&gt;

&lt;p&gt;暴力模拟复杂度为 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;O(q * (y - x))&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;优化：预处理第一个位置到任意一个位置的累计代价，通过前缀和求差，即可求出任意两个位置之间的代价。&lt;/p&gt;

&lt;p&gt;注意事项：两个方向的代价不一样，前缀和与后缀和需要分别计算。&lt;/p&gt;

&lt;h2 id=&quot;四删除元素后最大固定点数目&quot;&gt;四、删除元素后最大固定点数目&lt;/h2&gt;

&lt;p&gt;题意：给一个数组，删除若干元素，问最多可以使多少个下标与值恰好相等。&lt;/p&gt;

&lt;p&gt;思路：二维最长递增子序列。&lt;/p&gt;

&lt;p&gt;删除元素后，后面的元素会左移，所以只有 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;nums[i] &amp;lt;= i&lt;/code&gt; 时，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;nums[i]&lt;/code&gt; 才可能通过左移与位置匹配。&lt;br /&gt;
另外，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;nums[i]&lt;/code&gt; 要到达下标 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;nums[i]&lt;/code&gt;，左边需要恰好删除 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;i - nums[i]&lt;/code&gt; 个元素。&lt;/p&gt;

&lt;p&gt;假设预处理所有满足条件的数字对 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(v, i-v)&lt;/code&gt;，如何才能保证两个数字的值都与位置匹配呢？&lt;/p&gt;

&lt;p&gt;假设有两个数字 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;v1&lt;/code&gt; 和 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;v2&lt;/code&gt;，左移的偏移距离分别是 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;d1&lt;/code&gt; 和 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;d2&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://res2026.tiankonguse.com/images/2026/05/05/001.png&quot; alt=&quot;截图&quot; /&gt;&lt;/p&gt;

&lt;p&gt;如上图，显然 d1 需要小于等于 d2，d1 个数字在 v1 之前删除，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;d2-d1&lt;/code&gt; 个数字在 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;v1&lt;/code&gt; 到 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;v2&lt;/code&gt; 之间删除。&lt;br /&gt;
故只要满足 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;d1 &amp;lt;= d2&lt;/code&gt; 且 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;v1 &amp;lt; v2&lt;/code&gt;，就可以保证两个数字的值都与位置匹配。&lt;/p&gt;

&lt;p&gt;由此，这道题转化为一个二维最长递增子序列问题：对于 d 需要满足非递减，对于 v 需要满足严格递增。&lt;/p&gt;

&lt;p&gt;回顾一维 LIS 的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;n log(n)&lt;/code&gt; 算法，利用的是 DP + 二分思想。&lt;br /&gt;
dp 状态 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;tail[i]&lt;/code&gt; 定义为长度为 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;i+1&lt;/code&gt; 的递增子序列中，结尾元素的最小值。&lt;/p&gt;

&lt;p&gt;这样做的原因是：结尾越小，后面越容易接上更多数字。&lt;/p&gt;

&lt;p&gt;对于一个新的数字 v，我们需要找到小于 v 的最大答案。&lt;br /&gt;
在 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;tail&lt;/code&gt; 中，值小于 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;v&lt;/code&gt; 的都是合法答案，最后一个就是最长的答案，故 v 可以放在其下一个位置。&lt;/p&gt;

&lt;div class=&quot;language-cpp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;auto&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;it&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;upper_bound&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;dp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;begin&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(),&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;end&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(),&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;it&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;end&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;())&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;dp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;push_back&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;it&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;对于二维，比较抽象，有两种做法。&lt;/p&gt;

&lt;p&gt;做法1：第一维非递减，第二维严格递增，使用 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;lower_bound&lt;/code&gt;。&lt;br /&gt;
由于第二维要求严格递增，不能有等于，所以使用 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;lower_bound&lt;/code&gt;。&lt;/p&gt;

&lt;div class=&quot;language-cpp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;// 要求：第一维非递减，第二维递增&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// 排序：默认，需要使用 lower_bound&lt;/span&gt;
&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;Lis2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;vector&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pair&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&amp;amp;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pts&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;sort&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pts&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;begin&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(),&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pts&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;end&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;());&lt;/span&gt;

  &lt;span class=&quot;n&quot;&gt;vector&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;tails&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;auto&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;d&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pts&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;auto&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;it&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;lower_bound&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;tails&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;begin&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(),&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;tails&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;end&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(),&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;d&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;it&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;tails&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;end&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;())&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;tails&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;push_back&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;d&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;it&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;d&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;tails&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;做法2：第一维严格递增，第二维非递减，使用 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;upper_bound&lt;/code&gt;。&lt;br /&gt;
由于第二维允许重复，所以使用 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;upper_bound&lt;/code&gt;。&lt;/p&gt;

&lt;div class=&quot;language-cpp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;// 要求：第一维递增，第二维非递减&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// 排序：第一维升序，第二维降序，使用 upper_bound&lt;/span&gt;
&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;Lis2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;vector&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pair&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&amp;amp;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pts&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;// x 升序；x 相同则 d 降序&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;sort&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pts&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;begin&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(),&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pts&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;end&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(),&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[](&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;auto&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;auto&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;first&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;first&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;first&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;first&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;second&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;second&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;

  &lt;span class=&quot;c1&quot;&gt;// 在 d 上求最长非递减子序列&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;vector&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;tails&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;auto&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;d&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pts&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;auto&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;it&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;upper_bound&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;tails&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;begin&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(),&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;tails&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;end&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(),&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;d&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;it&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;tails&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;end&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;())&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;tails&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;push_back&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;d&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;it&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;d&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;tails&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;不管哪种做法，二维 LIS 都比一维 LIS 更抽象一些。&lt;/p&gt;

&lt;h2 id=&quot;五最后&quot;&gt;五、最后&lt;/h2&gt;

&lt;p&gt;这次比赛最后一题是二维 LIS，属于比较抽象的模板题。&lt;br /&gt;
如果比赛时遇到这类问题，我会选择使用线段树或者树状数组来求区间最大值，从而降低理解难度。&lt;/p&gt;

&lt;p&gt;《完》&lt;/p&gt;

&lt;p&gt;-EOF-&lt;/p&gt;

&lt;p&gt;本文公众号：天空的代码世界&lt;br /&gt;
个人微信号：tiankonguse&lt;br /&gt;
公众号 ID：tiankonguse-code&lt;/p&gt;
</content>
     <summary>二维最长递增子序列</summary>
     <category term="算法" scheme="https://github.tiankonguse.com/categories.html#算法-category-ref"/>
     <tag term="算法" scheme="https://github.tiankonguse.com/tags.html#算法-tag-ref"/><tag term="leetcode" scheme="https://github.tiankonguse.com/tags.html#leetcode-tag-ref"/><tag term="算法比赛" scheme="https://github.tiankonguse.com/tags.html#算法比赛-tag-ref"/>
   </entry>
   
    
   <entry>
     <title>leetcode 周赛 499</title>
     <link href="https://github.tiankonguse.com/blog/2026/04/26/leetcode-contest-499.html"/>
     <updated>2026-04-26T12:13:00+08:00</updated>
     <published>2026-04-26T12:13:00+08:00</published>
     <id>https://github.tiankonguse.com/blog/2026/04/26/leetcode-contest-499</id>
     <content type="html">&lt;h2 id=&quot;零背景&quot;&gt;零、背景&lt;/h2&gt;

&lt;p&gt;这次比赛其实不难，但是我写线段树被卡超时了，显示&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;超出时间限制 947 / 958 个通过的测试用例&lt;/code&gt;。&lt;br /&gt;
最后各种尝试常数优化，不小心还 WA 一次，然后才通过。&lt;/p&gt;

&lt;p&gt;本场题型概览如下。&lt;br /&gt;
A 题：前缀和。&lt;br /&gt;
B 题：统计排序。&lt;br /&gt;
C 题：贪心。&lt;br /&gt;
D 题：DP+滑动窗口+线段树。&lt;/p&gt;

&lt;h2 id=&quot;一数组中的有效元素&quot;&gt;一、数组中的有效元素&lt;/h2&gt;

&lt;p&gt;题意：如果一个元素严格大于左侧所有元素或者右侧所有元素，则称为有效元素。&lt;br /&gt;
要求按原顺序输出所有有效元素。&lt;/p&gt;

&lt;p&gt;思路：前缀和&lt;/p&gt;

&lt;p&gt;预处理出前缀最大值和后缀最大值，然后依次判断即可。&lt;/p&gt;

&lt;h2 id=&quot;二按频率对元音排序&quot;&gt;二、按频率对元音排序&lt;/h2&gt;

&lt;p&gt;题意：给一个字符串，要求对元音字符排序。&lt;br /&gt;
排序规则：优先按出现的频率排序，频率相同时按首次出现位置排序。&lt;/p&gt;

&lt;p&gt;思路：统计排序&lt;/p&gt;

&lt;p&gt;按题意，统计每个元音字符的频率和首次出现位置。&lt;br /&gt;
然后将 map 转化为数组并按题目要求排序，从而得到排序后的分组元音列表。&lt;br /&gt;
最后，遍历字符串，遇到元音，就从排序后的结果集中按顺序取一个元音。&lt;/p&gt;

&lt;p&gt;小技巧：排序后对容器翻转，则把获取第一个元素变成获取最后一个元素，从而可以 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;O(1)&lt;/code&gt;删除元音。&lt;/p&gt;

&lt;h2 id=&quot;三使数组非递减需要的最小累计值&quot;&gt;三、使数组非递减需要的最小累计值&lt;/h2&gt;

&lt;p&gt;题意：给一个数组，每次操作可以对一个子数组都加上一个数字。&lt;br /&gt;
求怎么操作，才能使得数组非递减有序，且操作的数字之和最小。&lt;/p&gt;

&lt;p&gt;思路：贪心&lt;/p&gt;

&lt;p&gt;目标是让数组递增，显然，每次操作时选择一个中间子数组，肯定不如选择整个后缀更优。&lt;/p&gt;

&lt;p&gt;证明：假设答案中某个操作不是后缀，则把这个操作的后缀都补齐，不影响最终答案。&lt;/p&gt;

&lt;p&gt;有了这个推论，就可以推导出贪心算法：每次相邻位置值变小时，下个位置的值就应该加上差值，使得与上个位置的值相等。&lt;/p&gt;

&lt;h2 id=&quot;四距离至少为-k-的交替子序列的最大和&quot;&gt;四、距离至少为 K 的交替子序列的最大和&lt;/h2&gt;

&lt;p&gt;题意：给一个数组和正整数 K，如果选择的子序列下标位置差不小于 K 且子序列是严格交替变大变小的，则称为有效子序列。&lt;br /&gt;
求有效子序列中子序列元素值之和的最大和。&lt;br /&gt;
严格交替变大变小定义：任意相邻三个元素，中间的值要么大于两边的元素，要么小于两边的元素。&lt;/p&gt;

&lt;p&gt;思路：动态规划+滑动窗口+线段树&lt;/p&gt;

&lt;p&gt;首先很容易想到一个简单的动态规划方程。&lt;/p&gt;

&lt;p&gt;状态定义：&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dp[i][dir]&lt;/code&gt; 第 i 个元素作为 dir 序结尾的最大有效子序列和。&lt;/p&gt;

&lt;p&gt;状态转移方程：&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;dp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;nums&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;max&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;dp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;j&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;j&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;k&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;nums&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;j&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;nums&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;dp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;nums&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;max&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;dp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;j&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;j&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;k&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;nums&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;j&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;nums&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;方程分两部分。&lt;br /&gt;
1）自身就是最大值。&lt;br /&gt;
2）前面还有元素，但是交替变大变小的方向需要与当前的相反，且满足大小关系。&lt;/p&gt;

&lt;p&gt;复杂度：&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;O(n^2)&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;优化方向显然在 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;max(dp[j][dir])&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;第一个优化：&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;j&lt;/code&gt; 永远比 i 小 k，显然是滑动窗口，窗口外的才参与计算，由此消除 k。&lt;/p&gt;

&lt;p&gt;此时，问题转化为了 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;max(dp[j][dir]) &amp;amp;&amp;amp; nums[j] &amp;lt; nums[i]&lt;/code&gt;。&lt;br /&gt;
对于&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;nums[j] &amp;lt; nums[i]&lt;/code&gt;，公式转化为文字就是，求值 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;nums[j]&lt;/code&gt; 小于 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;nums[i]&lt;/code&gt; 的所有位置里，最大的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dp[j][dir]&lt;/code&gt;。&lt;br /&gt;
对于&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;nums[j] &amp;gt; nums[i]&lt;/code&gt;，含义是类似的，这里暂时不重复描述了。&lt;/p&gt;

&lt;p&gt;如果把 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;nums[j]&lt;/code&gt; 当做下标，则问题转化为了，求区间 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[1, nums[i]-1]&lt;/code&gt; 的最大值。&lt;br /&gt;
显然可以使用线段树来做。&lt;/p&gt;

&lt;p&gt;正常情况下，是需要对 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;nums[j]&lt;/code&gt; 做离散化的。&lt;br /&gt;
不过这道题的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;nums&lt;/code&gt; 值域不大，且都是正整数，可以直接用来当做下标。&lt;/p&gt;

&lt;p&gt;由此，我们就可以通过线段树来解决一个维度小于指定值时另一个维度的最值问题。&lt;/p&gt;

&lt;p&gt;注意事项：不要从线段树中求最终答案，应该从 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;max(dp)&lt;/code&gt; 中求最终答案。&lt;br /&gt;
因为滑动窗口内的答案还没有加入线段树，直接求就会少算一部分，从而导致答案错误。&lt;/p&gt;

&lt;h2 id=&quot;五最后&quot;&gt;五、最后&lt;/h2&gt;

&lt;p&gt;这次比赛最后一题算是稍微复杂一点的题，结合了动态规划、滑动窗口、线段树。&lt;br /&gt;
如果把值域设置的大一些，再引入离散化，这道题就完美了。&lt;/p&gt;

&lt;p&gt;《完》。&lt;/p&gt;

&lt;p&gt;-EOF-&lt;/p&gt;

&lt;p&gt;本文公众号：天空的代码世界
个人微信号：tiankonguse
公众号 ID：tiankonguse-code&lt;/p&gt;
</content>
     <summary>线段树：小于一个数的最大答案</summary>
     <category term="算法" scheme="https://github.tiankonguse.com/categories.html#算法-category-ref"/>
     <tag term="算法" scheme="https://github.tiankonguse.com/tags.html#算法-tag-ref"/><tag term="leetcode" scheme="https://github.tiankonguse.com/tags.html#leetcode-tag-ref"/><tag term="算法比赛" scheme="https://github.tiankonguse.com/tags.html#算法比赛-tag-ref"/>
   </entry>
   
    
   <entry>
     <title>leetcode 周赛 498</title>
     <link href="https://github.tiankonguse.com/blog/2026/04/19/leetcode-contest-498.html"/>
     <updated>2026-04-19T12:13:00+08:00</updated>
     <published>2026-04-19T12:13:00+08:00</published>
     <id>https://github.tiankonguse.com/blog/2026/04/19/leetcode-contest-498</id>
     <content type="html">&lt;h2 id=&quot;零背景&quot;&gt;零、背景&lt;/h2&gt;

&lt;p&gt;这次比赛比较简单，第三题BFS，第四题数位DP。&lt;/p&gt;

&lt;p&gt;本场题型概览如下。&lt;br /&gt;
A 题：前缀和。&lt;br /&gt;
B 题：前缀和。&lt;br /&gt;
C 题：BFS。&lt;br /&gt;
D 题：数位DP。&lt;/p&gt;

&lt;h2 id=&quot;一最小稳定下标-i&quot;&gt;一、最小稳定下标 I&lt;/h2&gt;

&lt;p&gt;题意：给一个数组，前缀最大值减去后缀最大值如果小于等于K，则称为稳定值，求稳定值的最小下标。&lt;/p&gt;

&lt;p&gt;思路：前缀和&lt;/p&gt;

&lt;p&gt;预处理出前缀和与后缀和，然后计算出稳定值。&lt;/p&gt;

&lt;h2 id=&quot;二最小稳定下标-ii&quot;&gt;二、最小稳定下标 II&lt;/h2&gt;

&lt;p&gt;与第一题一模一样。&lt;/p&gt;

&lt;h2 id=&quot;三多源洪水灌溉&quot;&gt;三、多源洪水灌溉&lt;/h2&gt;

&lt;p&gt;题意：给一个矩阵，若干位置有颜色值，每一秒有颜色值的位置会朝上下左右的空位置染色。&lt;br /&gt;
如果同一个空位置同时被多个来源染色，则保留染色值最大的那一个。&lt;br /&gt;
问最终矩阵各个位置的颜色值。&lt;/p&gt;

&lt;p&gt;思路：BFS&lt;/p&gt;

&lt;p&gt;染色的过程是逐层进行的，所以需要使用 BFS。&lt;br /&gt;
数据结构：&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;queue&amp;lt;pair&amp;lt;x,y&amp;gt;&amp;gt;&lt;/code&gt;&lt;/p&gt;

&lt;div class=&quot;language-cpp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;q&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;empty&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;())&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;auto&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;c&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;q&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;front&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;q&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pop&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;auto&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;dr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dirs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;nr&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;r&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;nc&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;c&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;nr&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;nr&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;n&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;nc&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;nc&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;m&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;continue&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ans&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;nr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;nc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;// 没有被染色过&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;ans&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;nr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;nc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ans&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;c&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;q&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;emplace&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;nr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;nc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;同时染色时需要保留最大的那个。&lt;br /&gt;
故还需要记录一个层数，或者称为步长，或者称为时间戳。&lt;br /&gt;
数据结构：&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;vector&amp;lt;vector&amp;lt;int&amp;gt;&amp;gt; steps;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;对于层数相同的染色，不能重复入队，只需要更新染色值。&lt;/p&gt;

&lt;div class=&quot;language-cpp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;// 没有被染色过&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ans&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;nr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;nc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;  
  &lt;span class=&quot;n&quot;&gt;ans&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;nr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;nc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ans&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;c&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;steps&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;nr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;nc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;steps&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;c&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;q&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;emplace&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;nr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;nc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
 &lt;span class=&quot;c1&quot;&gt;// 同一时间被染色，选择颜色较大的&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;steps&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;nr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;nc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;steps&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;c&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; 
  &lt;span class=&quot;n&quot;&gt;ans&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;nr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;nc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;max&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ans&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;nr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;nc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ans&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;c&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;四统计网格路径中好整数的数目&quot;&gt;四、统计网格路径中好整数的数目&lt;/h2&gt;

&lt;p&gt;题意：一个不大于&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;9*10^15&lt;/code&gt;的数字，组成一个带前导零的16位数组，然后从高位开始每 4 位一行，组成一个 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;4*4&lt;/code&gt;的矩阵。&lt;br /&gt;
然后告诉你三个向右与三个向下的方向，从而得到一个从左上角到右下角的路径。&lt;br /&gt;
如果路径上的数字序列是非递减的，则称这个数字是好数字。&lt;br /&gt;
问区间 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[l,r]&lt;/code&gt; 内好数字的数量。&lt;/p&gt;

&lt;p&gt;思路：数位DP&lt;/p&gt;

&lt;p&gt;假设有一个函数 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;f(x)&lt;/code&gt; 可以求出 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[0,x]&lt;/code&gt;的所有好数字。&lt;br /&gt;
则区间 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[l,r]&lt;/code&gt; 内的好数字等价于 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;f(r) - f(l-1)&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;题目中的路径是确定的，所以数字的位数也是确定的，且涉及 7 个位数，从高位到低位保持非递减。&lt;br /&gt;
路径外的数字不需要遵循非递减这个性质。&lt;/p&gt;

&lt;p&gt;显然，可以从高位到低位枚举所有数字。&lt;br /&gt;
枚举的过程中，如果在路径上，需要保持非递减的性质，如果不在路径上，则可以任意选择。&lt;/p&gt;

&lt;p&gt;下面是数位DP的模板。&lt;/p&gt;

&lt;div class=&quot;language-cpp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;ll&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cache&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;17&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;11&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;ll&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;Dfs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pos&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;preSelectVal&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;bool&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;limit&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pos&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;16&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// 出口&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cache&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pos&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;preSelectVal&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;limit&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cache&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pos&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;preSelectVal&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;limit&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;up&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;limit&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pos&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;sc&quot;&gt;&apos;0&apos;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;9&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;ll&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ret&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;up&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;val&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;IsInpath&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pos&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;c1&quot;&gt;// 路径上的点，必须大于等于前一个点的值&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;preSelectVal&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;ret&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Dfs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pos&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;val&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;limit&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;up&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; 
      &lt;span class=&quot;c1&quot;&gt;// 非路径上的点，都可以选择&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;ret&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Dfs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pos&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;preSelectVal&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;limit&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;up&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cache&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pos&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;preSelectVal&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;limit&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ret&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;当然，上下界我们也可以一起实现。&lt;/p&gt;

&lt;div class=&quot;language-cpp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;SL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;SR&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;ll&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cache&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;17&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;11&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;ll&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;Dfs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pos&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;preSelectVal&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;bool&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;limitDown&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;bool&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;limitUp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pos&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;16&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cache&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pos&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;preSelectVal&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;limitDown&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;limitUp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cache&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pos&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;preSelectVal&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;limitDown&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;limitUp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;down&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;limitDown&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;SL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pos&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;sc&quot;&gt;&apos;0&apos;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;up&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;limitUp&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;SR&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pos&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;sc&quot;&gt;&apos;0&apos;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;9&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;ll&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ret&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;down&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;up&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;val&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;IsInpath&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pos&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;preSelectVal&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;ret&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Dfs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pos&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;val&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;limitDown&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;down&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;limitUp&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;up&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;ret&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Dfs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pos&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;preSelectVal&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;limitDown&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;down&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;limitUp&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;up&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cache&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pos&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;preSelectVal&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;limitDown&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;limitUp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ret&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;ll&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ans&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Dfs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;五最后&quot;&gt;五、最后&lt;/h2&gt;

&lt;p&gt;这次比赛题目比较简单，排名只有 92 名了。&lt;br /&gt;
之前的比赛，每次我都是手动敲数位DP的。&lt;br /&gt;
这次比赛我整理了自己的数位DP模板，下次应该就可以直接复制过来使用了吧。&lt;/p&gt;

&lt;p&gt;《完》。&lt;/p&gt;

&lt;p&gt;-EOF-&lt;/p&gt;

&lt;p&gt;本文公众号：天空的代码世界
个人微信号：tiankonguse
公众号 ID：tiankonguse-code&lt;/p&gt;
</content>
     <summary>数位DP</summary>
     <category term="算法" scheme="https://github.tiankonguse.com/categories.html#算法-category-ref"/>
     <tag term="算法" scheme="https://github.tiankonguse.com/tags.html#算法-tag-ref"/><tag term="leetcode" scheme="https://github.tiankonguse.com/tags.html#leetcode-tag-ref"/><tag term="算法比赛" scheme="https://github.tiankonguse.com/tags.html#算法比赛-tag-ref"/>
   </entry>
   
    
   <entry>
     <title>vibe coding：放弃 Node，转 Go</title>
     <link href="https://github.tiankonguse.com/blog/2026/04/17/vibecoding-node-to-go.html"/>
     <updated>2026-04-17T12:13:00+08:00</updated>
     <published>2026-04-17T12:13:00+08:00</published>
     <id>https://github.tiankonguse.com/blog/2026/04/17/vibecoding-node-to-go</id>
     <content type="html">&lt;h2 id=&quot;零背景&quot;&gt;零、背景&lt;/h2&gt;

&lt;p&gt;上上周我在《&lt;a href=&quot;https://mp.weixin.qq.com/s/UJv--yyyXgzyuKaRbQMvYA&quot;&gt;2周消耗4亿tokens做8个项目&lt;/a&gt;》提到，我连续两周 vibe coding 写了不少项目。&lt;/p&gt;

&lt;p&gt;上周我在《&lt;a href=&quot;https://mp.weixin.qq.com/s/sO03A59K8c_2fntQrRq4VQ&quot;&gt;spec code 将被 agent 替代&lt;/a&gt;》中分享了我使用 OpenSpec 的经验，以及在《&lt;a href=&quot;https://mp.weixin.qq.com/s/YnczzjN5Q92s_FAQcgeujQ&quot;&gt;更强大的 spec-kit&lt;/a&gt;》中分享了 Spec-kit 的经验。&lt;/p&gt;

&lt;p&gt;之前 vibe coding 写的代码全是前端代码，技术栈是 Node + JavaScript + SQLite。&lt;/p&gt;

&lt;p&gt;这周继续 vibe coding，发现当需要大量计算数据时，Node 性能太差了，所以我开始转向生成 Go 代码。&lt;/p&gt;

&lt;p&gt;回顾一下，这周 vibe coding 了 5 个工具：时钟误差检测工具、Redis 数据一致性监控系统、缓存中间件一致性监控系统、HTTP 代理系统、Union 压测系统。&lt;br /&gt;
另外，对于之前开发的热度值监控系统，我也使用 Go 进行了重构，重构后系统稳定多了。&lt;/p&gt;

&lt;p&gt;接下来简单分析下这些系统工具。&lt;/p&gt;

&lt;h2 id=&quot;一热度值监控&quot;&gt;一、热度值监控&lt;/h2&gt;

&lt;p&gt;热度值监控，是为了从用户视角监控每个剧集的热度值发生变化时，数据是否会回退。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://res2026.tiankonguse.com/images/2026/04/17/001.png&quot; alt=&quot;截图&quot; /&gt;&lt;/p&gt;

&lt;p&gt;虽然做了一版优化，监控数据显示，优化后依旧存在某些时刻会有数据回退的现象。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://res2026.tiankonguse.com/images/2026/04/17/002.png&quot; alt=&quot;截图&quot; /&gt;&lt;/p&gt;

&lt;p&gt;所以，需要分析是什么原因导致了数据回退。&lt;br /&gt;
为此，我开发了以下这些工具。&lt;/p&gt;

&lt;h2 id=&quot;二时钟误差检测工具&quot;&gt;二、时钟误差检测工具&lt;/h2&gt;

&lt;p&gt;每个剧集的热度值，会在固定的时间统一刷新。&lt;br /&gt;
如果多个机器的时间存在差异，那自然就会有些机器刷新较慢，导致拉到旧数据。&lt;br /&gt;
基于此，我 vibe coding 了一个容器时钟偏差检测服务 Time Checker。&lt;/p&gt;

&lt;p&gt;服务功能：一个基于 NTP 式四时间戳探测的中心化时钟偏差检测系统，用于检测多个容器之间的系统时钟是否一致。&lt;/p&gt;

&lt;p&gt;系统架构如下：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://res2026.tiankonguse.com/images/2026/04/17/003.png&quot; alt=&quot;截图&quot; /&gt;&lt;/p&gt;

&lt;p&gt;实现原理很简单，通过两个机器之间互相发送一个数据包，各自记录发送时间与接收时间，从而能够解方程计算出时钟偏差，还能顺便计算出平均网络延迟。&lt;/p&gt;

&lt;p&gt;NTP 式偏差计算原理如下：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://res2026.tiankonguse.com/images/2026/04/17/004.png&quot; alt=&quot;截图&quot; /&gt;&lt;/p&gt;

&lt;p&gt;跑完之后，发现各个机器之间的时间误差只有 1.5 毫秒，排除了时间误差这个方向。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://res2026.tiankonguse.com/images/2026/04/17/005.png&quot; alt=&quot;截图&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;三redis-热度值实时采集与监控系统&quot;&gt;三、Redis 热度值实时采集与监控系统&lt;/h2&gt;

&lt;p&gt;既然机器时钟没问题，那底层的存储是否有问题呢？&lt;br /&gt;
如果存储数据本身就存在来回跳变，那上游系统自然也会发生来回跳变。&lt;/p&gt;

&lt;p&gt;需求：实现一个高性能的 Redis 热度值监控后台服务，使用 Golang 开发，每秒从 Redis 中批量拉取 10 个剧集 ID 对应的两个热度值，解析后写入 MySQL 表中，并定时清理超过 24 小时的历史数据。&lt;/p&gt;

&lt;p&gt;选择 Go 的理由：性能优先；Go 的并发模型（goroutine + ticker）天然适合定时采集任务，内存占用低，编译为单一二进制部署简单。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;技术栈&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;语言&lt;/strong&gt;：Go 1.21+&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Redis 客户端&lt;/strong&gt;：github.com/redis/go-redis/v9（高性能，原生支持 Pipeline）&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;MySQL 客户端&lt;/strong&gt;：github.com/go-sql-driver/mysql + database/sql（标准库连接池）&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;日志&lt;/strong&gt;：Go 标准库 log/slog（Go 1.21+ 内置结构化日志）&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;配置管理&lt;/strong&gt;：硬编码 config 结构体（项目简单，无需配置文件解析库）&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;部署环境&lt;/strong&gt;：Linux（直接运行二进制，nohup 或 systemd 管理）&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;构建&lt;/strong&gt;：Go Modules&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;架构设计&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://res2026.tiankonguse.com/images/2026/04/17/006.png&quot; alt=&quot;截图&quot; /&gt;&lt;/p&gt;

&lt;p&gt;跑了半个小时，就再次遇到热度值波动。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://res2026.tiankonguse.com/images/2026/04/17/007.png&quot; alt=&quot;截图&quot; /&gt;&lt;/p&gt;

&lt;p&gt;分析这一分钟 Redis 的流水，发现 Redis 数据是正常的。&lt;/p&gt;

&lt;p&gt;分析 Redis 实例，发现 Redis 开启了副本读，会不会是副本的数据比较旧呢？&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://res2026.tiankonguse.com/images/2026/04/17/008.png&quot; alt=&quot;截图&quot; /&gt;&lt;/p&gt;

&lt;p&gt;于是我又改造这个服务，改成每秒读取 N 次，看是否存在数据不一致。&lt;br /&gt;
最后发现 Redis 数据依旧是一致的。&lt;/p&gt;

&lt;h2 id=&quot;四缓存一致性监控系统&quot;&gt;四、缓存一致性监控系统&lt;/h2&gt;

&lt;p&gt;由于热度值监控系统是通过外网页面接口抓取的，所以无法获取到数据是从哪个缓存节点读取的。&lt;/p&gt;

&lt;p&gt;而这个缓存组件有一个 debug 开关，支持对指定来源流量，把链路的缓存节点信息也返回出去。&lt;/p&gt;

&lt;p&gt;于是我便开发了一个缓存一致性监控系统，打开 debug 开关，把缓存节点信息和数据都当做流水存到 DB 中。&lt;/p&gt;

&lt;p&gt;需求：一个 Golang 后台常驻程序，定时从 Union 接口拉取指定剧集 ID 的热度值数据，解析后存储到 MySQL 数据库，并自动清理过期数据。&lt;/p&gt;

&lt;p&gt;系统架构：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://res2026.tiankonguse.com/images/2026/04/17/009.png&quot; alt=&quot;截图&quot; /&gt;&lt;/p&gt;

&lt;p&gt;数据流：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://res2026.tiankonguse.com/images/2026/04/17/010.png&quot; alt=&quot;截图&quot; /&gt;&lt;/p&gt;

&lt;p&gt;可以发现，这个系统和上面的 Redis 监控系统非常类似，区别在于把下游从 Redis 换成了缓存中间件，并且存储的数据多了缓存节点信息。&lt;/p&gt;

&lt;p&gt;当热度值监控系统发现有不一致时，分析缓存一致性监控的流水，竟然没有发现数据跳变。&lt;/p&gt;

&lt;p&gt;这时候，我就开始怀疑是不是热度值监控系统自身的问题了。&lt;br /&gt;
一个是 Go 系统，一个是 Node 系统，理论上结果应该是一致的，很奇怪。&lt;/p&gt;

&lt;h2 id=&quot;五http-代理服务&quot;&gt;五、HTTP 代理服务&lt;/h2&gt;

&lt;p&gt;上一小节提到，热度值监控系统因为使用外网接口，导致无法获取缓存节点调试信息。&lt;br /&gt;
那就有必要开发一个 HTTP 代理服务来代替外网接口，协议保持一致，从而可以返回调试信息。&lt;/p&gt;

&lt;p&gt;需求：一个轻量级的 HTTP 网关服务，将 HTTP 请求转换为 UnionPlus 内部 RPC 调用，实现对视频/内容热度值数据的批量查询。&lt;/p&gt;

&lt;p&gt;架构设计：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://res2026.tiankonguse.com/images/2026/04/17/011.png&quot; alt=&quot;截图&quot; /&gt;&lt;/p&gt;

&lt;p&gt;随后修改热度值监控系统，把返回的缓存节点信息也存下来。&lt;/p&gt;

&lt;p&gt;当再次发生返回旧数据的 case 后，分析流水，发现返回旧数据的那个容器在数据变化后的这段时间里从来没被访问过。&lt;br /&gt;
也就是说，对于同一个容器，数据一旦变化，确实都返回新数据了。&lt;/p&gt;

&lt;p&gt;那为什么偶尔一个节点在数据更新若干秒后，还会返回旧数据呢？&lt;br /&gt;
这时候，只有一个答案了：缓存节点确实返回了旧数据。&lt;/p&gt;

&lt;p&gt;什么场景会发生呢？&lt;br /&gt;
其实，当确定缓存节点返回旧数据时我就想到答案了。&lt;/p&gt;

&lt;p&gt;缓存系统在数据更新时，为了避免热 Key 的回源量太高，都会做一个 singleflight 功能。&lt;br /&gt;
singleflight 的含义是只让一个请求去回源。&lt;/p&gt;

&lt;p&gt;对于其他请求，目前缓存中间件的策略是直接使用旧数据。&lt;br /&gt;
也正是这个策略，有概率使得访问量不大的剧集热度值返回旧数据。&lt;/p&gt;

&lt;h2 id=&quot;六union-压测系统&quot;&gt;六、Union 压测系统&lt;/h2&gt;

&lt;p&gt;上一小节提到，热度值返回旧数据的原因猜测是 singleflight 导致的。&lt;br /&gt;
触发场景是对应的剧集访问量太小，缓存节点持续很久都没有被访问，突然来的访问又存在并发，从而触发了 singleflight。&lt;/p&gt;

&lt;p&gt;针对这个猜测，验证手段就是进行压测，看增加流量后是否还有这个问题。&lt;br /&gt;
于是，我又开发了一个 Union 压测系统。&lt;/p&gt;

&lt;p&gt;功能：batch_union 是一个基于 tRPC-Go 框架的 Union 数据平台批量压测工具。&lt;br /&gt;
它按照指定的 QPS（每秒请求数）持续向 UnionPlus 服务发起批量查询请求，用于对 Union 读取链路进行压力测试和稳定性验证。&lt;/p&gt;

&lt;p&gt;执行流程：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://res2026.tiankonguse.com/images/2026/04/17/013.png&quot; alt=&quot;截图&quot; /&gt;&lt;/p&gt;

&lt;p&gt;架构设计如下：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://res2026.tiankonguse.com/images/2026/04/17/012.png&quot; alt=&quot;截图&quot; /&gt;&lt;/p&gt;

&lt;p&gt;几分钟这个压测系统就写好了。&lt;br /&gt;
然后进行压测，发现热度值确实稳定多了。&lt;/p&gt;

&lt;p&gt;这个是压测前的监控：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://res2026.tiankonguse.com/images/2026/04/17/014.png&quot; alt=&quot;截图&quot; /&gt;&lt;/p&gt;

&lt;p&gt;下面是压测后的监控，跳回旧数据的波动明显小了很多。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://res2026.tiankonguse.com/images/2026/04/17/015.png&quot; alt=&quot;截图&quot; /&gt;&lt;/p&gt;

&lt;p&gt;为何还是会偶尔有波动呢？&lt;br /&gt;
针对这些 case 去分析流水，发现是热度值统计错误。&lt;br /&gt;
在数据变化的时候，偶尔会丢失几秒的数据，从而导致系统误判为波动。&lt;/p&gt;

&lt;h2 id=&quot;七go-重构-node&quot;&gt;七、Go 重构 Node&lt;/h2&gt;

&lt;p&gt;上一小节提到，系统偶尔会丢失数据，从而导致误判波动值。&lt;/p&gt;

&lt;p&gt;为啥会丢失数据呢？&lt;br /&gt;
那几秒 Node 刚好在定时压缩数据，负载跑得比较高。&lt;br /&gt;
看来跑数据还是不能使用 Node，使用高性能编程语言才是最优解。&lt;/p&gt;

&lt;p&gt;需求：将 HotVal 热度值监控系统中的数据采集模块从 Node.js Server 提取出来，使用 Golang 重新实现为独立的数据服务。&lt;br /&gt;
该服务负责数据采集、变化检测、数据压缩、数据修复、统计计算以及剧集管理 API。&lt;br /&gt;
同时对 Node.js Server 进行瘦身，使其仅保留前端页面服务和只读数据库查询，写操作代理转发到 Go 数据服务。&lt;/p&gt;

&lt;p&gt;架构演进：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://res2026.tiankonguse.com/images/2026/04/17/017.png&quot; alt=&quot;截图&quot; /&gt;&lt;/p&gt;

&lt;p&gt;系统架构：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://res2026.tiankonguse.com/images/2026/04/17/016.png&quot; alt=&quot;截图&quot; /&gt;&lt;/p&gt;

&lt;p&gt;优化效果：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://res2026.tiankonguse.com/images/2026/04/17/018.png&quot; alt=&quot;截图&quot; /&gt;&lt;/p&gt;

&lt;p&gt;优化后，问题出现的概率确实进一步降低了，但由于增大流量只是降低概率，还是偶尔会发生一次。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://res2026.tiankonguse.com/images/2026/04/17/019.png&quot; alt=&quot;截图&quot; /&gt;&lt;/p&gt;

&lt;p&gt;针对这个问题，我之前其实有相关的优化规划。&lt;/p&gt;

&lt;p&gt;产生旧数据的原因是相同 Key 并发过期时，只有一个请求下去更新。&lt;br /&gt;
如果其他使用旧数据的请求还有其他字段也需要去下游拉取最新数据，那么这些请求在回包之前，对于命中 singleflight 的字段可以再读一次缓存，就有很大的概率读到最新的数据。&lt;/p&gt;

&lt;p&gt;当然，这个优化也只是进一步降低概率，但再读一次共享缓存的成本非常低，为何不再读一次呢。&lt;/p&gt;

&lt;h2 id=&quot;八最后&quot;&gt;八、最后&lt;/h2&gt;

&lt;p&gt;&lt;img src=&quot;https://res2026.tiankonguse.com/images/2026/04/17/020.png&quot; alt=&quot;截图&quot; /&gt;&lt;/p&gt;

&lt;p&gt;这两周 vibe coding 又使用了几亿的 Tokens。&lt;br /&gt;
但是借助 vibe coding 确实大大提高了效率。&lt;/p&gt;

&lt;p&gt;如果是以前，写一个工具，半天就过去了。&lt;br /&gt;
工具稍微复杂点，一天甚至两天就过去了。&lt;/p&gt;

&lt;p&gt;而现在，有任何想法，马上就可以花十几分钟做出一个工具来，可以马上去验证想法了。&lt;/p&gt;

&lt;p&gt;部分场景下，效率提升确实是几十倍。&lt;/p&gt;

&lt;p&gt;《完》&lt;/p&gt;

&lt;p&gt;-EOF-&lt;/p&gt;

&lt;p&gt;本文公众号：天空的代码世界&lt;br /&gt;
个人微信号：tiankonguse&lt;br /&gt;
公众号ID：tiankonguse-code&lt;/p&gt;
</content>
     <summary>Node 性能太差了，Go 太香了。</summary>
     <category term="程序人生" scheme="https://github.tiankonguse.com/categories.html#程序人生-category-ref"/>
     <tag term="项目实践" scheme="https://github.tiankonguse.com/tags.html#项目实践-tag-ref"/>
   </entry>
   
 
</feed>