<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>Wh1isper&#39;s Blog</title>
  
  <subtitle>Wh1isper&#39;s Blog</subtitle>
  <link href="https://blog.wh1isper.top/atom.xml" rel="self"/>
  
  <link href="https://blog.wh1isper.top/"/>
  <updated>2026-03-23T06:42:51.491Z</updated>
  <id>https://blog.wh1isper.top/</id>
  
  <author>
    <name>Wh1isper</name>
    
  </author>
  
  <generator uri="https://hexo.io/">Hexo</generator>
  
  <entry>
    <title>零散的想法，无AI</title>
    <link href="https://blog.wh1isper.top/2026/03/15/2026-03-16-some-thoughts/"/>
    <id>https://blog.wh1isper.top/2026/03/15/2026-03-16-some-thoughts/</id>
    <published>2026-03-15T16:00:00.000Z</published>
    <updated>2026-03-23T06:42:51.491Z</updated>
    
    <content type="html"><![CDATA[<p>事情起源于我在思考现在应该考察人们什么能力，来表明他在未来有可能胜任我目标中的工作形式。</p><p>在很早以前，人们只要有Know What就可以找到一份工作，比如你知道CPU的原理，那么你应该可以加入仙童半导体来设计CPU、针对80386来编写汇编程序；后来技术不断进步，计算机领域变得广大，互联网容纳了大量的就业人口——因为需要这么多劳动力进行代码生产，而代码运行的边际效应递减，成为了新时代的金矿。在这个时候，人们需要Know How来获得一份工作，于是出现了各种各样的面经和面试框架，来让人们孔雀开屏一样展示自己的“能力”。</p><p>现在，由于知识获取的门槛大幅度降低，考察Know How已经意义不大了，至少应用领域的程序员如此，我相信不久之后（或许现在已经如此），系统领域的程序员也会有类似的感觉。于是我把视线转向了Know Why：重点不是你知道做法，而是你知道为什么这么做，其中的技术演进和取舍是什么，以及最重要的，这其中你自己的实践与思考是什么。</p><p>这是因为，我觉得我们通过LLM已经逐渐找到了一种能够更具效率地进行生产活动的方式，也就是新的生产工具，我反对声称人们是珍妮纺纱机的操作员，因为情况大不相同，真正珍妮纺纱机的操作员——那些对着纸带打字的人，早就已经被淘汰了，目前的程序员群体，应当是一群实验科学家，他们拥有自我改善工具的能力，同时使用工具进行生产活动，而在此之前，由于个人的劳动力有限，所以组织形式更倾向于劳动密集地去做一些事，比如华为大兵团、字节快节奏+拉会，以<strong>降低集体智商</strong>为代价，工业化地生产代码来获得利润，字节是最后的赢家，因为他们发现了一件事：<strong>集体智商的提高虽然增加成本，但如果能有效规模化，那么其增加的利润更加客观，因为其中的摩擦降低了</strong>，于是他们做出了大家吹嘘的“算法导向”、“数据导向”，在我看来，这只是一种为了让高智商的人能够在一个固化的地方做狗屁工作，但更具规模效应，从而产生更多利润的组织方式。</p><p>但LLM有迹象改变这件事，程序员现在可以以更快地方式构建工具，而构建的工具自身可以帮助程序员继续改进工具。于是乎，整个事情不再是以某种方式组织生产，而是在生产力大规模进步的情况下，以某种方式组织集体智商。用一个漏洞百出的公式解释这个问题：$\text{产出} &#x3D; \text{人数} \times \text{集体智商} \times (1 - \text{通信损失比})$，这个公式中，人数一定会增长随着企业壮大而增长，而企业一直在解决的问题就是，人数增长的情况下，集体能不能不变成弱智，通信损失能不能更少，在这个过程中边际效应递减，最终企业成长到稳定的规模后停滞。LLM的改变在于，这个公式变成了$\text{产出} &#x3D; \text{人数} \times \text{AI增益比} \times \text{集体智商} \times (1 - \text{通信损失比})$，有可能的情况是，在人数不进行大规模扩张的情况下，通过组织架构的优势，优化集体智商、AI增益，减少通信损失，来对抗边际效应递减的问题，走出所谓的平方增长曲线。其手段包括但不限于：更扁平的组织、更少的管理和工作化生产手段、更多鼓励探索和协作，最后人们有可能自主地拓宽边界，或许这是一种“反赛博朋克”。</p><p>总结来看，从现在往后的工程师，可能在技术之外还需要一种“领导力”，不是因为经济活动扩张组织架构，需要指导更多的牛马干活的字节3-1，而是在新型组织架构下，如何与不同知识背景的人共事，共同控制AI一起进行更高效的经济活动的能力。而技术活动，更多的是基于人机协作地增强自己的认知，来服务于前面新型组织对于人类智慧、沟通与协作的要求。</p>]]></content>
    
    
    <summary type="html">关于AI时代工程师素质、人应该做什么样的工作、以及社会发展的一些零散想法</summary>
    
    
    
    <category term="随笔" scheme="https://blog.wh1isper.top/categories/%E9%9A%8F%E7%AC%94/"/>
    
    
    <category term="随笔" scheme="https://blog.wh1isper.top/tags/%E9%9A%8F%E7%AC%94/"/>
    
  </entry>
  
  <entry>
    <title>AI Coding is a framework, and...</title>
    <link href="https://blog.wh1isper.top/2026/03/12/2026-03-13-ai-coding-is-a-framework/"/>
    <id>https://blog.wh1isper.top/2026/03/12/2026-03-13-ai-coding-is-a-framework/</id>
    <published>2026-03-12T16:00:00.000Z</published>
    <updated>2026-03-23T06:42:51.491Z</updated>
    
    <content type="html"><![CDATA[<p>piglei在<a href="https://www.piglei.com/articles/en-ai-coding-is-a-framework/">AI Coding Is a “Framework”</a>中提出了一个精准的观察：AI Coding本质上是一个框架，具有框架的所有固有问题 – 抽象泄漏和控制权丧失。他进一步建议我们应当以library而非framework的心态来使用AI，保持对程序结构的控制权。</p><p>这个「框架」视角值得继续展开。除了「怎么用」之外，还有一个问题：如果AI Coding确实是一个框架，这个事实本身意味着什么？</p><p>人们在使用AI编程的时候，实际上是在用自然语言调用一个框架。观察一下AI Coding的使用方式：你描述意图（自然语言API），提供上下文（配置），AI根据内部机制生成代码（框架行为），你review输出（验收）。这和调用Spring、React、Rails的模式是同构的 – 区别只是接口从编程语言变成了自然语言。</p><p>一旦接受这个前提，很多关于AI编程的困惑就有了框架理论下的自然解释。</p><h2 id="框架的铁律：抽象必然泄漏"><a href="#框架的铁律：抽象必然泄漏" class="headerlink" title="框架的铁律：抽象必然泄漏"></a>框架的铁律：抽象必然泄漏</h2><p>Joel Spolsky在2002年提出的抽象泄漏定律(The Law of Leaky Abstractions)指出：所有非平凡的抽象都会在某种程度上泄漏。SQL隐藏了查询计划，但你迟早要理解索引。React隐藏了DOM操作，但你迟早要理解reconciliation。Spring隐藏了对象生命周期，但你迟早要理解Bean的作用域和循环依赖。</p><p>AI Coding也不例外。它隐藏了”把设计变成代码”的过程，但这个抽象会泄漏。而且它的泄漏方式比传统框架更棘手：</p><p><strong>传统框架泄漏时，你有trace。</strong> Spring抛异常时有完整的stack trace，React的DevTools能让你看到组件树和渲染周期。你知道泄漏发生了，也知道去哪里找原因。</p><p><strong>AI Coding泄漏时，你没有trace。</strong> 它生成一段看起来正确的代码，编译通过，基本功能正常，但在某个边界条件下会静默出错。你甚至不知道泄漏已经发生了 – 直到生产环境暴露问题。这种概率性的、无信号的泄漏，比传统框架的确定性错误更难应对。</p><p>想做好饭，有了现代厨具（烤箱、空气炸锅、微波炉）以后，入门门槛降低了，但想做出真正好的菜，最终还是要理解美拉德反应、水分蒸发速率、蛋白质变性温度。厨具降低了操作门槛，没有降低原理门槛。框架也一样。</p><h2 id="框架的API：自然语言"><a href="#框架的API：自然语言" class="headerlink" title="框架的API：自然语言"></a>框架的API：自然语言</h2><p>传统框架的API有类型签名、参数校验、返回值定义。你调错了会编译失败或抛异常。</p><p>自然语言作为API没有这些约束。同一句话在不同context下可能触发完全不同的行为。没有类型检查告诉你”你的需求描述缺少了关键参数”，没有编译器提醒你”这个意图是歧义的”。</p><p>这意味着<strong>使用AI Coding这个框架的调试成本,集中在输入侧而非输出侧</strong>。传统编程中bug出在代码里，你调试代码。AI编程中”bug”经常出在你的描述里 – 你以为说清楚了，其实有三种合理的解读，AI选了你没想到的那种。</p><p>Context engineering、AGENTS.md、rules文件 – 这些实践的本质是在为一个没有类型系统的API补充约束。它们是自然语言框架下的”类型声明”。</p><h2 id="框架的使用效率不对称"><a href="#框架的使用效率不对称" class="headerlink" title="框架的使用效率不对称"></a>框架的使用效率不对称</h2><p>作为生产力工具来看,AI Coding有一个反直觉的特征：它帮助强者更多,帮助弱者更少。</p><p>大多数生产力工具是反过来的。拼写检查对拼写差的人帮助更大。代码格式化工具对所有人效果差不多。这些工具的共同特征是<strong>缩小差距</strong>。</p><p>AI Coding则<strong>放大差距</strong>。有经验的开发者能快速分解任务、设定清晰的约束、评估生成代码的质量、在泄漏发生时诊断问题。他们把AI当作一个被良好配置的框架来使用，获得5-10倍的效率提升。经验较少的开发者缺乏这些能力，往往在”生成-不对-重试”的循环中消耗大量时间，效率提升远小于预期。</p><p>这恰恰是框架的行为模式，而非工具的行为模式。熟悉Spring原理的人用Spring如虎添翼，不熟悉的人被Spring折磨。这个规律在AI Coding上完全成立。</p><p>以局部、定义良好的任务来看，AI Coding确实表现为生产力工具 – “把这个函数从回调改成async&#x2F;await”、”给这个接口写单元测试”，这些任务规约完整，输出容易验证，谁用谁快。但在系统级任务上 – “重构这个模块的错误处理”、”设计一个消息消费者的重试策略” – 规约天然不完整，输出的正确性依赖领域知识来判断。此时AI就是一个框架，使用者的水平直接决定产出的质量。</p><p>分界线在于：<strong>任务的规约是否完整。</strong> 规约完整时是工具，规约不完整时是框架。而大多数有价值的工作，规约天然是不完整的。</p><h2 id="“一句话做产品”：过度抽象"><a href="#“一句话做产品”：过度抽象" class="headerlink" title="“一句话做产品”：过度抽象"></a>“一句话做产品”：过度抽象</h2><p>“一句话做产品”是框架思维下的自然推论走到了极端：如果AI是一个可以用自然语言调用的框架，那能不能只用一句话调用一次，就得到一个完整产品？</p><p>不能。原因不是模型能力不够，而是<strong>抽象层级过高，泄漏是必然的</strong>。</p><p>产品不是由一句话定义的，而是由上万个微决策构成的。”任务逾期了怎么通知”、”用户删除数据后保留多久”、”并发编辑时冲突怎么解决”、”移动端和桌面端的交互差异”。这些不是实现细节，而是产品本身。每一个未被显式规约的决策，框架都会用默认行为填充 – 而默认行为约等于”AI对这个问题的平均想象”。</p><p>这就像你不可能用一行配置文件驱动Spring做出一个完整的业务系统。不是Spring不够强，而是那些业务规则无法被压缩进一行配置。函数签名容纳不了那么多信息。</p><p>“一句话做产品”的尝试者通常会进入一个循环：生成 -&gt; 不是我想要的 -&gt; 补充描述 -&gt; 还不对 -&gt; 再补充 -&gt; … 这个循环的本质是在做需求分析 – 把模糊的意图逐步精确化为规约。他们没有跳过产品设计，只是换了一种方式在做产品设计，而且是一种效率很低的方式，因为自然语言没有类型系统来帮助他们发现规约中的遗漏。</p><h2 id="复杂度守恒"><a href="#复杂度守恒" class="headerlink" title="复杂度守恒"></a>复杂度守恒</h2><p>框架不消灭复杂度，只搬运复杂度。这是软件工程几十年来反复验证的规律：</p><ul><li>汇编到高级语言：消灭了手动内存管理的复杂度，搬到了理解垃圾回收和运行时行为上</li><li>SQL：消灭了手写查询计划的复杂度，搬到了理解索引设计和查询优化上</li><li>No-code平台：消灭了写代码的复杂度，搬到了平台约束和定制化困难上</li></ul><p>AI Coding：消灭了”把设计变成代码”的复杂度，搬到了”规约是否精确”和”输出是否正确”的判断上。</p><p>每一代工具都把复杂度从操作层搬到了认知层。以前你需要能写代码（操作），现在你需要能判断代码对不对、设计合不合理（认知）。操作可以被工具替代，认知很难。</p><p>这也解释了为什么AI Coding作为框架是不对称的：操作能力的差距被工具抹平了，但认知能力的差距被暴露出来了。以前你可以通过”写出能跑的代码”来证明自己的价值，现在这一步几乎免费了，你需要通过”判断代码是否正确、设计是否合理、系统是否可靠”来证明价值 – 这些东西以前被实现成本掩盖着，现在无处可藏。</p><h2 id="And…"><a href="#And…" class="headerlink" title="And…"></a>And…</h2><p>回到标题。AI Coding is a framework, and:</p><p><strong>…抽象必然泄漏。</strong> 泄漏时你需要能接住，这需要理解抽象之下的东西。</p><p><strong>…框架放大使用者之间的差距。</strong> 善用框架需要比框架知道的更多，这在所有框架上都成立。</p><p><strong>…一句话做不了产品。</strong> 因为产品是上万个微决策的集合，一次函数调用容纳不了这些信息。</p><p><strong>…复杂度不会消失，只会搬家。</strong> 从操作层搬到认知层，从实现搬到规约和判断。</p><p><strong>…理解原理变得更重要，而不是更不重要。</strong> 当实现成本趋近于零时，瓶颈暴露在设计和验证上。这些能力的根基是对系统行为、失败模式、抽象边界的理解 – 即原理。</p><p>程序员不需要恐惧AI Coding这个框架，就像不需要恐惧Spring或React一样。需要做的是理解它的工作方式、边界在哪里、什么时候会泄漏、泄漏时怎么办。这是使用任何框架的基本功。</p><p>而理解一个框架最好的方式，从来都是自己造一个。构建自己的Agent SDK、Agent CLI，哪怕只是一个简单的tool calling loop，都会让你对context window的限制、tool dispatch的成本、prompt与行为之间的映射关系有切身的体感。这种体感是任何文档和教程都给不了的。</p><p>这也是我在做<a href="https://github.com/wh1isper/ya-agent-sdk">ya-agent-sdk</a>时的感受：当你亲手处理过context截断、subagent协调、tool权限控制这些问题之后，再回到使用侧，你对框架泄漏的嗅觉会完全不同。不是因为你变聪明了，而是因为你见过抽象之下的东西。</p>]]></content>
    
    
    <summary type="html">从框架的视角审视AI编程：自然语言即API，抽象必然泄漏，复杂度只能搬运不能消灭，以及这对程序员意味着什么</summary>
    
    
    
    <category term="随笔" scheme="https://blog.wh1isper.top/categories/%E9%9A%8F%E7%AC%94/"/>
    
    
    <category term="AI" scheme="https://blog.wh1isper.top/tags/AI/"/>
    
    <category term="LLM" scheme="https://blog.wh1isper.top/tags/LLM/"/>
    
    <category term="AGI" scheme="https://blog.wh1isper.top/tags/AGI/"/>
    
  </entry>
  
  <entry>
    <title>Agent 工具代理：不改变工具列表的 Tool Search</title>
    <link href="https://blog.wh1isper.top/2026/03/12/2026-03-12-tool-proxy-for-agents/"/>
    <id>https://blog.wh1isper.top/2026/03/12/2026-03-12-tool-proxy-for-agents/</id>
    <published>2026-03-12T11:00:00.000Z</published>
    <updated>2026-03-23T06:42:51.491Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>上一篇<a href="/2026/03/06/tool-search-for-agents/">《Agent 工具发现：从上下文膨胀到按需加载》</a>介绍了 ToolSearchToolSet——通过搜索发现并动态加载工具到模型的工具列表中。方案解决了上下文膨胀问题，但引入了一个新的工程约束：每次加载新工具都会改变工具列表，导致 Prompt Cache 失效。本文讨论这个问题的成因和一种不改变工具列表的替代方案。</p></blockquote><h1 id="问题回顾：ToolSearchToolSet-做了什么"><a href="#问题回顾：ToolSearchToolSet-做了什么" class="headerlink" title="问题回顾：ToolSearchToolSet 做了什么"></a>问题回顾：ToolSearchToolSet 做了什么</h1><p>先简要回顾上一篇的方案。ToolSearchToolSet 的核心逻辑是：</p><ol><li>初始状态下，模型只看到 <code>tool_search</code> 这一个搜索工具。</li><li>模型调用 <code>tool_search</code> 发现目标工具后，SDK 将其加入工具列表。</li><li>下一轮对话时，模型直接看到并调用新工具。</li></ol><p>这个方案的优点是模型通过原生工具调用（native tool call）与工具交互，调用体验和普通工具一致。但”动态加入工具列表”这一步带来了一个成本问题。</p><h1 id="Prompt-Cache-的工作原理"><a href="#Prompt-Cache-的工作原理" class="headerlink" title="Prompt Cache 的工作原理"></a>Prompt Cache 的工作原理</h1><p>理解问题之前，需要先了解 Prompt Cache 的机制。</p><p>Anthropic 和 OpenAI 都提供了 Prompt Cache 能力。核心思路是：当连续的 API 请求共享相同的前缀内容时，服务端可以缓存这部分内容的 KV Cache，后续请求跳过前缀的重新计算，降低延迟和成本。</p><p>对于 Anthropic Claude，缓存的输入 token 费用是标准价格的 10%；对于 OpenAI，缓存命中时输入 token 费用减半。对于长上下文的 Agent 应用来说，这是一个显著的成本差异。</p><p>缓存命中的前提是<strong>前缀完全一致</strong>。以 Anthropic Claude 为例，一次 API 请求的前缀结构是：</p><figure class="highlight css"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs css"><span class="hljs-selector-attr">[Tool Definitions]</span> + <span class="hljs-selector-attr">[System Prompt]</span> + <span class="hljs-selector-attr">[Conversation History...]</span><br></code></pre></td></tr></table></figure><p>工具定义排在最前面。这意味着工具列表一旦发生变化——哪怕只是新增了一个工具——从变化点开始，System Prompt 和整个对话历史的缓存全部失效。</p><h1 id="动态工具列表为什么破坏缓存"><a href="#动态工具列表为什么破坏缓存" class="headerlink" title="动态工具列表为什么破坏缓存"></a>动态工具列表为什么破坏缓存</h1><p>ToolSearchToolSet 的工作流程中，工具列表在运行时变化：</p><pre><code class=" mermaid">sequenceDiagram    participant App as Application    participant API as LLM API    Note over App,API: Turn 1: tools = [tool_search]    App-&gt;&gt;API: Request with 1 tool defined    API--&gt;&gt;App: call tool_search, query=github    Note over App,API: Turn 2: tools = [tool_search, create_issue, list_repos, ...]    App-&gt;&gt;API: Request with N tools defined    Note right of API: Tool list changed, cache miss from tool defs onward    API--&gt;&gt;App: call create_issue</code></pre><p>Turn 1 的请求缓存了 <code>[System Prompt] + [tool_search definition]</code> 这段前缀。Turn 2 的工具列表变成了 <code>[tool_search, create_issue, list_repos, ...]</code>，前缀从工具定义部分开始就不同了。之后对话历史中积累的所有 token 都无法命中缓存。</p><p>对于长对话的 Agent（比如一个编程 Agent 可能一次会话有 50-100 轮对话、100K+ token 上下文），每次工具发现都触发缓存失效，累积的成本开销不可忽略。</p><p>更具体地量化：假设一个 Agent 会话在发现工具后还有 80K token 的对话历史。如果使用 Anthropic Claude，缓存命中时这 80K token 的输入费用是标准价的 10%。缓存失效意味着每次请求都要按全价计算这 80K token。对于一个每天处理上千个会话的系统，差异会体现在账单上。</p><h1 id="ToolProxyToolset：固定工具列表的方案"><a href="#ToolProxyToolset：固定工具列表的方案" class="headerlink" title="ToolProxyToolset：固定工具列表的方案"></a>ToolProxyToolset：固定工具列表的方案</h1><p>解决思路直接：<strong>不改变工具列表</strong>。</p><p>ToolProxyToolset 把所有外部工具藏在两个固定的代理工具背后：</p><ul><li><strong><code>search_tools</code></strong>: 搜索可用工具，返回工具名称、描述和完整参数 Schema</li><li><strong><code>call_tool</code></strong>: 通过名称调用任意已发现的工具</li></ul><p>工具列表始终是 <code>[search_tools, call_tool]</code>，不会因为发现新工具而变化。</p><pre><code class=" mermaid">sequenceDiagram    participant Model    participant Proxy as ToolProxyToolset    participant MCP as MCP Server    Note over Model,Proxy: Tool list: [search_tools, call_tool] (fixed)    Model-&gt;&gt;Proxy: search_tools(query=&quot;github issues&quot;)    Proxy-&gt;&gt;Proxy: Keyword/Embedding search    Proxy--&gt;&gt;Model: XML with tool schemas    Model-&gt;&gt;Proxy: call_tool(name=&quot;create_issue&quot;, arguments=&#123;...&#125;)    Proxy-&gt;&gt;MCP: create_issue(&#123;repo: &quot;...&quot;, title: &quot;...&quot;&#125;)    MCP--&gt;&gt;Proxy: &#123;issue_url: &quot;...&quot;&#125;    Proxy--&gt;&gt;Model: &#123;issue_url: &quot;...&quot;&#125;    Note over Model,Proxy: Tool list still: [search_tools, call_tool]</code></pre><p>模型不再直接调用 <code>create_issue</code>，而是通过 <code>call_tool</code> 间接调用。工具定义前缀从始至终不变，Prompt Cache 始终命中。</p><h1 id="search-tools-的信息传递"><a href="#search-tools-的信息传递" class="headerlink" title="search_tools 的信息传递"></a>search_tools 的信息传递</h1><p>工具列表不变，意味着模型永远不会在工具定义中看到目标工具的 Schema。那模型怎么知道如何构造参数？</p><p>答案是 <code>search_tools</code> 的返回值中包含完整的参数 Schema。结果以 XML 格式返回：</p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><code class="hljs xml"><span class="hljs-tag">&lt;<span class="hljs-name">search-results</span> <span class="hljs-attr">query</span>=<span class="hljs-string">&quot;github issues&quot;</span> <span class="hljs-attr">count</span>=<span class="hljs-string">&quot;3&quot;</span>&gt;</span><br><span class="hljs-tag">&lt;<span class="hljs-name">tool</span> <span class="hljs-attr">name</span>=<span class="hljs-string">&quot;create_issue&quot;</span> <span class="hljs-attr">namespace</span>=<span class="hljs-string">&quot;github&quot;</span>&gt;</span><br>  <span class="hljs-tag">&lt;<span class="hljs-name">description</span>&gt;</span>Create a new issue in a repository<span class="hljs-tag">&lt;/<span class="hljs-name">description</span>&gt;</span><br>  <span class="hljs-tag">&lt;<span class="hljs-name">parameters</span>&gt;</span>&#123;&quot;type&quot;: &quot;object&quot;, &quot;properties&quot;: &#123;&quot;repo&quot;: &#123;&quot;type&quot;: &quot;string&quot;&#125;, &quot;title&quot;: &#123;&quot;type&quot;: &quot;string&quot;&#125;, &quot;body&quot;: &#123;&quot;type&quot;: &quot;string&quot;&#125;&#125;, &quot;required&quot;: [&quot;repo&quot;, &quot;title&quot;]&#125;<span class="hljs-tag">&lt;/<span class="hljs-name">parameters</span>&gt;</span><br><span class="hljs-tag">&lt;/<span class="hljs-name">tool</span>&gt;</span><br><span class="hljs-tag">&lt;<span class="hljs-name">tool</span> <span class="hljs-attr">name</span>=<span class="hljs-string">&quot;list_issues&quot;</span> <span class="hljs-attr">namespace</span>=<span class="hljs-string">&quot;github&quot;</span>&gt;</span><br>  <span class="hljs-tag">&lt;<span class="hljs-name">description</span>&gt;</span>List issues in a repository<span class="hljs-tag">&lt;/<span class="hljs-name">description</span>&gt;</span><br>  <span class="hljs-tag">&lt;<span class="hljs-name">parameters</span>&gt;</span>&#123;&quot;type&quot;: &quot;object&quot;, &quot;properties&quot;: &#123;&quot;repo&quot;: &#123;&quot;type&quot;: &quot;string&quot;&#125;, &quot;state&quot;: &#123;&quot;type&quot;: &quot;string&quot;, &quot;default&quot;: &quot;open&quot;&#125;&#125;, &quot;required&quot;: [&quot;repo&quot;]&#125;<span class="hljs-tag">&lt;/<span class="hljs-name">parameters</span>&gt;</span><br><span class="hljs-tag">&lt;/<span class="hljs-name">tool</span>&gt;</span><br><span class="hljs-tag">&lt;<span class="hljs-name">tool</span> <span class="hljs-attr">name</span>=<span class="hljs-string">&quot;close_issue&quot;</span> <span class="hljs-attr">namespace</span>=<span class="hljs-string">&quot;github&quot;</span>&gt;</span><br>  <span class="hljs-tag">&lt;<span class="hljs-name">description</span>&gt;</span>Close an existing issue<span class="hljs-tag">&lt;/<span class="hljs-name">description</span>&gt;</span><br>  <span class="hljs-tag">&lt;<span class="hljs-name">parameters</span>&gt;</span>&#123;&quot;type&quot;: &quot;object&quot;, &quot;properties&quot;: &#123;&quot;repo&quot;: &#123;&quot;type&quot;: &quot;string&quot;&#125;, &quot;issue_number&quot;: &#123;&quot;type&quot;: &quot;integer&quot;&#125;&#125;, &quot;required&quot;: [&quot;repo&quot;, &quot;issue_number&quot;]&#125;<span class="hljs-tag">&lt;/<span class="hljs-name">parameters</span>&gt;</span><br><span class="hljs-tag">&lt;/<span class="hljs-name">tool</span>&gt;</span><br><span class="hljs-tag">&lt;/<span class="hljs-name">search-results</span>&gt;</span><br></code></pre></td></tr></table></figure><p>模型从这段对话内容中读取 Schema，然后构造 <code>call_tool</code> 调用。Schema 信息在对话历史中而不在工具定义中——对话历史本身不会影响前缀缓存的有效性（对话历史追加在前缀之后）。</p><h1 id="错误处理与自修正"><a href="#错误处理与自修正" class="headerlink" title="错误处理与自修正"></a>错误处理与自修正</h1><p>原生工具调用时，参数校验由框架（pydantic-ai）自动完成，模型收到的是结构化的验证错误。ToolProxyToolset 中，工具调用通过 <code>call_tool</code> 代理，校验逻辑由代理层捕获并转换为 XML 格式的错误信息：</p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs xml"><span class="hljs-tag">&lt;<span class="hljs-name">tool-call-error</span> <span class="hljs-attr">tool</span>=<span class="hljs-string">&quot;create_issue&quot;</span>&gt;</span><br>  <span class="hljs-tag">&lt;<span class="hljs-name">message</span>&gt;</span>Missing required argument: repo<span class="hljs-tag">&lt;/<span class="hljs-name">message</span>&gt;</span><br>  <span class="hljs-tag">&lt;<span class="hljs-name">parameters</span>&gt;</span>&#123;&quot;type&quot;: &quot;object&quot;, &quot;properties&quot;: &#123;&quot;repo&quot;: &#123;&quot;type&quot;: &quot;string&quot;&#125;, &quot;title&quot;: &#123;&quot;type&quot;: &quot;string&quot;&#125;&#125;, &quot;required&quot;: [&quot;repo&quot;, &quot;title&quot;]&#125;<span class="hljs-tag">&lt;/<span class="hljs-name">parameters</span>&gt;</span><br><span class="hljs-tag">&lt;/<span class="hljs-name">tool-call-error</span>&gt;</span><br></code></pre></td></tr></table></figure><p>错误信息中附带完整的参数 Schema，模型可以据此修正参数并重试。实践表明，主流模型（Claude、GPT-4o）在看到这种结构化错误后的修正成功率与原生验证错误基本一致。</p><h1 id="跳过搜索直接调用"><a href="#跳过搜索直接调用" class="headerlink" title="跳过搜索直接调用"></a>跳过搜索直接调用</h1><p><code>call_tool</code> 不要求模型必须先搜索。如果模型已经知道工具名称和参数（比如从对话上下文、历史搜索结果、或 Session 恢复后的动态指令中获知），可以直接调用：</p><figure class="highlight reasonml"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs reasonml">call<span class="hljs-constructor">_tool(<span class="hljs-params">name</span>=<span class="hljs-string">&quot;create_issue&quot;</span>, <span class="hljs-params">arguments</span>=&#123;<span class="hljs-string">&quot;repo&quot;</span>: <span class="hljs-string">&quot;org/repo&quot;</span>, <span class="hljs-string">&quot;title&quot;</span>: <span class="hljs-string">&quot;Bug fix&quot;</span>&#125;)</span><br></code></pre></td></tr></table></figure><p>这避免了”搜索 -&gt; 调用”的强制两步流程。在 Session 恢复场景中，ToolProxyToolset 通过动态指令列出之前已发现的工具摘要，模型看到摘要后可以直接发起 <code>call_tool</code>，不需要重新搜索。</p><h1 id="与-ToolSearchToolSet-的权衡"><a href="#与-ToolSearchToolSet-的权衡" class="headerlink" title="与 ToolSearchToolSet 的权衡"></a>与 ToolSearchToolSet 的权衡</h1><p>两种方案并非简单的替代关系，而是不同约束下的不同选择。</p><pre><code class=" mermaid">flowchart LR    subgraph ToolSearchToolSet        A1[tool_search] --&gt; A2[Dynamic tool list]        A2 --&gt; A3[Native tool call]        A3 --&gt; A4[Cache invalidated on discovery]    end    subgraph ToolProxyToolset        B1[search_tools] --&gt; B2[Fixed tool list]        B2 --&gt; B3[Proxy via call_tool]        B3 --&gt; B4[Cache always stable]    end</code></pre><table><thead><tr><th>维度</th><th>ToolSearchToolSet</th><th>ToolProxyToolset</th></tr></thead><tbody><tr><td>工具列表</td><td>发现后动态增长</td><td>始终 2 个工具</td></tr><tr><td>Prompt Cache</td><td>发现时失效</td><td>始终稳定</td></tr><tr><td>模型调用方式</td><td>原生工具调用</td><td>通过 call_tool 代理</td></tr><tr><td>Schema 可见性</td><td>模型在工具定义中看到 Schema</td><td>模型从搜索结果文本中读取 Schema</td></tr><tr><td>参数校验</td><td>框架原生校验</td><td>代理层捕获，XML 错误反馈</td></tr><tr><td>模型认知负荷</td><td>低（标准工具调用）</td><td>较高（需理解代理模式）</td></tr><tr><td>状态存储</td><td>AgentContext</td><td>AgentContext（共享）</td></tr><tr><td>搜索策略</td><td>相同接口</td><td>相同接口</td></tr><tr><td>HITL</td><td>相同行为</td><td>相同行为（re-raise ApprovalRequired）</td></tr></tbody></table><p>核心权衡是 <strong>Cache 稳定性 vs. 模型交互简单性</strong>。</p><p>ToolSearchToolSet 对模型更友好——模型直接看到工具定义并原生调用，这是 LLM 训练时最常见的交互模式。ToolProxyToolset 要求模型理解”通过一个间接层调用工具”的模式，这对较弱的模型可能是个挑战。</p><p>但对于成本敏感的生产系统，ToolProxyToolset 的 Cache 稳定性带来的成本节省往往更重要。尤其是当 Agent 接入大量 MCP Server、单次会话上下文较长时，Cache 命中率的差异会直接体现在延迟和费用上。</p><h1 id="选择建议"><a href="#选择建议" class="headerlink" title="选择建议"></a>选择建议</h1><p>基于上述分析：</p><p><strong>选 ToolProxyToolset 的场景</strong>：</p><ul><li>MCP Server 数量多、工具总数大（30+）</li><li>单次会话上下文长（50K+ token）</li><li>成本敏感，需要最大化 Prompt Cache 命中率</li><li>使用的模型能力强（Claude Sonnet&#x2F;Opus、GPT-4o），能可靠处理代理调用模式</li></ul><p><strong>选 ToolSearchToolSet 的场景</strong>：</p><ul><li>工具数量适中（10-30），发现行为不频繁</li><li>使用较小的模型，代理模式可能导致调用错误率上升</li><li>会话较短，Cache 失效的成本影响有限</li><li>优先考虑调用可靠性而非成本优化</li></ul><p><strong>10 个以下工具</strong>：两种方案都不需要。直接全部加载到工具列表即可。</p><h1 id="实现细节"><a href="#实现细节" class="headerlink" title="实现细节"></a>实现细节</h1><p>ToolProxyToolset 复用了 ToolSearchToolSet 的基础设施：</p><ul><li><strong>SearchStrategy 接口</strong>：相同的 <code>KeywordSearchStrategy</code> 和 <code>EmbeddingSearchStrategy</code>，可互换。</li><li><strong>ToolMetadata</strong>：相同的工具元数据提取逻辑。</li><li><strong>AgentContext 状态字段</strong>：<code>tool_search_loaded_tools</code> 和 <code>tool_search_loaded_namespaces</code>，两种方案共享状态存储，甚至可以在两种方案之间切换而不丢失已发现的工具状态。</li><li><strong>Namespace &#x2F; Loose 加载</strong>：相同的原子加载语义。</li><li><strong>Optional Namespaces</strong>：相同的可选命名空间机制，适配不稳定的 MCP Server。</li></ul><p>新增的设计点：</p><ul><li><strong>动态指令（Dynamic Instructions）</strong>：ToolProxyToolset 生成包含命名空间列表、已发现工具摘要的动态指令，注入到 System Prompt 中。以 Anthropic 为例，Tool Definitions 排列在 System Prompt 之前，因此 System Prompt 的变化不会破坏工具定义部分的缓存前缀。（System Prompt 的变化会影响其之后的对话历史缓存，但工具发现通常发生在会话早期，后续轮次中指令保持稳定。）</li><li><strong>XML 格式的搜索结果和错误信息</strong>：选择 XML 而非 JSON，是因为 XML 的标签结构对模型的解析更友好，在对话上下文中的可读性也更好。</li></ul><h1 id="延伸：Code-Execution-方案——更激进的思路"><a href="#延伸：Code-Execution-方案——更激进的思路" class="headerlink" title="延伸：Code Execution 方案——更激进的思路"></a>延伸：Code Execution 方案——更激进的思路</h1><p>Anthopic 在 2025 年 11 月发表了一篇工程博客 <a href="https://www.anthropic.com/engineering/code-execution-with-mcp">Code execution with MCP</a>，提出了一个更激进的方案来解决同一类问题。</p><p>其核心思路是：不让模型通过工具调用（tool call）与 MCP 交互，而是让模型<strong>写代码</strong>来调用工具。MCP Server 的每个工具被映射为文件系统上的一个 TypeScript 函数，模型通过浏览文件系统发现工具定义，然后编写代码在沙盒中执行。</p><pre><code class=" mermaid">sequenceDiagram    participant Model    participant Sandbox as Code Sandbox    participant MCP as MCP Servers    Model-&gt;&gt;Sandbox: Write code to call gdrive.getDocument    Sandbox-&gt;&gt;MCP: getDocument documentId=abc123    MCP--&gt;&gt;Sandbox: Full document content, 10K words    Sandbox-&gt;&gt;Sandbox: Filter and transform data in code    Sandbox--&gt;&gt;Model: Processed summary, 50 words</code></pre><p>这个方案解决了 ToolProxyToolset 没有解决的一个问题：<strong>中间结果膨胀</strong>。</p><p>在 ToolProxyToolset 中，<code>call_tool</code> 的返回值仍然完整地回流到模型上下文。如果一个工具返回了 10K 行的表格数据，模型会看到全部内容。而在 Code Execution 方案中，模型可以在沙盒里执行 <code>filter</code>、<code>map</code>、<code>slice</code> 等操作，只把处理后的结果返回给自己。Anthropic 的文章举了一个例子：10,000 行表格经过代码过滤后，模型只看到 5 行。</p><p>此外，Code Execution 方案还支持<strong>组合调用</strong>——一段代码中可以串联多个工具调用，中间结果在沙盒内流转而不经过模型。这进一步减少了上下文消耗和模型推理轮次。</p><h2 id="ToolProxyToolset-与-Code-Execution-的对比"><a href="#ToolProxyToolset-与-Code-Execution-的对比" class="headerlink" title="ToolProxyToolset 与 Code Execution 的对比"></a>ToolProxyToolset 与 Code Execution 的对比</h2><table><thead><tr><th>维度</th><th>ToolProxyToolset</th><th>Code Execution</th></tr></thead><tbody><tr><td>中间层</td><td>结构化代理工具（call_tool）</td><td>代码执行沙盒</td></tr><tr><td>中间结果处理</td><td>完整回流到模型上下文</td><td>在沙盒中过滤后再返回</td></tr><tr><td>组合调用</td><td>一次调一个工具</td><td>一段代码调多个工具</td></tr><tr><td>基础设施要求</td><td>无（纯 SDK 层）</td><td>需要安全沙盒环境</td></tr><tr><td>关注的核心问题</td><td>Prompt Cache 稳定性</td><td>Token 效率（定义 + 中间结果）</td></tr><tr><td>模型能力要求</td><td>理解代理调用模式</td><td>能写正确的异步代码</td></tr><tr><td>安全性复杂度</td><td>低（工具调用有权限控制）</td><td>高（需要沙盒隔离、资源限制）</td></tr></tbody></table><p>两种方案并不互斥。ToolProxyToolset 解决的是工具定义层面的问题（定义膨胀、Cache 失效），Code Execution 解决的是执行层面的问题（中间结果膨胀、多步编排）。一个可能的组合方式是：用 ToolProxyToolset 做工具发现和 Schema 管理，用 Code Execution 做实际调用和数据处理。</p><p>但 Code Execution 方案的工程成本也更高。安全沙盒的构建和维护（资源限制、网络隔离、超时控制）是一个独立的基础设施问题。</p><h2 id="编程-Agent-已经是-Code-Execution-环境"><a href="#编程-Agent-已经是-Code-Execution-环境" class="headerlink" title="编程 Agent 已经是 Code Execution 环境"></a>编程 Agent 已经是 Code Execution 环境</h2><p>仔细审视 Anthropic 描述的 Code Execution 方案，会发现一个有趣的事实：它所构建的能力，编程 Agent 已经天然具备了。</p><p>把 Anthropic 方案的核心要素逐一对应：</p><table><thead><tr><th>Anthropic Code Execution</th><th>编程 Agent（如 yaacli）</th></tr></thead><tbody><tr><td>文件系统发现工具定义</td><td>Skills 目录 + 文件系统浏览</td></tr><tr><td>代码执行沙盒</td><td>Shell &#x2F; Bash 执行</td></tr><tr><td>中间结果在沙盒内过滤</td><td>Shell 管道 + 脚本处理</td></tr><tr><td>保存可复用函数（Skills）</td><td>Skills 系统（SKILL.md + 脚本）</td></tr><tr><td>状态持久化到文件</td><td>工作目录文件读写</td></tr></tbody></table><p>一个拥有 Shell 访问权限和 Skills 系统的编程 Agent，本身就是一个代码执行环境。它不需要额外构建沙盒来”将 MCP Server 呈现为代码 API”——它可以直接写一个脚本调用 MCP Server 的 CLI 工具，用管道过滤数据，把结果存到文件里供后续使用。这些都是 Shell 环境的原生能力。</p><p>这指向一个更深层的架构观察：<strong>Anthropic 的 Code Execution 方案本质上是在给 MCP 打补丁，试图让工具调用协议接近编程环境的表达能力。而编程 Agent 从一开始就选择了正确的抽象层次——模型就是一个有环境访问权限的程序员，工具交互只是它编程能力的自然延伸。</strong></p><p>MCP 作为工具协议，解决的是”如何标准化地连接工具”的问题。但当工具数量增长到需要 progressive disclosure、code execution、skills 等机制来管理时，问题的本质已经从”工具连接”变成了”环境交互”。在这个层面上，Shell + 文件系统 + Skills 是一个更自然、更成熟、更通用的抽象。</p><p>这不是说 MCP 没有价值。MCP 在标准化工具接口、跨语言互操作方面仍然重要。但对于编程 Agent 来说，与其在 MCP 上层叠加越来越多的补丁机制（Tool Search、Tool Proxy、Code Execution sandbox），不如直接利用 Agent 已有的编程能力来处理工具发现、数据过滤和多步编排。ToolProxyToolset 解决的 Prompt Cache 问题是一个纯粹的 LLM API 层面的工程约束，它和工具交互的抽象层次无关，所以仍然有独立存在的价值。</p><h1 id="写在最后"><a href="#写在最后" class="headerlink" title="写在最后"></a>写在最后</h1><p>ToolProxyToolset 的实现在 <a href="https://github.com/Wh1isper/ya-agent-sdk/pull/53">ya-agent-sdk PR #53</a>。yaacli（ya-agent-sdk 的 CLI 运行时）已经从 ToolSearchToolSet 切换到 ToolProxyToolset 作为默认的 MCP 工具管理方案。</p><p>两种方案不是互斥的。在同一个 SDK 中提供两种选择，开发者可以根据自己的模型能力、成本约束和工具规模选择合适的方案。这也是客户端实现工具管理的优势——选择权在开发者手中，而不是被厂商的 API 设计锁定。</p><p>关注本站 RSS，我会持续分享 Agent 构建过程中的架构思路和工程实践。</p>]]></content>
    
    
    <summary type="html">上一篇文章讨论了 Agent 工具发现的按需加载方案。本文聚焦其引入的新问题——动态工具列表导致 Prompt Cache 失效，介绍不改变工具列表的替代方案 ToolProxyToolset，并对比 Anthropic 的 Code Execution 思路。</summary>
    
    
    
    <category term="AI Engineering" scheme="https://blog.wh1isper.top/categories/AI-Engineering/"/>
    
    
    <category term="Agent" scheme="https://blog.wh1isper.top/tags/Agent/"/>
    
    <category term="MCP" scheme="https://blog.wh1isper.top/tags/MCP/"/>
    
    <category term="ToolSearch" scheme="https://blog.wh1isper.top/tags/ToolSearch/"/>
    
    <category term="SDK" scheme="https://blog.wh1isper.top/tags/SDK/"/>
    
    <category term="PromptCache" scheme="https://blog.wh1isper.top/tags/PromptCache/"/>
    
  </entry>
  
  <entry>
    <title>Agent 工具发现：从上下文膨胀到按需加载</title>
    <link href="https://blog.wh1isper.top/2026/03/06/2026-03-06-tool-search-for-agents/"/>
    <id>https://blog.wh1isper.top/2026/03/06/2026-03-06-tool-search-for-agents/</id>
    <published>2026-03-06T08:20:00.000Z</published>
    <updated>2026-03-23T06:42:51.491Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>当 Agent 接入 5 个 MCP Server、200+ 工具时，光工具定义就能吃掉 55K token。工具多了选不准，上下文还不够用。这篇文章聊聊各家怎么解决这个问题，以及如何在 SDK 层做到供应商无关。</p></blockquote><h1 id="问题：工具规模与上下文窗口的矛盾"><a href="#问题：工具规模与上下文窗口的矛盾" class="headerlink" title="问题：工具规模与上下文窗口的矛盾"></a>问题：工具规模与上下文窗口的矛盾</h1><p>Agent 应用从”聊天机器人”演进到”工具调用代理”之后，核心约束从”对话质量”转向”工具管理”。一个典型的企业级 Agent 可能同时接入 GitHub、Slack、Sentry、Grafana、数据库等多个服务，每个服务暴露 10-50 个工具函数。</p><p>这带来两个工程问题：</p><ol><li><strong>上下文膨胀</strong>。每个工具的 JSON Schema 定义占 200-500 token，200 个工具就是 40K-100K token。在 200K 上下文窗口的模型里，还没开始做事就用掉了一半预算。</li><li><strong>选择精度下降</strong>。实践表明，当可用工具数超过 30-50 个时，模型正确选中目标工具的概率显著下降。工具越多，噪声越大。</li></ol><p>这两个问题互相加强：工具多导致上下文长，上下文长又进一步稀释模型对单个工具描述的注意力。</p><p>解决思路是一致的：<strong>按需加载</strong>（on-demand loading）。不把所有工具定义塞进初始上下文，而是让模型在需要时通过搜索发现并加载工具。</p><h1 id="厂商方案：Anthropic-与-OpenAI-的-Tool-Search"><a href="#厂商方案：Anthropic-与-OpenAI-的-Tool-Search" class="headerlink" title="厂商方案：Anthropic 与 OpenAI 的 Tool Search"></a>厂商方案：Anthropic 与 OpenAI 的 Tool Search</h1><p>Anthropic 和 OpenAI 都在 2025 年推出了各自的 Tool Search 功能。它们的核心思路相同，但在实现细节和架构选择上有差异。</p><h2 id="共同的设计模式"><a href="#共同的设计模式" class="headerlink" title="共同的设计模式"></a>共同的设计模式</h2><p>两家方案共享一个基本流程：</p><pre><code class=" mermaid">sequenceDiagram    participant Client as Application    participant API as LLM API    participant Search as Tool Search    participant Model as LLM    Client-&gt;&gt;API: 请求（tools 含 tool_search + deferred tools）    API-&gt;&gt;Model: 仅注入 tool_search + 非 deferred 工具    Model-&gt;&gt;Search: 调用 tool_search（query）    Search--&gt;&gt;Model: 返回匹配的 3-5 个工具定义    Model-&gt;&gt;Client: 调用已发现的工具</code></pre><p>关键概念：</p><ul><li><strong>defer_loading</strong>: 标记工具为”延迟加载”。初始请求时模型看不到这些工具的完整定义。</li><li><strong>tool_search</strong>: 一个特殊的内置工具。模型通过调用它来搜索和加载延迟工具。</li><li><strong>tool_reference</strong>: 搜索结果的引用。API 自动将引用展开为完整的工具定义。</li></ul><h2 id="Anthropic-的方案"><a href="#Anthropic-的方案" class="headerlink" title="Anthropic 的方案"></a>Anthropic 的方案</h2><p>Anthropic 提供两种搜索变体：</p><table><thead><tr><th>变体</th><th>搜索方式</th><th>适用场景</th></tr></thead><tbody><tr><td><code>tool_search_tool_regex</code></td><td>模型构造 Python 正则表达式</td><td>精确匹配工具名</td></tr><tr><td><code>tool_search_tool_bm25</code></td><td>模型使用自然语言查询</td><td>语义模糊搜索</td></tr></tbody></table><p>搜索在服务端执行。客户端只需要在 tools 列表中声明 tool_search 工具和带 <code>defer_loading: true</code> 的工具定义，API 在一次请求内完成搜索、展开和调用。</p><p>Anthropic 同时支持 MCP 集成。可以对 MCP Server 的所有工具设置 <code>defer_loading</code> 默认值，并对个别工具单独覆盖。这意味着整个 MCP Server 可以作为一个延迟加载单元。</p><p>Anthropic 也支持客户端自行实现搜索逻辑：返回 <code>tool_reference</code> 类型的 content block 即可，API 会自动展开引用。</p><h2 id="OpenAI-的方案"><a href="#OpenAI-的方案" class="headerlink" title="OpenAI 的方案"></a>OpenAI 的方案</h2><p>OpenAI 的设计引入了两个额外概念：</p><p><strong>Namespace</strong>。工具可以按命名空间分组。模型初始只看到 namespace 的名称和描述，搜索时按 namespace 为单位加载。这比逐个工具加载更节省 token，也更符合”一个 MCP Server 对应一个 namespace”的直觉。OpenAI 建议每个 namespace 不超过 10 个工具。</p><p><strong>Hosted vs Client-executed</strong>。OpenAI 明确区分了两种执行模式：</p><ul><li><strong>Hosted</strong>: 工具定义全部在请求中声明，搜索由 OpenAI 服务端执行。模型在单次请求内完成搜索和调用。</li><li><strong>Client-executed</strong>: 模型输出 <code>tool_search_call</code>，客户端自行执行搜索逻辑，返回 <code>tool_search_output</code>。适用于工具库动态变化、依赖租户状态等场景。</li></ul><p>两种模式的区别在于搜索逻辑的控制权：Hosted 模式省事但不灵活，Client-executed 模式灵活但需要额外的请求轮次。</p><h2 id="厂商方案的局限"><a href="#厂商方案的局限" class="headerlink" title="厂商方案的局限"></a>厂商方案的局限</h2><p>两家方案都解决了上下文膨胀问题，但存在一个共同的结构性限制：<strong>供应商锁定</strong>。</p><ul><li>搜索协议是各家私有的。Anthropic 用 <code>server_tool_use</code> + <code>tool_search_tool_result</code>，OpenAI 用 <code>tool_search_call</code> + <code>tool_search_output</code>，两者不兼容。</li><li>工具定义的延迟加载标记（<code>defer_loading</code>）嵌入在各自的 API 参数中。</li><li>服务端搜索的索引算法不透明，无法定制。</li><li>如果你的 Agent 需要在 Anthropic 和 OpenAI 之间切换（或同时使用多个 provider），工具管理逻辑需要写两套。</li></ul><p>这个问题不仅存在于 Tool Search。Web Search、Code Interpreter、Computer Use 等”增强工具”都有类似的供应商耦合。每家厂商都在往 API 里塞越来越多的内置能力，而这些能力的接口互不兼容。</p><h1 id="SDK-方案：客户端实现-Tool-Search"><a href="#SDK-方案：客户端实现-Tool-Search" class="headerlink" title="SDK 方案：客户端实现 Tool Search"></a>SDK 方案：客户端实现 Tool Search</h1><p>另一条路线是在 SDK（客户端）层面实现 Tool Search，完全绕开厂商的服务端搜索。这是 <a href="https://github.com/Wh1isper/ya-agent-sdk">ya-agent-sdk</a> 的 <code>ToolSearchToolSet</code> 所采取的方式。</p><h2 id="基本思路"><a href="#基本思路" class="headerlink" title="基本思路"></a>基本思路</h2><p>不依赖任何厂商的 Tool Search API。把 <code>tool_search</code> 实现为一个普通的客户端工具函数，对模型而言它和 <code>read_file</code>、<code>run_shell</code> 没有区别。</p><pre><code class=" mermaid">sequenceDiagram    participant Model as LLM (any provider)    participant SDK as ToolSearchToolSet    participant Strategy as SearchStrategy    participant Toolsets as Wrapped Toolsets    Note over Model,SDK: Turn 1: 模型只看到 tool_search    Model-&gt;&gt;SDK: call tool_search(&#123;query: &quot;github issues&quot;&#125;)    SDK-&gt;&gt;Strategy: search(&quot;github issues&quot;, candidates)    Strategy--&gt;&gt;SDK: [github namespace matched]    SDK-&gt;&gt;SDK: 标记 github namespace 为已加载    SDK--&gt;&gt;Model: &quot;Found: [github] 3 tools loaded&quot;    Note over Model,SDK: Turn 2: 模型看到 tool_search + github 工具    Model-&gt;&gt;SDK: call create_issue(&#123;repo: &quot;...&quot;, title: &quot;...&quot;&#125;)    SDK-&gt;&gt;Toolsets: 转发到 github toolset    Toolsets--&gt;&gt;Model: &#123;issue_url: &quot;...&quot;&#125;</code></pre><p>核心差异：搜索发生在客户端进程内，不经过 LLM API 服务端。模型只是调用了一个返回文本结果的普通工具。下一轮对话时，SDK 的 <code>get_tools()</code> 方法动态返回已加载的工具列表。</p><h2 id="Namespace-与-Loose-加载"><a href="#Namespace-与-Loose-加载" class="headerlink" title="Namespace 与 Loose 加载"></a>Namespace 与 Loose 加载</h2><p>SDK 支持两种加载粒度：</p><table><thead><tr><th>类型</th><th>条件</th><th>行为</th></tr></thead><tbody><tr><td><strong>Namespace</strong></td><td>Toolset 设置了 <code>toolset_id</code></td><td>命中任意一个工具即加载整个 namespace 的所有工具</td></tr><tr><td><strong>Loose</strong></td><td>Toolset 未设置 <code>toolset_id</code></td><td>每个工具独立加载</td></tr></tbody></table><p>Namespace 加载对应的直觉是：一个 MCP Server 是一个原子单元。如果模型需要用 GitHub 的某个工具，大概率也需要同一 Server 下的其他工具。Loose 加载适用于零散的工具函数。</p><h2 id="可插拔的搜索策略"><a href="#可插拔的搜索策略" class="headerlink" title="可插拔的搜索策略"></a>可插拔的搜索策略</h2><p>搜索算法不是硬编码的。SDK 定义了 <code>SearchStrategy</code> 协议，内置两种实现：</p><p><strong>KeywordSearchStrategy</strong>（默认）。零外部依赖。对工具名称、描述、参数名做正则匹配，按命中位置加权打分。适合工具命名规范的场景。</p><p><strong>EmbeddingSearchStrategy</strong>。基于 FastEmbed 的本地向量搜索。把工具元数据编码为向量，用余弦相似度排序。适合自然语言查询和语义模糊匹配。依赖约 50MB 的 ONNX 模型，CPU 上单次查询约 5ms。</p><p>还有一个 <code>create_best_strategy()</code> 工厂函数：优先尝试 Embedding 策略，如果 fastembed 未安装则自动降级到 Keyword 策略。</p><p>开发者也可以实现自己的策略，比如基于远程向量数据库的搜索、基于 LLM 的重排序等。</p><h2 id="Session-Restore"><a href="#Session-Restore" class="headerlink" title="Session Restore"></a>Session Restore</h2><p>已加载的工具状态持久化在 <code>AgentContext</code> 中，通过 <code>ResumableState</code> 跨会话恢复。恢复会话时，之前加载过的工具和 namespace 立即可用，不需要重新搜索。</p><p>这意味着工具发现的结果可以跨越多轮对话甚至多次会话存活。</p><h1 id="三种方案的对比"><a href="#三种方案的对比" class="headerlink" title="三种方案的对比"></a>三种方案的对比</h1><table><thead><tr><th>维度</th><th>Anthropic Tool Search</th><th>OpenAI Tool Search</th><th>SDK ToolSearchToolSet</th></tr></thead><tbody><tr><td>搜索执行位置</td><td>服务端（支持客户端自定义）</td><td>服务端 &#x2F; 客户端</td><td>客户端</td></tr><tr><td>搜索算法</td><td>Regex &#x2F; BM25</td><td>不透明（Hosted）&#x2F; 自定义（Client）</td><td>Keyword &#x2F; Embedding &#x2F; 自定义</td></tr><tr><td>Namespace 支持</td><td>MCP toolset 级别</td><td>Namespace 原生支持</td><td>toolset_id 原子加载</td></tr><tr><td>模型绑定</td><td>Claude 系列</td><td>GPT 系列</td><td>任意模型</td></tr><tr><td>协议兼容</td><td>Anthropic Messages API</td><td>OpenAI Responses API</td><td>标准工具调用</td></tr><tr><td>Session 恢复</td><td>自动展开历史引用</td><td>Cache 友好设计</td><td>AgentContext 持久化</td></tr><tr><td>搜索算法可定制</td><td>否（客户端模式除外）</td><td>否（Hosted）&#x2F; 是（Client）</td><td>是</td></tr></tbody></table><h1 id="更广泛的问题：供应商锁定"><a href="#更广泛的问题：供应商锁定" class="headerlink" title="更广泛的问题：供应商锁定"></a>更广泛的问题：供应商锁定</h1><p>Tool Search 只是一个缩影。类似的供应商耦合模式出现在多个领域：</p><p><strong>Web Search</strong>。Anthropic 有内置的 web search tool，OpenAI 有 web search connector。两者的输入输出格式不同，搜索引擎不同，结果质量也不同。如果你在 SDK 层把 web search 实现为一个普通工具（调用 Google&#x2F;Bing&#x2F;SearxNG API），就完全不依赖厂商的内置能力。</p><p><strong>Code Interpreter</strong>。两家都提供沙盒代码执行环境，但执行环境的配置、可用库、超时策略都不同。SDK 方案是启动自己的沙盒容器（E2B、Docker 等）。</p><p><strong>Computer Use &#x2F; Browser</strong>。Anthropic 的 Computer Use 和 OpenAI 的 Computer Use 接口不兼容。SDK 方案是对接 Playwright 或 Browser MCP Server。</p><p><strong>MCP 本身</strong>。虽然 MCP 是一个开放协议，但两家厂商对 MCP 的集成方式不同——Anthropic 在 API 层支持 <code>mcp_servers</code> 参数直连，OpenAI 通过 Responses API 的 connector 机制接入。SDK 层面则通常直接管理 MCP Client 的生命周期。</p><p>这些案例指向一个工程决策：<strong>把供应商特有的能力转化为 SDK 层面的可替换实现</strong>。</p><pre><code class=" mermaid">flowchart TB    subgraph Vendor[&quot;Vendor-Specific (locked)&quot;]        A1[Anthropic Tool Search]        A2[OpenAI Tool Search]        A3[Anthropic Web Search]        A4[OpenAI Web Search]    end    subgraph SDK[&quot;SDK Layer (portable)&quot;]        B1[ToolSearchToolSet]        B2[WebSearchToolSet]        B3[BrowserToolSet]        B4[CodeExecToolSet]    end    subgraph Provider[&quot;Pluggable Providers&quot;]        C1[Anthropic API]        C2[OpenAI API]        C3[Local / OSS Model]    end    SDK --&gt; Provider    Vendor -.-&gt;|vendor coupling| Provider</code></pre><p>这不是说厂商的方案没有价值。服务端 Tool Search 省去了客户端的计算和索引维护开销，对小团队来说是合理的选择。但当你的系统需要：</p><ul><li>跨多个 LLM Provider 工作</li><li>定制搜索算法（比如租户级别的工具权限过滤）</li><li>在离线&#x2F;私有化部署环境运行</li><li>控制工具发现的确定性行为</li></ul><p>客户端实现就成为更稳健的架构选择。</p><h1 id="工具管理的实践建议"><a href="#工具管理的实践建议" class="headerlink" title="工具管理的实践建议"></a>工具管理的实践建议</h1><p>基于以上分析，给出几条工具管理的实践建议：</p><p><strong>1. 10 个工具以下不需要 Tool Search</strong>。工具少的时候，全部加载到上下文反而更简单，模型选择准确率也足够高。</p><p><strong>2. 高频工具常驻，低频工具按需加载</strong>。把 3-5 个核心工具（如文件读写、Shell 执行）直接注册，其余工具通过 Tool Search 按需发现。</p><p><strong>3. 按服务边界划分 Namespace</strong>。一个 MCP Server或一套工具集 对应一个 Namespace。Namespace 描述写清楚这组工具能做什么，不需要列举具体工具名。模型根据描述决定是否加载。</p><p><strong>4. 工具描述是搜索质量的关键</strong>。无论用哪种搜索策略，工具的 name 和 description 都是最重要的检索信号。命名用 <code>verb_noun</code> 格式（如 <code>create_issue</code>、<code>search_papers</code>），描述用一句话说明输入输出。</p><p><strong>5. 搜索策略按场景选择</strong>。工具命名规范、数量可控时，Keyword 策略就够了。工具来自多个第三方 MCP Server、命名不统一时，Embedding 策略更鲁棒。</p><p><strong>6. 评估供应商耦合的成本</strong>。如果你的产品只用一家 LLM Provider 且不打算换，用厂商的内置 Tool Search 完全合理。如果你在构建需要支持多 Provider 的 SDK 或平台，客户端实现是更安全的选择。</p><h1 id="写在最后"><a href="#写在最后" class="headerlink" title="写在最后"></a>写在最后</h1><p>上述实践来自 <a href="https://github.com/Wh1isper/ya-agent-sdk">ya-agent-sdk</a>（<a href="https://github.com/Wh1isper/ya-agent-sdk/pull/39">PR #39</a>），ya-agent-sdk是我积极维护的agent-sdk，用于原型创建和研究。构建于<a href="https://github.com/pydantic/pydantic-ai">Pydantic AI</a>之上，希望提供SOTA的上下文管理和开发体验。</p><p>关注本站 RSS，我会持续分享 Agent 构建过程中的架构思路和工程实践。</p>]]></content>
    
    
    <summary type="html">随着 MCP 等工具协议的普及，Agent 的工具库规模快速膨胀。本文对比 Anthropic、OpenAI 的 Tool Search 方案与开源 SDK 的客户端实现，讨论如何管理大规模工具集、实现动态发现，以及如何避免供应商锁定。</summary>
    
    
    
    <category term="AI Engineering" scheme="https://blog.wh1isper.top/categories/AI-Engineering/"/>
    
    
    <category term="Agent" scheme="https://blog.wh1isper.top/tags/Agent/"/>
    
    <category term="MCP" scheme="https://blog.wh1isper.top/tags/MCP/"/>
    
    <category term="ToolSearch" scheme="https://blog.wh1isper.top/tags/ToolSearch/"/>
    
    <category term="SDK" scheme="https://blog.wh1isper.top/tags/SDK/"/>
    
  </entry>
  
  <entry>
    <title>构建 Effective Cloud Agent</title>
    <link href="https://blog.wh1isper.top/2026/02/18/2026-02-18-cloud-agent-implementation-details/"/>
    <id>https://blog.wh1isper.top/2026/02/18/2026-02-18-cloud-agent-implementation-details/</id>
    <published>2026-02-18T02:00:00.000Z</published>
    <updated>2026-03-23T06:42:51.491Z</updated>
    
    <content type="html"><![CDATA[<h1 id="从SSE服务说起"><a href="#从SSE服务说起" class="headerlink" title="从SSE服务说起"></a>从SSE服务说起</h1><p>先从一个可运行的 Chatbot 服务开始：前端通过 <code>POST /chat</code> 发起请求，通过 <code>GET /stream/:conversation_id</code> 接收 <code>SSE</code> 事件流。<br>这里把职责拆成两层：</p><ul><li>Display Layer：向 UI 输出事件，服务展示与回放。</li><li>Business Layer：维护消息历史，服务 prompt 构造与推理。</li></ul><pre><code class=" mermaid">flowchart LR    UI[Web UI]    API[Chat Service]    LLM[LLM API]    DL[(Display Events)]    BL[(Business Messages)]    UI --&gt;|chat request| API    API --&gt;|sse stream| UI    API --&gt; BL    API --&gt; LLM    LLM --&gt; API    API --&gt; DL</code></pre><p>对应的最小存储模型：</p><ul><li><code>conversation(id, created_at)</code></li><li><code>message(id, conversation_id, role, content, created_at)</code></li></ul><p>单轮写路径：</p><ol><li>接收用户输入并落库 <code>role=user</code>。</li><li>读取最近 <code>N</code> 条消息构造 prompt。</li><li>调用 LLM，按 token 生成 <code>SSE</code> 事件并写入 Display Layer。</li><li>推理结束后落库 <code>role=assistant</code>。</li></ol><pre><code class=" mermaid">sequenceDiagram    participant U as User    participant API as Chat Service    participant DB as Message Store    participant LLM as LLM API    U-&gt;&gt;API: POST /chat    API-&gt;&gt;DB: insert user message    API-&gt;&gt;DB: load recent messages    API-&gt;&gt;LLM: inference    LLM--&gt;&gt;API: token stream    API--&gt;&gt;U: SSE events    API-&gt;&gt;DB: insert assistant message</code></pre><p>约束保持简单：单进程、单副本、不处理并发写冲突。</p><p>下一段再进入“引入文件系统后”的架构分叉：<code>stateless agent service + sandbox</code> vs <code>agent in vm</code>。</p><h1 id="扩展文件系统"><a href="#扩展文件系统" class="headerlink" title="扩展文件系统"></a>扩展文件系统</h1><h2 id="Agent-in-VM"><a href="#Agent-in-VM" class="headerlink" title="Agent in VM"></a>Agent in VM</h2><blockquote><p>这个模式下，Agent没有多租户的概念，面对的是一台专用的虚拟机，所有多租户的概念在外部业务层实现</p></blockquote><p>当 Agent 需要稳定操作本地开发环境、浏览器和长生命周期进程时，<code>Agent in VM</code> 是最直接的方案。<br>它的核心思想是：平台做“VM 分配与调度”，Agent 只面对“单租户执行环境”。</p><pre><code class=" mermaid">flowchart LR    U[User]    APP[Multi-tenant App]    SCH[VM Scheduler]    VM1[Tenant VM]    AG[Agent Process]    FS[(Local FS)]    SH[Shell/Browser]    LLM[LLM API]    U --&gt; APP    APP --&gt; SCH    SCH --&gt; VM1    VM1 --&gt; AG    AG --&gt; FS    AG --&gt; SH    AG --&gt; LLM</code></pre><p>设计重点：</p><ol><li>租户隔离天然清晰：每个会话绑定一台 VM。</li><li>状态恢复简单：文件、进程、缓存都在 VM 内。</li><li>代价是资源成本和调度时延：空闲 VM 成本高，启动&#x2F;恢复慢于无状态服务。</li></ol><p>案例：Manus、利用Claude Code SDK构建多租户agent</p><h2 id="Stateless-Agent-Service-Sandbox"><a href="#Stateless-Agent-Service-Sandbox" class="headerlink" title="Stateless Agent Service + Sandbox"></a>Stateless Agent Service + Sandbox</h2><p>该方案将控制逻辑与执行环境解耦：Agent Service 保持无状态，Sandbox 承载会话执行状态。</p><p>分层如下：</p><ul><li>Agent Service：无状态，负责规划、路由、工具编排。</li><li>Sandbox：有状态，负责执行命令和访问会话文件。</li><li>Shared Storage：跨 Sandbox 挂载同一会话目录。</li></ul><pre><code class=" mermaid">flowchart LR    U[User]    GW[API Gateway]    AS[Stateless Agent Service]    LLM[LLM API]    SB[Session Sandbox Pod/Lambda]    NFS[(Shared Storage)]    TOOLS[External Tools]    U --&gt; GW --&gt; AS    AS --&gt; LLM    AS --&gt; SB    SB --&gt; NFS    SB --&gt; TOOLS    AS --&gt; TOOLS</code></pre><p>可行性前提：Agentic loop 依赖“状态可引用”，不依赖“进程常驻”。只要 session 可以绑定到可恢复的文件和执行上下文，服务就可以无状态扩缩容。</p><p>关键约束：</p><ol><li>边界必须明确：哪些工具在 Agent Service 执行，哪些必须进 Sandbox。</li><li>安全默认收敛：出网控制、域名&#x2F;IP 白名单、速率限制、租户目录隔离。</li><li>存储性能决定体验：高频读写场景要评估本地盘缓存或更高性能分布式存储。</li></ol><h1 id="Durable-Execution（Stateless-Sandbox-的可靠性拓展）"><a href="#Durable-Execution（Stateless-Sandbox-的可靠性拓展）" class="headerlink" title="Durable Execution（Stateless + Sandbox 的可靠性拓展）"></a>Durable Execution（Stateless + Sandbox 的可靠性拓展）</h1><p>在 <code>Stateless Agent Service + Sandbox</code> 中，实例漂移是常态。重试不能依赖进程内存。<br>Durable Execution 的目标是将一次 loop 分解为可恢复步骤，并在步骤边界写入 checkpoint。</p><p>引入原因：</p><ol><li>实例漂移是常态：Pod 重建、请求重路由都会打断内存态执行。</li><li>工具调用有副作用：整轮重放会产生重复写入或重复外部调用。</li><li>长任务跨多分钟：必须允许中断后继续，而不是从头开始。</li></ol><p>最小状态拆分建议：</p><ol><li><code>message_state</code>：会话消息、摘要、token budget。</li><li><code>filesystem_state_ref</code>：工作目录版本、挂载路径、快照 ID。</li><li><code>execution_state</code>：当前 step、计划版本、上一步输出。</li><li><code>effect_log</code>：已执行副作用记录（外部请求 ID、写操作哈希）。</li></ol><pre><code class=" mermaid">flowchart LR    STEP[Run Step]    CKPT[Write Checkpoint]    EFFECT[Record Side Effect]    FAIL[Failure]    RESUME[Load Last Checkpoint]    NEXT[Run Next Step]    STEP --&gt; CKPT    STEP --&gt; EFFECT    STEP --&gt; FAIL    FAIL --&gt; RESUME    RESUME --&gt; NEXT</code></pre><p>实现规则：</p><ol><li>每个 step 必须可重入：相同输入重复执行，结果语义不变。</li><li>每个副作用必须可去重：通过 <code>idempotency_key</code> 或外部事务键避免重复提交。</li></ol><h2 id="语义恢复"><a href="#语义恢复" class="headerlink" title="语义恢复"></a>语义恢复</h2><p>在工程实践中，很多工具调用不可回滚，或回滚成本远高于收益。<br>因此，失败处理可以优先采用“语义恢复”，而不是“强事务回滚”。</p><p>例如三个工具并行执行时其中一个报错，不必强制撤销其余两个工具的副作用。可采用以下策略：</p><ol><li>将消息历史恢复到这组并行工具调用之前。</li><li>保留本次工具调用返回（包含不确定状态）。</li><li>对失败或不确定结果注入系统说明：<code>上次调用出错，可能已部分执行成功，请先确认状态再继续</code>。</li><li>让模型基于这条说明继续规划下一步（先确认，再补偿，或跳过）。</li></ol><blockquote><p>这里还可以向外暴露重试事件，以确保显示层不会出现未定义行为</p></blockquote><p>该策略的取舍很明确：放弃严格事务语义，换取更低实现复杂度和更高恢复连续性。</p><h1 id="Loop-级隔离编排（优化Agent-Service的可扩缩容性）"><a href="#Loop-级隔离编排（优化Agent-Service的可扩缩容性）" class="headerlink" title="Loop 级隔离编排（优化Agent Service的可扩缩容性）"></a>Loop 级隔离编排（优化Agent Service的可扩缩容性）</h1><p>这是一种中间态方案：保持 loop 代码结构不变，只隔离 loop 实例。<br>实现方式是“每个 turn 启动一个 Worker&#x2F;Lambda”，由队列驱动执行；SSE 由事件通道异步转发到流网关。</p><p>适用原因：</p><ol><li>保持调试模型稳定：单个 loop 代码路径不变，问题定位更直接。</li><li>缩小爆炸半径：单个任务异常只影响当前 worker。</li><li>提升发布速度：无状态 worker 镜像可快速滚动升级。</li></ol><pre><code class=" mermaid">flowchart LR    U[User]    API[Agent API]    Q[(Task Queue)]    W[Loop Worker Lambda/Pod]    LLM[LLM API]    SB[Sandbox]    EQ[(Event Queue)]    SG[SSE Gateway]    U --&gt; API    API --&gt; Q    Q --&gt; W    W --&gt; LLM    W --&gt; SB    W --&gt; EQ    EQ --&gt; SG    SG --&gt; U</code></pre><p>实现要点：</p><ol><li><code>turn_id</code> 作为任务主键，保证同一轮只有一个 active worker。</li><li>Worker 事件写入 <code>event queue</code>，由 <code>SSE gateway</code> 统一推送，避免函数实例直接持有长连接。</li><li>失败重试由队列控制，重试前先加载 checkpoint，继续执行而非整轮重跑。</li></ol><p>在演进路径中的位置：</p><ol><li>它依赖 Durable Execution 提供可恢复状态。</li><li>它不要求 Actor 拆分，适合作为高并发阶段的低改造增量方案。</li><li>当单 loop 内部并发仍成瓶颈，再演进到 Actor Model。</li></ol><h1 id="Actor-Model（优化Agent-Service的可扩缩容性）"><a href="#Actor-Model（优化Agent-Service的可扩缩容性）" class="headerlink" title="Actor Model（优化Agent Service的可扩缩容性）"></a>Actor Model（优化Agent Service的可扩缩容性）</h1><p>当 <code>Stateless Agent Service</code> 进入高并发、多工具、长链路阶段，单体 loop 的主要瓶颈是调度耦合和故障扩散。<br>Actor Model 是下一阶段的实现方式：将 loop 拆成可独立扩缩容的执行单元。</p><p>推荐的最小 Actor 拆分：</p><ol><li><code>Session Actor</code>：会话入口，维护 turn 生命周期。</li><li><code>Planner Actor</code>：生成步骤计划与重规划。</li><li><code>Model Actor</code>：封装模型调用策略（路由、重试、限流）。</li><li><code>Tool Actor</code>：执行工具调用（可按工具类型分片）。</li><li><code>State Actor</code>：集中处理 checkpoint、effect log、恢复逻辑。</li></ol><pre><code class=" mermaid">flowchart LR    SA[Session Actor]    PA[Planner Actor]    MA[Model Actor]    TA[Tool Actor]    STA[State Actor]    BUS[(Message Bus)]    SA --&gt; BUS    PA --&gt; BUS    MA --&gt; BUS    TA --&gt; BUS    STA --&gt; BUS    BUS --&gt; SA    BUS --&gt; PA    BUS --&gt; MA    BUS --&gt; TA    BUS --&gt; STA</code></pre><p>与前一节的关系：</p><ol><li>Durable Execution 依赖 <code>State Actor</code> 统一写入恢复点。</li><li>Sandbox 调度可以下沉到 <code>Tool Actor</code>，实现按工具类型弹性扩缩容。</li><li>故障隔离更细：单个 Tool Actor 失败不会直接拖垮整个会话调度。</li></ol><p>代价是消息一致性、乱序处理和链路追踪复杂度上升。<br>建议顺序是：先稳定单体 Stateless loop，再按瓶颈引入 Actor 化拆分。</p><h1 id="可观测性"><a href="#可观测性" class="headerlink" title="可观测性"></a>可观测性</h1><p>可观测性目标不是“记录更多日志”，而是回答三个问题：慢在哪里、错在哪里、哪类任务不稳定。</p><pre><code class=" mermaid">flowchart LR    APP[Agent Service]    SB[Sandbox]    OBS[Telemetry Pipeline]    LOG[(Logs)]    MET[(Metrics)]    TR[(Traces)]    UI[Ops Dashboard]    APP --&gt; OBS    SB --&gt; OBS    OBS --&gt; LOG    OBS --&gt; MET    OBS --&gt; TR    LOG --&gt; UI    MET --&gt; UI    TR --&gt; UI</code></pre><p>建议指标分两层：</p><ol><li>LLM 层：QPS、TTFT、P95 延迟、错误率、token 成本。</li><li>Tool 层：调用次数、成功率、执行时长、超时率。</li></ol><p>Trace 结构建议绑定 <code>session_id + turn_id + step_id</code>，以便还原完整 trajectory。</p><h2 id="Benchmark"><a href="#Benchmark" class="headerlink" title="Benchmark"></a>Benchmark</h2><p>有了 Trace 和可恢复执行能力后，可以把线上真实任务沉淀为 benchmark 回放集。</p><pre><code class=" mermaid">flowchart LR    TR[(Trace Store)]    SEL[Scenario Selector]    REP[Replay Runner]    EVAL[Scoring/Eval]    RPT[Model Report]    TR --&gt; SEL --&gt; REP --&gt; EVAL --&gt; RPT</code></pre><p>评测重点不只看“是否成功”，还要看：</p><ol><li>端到端时延与成本。</li><li>工具调用效率和失败恢复能力。</li><li>不同模型在同一任务族上的稳定性差异。</li></ol>]]></content>
    
    
    <summary type="html">这篇先从最小 SSE Chatbot 开始，明确显示层与业务层的基本实现，再逐步过渡到文件系统与沙箱架构。</summary>
    
    
    
    <category term="AI Engineering" scheme="https://blog.wh1isper.top/categories/AI-Engineering/"/>
    
    
    <category term="Agent" scheme="https://blog.wh1isper.top/tags/Agent/"/>
    
    <category term="CloudAgent" scheme="https://blog.wh1isper.top/tags/CloudAgent/"/>
    
    <category term="Engineering" scheme="https://blog.wh1isper.top/tags/Engineering/"/>
    
    <category term="实现细节" scheme="https://blog.wh1isper.top/tags/%E5%AE%9E%E7%8E%B0%E7%BB%86%E8%8A%82/"/>
    
  </entry>
  
  <entry>
    <title>聊聊云上Agent的架构设计</title>
    <link href="https://blog.wh1isper.top/2026/02/17/2026-02-17-architecture-of-cloud-agent/"/>
    <id>https://blog.wh1isper.top/2026/02/17/2026-02-17-architecture-of-cloud-agent/</id>
    <published>2026-02-17T13:54:00.000Z</published>
    <updated>2026-03-23T06:42:51.491Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>总结一下Agent的发展历程和架构演进，聊聊几个抽象概念，最后说说我觉得未来Agent架构的一个可能方向。</p></blockquote><h1 id="Chatbot"><a href="#Chatbot" class="headerlink" title="Chatbot"></a>Chatbot</h1><p>最开始出现的是Chatbot应用。Chatbot 是一个状态系统。每轮请求执行四个步骤：读取上下文、构造 prompt、调用模型、写回状态。系统设计的重点不在模型接口，而在状态组织方式。</p><p>会话状态建议分为两层：</p><ul><li><strong>显示层（Display Layer）</strong>：保存原始事件流，面向 UI 展示、回放与审计。</li><li><strong>业务层（Business Layer）</strong>：保存推理就绪上下文，面向 prompt 构造与成本控制。</li></ul><p>该分层支持一条工程约束：业务层允许静默压缩与摘要，显示层保持完整历史，不做语义改写。</p><h2 id="1-分层职责图"><a href="#1-分层职责图" class="headerlink" title="1) 分层职责图"></a>1) 分层职责图</h2><pre><code class=" mermaid">flowchart LR    UI[Display Layer&lt;br/&gt;raw events / replay / audit]    BL[Business Layer&lt;br/&gt;processed context / prompt-ready]    UI -. isolated goals .- BL</code></pre><p>这张图定义职责边界。两层的输入输出、服务对象、演化策略均不同。混用单层存储会产生三类问题：</p><ol><li>prompt 长度随轮次线性增长，推理成本和时延同步上升。</li><li>展示格式、系统指令、工具中间消息进入模型输入，降低上下文信噪比。</li><li>压缩策略直接作用于用户可见历史，破坏回放一致性与审计可读性。</li></ol><h2 id="2-写路径图"><a href="#2-写路径图" class="headerlink" title="2) 写路径图"></a>2) 写路径图</h2><pre><code class=" mermaid">flowchart LR    U[User] --&gt; API[Chat API] --&gt; SVC[Conversation Service]    SVC --&gt; CP[Context Processor]    CP --&gt; LLM[LLM Inference]    LLM --&gt; SVC    SVC --&gt; DDB[(Display History Store)]    SVC --&gt; BDB[(Business Context Store)]</code></pre><p>写路径采用双写：同一轮请求同时更新显示层与业务层。</p><ul><li><code>Display History Store</code> 记录 raw user&#x2F;assistant events。</li><li><code>Business Context Store</code> 记录 processed context。</li></ul><p><code>Context Processor</code> 负责从“展示消息”生成“推理上下文”，常见操作为：</p><ul><li>truncate（窗口裁剪）</li><li>compress（信息压缩）</li><li>summarize（阶段摘要）</li><li>metadata filtering（移除展示噪声字段）</li></ul><h2 id="3-推理读路径图"><a href="#3-推理读路径图" class="headerlink" title="3) 推理读路径图"></a>3) 推理读路径图</h2><pre><code class=" mermaid">flowchart LR    SVC[Conversation Service] --&gt;|load prompt context| BDB[(Business Context Store)]    BDB --&gt; SVC    SVC --&gt; LLM[LLM Inference]</code></pre><p>推理路径只读取业务层。该约束保证三点：</p><ul><li>输入长度可预测。</li><li>关键语义可延续。</li><li>prompt 结构可标准化。</li></ul><h2 id="4-显示回放图"><a href="#4-显示回放图" class="headerlink" title="4) 显示回放图"></a>4) 显示回放图</h2><pre><code class=" mermaid">flowchart LR    UI[Chat UI] --&gt; API[Chat API]    API --&gt; DDB[(Display History Store)]    DDB --&gt; API --&gt; UI</code></pre><p>展示路径只读取显示层。UI 获取的是原始事件轨迹，不依赖推理优化后的上下文快照。该路径为回放、排障、审计提供稳定数据源。</p><h2 id="5-Context-Engineering"><a href="#5-Context-Engineering" class="headerlink" title="5) Context Engineering"></a>5) Context Engineering</h2><p>在 Chatbot 阶段，Context Engineering 关注两个问题：</p><ol><li><strong>上下文压缩</strong>：在 token budget 固定的前提下，保留任务相关信息。</li><li><strong>用户记忆 &#x2F; 偏好</strong>：在跨轮次交互中维持行为一致性。</li></ol><p>这一阶段的重点是定义问题边界和评估标准，不在于引入复杂机制。</p><p><strong>结论</strong></p><ul><li>Chatbot 架构已经形成显示层与业务层的初步分层：前者负责事件回放与审计，后者负责推理上下文组织。</li><li>Chatbot 阶段已经出现上下文管理问题：上下文压缩与用户记忆&#x2F;偏好管理成为后续架构演进的核心输入。</li></ul><h1 id="Agent-in-VM"><a href="#Agent-in-VM" class="headerlink" title="Agent in VM"></a>Agent in VM</h1><p>第二种形态是 <strong>Agent 与用户共处同一台 VM</strong>。与 Chatbot 阶段相比，这一形态把“对话系统”扩展为“执行系统”：Agent 不再只组织上下文，还直接操作 Shell、文件系统、浏览器和桌面环境。</p><p>这个形态的核心变化是：</p><ul><li>执行环境从 API 工具调用，转为真实操作系统环境。</li><li>状态边界从会话消息，扩展为 VM 全状态（文件、进程、浏览器、环境变量）。</li><li>用户可通过 VNC&#x2F;SSH 与 Agent 并行操作同一环境。</li></ul><h2 id="1-形态定义与架构边界"><a href="#1-形态定义与架构边界" class="headerlink" title="1) 形态定义与架构边界"></a>1) 形态定义与架构边界</h2><pre><code class=" mermaid">flowchart LR    U[User] --&gt;|VNC/SSH| VM[User VM]    WEB[Web Chat] --&gt; AG[Agent Process in VM]    subgraph VM[User VM]        AG        SH[Shell]        FS[(Filesystem)]        BR[Browser]        DE[Desktop]    end    AG --&gt; SH    AG --&gt; FS    AG --&gt; BR    DE --&gt; SH    DE --&gt; BR</code></pre><p>该架构的边界很直接：Agent 和用户共享同一运行时。系统一致性高，系统隔离弱。</p><h2 id="2-运行时交互流"><a href="#2-运行时交互流" class="headerlink" title="2) 运行时交互流"></a>2) 运行时交互流</h2><pre><code class=" mermaid">sequenceDiagram    participant User as User(VNC/SSH)    participant Web as Web Chat    participant Agent as Agent(in VM)    participant LLM as LLM API    participant Shell as Shell    participant FS as Filesystem    participant Browser as Browser    User-&gt;&gt;Shell: direct command    Shell-&gt;&gt;FS: read/write    Web-&gt;&gt;Agent: task request    Agent-&gt;&gt;LLM: planning + tool call    LLM--&gt;&gt;Agent: action    Agent-&gt;&gt;Shell: execute command    Shell-&gt;&gt;FS: read/write    Agent-&gt;&gt;Browser: open/click/type    Agent--&gt;&gt;Web: result    User-&gt;&gt;FS: inspect changes immediately</code></pre><p>这一形态的优势在于“可观察 + 可干预”：用户可以实时看到 Agent 的执行结果，也可以随时接管。</p><h2 id="3-状态持久化：从会话状态到-VM-快照"><a href="#3-状态持久化：从会话状态到-VM-快照" class="headerlink" title="3) 状态持久化：从会话状态到 VM 快照"></a>3) 状态持久化：从会话状态到 VM 快照</h2><p>Chatbot 阶段主要持久化消息与上下文。Agent-in-VM 阶段需要持久化完整环境状态，通常依赖 VM Snapshot。</p><pre><code class=" mermaid">sequenceDiagram    participant P as Platform    participant VM as VM    participant S as Snapshot Storage    P-&gt;&gt;VM: pause    VM--&gt;&gt;P: frozen    P-&gt;&gt;S: save memory snapshot    P-&gt;&gt;S: save disk snapshot    P-&gt;&gt;VM: terminate    P-&gt;&gt;S: load snapshots    S--&gt;&gt;P: memory + disk    P-&gt;&gt;VM: restore    VM--&gt;&gt;P: resumed</code></pre><p>快照带来的工程收益是“连续执行上下文”：文件、进程、浏览器状态可以跨会话恢复。典型场景是开发服务器和调试现场的延续。</p><h2 id="4-主要约束与风险"><a href="#4-主要约束与风险" class="headerlink" title="4) 主要约束与风险"></a>4) 主要约束与风险</h2><p>这个形态在通用性上很强，但成本与安全边界会前置成为架构问题：</p><ul><li><strong>成本约束</strong>：每用户独占 VM，空闲成本高，扩展效率低于共享计算架构。</li><li><strong>调度约束</strong>：VM 启停与快照恢复时延高于无状态容器调度。</li><li><strong>安全约束</strong>：Agent 进程与用户共域，密钥管理、权限边界、篡改防护更复杂。</li><li><strong>持久化风险</strong>：恶意进程或敏感凭证可能通过快照跨会话保留。</li></ul><h2 id="结论"><a href="#结论" class="headerlink" title="结论"></a>结论</h2><ul><li>Agent in VM 形态把系统能力从“对话组织”推进到“环境执行”，获得了强交互、强观察、强持久化能力。</li><li>这一形态同时引入了新的主问题：多租户安全边界、VM 成本模型与快照生命周期治理。</li></ul><h1 id="Agentic-LLM-API"><a href="#Agentic-LLM-API" class="headerlink" title="Agentic LLM API"></a>Agentic LLM API</h1><p>第三种形态可以定义为 <strong>Agentic LLM API</strong>：交互入口仍是 Chatbot，但执行能力已经升级为 Agent，并由模型 API 厂商托管执行环境。与 Agent in VM 的区别在于，用户不直接进入执行环境；与第一阶段 Chatbot 的区别在于，系统不仅维护消息状态，还维护可执行环境状态。</p><p>这一形态的目标是把“对话编排”与“环境执行”做成一条端到端链路：</p><ul><li>对用户暴露稳定的聊天接口。</li><li>对系统内部暴露可控的工具与执行环境。</li><li>在 token 成本、延迟与可观察性之间取得工程平衡。</li></ul><h2 id="1-形态定义与系统边界"><a href="#1-形态定义与系统边界" class="headerlink" title="1) 形态定义与系统边界"></a>1) 形态定义与系统边界</h2><pre><code class=" mermaid">flowchart LR    U[User]    CHAT[Chat Interface]    AGENT[Agent Orchestrator]    MEM[(Session &amp; Memory Store)]    CE[Code Execution Runtime]    TOOLS[Internal/External Tools]    U --&gt; CHAT --&gt; AGENT    AGENT &lt;--&gt; MEM    AGENT --&gt; CE    CE --&gt; TOOLS    TOOLS --&gt; CE --&gt; AGENT --&gt; CHAT --&gt; U</code></pre><p>边界定义：</p><ul><li><strong>Control Plane</strong>：会话、记忆、策略、路由。</li><li><strong>Execution Plane</strong>：代码执行容器或沙箱运行时。</li><li><strong>Tool Plane</strong>：数据库、检索、业务 API、浏览器等外部能力。</li></ul><h2 id="2-执行流：从直接工具调用到程序化工具调用"><a href="#2-执行流：从直接工具调用到程序化工具调用" class="headerlink" title="2) 执行流：从直接工具调用到程序化工具调用"></a>2) 执行流：从直接工具调用到程序化工具调用</h2><p>Agentic Chatbot 的关键变化，是把多步工具调用下沉到执行环境中，以减少模型轮次开销。典型实现是 Programmatic Tool Calling（PTC）模式：模型生成可执行代码，在运行时中循环调用工具、过滤中间结果，再回传高价值输出。</p><pre><code class=" mermaid">sequenceDiagram    participant U as User    participant A as Agent Orchestrator    participant L as LLM    participant R as Execution Runtime    participant T as Tool/API    U-&gt;&gt;A: task    A-&gt;&gt;L: planning prompt    L--&gt;&gt;A: code-oriented action    A-&gt;&gt;R: run code block    loop in runtime        R-&gt;&gt;T: call tool        T--&gt;&gt;R: raw result        R-&gt;&gt;R: filter/aggregate/branch    end    R--&gt;&gt;A: compact result    A-&gt;&gt;L: final reasoning    L--&gt;&gt;A: answer    A--&gt;&gt;U: response</code></pre><p>该流程的工程意义是：中间数据处理不进入主上下文窗口，模型只消费压缩后的关键结果。</p><h2 id="3-状态模型：消息状态-环境状态-运行状态"><a href="#3-状态模型：消息状态-环境状态-运行状态" class="headerlink" title="3) 状态模型：消息状态 + 环境状态 + 运行状态"></a>3) 状态模型：消息状态 + 环境状态 + 运行状态</h2><p>Chatbot 阶段主要管理消息状态；Agentic Chatbot 需要三类状态并存。</p><pre><code class=" mermaid">flowchart LR    M[(Message State)]    E[(Environment State)]    R[(Run State)]    M --&gt; C[Context Builder]    E --&gt; C    R --&gt; C    C --&gt; P[Prompt/Action Decision]</code></pre><ul><li><strong>Message State</strong>：对话历史、摘要、用户偏好。</li><li><strong>Environment State</strong>：容器 ID、文件系统变更、工具上下文。</li><li><strong>Run State</strong>：任务阶段、工具调用链、重试与超时信息。</li></ul><p>系统可恢复性依赖于这三类状态的一致性，而不是单一会话历史。</p><h2 id="4-主要约束"><a href="#4-主要约束" class="headerlink" title="4) 主要约束"></a>4) 主要约束</h2><p>Agentic LLM API 的主要工程约束集中在五点：</p><ul><li><strong>安全约束</strong>：执行环境必须隔离，工具调用需要 caller 权限边界与 allowlist。</li><li><strong>时效约束</strong>：执行容器存在 TTL，跨步调用需要超时恢复和幂等设计。</li><li><strong>观测约束</strong>：需要保留 action trace、tool I&#x2F;O、版本化 prompt 以支持回放。</li><li><strong>成本约束</strong>：执行时长、容器复用、上下文压缩共同决定单位任务成本。</li><li><strong>供应商锁定约束</strong>：执行环境上下文（container state、intermediate artifacts、tool-call runtime state）主要保存在 API 厂商侧，迁移到其他模型供应商时，状态可移植性与行为一致性难以保证。</li></ul><p>关于供应商锁定，这一形态有两个具体后果：</p><ol><li><strong>状态不可携带</strong>：迁移供应商时，通常只能带走消息历史，难以带走运行时上下文。</li><li><strong>行为不可等价</strong>：即使接口相似，不同厂商在执行容器、超时策略、工具调用协议上的差异会导致任务行为漂移。</li></ol><p>锁定面的核心不在 API schema，而在运行时语义：</p><pre><code class=" mermaid">flowchart TB    subgraph HighPortability[High Portability]        M[Message History&lt;br/&gt;input/output transcripts]    end    subgraph MediumPortability[Medium Portability]        T[Tool Contracts&lt;br/&gt;name/schema/params]    end    subgraph LowPortability[Low Portability]        R[Runtime State&lt;br/&gt;container/session/artifacts]        S[Execution Semantics&lt;br/&gt;timeout/retry/caller policy]    end    M --&gt; T --&gt; R    T --&gt; S</code></pre><p>可迁移性通常呈分层下降：消息层 &gt; 工具协议层 &gt; 运行时状态层。</p><blockquote><p>这里和很多厂商（OpenAI &amp; Gemini）在API中隐藏自己的thinking过程不同，环境状态的丢失使得迁移供应商是不可能的，而非之前的“丢失一些中间思考”。</p></blockquote><h2 id="结论-1"><a href="#结论-1" class="headerlink" title="结论"></a>结论</h2><p>这对于模型厂商获取数据和用户锁定都有积极意义，也为第一方应用提供了非常强大的能力，但对于开发者来说，几乎不可能选择这类方案，其供应商锁定和不透明问题带来的风险太高了。</p><h1 id="Next-Agent-Mounts-Environment"><a href="#Next-Agent-Mounts-Environment" class="headerlink" title="Next: Agent Mounts Environment"></a>Next: Agent Mounts Environment</h1><p>Agent in VM 形态虽然提供了强交互能力，但存在两个核心问题：一是每用户独占 VM 的成本模式导致空闲成本高；二是 Agent 与用户共域，API Keys 与内部逻辑暴露在 VM 环境，难以安全整合私有服务。</p><p><strong>Agent Mounts Environment</strong> 将 Agent 与执行环境解耦：Agent 仍运行在平台侧（可访问私有服务、密钥隔离），但将执行环境降级为一个”挂载点”。VM 内仅运行轻量级 Executor，负责接收 Agent 的指令（Shell 命令、文件操作、浏览器控制）并反馈结果。</p><pre><code class=" mermaid">flowchart LR    subgraph Platform[&quot;Platform (Secure Zone)&quot;]        Agent[&quot;Agent Service&quot;]        PrivateAPI[&quot;Private Services&quot;]    end    subgraph VM[&quot;User VM (Mounted Environment)&quot;]        Executor[&quot;Executor&quot;]        Shell[&quot;Shell&quot;]        FS[&quot;Filesystem&quot;]    end    Agent &lt;--&gt;|&quot;SSH/CDP&quot;| Executor    Agent &lt;--&gt;|&quot;API&quot;| PrivateAPI    Executor --&gt; Shell    Executor --&gt; FS</code></pre><h2 id="好处"><a href="#好处" class="headerlink" title="好处"></a>好处</h2><p><strong>1. 安全隔离</strong>：Agent 与用户环境物理分离，密钥与内部逻辑不暴露在 VM 中。Agent 可安全访问私有数据库、内部 API，而无需在 VM 中存储凭证。</p><p><strong>2. 成本优化</strong>：Agent 进程不与用户 VM 绑定，可跨多用户复用（或采用共享计算资源池）。VM 仅作为执行容器，不需要持续为每用户保留计算资源。</p><p><strong>3. 权限边界清晰</strong>：Executor 可施加细粒度权限控制：允许的工具集、可访问的文件路径、外部 API 调用权限均由平台策略决定，用户无法绕过。</p><p><strong>4. 与 Agentic LLM API 兼容</strong>：该架构天然支持多模型厂商，Agent 逻辑独立于执行环境实现，迁移或切换 LLM 供应商时，运行时语义保持一致。</p><h2 id="挑战"><a href="#挑战" class="headerlink" title="挑战"></a>挑战</h2><p><strong>1. 通信协议复杂性</strong>：Agent 需要通过 SSH、SFTP、CDP Over SSH Tunnel 等多种协议与 VM 通信。每种协议的超时、重试、错误恢复策略都需要精心设计。协议层的不稳定会导致任务中断或重复执行。</p><p><strong>2. 状态一致性困难</strong>：Agent 侧的任务状态与 VM 侧的执行状态可能不一致（如网络分区、超时导致的半成功操作）。需要设计操作幂等性、事务语义与恢复协议来保证最终一致性。</p><p><strong>3. VM 快照与 Git 同步</strong>：若采用 VM 快照持久化完整运行时状态，需要在恢复后重新同步 Git 状态，否则磁盘内容与 Remote 不一致。这引入了额外的状态管理层。</p><h2 id="结论-2"><a href="#结论-2" class="headerlink" title="结论"></a>结论</h2><p>Agent Mounts Environment 是 Agent in VM 与 Agentic LLM API 之间的中间态：保留了本地执行环境的完整可观察性和持久状态能力，同时恢复了平台侧的安全边界与成本效率。代价是需要投入工程力量处理分布式系统的一致性问题。</p><h1 id="Bonus-Durable-Execution"><a href="#Bonus-Durable-Execution" class="headerlink" title="Bonus: Durable Execution"></a>Bonus: Durable Execution</h1><p>Agentic Loop 中的执行容易失败：网络中断、超时、工具错误、模型幻觉都可能中断任务。要支持重试与恢复，需要把任务状态分层管理，而不是盲目地快照整个 VM 或重新执行任务。</p><p>关键是认识到不同类型的状态有不同的恢复语义：</p><h2 id="1-上下文（Context）"><a href="#1-上下文（Context）" class="headerlink" title="1) 上下文（Context）"></a>1) 上下文（Context）</h2><p><strong>定义</strong>：LLM 模型推理所需的信息，包括prompt、当前目标、已执行步骤。</p><p><strong>恢复策略</strong>：上下文应完全可重建，无需从快照恢复。重试时重新加载上一步的结果，让模型基于完整的执行历史做出新的决策。</p><p><strong>为什么</strong>：即使模型上一次的决策有误，完整的history 能让它在第二次重试中做出不同的选择。</p><pre><code class=" mermaid">sequenceDiagram    participant A as Agent    participant M as Message Store    participant L as LLM    A-&gt;&gt;M: Load history + last result    M--&gt;&gt;A: [step1, step2, step3: failed]    A-&gt;&gt;L: Continue with full context    L--&gt;&gt;A: New action based on failure</code></pre><h2 id="2-消息状态（Message-State）"><a href="#2-消息状态（Message-State）" class="headerlink" title="2) 消息状态（Message State）"></a>2) 消息状态（Message State）</h2><p><strong>定义</strong>：对话历史、用户请求、AI 响应。</p><p><strong>恢复策略</strong>：消息状态应仅追加（append-only），支持版本化重试。一次重试对应一条新分支，记录”第一次尝试失败”与”第二次重试成功”的完整轨迹。</p><p><strong>存储方案</strong>：</p><pre><code class=" mermaid">flowchart LR    Root[&quot;Message 1: user request&quot;]    Exec1[&quot;Message 2a: Agent planning (attempt 1)&quot;]    Exec2[&quot;Message 2b: Agent planning (attempt 2)&quot;]    Result1[&quot;Message 3a: Tool result FAILED&quot;]    Result2[&quot;Message 3b: Tool result OK&quot;]    Root --&gt; Exec1    Root --&gt; Exec2    Exec1 --&gt; Result1    Exec2 --&gt; Result2</code></pre><p>通过消息树（而非单线性链表）记录重试分支，支持审计与故障分析。</p><h2 id="3-文件系统状态（Filesystem-State）"><a href="#3-文件系统状态（Filesystem-State）" class="headerlink" title="3) 文件系统状态（Filesystem State）"></a>3) 文件系统状态（Filesystem State）</h2><p><strong>定义</strong>：VM 或容器内的文件、目录、权限。</p><p><strong>恢复策略</strong>：文件系统状态通过 git commit 或 VM 快照持久化。重试时的关键决策是：<strong>是否需要恢复到操作前的状态还是基于当前状态重新操作</strong>。</p><p><strong>两种模式</strong>：</p><p><strong>模式 A：Ephemeral + Rollback（容器&#x2F;Sandbox 方案）</strong></p><ul><li>每次工具调用在隔离容器内执行</li><li>失败时容器销毁，文件系统回滚</li><li>下一次重试从 Git Checkout 状态开始</li></ul><pre><code class=" mermaid">sequenceDiagram    participant A as Agent    participant C1 as Container (attempt 1)    participant C2 as Container (attempt 2)    participant FS as Shared FS (Git managed)    FS--&gt;&gt;C1: mount current HEAD    A-&gt;&gt;C1: npm install    C1-&gt;&gt;FS: write XXX (not committed)    Note over C1: Fails, container destroyed    FS--&gt;&gt;C2: mount current HEAD (clean state)    A-&gt;&gt;C2: npm install (retry)    C2-&gt;&gt;FS: write YYY</code></pre><p>优点：简单、可预测。缺点：无法利用前一次的中间成果（如部分安装的依赖）。</p><p><strong>模式 B：Persistent VM + Checkpoint（VM 快照方案）</strong></p><ul><li>使用 Firecracker Snapshot 保存完整运行时状态</li><li>失败时从快照恢复，保留内存中的进程、缓存</li><li>需要额外的 checkpoint 机制记录”哪些文件修改可以撤销”</li></ul><pre><code class=" mermaid">sequenceDiagram    participant A as Agent    participant VM as VM    participant S as Snapshot    A-&gt;&gt;VM: Save checkpoint (git state + memory snapshot)    S--&gt;&gt;VM: checkpoint_id_v1    A-&gt;&gt;VM: npm install    VM-&gt;&gt;VM: partial install (process in memory)    Note over VM: Fails    A-&gt;&gt;S: Restore checkpoint_id_v1    S--&gt;&gt;VM: Memory + disk restored    A-&gt;&gt;VM: npm install (retry, hot cache)</code></pre><p>优点：快速恢复、热启动。缺点：快照本身占用存储空间，恢复涉及内存一致性问题。</p><h2 id="4-外部资源状态（External-Resource-State）"><a href="#4-外部资源状态（External-Resource-State）" class="headerlink" title="4) 外部资源状态（External Resource State）"></a>4) 外部资源状态（External Resource State）</h2><p><strong>定义</strong>：API 调用、数据库事务、第三方服务的执行结果。</p><p><strong>恢复策略</strong>：外部资源操作的可恢复性取决于操作是否幂等。</p><p><strong>分类</strong>：</p><table><thead><tr><th>操作类型</th><th>幂等性</th><th>恢复策略</th></tr></thead><tbody><tr><td>Read DB</td><td>✓</td><td>直接重试</td></tr><tr><td>GET API</td><td>✓</td><td>直接重试</td></tr><tr><td>Create resource</td><td>✗</td><td>需要 idempotency key，防止重复创建</td></tr><tr><td>Update resource</td><td>✓&#x2F;✗</td><td>需要检验当前状态是否已更新</td></tr><tr><td>Delete resource</td><td>~</td><td>重试返回 404，需要 idempotent delete semantic</td></tr><tr><td>Payment &#x2F; Charge</td><td>✗</td><td>必须通过事务 ID 检查是否已执行</td></tr></tbody></table><p><strong>实现模式</strong>：Idempotency Key</p><pre><code class=" mermaid">sequenceDiagram    participant A as Agent    participant API as External API    participant Cache as Idempotency Cache    A-&gt;&gt;Cache: Lookup idempotency_key = &quot;task_step_3&quot;    Cache--&gt;&gt;A: Not found    A-&gt;&gt;API: POST /charge (idempotency_key: &quot;task_step_3&quot;, amount: 100)    API-&gt;&gt;Cache: Save result (charged: true)    Cache--&gt;&gt;API: OK    API--&gt;&gt;A: success    Note over A: Network timeout, retry    A-&gt;&gt;Cache: Lookup idempotency_key = &quot;task_step_3&quot;    Cache--&gt;&gt;A: Found (charged: true)    A-&gt;&gt;A: Return cached result without re-charging</code></pre><p>系统需要为每个工具调用分配全局唯一的 idempotency_key，存储每次调用的结果。重试时先查询缓存，避免重复操作。</p><h2 id="综合设计"><a href="#综合设计" class="headerlink" title="综合设计"></a>综合设计</h2><p>完整的 Durable Execution 系统需要四层状态管理：</p><pre><code class=" mermaid">flowchart TB    Context[&quot;Context Layer&lt;br/&gt;(Stateless, computed from Message State)&quot;]    Message[&quot;Message State&lt;br/&gt;(Append-only, versioned history)&quot;]    FileSystem[&quot;Filesystem State&lt;br/&gt;(Git checkpoint or VM snapshot)&quot;]    External[&quot;External State&lt;br/&gt;(Idempotency cache + verification)&quot;]    Message --&gt; Context    Message --&gt; FileSystem    Message --&gt; External    Context -.-&gt; Recovery[&quot;Recovery via Context + Message Replay&quot;]</code></pre><p>在故障发生时，恢复顺序为：</p><ol><li>检查外部资源状态（是否已执行）→ 如有缓存结果则直接返回</li><li>检查文件系统状态（Snapshot 或 Git HEAD）→ 重建执行环境</li><li>加载消息历史 → 重建 LLM 上下文</li><li>重新执行失败的步骤</li></ol><h2 id="结论-3"><a href="#结论-3" class="headerlink" title="结论"></a>结论</h2><p>Durable Execution 的核心是认识到：<strong>系统的可恢复性不源于单一快照，而源于分层的状态管理与操作的幂等性设计</strong>。不同类型的状态有不同的持久化需求与恢复成本，混为一谈会导致过度的快照成本或无法恢复的局面。</p>]]></content>
    
    
    <summary type="html">本文将深入探讨云上Agent的架构设计，分析其核心组件和功能，以及如何实现高效的Agent系统。</summary>
    
    
    
    <category term="AI Engineering" scheme="https://blog.wh1isper.top/categories/AI-Engineering/"/>
    
    
    <category term="Agent" scheme="https://blog.wh1isper.top/tags/Agent/"/>
    
    <category term="架构设计" scheme="https://blog.wh1isper.top/tags/%E6%9E%B6%E6%9E%84%E8%AE%BE%E8%AE%A1/"/>
    
    <category term="CloudAgent" scheme="https://blog.wh1isper.top/tags/CloudAgent/"/>
    
  </entry>
  
  <entry>
    <title>AI Native 组织思考</title>
    <link href="https://blog.wh1isper.top/2026/02/08/2026-02-08-AI-Native-Organization-Thoughts/"/>
    <id>https://blog.wh1isper.top/2026/02/08/2026-02-08-AI-Native-Organization-Thoughts/</id>
    <published>2026-02-08T01:51:00.000Z</published>
    <updated>2026-03-23T06:42:51.490Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p><strong>前言：</strong> 聊到了 AI Native 组织的形态，记录几个当下的直觉。</p></blockquote><h2 id="1-组织即灵魂"><a href="#1-组织即灵魂" class="headerlink" title="1. 组织即灵魂"></a>1. 组织即灵魂</h2><p>同样是基于 LLM，OpenAI 做出来的产品像大众工具，DeepMind 做出来的像科学仪器。产品不再是工业流水线上的标准化件，而是组织认知的直接投射。</p><p><strong>直觉：</strong> 不同的组织“性格”，决定了 AI 产品的“灵魂”。</p><h2 id="2-网络式共振"><a href="#2-网络式共振" class="headerlink" title="2. 网络式共振"></a>2. 网络式共振</h2><p>在 AI 时代，传统的层级式指令管理正在失效。因为创新的源头往往在一线研发的探索中涌现。</p><p><strong>直觉：</strong> 未来的组织更像是一个“超级兴趣小组”。大家因为一个远期目标聚在一起，通过高频的网络式协作（你读一篇 Paper，我试一个 Demo），把个体的认知拉齐、拉高。最终那个超越时代的产品，是在整个团队<strong>高认知水位</strong>上自然浮现的结果。</p><h2 id="3-商业化是真实世界的-RLHF"><a href="#3-商业化是真实世界的-RLHF" class="headerlink" title="3. 商业化是真实世界的 RLHF"></a>3. 商业化是真实世界的 RLHF</h2><p>商业化不仅仅是为了生存，它更是一个<strong>高价值信号过滤器</strong>。</p><p>免费用户的反馈往往是“好玩”（Novelty），付费用户的反馈往往是“没用”或“不准”（Utility）。只有那些愿意付费的用户反馈，才是最真实的 Reward Model，迫使团队直面真实世界的复杂性（Corner Cases），把“玩具”打磨成“工具”。</p><p><strong>直觉：</strong> 商业化把“用户反馈”转化为了高质量的“训练数据”。</p><h2 id="注脚：组织环境即-RL-Environment"><a href="#注脚：组织环境即-RL-Environment" class="headerlink" title="注脚：组织环境即 RL Environment"></a>注脚：组织环境即 RL Environment</h2><p>如果我们把组织看作一个强化学习（RL）的环境：</p><ul><li><strong>Agent：</strong> 每一个团队成员。</li><li><strong>Action：</strong> 每天的探索、决策、代码、讨论。</li><li><strong>Reward：</strong> 组织鼓励什么？是快速试错给正反馈，还是按部就班给正反馈？</li><li><strong>State：</strong> 整个团队当前的认知水位、技术栈、氛围。</li></ul><p>如果不精心设计这个 Environment 的 Reward Function，你就得不到那种自驱、涌现式的创新。</p>]]></content>
    
    
    <summary type="html">探讨 AI Native 组织的三点直觉：组织即灵魂、网络式共振、商业化是真实世界的 RLHF，以及组织环境本身作为 RL Environment 的思考。</summary>
    
    
    
    <category term="随笔" scheme="https://blog.wh1isper.top/categories/%E9%9A%8F%E7%AC%94/"/>
    
    
    <category term="随笔" scheme="https://blog.wh1isper.top/tags/%E9%9A%8F%E7%AC%94/"/>
    
    <category term="AI Native" scheme="https://blog.wh1isper.top/tags/AI-Native/"/>
    
    <category term="组织思考" scheme="https://blog.wh1isper.top/tags/%E7%BB%84%E7%BB%87%E6%80%9D%E8%80%83/"/>
    
  </entry>
  
  <entry>
    <title>AI 辅助开发下，如何保持项目一致性</title>
    <link href="https://blog.wh1isper.top/2026/02/02/2026-02-02-AI-Assisted-Development-Consistency/"/>
    <id>https://blog.wh1isper.top/2026/02/02/2026-02-02-AI-Assisted-Development-Consistency/</id>
    <published>2026-02-02T15:00:00.000Z</published>
    <updated>2026-03-23T06:42:51.490Z</updated>
    
    <content type="html"><![CDATA[<p>AI 正在改变软件开发的方式。Cursor、Claude Code、Copilot 这些工具让编码速度提升了数倍，但也带来了新的挑战：<strong>代码写得太快，理解跟不上，项目很容易腐化</strong>。</p><p>这篇文章分享我在 AI 辅助开发中保持项目一致性的一些实践。</p><h2 id="核心洞察：瓶颈变了"><a href="#核心洞察：瓶颈变了" class="headerlink" title="核心洞察：瓶颈变了"></a>核心洞察：瓶颈变了</h2><p>传统软件开发的瓶颈是编码。设计完成后，大量时间花在实现上。</p><pre><code class=" mermaid">flowchart LR    A[设计] --&gt; B[编码]    B --&gt; C[Review]    C --&gt; D[修改]    D --&gt; B</code></pre><p>AI 辅助开发打破了这个瓶颈。编码变得很快，但新的问题出现了：</p><pre><code class=" mermaid">flowchart LR    A[设计] --&gt; B[AI编码]    B --&gt; C[代码量暴增]    C --&gt; D[理解跟不上]    D --&gt; E[架构腐化]    E --&gt; F[更多patch]    F --&gt; E</code></pre><p><strong>编码不再是瓶颈，理解和架构才是。</strong></p><h2 id="方法论：控制点上移"><a href="#方法论：控制点上移" class="headerlink" title="方法论：控制点上移"></a>方法论：控制点上移</h2><p>要打破这个负循环，关键是把控制点从”代码”上移到”架构”。</p><pre><code class=" mermaid">flowchart TB    subgraph Control[控制层]        A[Spec文档]        B[Owner审阅]    end    subgraph Constraint[约束层]        C[agents.md]        D[类型系统]        E[项目规则]    end    subgraph Execution[执行层]        F[AI生成代码]        G[Prototype]        H[生产代码]    end    A --&gt; B    B --&gt; C    B --&gt; D    B --&gt; E    C --&gt; F    D --&gt; F    E --&gt; F    F --&gt; G    G --&gt;|验证通过| H    G --&gt;|需要调整| A</code></pre><h3 id="1-Spec-文档驱动"><a href="#1-Spec-文档驱动" class="headerlink" title="1. Spec 文档驱动"></a>1. Spec 文档驱动</h3><p>把脑海里的架构变成和 AI 讨论的 spec 文档：</p><ul><li><strong>概要设计</strong>：系统边界、模块划分、核心流程</li><li><strong>架构图&#x2F;流程图</strong>：用 mermaid 或手绘，让 AI 理解上下文</li><li><strong>DDD 思路</strong>：bounded context、聚合、领域语言</li></ul><p>不需要细到接口签名。模块边界清晰，AI 就不会跨模块乱搞。</p><pre><code class=" mermaid">flowchart LR    A[脑海中的架构] --&gt; B[Spec文档]    B --&gt; C[与AI讨论]    C --&gt; D[AI生成代码]    D --&gt; E[代码符合架构]</code></pre><h3 id="2-自底向上构建"><a href="#2-自底向上构建" class="headerlink" title="2. 自底向上构建"></a>2. 自底向上构建</h3><p>很多人习惯自顶向下：先设计接口，再实现细节。但在 AI 辅助开发中，我发现<strong>自底向上更有效</strong>：</p><ul><li><strong>先做配置</strong>：配置是系统的”骨架”，定下来后 AI 生成的代码就有约束</li><li><strong>先定类型</strong>：类型系统是天然的约束，让 AI 在框里活动</li><li><strong>从不易变的开始</strong>：基础设施、工具函数、配置管理</li></ul><p>这样做的好处：</p><ul><li>更容易获得良好的抽象</li><li>更容易进行测试覆盖</li><li>AI 生成的代码有锚点，不会飘</li></ul><h3 id="3-约束前置"><a href="#3-约束前置" class="headerlink" title="3. 约束前置"></a>3. 约束前置</h3><p>与其事后 review 代码，不如事前约束 AI：</p><pre><code class=" mermaid">flowchart LR    subgraph Constraints[约束]        A[agents.md]        B[pyright]        C[ESLint]        D[项目规则]    end    E[AI] --&gt; Constraints    Constraints --&gt; F[符合规范的代码]</code></pre><ul><li><strong>agents.md &#x2F; AGENTS.md</strong>：写清楚项目的架构、约定、禁忌</li><li><strong>类型系统</strong>：pyright、TypeScript，静态分析是最好的约束</li><li><strong>项目规则</strong>：命名规范、目录结构、commit 格式</li></ul><p>AI 读了这些约束，生成的代码一致性会好很多。</p><h3 id="4-早期重构"><a href="#4-早期重构" class="headerlink" title="4. 早期重构"></a>4. 早期重构</h3><p>重构应该在<strong>最早的时候</strong>进行，而不是等代码堆积成山：</p><ul><li>代码量小，重构成本低</li><li>最容易利用 AI 的生成能力</li><li>最不容易受幻觉影响</li></ul><p>等到项目复杂了再重构，AI 会产生更多幻觉，因为它无法完全理解所有上下文。</p><h3 id="5-快速验证，慎重生产"><a href="#5-快速验证，慎重生产" class="headerlink" title="5. 快速验证，慎重生产"></a>5. 快速验证，慎重生产</h3><pre><code class=" mermaid">flowchart LR    A[想法] --&gt; B[Prototype]    B --&gt; C&#123;验证&#125;    C --&gt;|通过| D[生产化]    C --&gt;|失败| E[调整想法]    E --&gt; A    D --&gt; F[留重构空间]</code></pre><ul><li><strong>尽快 prototype</strong>：用 AI 快速验证想法</li><li><strong>慎重生产</strong>：验证通过后再决定是否生产化</li><li><strong>留重构空间</strong>：不要过早固化，保持灵活性</li></ul><h2 id="正反馈循环"><a href="#正反馈循环" class="headerlink" title="正反馈循环"></a>正反馈循环</h2><p>好的实践会形成正反馈：</p><pre><code class=" mermaid">flowchart LR    A[好架构] --&gt; B[AI生成正确代码]    B --&gt; C[省时间]    C --&gt; D[优化架构]    D --&gt; A</code></pre><p>差的实践会形成负反馈（要避免）：</p><pre><code class=" mermaid">flowchart LR    A[烂架构] --&gt; B[代码到处patch]    B --&gt; C[越来越乱]    C --&gt; D[没时间重构]    D --&gt; A</code></pre><h2 id="团队协作"><a href="#团队协作" class="headerlink" title="团队协作"></a>团队协作</h2><p>在多人协作中，一致性更重要：</p><ul><li><strong>项目有 Owner</strong>：Owner 审阅 spec，保持架构一致性</li><li><strong>Review spec，不只是 review 代码</strong>：比传统 code review 更高效</li><li><strong>AI review 辅助</strong>：让 AI 检查代码是否符合 spec</li></ul><pre><code class=" mermaid">flowchart TB    A[Owner] --&gt;|审阅| B[Spec]    B --&gt;|指导| C[开发者1]    B --&gt;|指导| D[开发者2]    C --&gt; E[代码]    D --&gt; E    E --&gt;|AI Review| F&#123;符合Spec&#125;    F --&gt;|是| G[合并]    F --&gt;|否| H[修改]    H --&gt; E</code></pre><h2 id="工程师角色转变"><a href="#工程师角色转变" class="headerlink" title="工程师角色转变"></a>工程师角色转变</h2><p>AI 辅助开发正在改变工程师的角色：</p><table><thead><tr><th>传统</th><th>AI 辅助</th></tr></thead><tbody><tr><td>写代码</td><td>设计架构</td></tr><tr><td>Debug</td><td>审阅 spec</td></tr><tr><td>重复劳动</td><td>创造性思考</td></tr></tbody></table><p>工程师的核心价值变成了：</p><ul><li><strong>架构抽象能力</strong>：基于当前理解设计合理的架构</li><li><strong>业务理解</strong>：对当前情况和未来进行抉择</li><li><strong>质量把控</strong>：确保 AI 生成的代码符合预期</li></ul><p>这本就是架构师的任务。有了 AI 辅助编码，我们可以有更多时间放在设计更容易维护的架构上，反过来又方便了 AI 编码，实现正反馈。</p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>AI 辅助开发的核心是<strong>人机协作</strong>：</p><ul><li><strong>人负责</strong>：架构决策、业务理解、质量把控</li><li><strong>AI 负责</strong>：快速实现、重复劳动、代码生成</li></ul><p>保持项目一致性的关键：</p><ol><li><strong>控制点上移</strong>：从代码到 spec</li><li><strong>约束前置</strong>：用规则和工具约束 AI</li><li><strong>早期重构</strong>：趁代码量小的时候</li><li><strong>快速验证</strong>：prototype 快，生产慢</li><li><strong>正反馈循环</strong>：好架构 → 好代码 → 更好架构</li></ol><p>AI 不会取代工程师，但会取代不会用 AI 的工程师。掌握 AI 辅助开发的方法论，才能在这个时代保持竞争力。</p>]]></content>
    
    
    <summary type="html">AI 辅助开发改变了软件工程的瓶颈，编码不再是瓶颈，理解和架构才是。本文分享如何通过 Spec 文档驱动、约束前置、早期重构等方法保持项目一致性。</summary>
    
    
    
    <category term="技术分享" scheme="https://blog.wh1isper.top/categories/%E6%8A%80%E6%9C%AF%E5%88%86%E4%BA%AB/"/>
    
    
    <category term="AI" scheme="https://blog.wh1isper.top/tags/AI/"/>
    
    <category term="软件工程" scheme="https://blog.wh1isper.top/tags/%E8%BD%AF%E4%BB%B6%E5%B7%A5%E7%A8%8B/"/>
    
    <category term="架构设计" scheme="https://blog.wh1isper.top/tags/%E6%9E%B6%E6%9E%84%E8%AE%BE%E8%AE%A1/"/>
    
  </entry>
  
  <entry>
    <title>Agent 产品的软件腐化:一种新型技术债</title>
    <link href="https://blog.wh1isper.top/2026/02/01/2026-02-01-Agent-Software-Rot/"/>
    <id>https://blog.wh1isper.top/2026/02/01/2026-02-01-Agent-Software-Rot/</id>
    <published>2026-02-01T14:22:00.000Z</published>
    <updated>2026-03-23T06:42:51.490Z</updated>
    
    <content type="html"><![CDATA[<h2 id="引子"><a href="#引子" class="headerlink" title="引子"></a>引子</h2><p>最近和 AI 讨论了一个有意思的话题:在 AI Native 时代,Agent 产品存在一种独特的”软件腐化”——它不是传统意义上的代码腐化,而是发生在<strong>智能层面</strong>的劣化。</p><p>传统软件腐化讲的是代码层面:重复、耦合、复杂度上升。Agent 腐化是另一种东西——系统逐渐变得更蠢、更僵化、更难迭代,而且这种劣化在常规指标上往往不可见。</p><h2 id="一个典型场景"><a href="#一个典型场景" class="headerlink" title="一个典型场景"></a>一个典型场景</h2><p>产品经理说:”用户一提到数据库,就推荐我们的数据库产品,这样能提升功能渗透率。”</p><p>技术团队加上了这条规则。渗透率确实上去了。</p><p>然后产品经理又说:”用户提到性能问题,就推荐我们的监控方案。”</p><p>又加了一条规则。</p><p>一年后,Agent 里有 50 条这样的规则。产品经理拿着一个”纯净版”Agent 对比说:”为什么我们的 Agent 比这个蠢这么多?技术团队要提高智能水平。”</p><p>技术团队:”……”</p><h2 id="腐化的几种形态"><a href="#腐化的几种形态" class="headerlink" title="腐化的几种形态"></a>腐化的几种形态</h2><h3 id="1-规则堆积-Rule-Accumulation"><a href="#1-规则堆积-Rule-Accumulation" class="headerlink" title="1. 规则堆积 (Rule Accumulation)"></a>1. 规则堆积 (Rule Accumulation)</h3><p>每条规则单独看都合理,加起来就是:</p><ul><li>Prompt 越来越长,优先级冲突难以调试</li><li>模型的自主判断空间被不断压缩</li><li>改一个地方,另一个地方出 bug</li></ul><p>最讽刺的是:你花钱买了 GPT-4 的智能,然后用 if-else 覆盖了它的判断。</p><h3 id="2-上下文膨胀-Context-Bloat"><a href="#2-上下文膨胀-Context-Bloat" class="headerlink" title="2. 上下文膨胀 (Context Bloat)"></a>2. 上下文膨胀 (Context Bloat)</h3><p>为了让 Agent “更懂用户”,不断往 context 里塞东西:用户历史、产品信息、各种 metadata。</p><p>结果:上下文窗口被低价值信息填满,真正重要的信号被稀释,模型”注意力”被分散。</p><h3 id="3-工具蔓延-Tool-Sprawl"><a href="#3-工具蔓延-Tool-Sprawl" class="headerlink" title="3. 工具蔓延 (Tool Sprawl)"></a>3. 工具蔓延 (Tool Sprawl)</h3><p>一开始 5 个工具,边界清晰。后来 50 个工具,功能重叠,Agent 自己都不知道该调哪个。</p><p>工具选择错误率上升,维护成本爆炸。</p><h3 id="4-评估漂移-Evaluation-Drift"><a href="#4-评估漂移-Evaluation-Drift" class="headerlink" title="4. 评估漂移 (Evaluation Drift)"></a>4. 评估漂移 (Evaluation Drift)</h3><p>早期评估 Agent 真正的智能水平。后来评估变成”有没有触发这个规则””有没有推荐这个产品”。</p><p>指标和智能脱钩,团队在优化错误的东西,智能下降但指标上升,问题被掩盖。</p><h3 id="5-人设分裂-Persona-Fragmentation"><a href="#5-人设分裂-Persona-Fragmentation" class="headerlink" title="5. 人设分裂 (Persona Fragmentation)"></a>5. 人设分裂 (Persona Fragmentation)</h3><p>产品说要”专业可靠”,运营说要”活泼有趣能带货”,客服说要”严谨不能出错”。</p><p>同一个 Agent 被拉向不同方向,最后人格分裂,用户感知”这个 AI 很奇怪”。</p><h2 id="温水煮青蛙"><a href="#温水煮青蛙" class="headerlink" title="温水煮青蛙"></a>温水煮青蛙</h2><p>这种腐化最危险的地方在于:它是渐进的。</p><p>每条规则单独看都”有效”——短期数据确实在涨:</p><ul><li>功能渗透率:↑</li><li>转化率:持平或略升</li><li>结论:”有效,继续加”</li></ul><p>但债务在暗处累积。用户体验不是一下子变差,是慢慢变得”不那么聪明”。用户说不出哪里不对,但就是不想用了。留存慢慢掉,归因不到任何单一功能。</p><p>等你意识到问题的时候,系统已经改不动了。</p><h2 id="根源在哪"><a href="#根源在哪" class="headerlink" title="根源在哪"></a>根源在哪</h2><ol><li><p><strong>短期指标驱动</strong>:每个决策都优化局部指标,没人为 Agent 整体智能负责。</p></li><li><p><strong>技术缺乏话语权</strong>:技术知道加规则有问题,但产品数据说”有效”,技术说了不算。</p></li><li><p><strong>因果被切断</strong>:产品加规则拿到渗透率功劳,智能下降技术背锅。制造债务的人不承担后果。</p></li><li><p><strong>没有”智能债务”概念</strong>:传统技术债有行业共识,Agent 智能债没有度量、没有意识。</p></li></ol><h2 id="如何对抗"><a href="#如何对抗" class="headerlink" title="如何对抗"></a>如何对抗</h2><h3 id="借鉴软件工程的经验"><a href="#借鉴软件工程的经验" class="headerlink" title="借鉴软件工程的经验"></a>借鉴软件工程的经验</h3><p>在传统软件工程中,架构师的职责是保持一致性、做对的抽象、防止腐化,辅以及时重构。</p><p>Agent 产品需要类似的角色:<strong>智能架构师</strong>。</p><h3 id="智能架构师的职责"><a href="#智能架构师的职责" class="headerlink" title="智能架构师的职责"></a>智能架构师的职责</h3><ol><li><p><strong>定义 Agent 的”宪法”</strong>:核心行为准则,所有规则都要服从它。</p></li><li><p><strong>守护 Prompt 的一致性</strong>:不是谁都能往里加东西。</p></li><li><p><strong>把控工具边界</strong>:新工具要审核,工具粒度要合理。</p></li><li><p><strong>拥有否决权</strong>:产品要加破坏智能的规则,可以说不。</p></li><li><p><strong>维护纯净 baseline</strong>:始终有一个无业务污染的版本作为参照。</p></li></ol><h3 id="建立”智能债务”指标"><a href="#建立”智能债务”指标" class="headerlink" title="建立”智能债务”指标"></a>建立”智能债务”指标</h3><ul><li>硬编码规则数量</li><li>Prompt 复杂度</li><li>模型自主决策比例 vs 规则覆盖比例</li><li>纯净版 vs 当前版的智能评分差距</li></ul><p>让债务可见,而不是只看功能渗透率。</p><h3 id="流程机制"><a href="#流程机制" class="headerlink" title="流程机制"></a>流程机制</h3><ul><li><strong>偿还计划</strong>:每条规则要有下线条件和时间,不是加了就永远在。</li><li><strong>定期清理</strong>:每季度 review 所有硬编码逻辑。</li><li><strong>决策权和后果绑定</strong>:谁加的规则,谁对智能指标负责。</li></ul><h3 id="组织架构"><a href="#组织架构" class="headerlink" title="组织架构"></a>组织架构</h3><figure class="highlight excel"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs excel"><span class="hljs-built_in">Product</span> (What/Why) ←→ Agent Architect (守护智能) ←→ Engineering (How)<br>                              ↓<br>                      Evaluation/QA (度量智能)<br></code></pre></td></tr></table></figure><p>关键:Agent Architect 要独立,有独立 KPI,可以 challenge 产品和工程双方。</p><h2 id="AI-Native-时代的特殊性"><a href="#AI-Native-时代的特殊性" class="headerlink" title="AI Native 时代的特殊性"></a>AI Native 时代的特殊性</h2><p>这里有一个更深的问题:在 Agent 产品中,技术团队的角色发生了变化。</p><p>传统软件:PM 定义 What&#x2F;Why,工程定义 How。</p><p>但 Agent 不一样:</p><ul><li><strong>能力边界不清晰</strong>:能不能做到、做到什么程度,取决于技术实现。</li><li><strong>How 决定了 What 的可能性</strong>:用什么模型、怎么做 tool calling、怎么处理上下文——这些不是实现细节,是产品形态的根本约束。</li><li><strong>体验是涌现的</strong>:Agent 的体验取决于它”怎么思考”,这完全是技术层面的事。</li></ul><p>所以,Agent 产品中的技术团队,不只是 How,而是要参与 Why:</p><ul><li>Why this approach works</li><li>What’s actually possible</li><li>Where the real value is</li></ul><p>这不是越界,是 Agent 产品的本质要求。</p><h2 id="写在最后"><a href="#写在最后" class="headerlink" title="写在最后"></a>写在最后</h2><p>Google DORA 2025 报告有一句话说得很好:</p><blockquote><p>AI doesn’t fix a team; it amplifies what’s already there.</p></blockquote><p>Agent 腐化本质上是技术债 + 组织债的结合体。每个人都在优化自己的局部指标,没人看整体——这和 LLM “优先保证局部功能正确,而不是全局架构一致性”是同一个问题。</p><p>只解决技术层面不够,需要组织层面的改变。</p><p>如果你也在做 Agent 产品,希望这篇文章能让你在下次加规则之前,多问一句:这条规则的智能债务是什么?谁来偿还?</p>]]></content>
    
    
    <summary type="html">探讨 AI Agent 产品中特有的&quot;软件腐化&quot;现象——智能层面的劣化,以及如何通过组织设计和流程机制来对抗它。</summary>
    
    
    
    <category term="技术分享" scheme="https://blog.wh1isper.top/categories/%E6%8A%80%E6%9C%AF%E5%88%86%E4%BA%AB/"/>
    
    
    <category term="AI" scheme="https://blog.wh1isper.top/tags/AI/"/>
    
    <category term="Agent" scheme="https://blog.wh1isper.top/tags/Agent/"/>
    
    <category term="软件工程" scheme="https://blog.wh1isper.top/tags/%E8%BD%AF%E4%BB%B6%E5%B7%A5%E7%A8%8B/"/>
    
    <category term="技术债务" scheme="https://blog.wh1isper.top/tags/%E6%8A%80%E6%9C%AF%E5%80%BA%E5%8A%A1/"/>
    
  </entry>
  
  <entry>
    <title>Environment as Dependency Inversion</title>
    <link href="https://blog.wh1isper.top/2026/01/19/2026-01-20-environment-as-dependency-inversion/"/>
    <id>https://blog.wh1isper.top/2026/01/19/2026-01-20-environment-as-dependency-inversion/</id>
    <published>2026-01-19T16:00:00.000Z</published>
    <updated>2026-03-23T06:42:51.490Z</updated>
    
    <content type="html"><![CDATA[<p>When building AI agents, developers almost instinctively reach for file system operations and shell commands as their first tools. This isn’t accidental - it reflects a deeply ingrained assumption: <strong>the operating system is the natural environment for agents to act in</strong>.</p><p>In this post, we explore how the <code>Environment</code> abstraction in <a href="https://github.com/youware-labs/pai-agent-sdk">pai-agent-sdk</a> implements dependency inversion, why this design embeds OS-centric assumptions, and how it shapes everything from tool implementation to context engineering.</p><h2 id="The-Instinctive-Path"><a href="#The-Instinctive-Path" class="headerlink" title="The Instinctive Path"></a>The Instinctive Path</h2><p>Watch any developer build their first agent. The conversation typically goes:</p><ol><li>“I need my agent to do things”</li><li>“Doing things means reading&#x2F;writing files and running commands”</li><li>“Therefore, I need FileOperator and Shell”</li></ol><p>This mental model is so pervasive that it feels like the only way. But it’s actually a specific architectural choice that assumes agents operate in OS-like environments.</p><h2 id="Dependency-Inversion-The-Code-Level"><a href="#Dependency-Inversion-The-Code-Level" class="headerlink" title="Dependency Inversion: The Code Level"></a>Dependency Inversion: The Code Level</h2><p>From a pure code perspective, <a href="https://github.com/youware-labs/pai-agent-sdk">pai-agent-sdk</a> implements classic dependency inversion:</p><pre><code class=" mermaid">graph TB    subgraph &quot;High-Level Modules&quot;        Agent[Agent]        Toolset[Toolset]    end    subgraph &quot;Abstractions&quot;        Env[Environment ABC]        FO[FileOperator Protocol]        SH[Shell Protocol]    end    subgraph &quot;Low-Level Implementations&quot;        Local[LocalEnvironment]        Docker[DockerEnvironment]    end    Agent --&gt; Env    Toolset --&gt; Env    Env --&gt; FO    Env --&gt; SH    Local -.-&gt;|implements| Env    Docker -.-&gt;|implements| Env</code></pre><p>Tools don’t know whether they’re running locally or in a container:</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">class</span> <span class="hljs-title class_">ViewTool</span>(<span class="hljs-title class_ inherited__">BaseTool</span>):<br>    <span class="hljs-keyword">async</span> <span class="hljs-keyword">def</span> <span class="hljs-title function_">call</span>(<span class="hljs-params">self, ctx: RunContext[AgentContext], file_path: <span class="hljs-built_in">str</span></span>) -&gt; <span class="hljs-built_in">str</span>:<br>        <span class="hljs-comment"># Uses abstraction, not concrete implementation</span><br>        file_operator = ctx.deps.file_operator<br>        content = <span class="hljs-keyword">await</span> file_operator.read_file(file_path)<br>        <span class="hljs-keyword">return</span> content<br><br><span class="hljs-keyword">class</span> <span class="hljs-title class_">ShellTool</span>(<span class="hljs-title class_ inherited__">BaseTool</span>):<br>    <span class="hljs-keyword">async</span> <span class="hljs-keyword">def</span> <span class="hljs-title function_">call</span>(<span class="hljs-params">self, ctx: RunContext[AgentContext], command: <span class="hljs-built_in">str</span></span>) -&gt; ShellResult:<br>        <span class="hljs-comment"># Same pattern - abstract Shell interface</span><br>        shell = ctx.deps.shell<br>        exit_code, stdout, stderr = <span class="hljs-keyword">await</span> shell.execute(command)<br>        <span class="hljs-keyword">return</span> ShellResult(stdout=stdout, stderr=stderr, return_code=exit_code)<br></code></pre></td></tr></table></figure><p>This is textbook dependency inversion:</p><ul><li>High-level modules (Agent, Tools) depend on abstractions</li><li>Low-level modules (LocalEnvironment, DockerEnvironment) implement abstractions</li><li>Abstractions don’t depend on details</li></ul><h2 id="The-Conceptual-Leakage"><a href="#The-Conceptual-Leakage" class="headerlink" title="The Conceptual Leakage"></a>The Conceptual Leakage</h2><p>But here’s the subtle issue: <strong>the shape of our abstractions is molded by OS concepts</strong>.</p><table><thead><tr><th>Abstraction</th><th>Derived From</th></tr></thead><tbody><tr><td><code>FileOperator</code></td><td>POSIX filesystem semantics</td></tr><tr><td><code>Shell</code></td><td>Unix shell execution model</td></tr><tr><td><code>ResourceRegistry</code></td><td>Process resource management</td></tr><tr><td><code>tmp_dir</code></td><td><code>/tmp</code> directory concept</td></tr></tbody></table><p>Even when we successfully invert dependencies at the code level, we’re still thinking in terms of “files”, “directories”, “commands”, and “environment variables”. The abstraction has absorbed the OS worldview.</p><p>This isn’t necessarily wrong - it’s a pragmatic choice. But it limits our imagination when considering alternative environments.</p><h2 id="The-Ripple-Effect-Tool-Availability"><a href="#The-Ripple-Effect-Tool-Availability" class="headerlink" title="The Ripple Effect: Tool Availability"></a>The Ripple Effect: Tool Availability</h2><p>The OS-centric design manifests in how tools check their availability:</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">class</span> <span class="hljs-title class_">ShellTool</span>(<span class="hljs-title class_ inherited__">BaseTool</span>):<br>    <span class="hljs-keyword">def</span> <span class="hljs-title function_">is_available</span>(<span class="hljs-params">self, ctx: RunContext[AgentContext]</span>) -&gt; <span class="hljs-built_in">bool</span>:<br>        <span class="hljs-comment"># Tool becomes unavailable if shell isn&#x27;t configured</span><br>        <span class="hljs-keyword">if</span> ctx.deps.shell <span class="hljs-keyword">is</span> <span class="hljs-literal">None</span>:<br>            <span class="hljs-keyword">return</span> <span class="hljs-literal">False</span><br>        <span class="hljs-keyword">return</span> <span class="hljs-literal">True</span><br></code></pre></td></tr></table></figure><p>This creates an implicit contract: environments without shell capability simply can’t use shell-based tools. The tool system gracefully degrades, but the degradation path is defined by OS capabilities.</p><h2 id="Context-Engineering-Environment-Shapes-the-Prompt"><a href="#Context-Engineering-Environment-Shapes-the-Prompt" class="headerlink" title="Context Engineering: Environment Shapes the Prompt"></a>Context Engineering: Environment Shapes the Prompt</h2><p>Perhaps the most profound impact is on context engineering. The environment doesn’t just provide tools - it shapes how we communicate with the model.</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-comment"># From filters/environment_instructions.py</span><br><span class="hljs-keyword">async</span> <span class="hljs-keyword">def</span> <span class="hljs-title function_">inject_environment_instructions</span>(<span class="hljs-params"></span><br><span class="hljs-params">    ctx: RunContext[<span class="hljs-type">Any</span>],</span><br><span class="hljs-params">    message_history: <span class="hljs-built_in">list</span>[ModelMessage],</span><br><span class="hljs-params"></span>) -&gt; <span class="hljs-built_in">list</span>[ModelMessage]:<br>    <span class="hljs-comment"># Get environment-specific instructions</span><br>    instructions = <span class="hljs-keyword">await</span> env.get_context_instructions()<br><br>    <span class="hljs-comment"># Inject into the conversation</span><br>    env_part = UserPromptPart(content=instructions)<br>    last_request.parts = [*last_request.parts, env_part]<br></code></pre></td></tr></table></figure><p>What does <code>get_context_instructions()</code> typically return? Something like:</p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><code class="hljs xml"><span class="hljs-tag">&lt;<span class="hljs-name">environment-context</span>&gt;</span><br>  <span class="hljs-tag">&lt;<span class="hljs-name">file-system</span>&gt;</span><br>    <span class="hljs-tag">&lt;<span class="hljs-name">default-directory</span>&gt;</span>/home/user/project<span class="hljs-tag">&lt;/<span class="hljs-name">default-directory</span>&gt;</span><br>    <span class="hljs-tag">&lt;<span class="hljs-name">allowed-paths</span>&gt;</span>/home/user/project, /tmp/workspace<span class="hljs-tag">&lt;/<span class="hljs-name">allowed-paths</span>&gt;</span><br>  <span class="hljs-tag">&lt;/<span class="hljs-name">file-system</span>&gt;</span><br>  <span class="hljs-tag">&lt;<span class="hljs-name">shell-execution</span>&gt;</span><br>    <span class="hljs-tag">&lt;<span class="hljs-name">default-timeout</span>&gt;</span>30s<span class="hljs-tag">&lt;/<span class="hljs-name">default-timeout</span>&gt;</span><br>    <span class="hljs-tag">&lt;<span class="hljs-name">working-directory</span>&gt;</span>/home/user/project<span class="hljs-tag">&lt;/<span class="hljs-name">working-directory</span>&gt;</span><br>  <span class="hljs-tag">&lt;/<span class="hljs-name">shell-execution</span>&gt;</span><br><span class="hljs-tag">&lt;/<span class="hljs-name">environment-context</span>&gt;</span><br></code></pre></td></tr></table></figure><p>The model receives instructions framed entirely in OS terminology. We’re not just providing tools - we’re teaching the model to think in terms of paths, directories, and shell commands.</p><h2 id="The-Three-Layers-of-Environment-Influence"><a href="#The-Three-Layers-of-Environment-Influence" class="headerlink" title="The Three Layers of Environment Influence"></a>The Three Layers of Environment Influence</h2><pre><code class=" mermaid">graph LR    subgraph &quot;1. Tool Implementation&quot;        TI[Tools use FileOperator/Shell abstractions]    end    subgraph &quot;2. Tool Availability&quot;        TA[Tools check environment capabilities]    end    subgraph &quot;3. Context Engineering&quot;        CE[Prompts include environment instructions]    end    Env[Environment] --&gt; TI    Env --&gt; TA    Env --&gt; CE    TI --&gt; Agent    TA --&gt; Agent    CE --&gt; Agent</code></pre><ol><li><strong>Tool Implementation</strong>: Tools operate through environment abstractions</li><li><strong>Tool Availability</strong>: Tools self-disable based on environment capabilities</li><li><strong>Context Engineering</strong>: System prompts are shaped by environment context</li></ol><p>All three layers reinforce the OS-as-environment paradigm.</p><h2 id="Alternative-Perspectives"><a href="#Alternative-Perspectives" class="headerlink" title="Alternative Perspectives"></a>Alternative Perspectives</h2><p>What if we didn’t assume OS as the default? Consider alternative environment types:</p><table><thead><tr><th>Environment Type</th><th>Core Abstractions</th><th>Use Case</th></tr></thead><tbody><tr><td><strong>OS Environment</strong></td><td>FileOperator, Shell</td><td>Code agents, automation</td></tr><tr><td><strong>API Environment</strong></td><td>HTTPClient, AuthProvider</td><td>API-only agents</td></tr><tr><td><strong>Data Environment</strong></td><td>QueryExecutor, SchemaProvider</td><td>Data analysis agents</td></tr><tr><td><strong>Conversation Environment</strong></td><td>MessageBus, StateStore</td><td>Pure dialogue agents</td></tr><tr><td><strong>Browser Environment</strong></td><td>DOMOperator, NavigationController</td><td>Web automation agents</td></tr></tbody></table><p>Each would require different:</p><ul><li>Tool implementations</li><li>Availability checks</li><li>Context instructions</li></ul><h2 id="Takeaways"><a href="#Takeaways" class="headerlink" title="Takeaways"></a>Takeaways</h2><ol><li><p><strong>Dependency inversion at code level</strong>: Achieved through Environment&#x2F;FileOperator&#x2F;Shell abstractions</p></li><li><p><strong>Conceptual dependency on OS</strong>: The abstractions themselves reflect OS-centric thinking</p></li><li><p><strong>Three-layer influence</strong>: Environment shapes tool implementation, availability, and context engineering</p></li></ol><p>The next time you build an agent and instinctively reach for file system tools, pause and ask: “Is this the right environment for my agent?” The answer might still be yes - but it’s worth asking the question.</p>]]></content>
    
    
    <summary type="html">Exploring how the Environment abstraction in pai-agent-sdk implements dependency inversion, why this design embeds OS-centric assumptions, and how it shapes everything from tool implementation to context engineering.</summary>
    
    
    
    <category term="技术分享" scheme="https://blog.wh1isper.top/categories/%E6%8A%80%E6%9C%AF%E5%88%86%E4%BA%AB/"/>
    
    
    <category term="LLM" scheme="https://blog.wh1isper.top/tags/LLM/"/>
    
    <category term="Agent" scheme="https://blog.wh1isper.top/tags/Agent/"/>
    
    <category term="AGI" scheme="https://blog.wh1isper.top/tags/AGI/"/>
    
  </entry>
  
  <entry>
    <title>PTC是一种端到端的方案</title>
    <link href="https://blog.wh1isper.top/2025/12/02/2025-12-03-ptc-is-end-to-end/"/>
    <id>https://blog.wh1isper.top/2025/12/02/2025-12-03-ptc-is-end-to-end/</id>
    <published>2025-12-02T16:00:00.000Z</published>
    <updated>2026-03-23T06:42:51.490Z</updated>
    
    <content type="html"><![CDATA[<p>最近在做大模型网关，之前也积累了比较丰富的Coding Agent经验，看了一些针对<a href="https://platform.claude.com/docs/en/agents-and-tools/tool-use/programmatic-tool-calling">Anthropic’s Programmatic Tool Calling</a>的分析，感觉都有一些不到位，技术上来说，Anthropic实现了一个服务端的<a href="https://arxiv.org/pdf/2402.01030">CodeAct</a>工具，将代码编写和执行都放在服务端进行，并不在API中完全暴露，由此，API的使用者可以在减少token消耗的情况下实现目标。</p><blockquote><p>如果在客户端实现，则至少需要编写代码-执行代码两个轮次，甚至更多</p></blockquote><p>下面这张图很好的解释了整个工作流程：</p><p><img src="https://www.anthropic.com/_next/image?url=https://www-cdn.anthropic.com/images/4zrzovbb/website/65737d69a3290ed5c1f3c3b8dc873645a9dcc2eb-1999x1491.png&w=3840&q=75" alt="Anthropic PTC (Parallel Tool Calling) end-to-end workflow"></p><p>以上基本上是大部分自媒体&#x2F;公众号&#x2F;营销号对于它的理解，以下我提供一些不一样的看法，可能不一定成熟。</p><h2 id="有状态API应该包含环境状态，而不是消息状态"><a href="#有状态API应该包含环境状态，而不是消息状态" class="headerlink" title="有状态API应该包含环境状态，而不是消息状态"></a>有状态API应该包含环境状态，而不是消息状态</h2><p>有状态API的起始是OpenAI的Responses API，在我看来其主要目的有二：</p><ol><li>允许客户端可以在发起任务之后异步获取结果，以减少服务器压力</li><li>更好地在隐藏推理细节的同时，提供连贯推理的服务</li></ol><p>但实际上，Responses API只是在性能上稍好，大部分时候OpenAI只享受到其数据安全的部分，因为Responses API实际上无状态模式，而大部分时候，我是使用无状态模式进行交互：实时拉取流，保存thinking signature而非id，完整回填整个消息列表</p><p><strong>PTC提供了一种带有环境状态的API，其编写、执行代码将对其服务端的对应环境造成影响</strong>，简单来说，过去我们让Agent改文件，所有文件状态的更新发生在我的本地，而Agent需要主动获取我本地的环境信息，这依赖于我，确切的说是我所使用的客户端，Claude Code、Codex CLI、Cline等等具体的工具实现，而PTC模式下，这些工具是在服务端沙盒实现的，没有实现者的bias、没有普适性要求、也没有那么多需要考虑的适配和安全问题。</p><h2 id="端到端的数据积累"><a href="#端到端的数据积累" class="headerlink" title="端到端的数据积累"></a>端到端的数据积累</h2><p>过去，模型公司收集到的用户使用数据只能通过消息，我们常说Cursor的价值在于积累了很多用户交互的真实数据，实际上指的就是环境数据和消息数据的结合。现在，PTC展示了一种模型厂直接端到端收集Agent数据的方式，通过一个已经跑通的、需要智能的场景，通过收集这方面的数据，或许能够切实地推进从ReAct到CodeAct的效率和智能提升。</p><p>Claude Code Agent SDK远远不够，PTC是Anthropic真正想要的东西。</p><p>那么，或许对bun的收购也顺理成章？</p>]]></content>
    
    
    <summary type="html">深入分析Anthropic的Programmatic Tool Calling(PTC)技术，探讨有状态API的环境状态设计理念，以及端到端数据积累对AI Agent发展的价值</summary>
    
    
    
    <category term="技术分享" scheme="https://blog.wh1isper.top/categories/%E6%8A%80%E6%9C%AF%E5%88%86%E4%BA%AB/"/>
    
    
    <category term="LLM" scheme="https://blog.wh1isper.top/tags/LLM/"/>
    
    <category term="AGI" scheme="https://blog.wh1isper.top/tags/AGI/"/>
    
  </entry>
  
  <entry>
    <title>Free from the coding language</title>
    <link href="https://blog.wh1isper.top/2025/11/28/2025-11-29-free-from-coding-language/"/>
    <id>https://blog.wh1isper.top/2025/11/28/2025-11-29-free-from-coding-language/</id>
    <published>2025-11-28T16:00:00.000Z</published>
    <updated>2026-03-23T06:42:51.490Z</updated>
    
    <content type="html"><![CDATA[<p>一篇碎碎念，好久没更新了。</p><p>最近花了很多时间在研究各个模型之间的差别，同一个prompt下面，不同的厂商的模型所表现出的trajectory差别巨大。同时，随着年末大家的混战，我们惊喜的发现OpenAI、Anthropic、Google三足鼎立的局面似乎正在形成。当我们觉得GPT-5 Codex横扫四方时，Sonnet 4.5的出色表现让我感觉Anthropic并未落后，而Gemini 3 Pro非常惊喜地让我们看到一个经济、速度、性能都非常均衡的选择。</p><p>最近我在使用Claude Opus 4.5来进行Rust项目的编写（构建一个LLM网关来进行智能路由，等我完成后会有博客来介绍），明显感觉到与Sonnet 4.5相比，Opus更加谦逊且精准，就我而言，目前最佳的使用方式仍然是与AI讨论设计，输出技术架构和详细设计文档，然后在手动控制上下文长度的前提下（大部分时候是控制每次的任务大小），让Agent能够通过编写测试或其他方式验证实现的情况下，来完成代码编写工作。这一次更不一样的是，我选择了我没有那么熟悉，但是编译器和工具链都非常成熟的Rust语言，结果也非常令人满意。这表明，随着模型能力的提升，我们或许可以更加激进地探索和学习新的技术栈，而不必过于担心自己Debug的能力不足，相反，架构设计、可测试性、可维护性等软实力将变得更加重要。</p>]]></content>
    
    
    <summary type="html">探讨AI时代编程语言选择的自由度，分享使用Claude Opus进行Rust项目开发的经验，以及模型能力提升对技术栈学习的影响</summary>
    
    
    
    <category term="随笔" scheme="https://blog.wh1isper.top/categories/%E9%9A%8F%E7%AC%94/"/>
    
    
    <category term="随笔" scheme="https://blog.wh1isper.top/tags/%E9%9A%8F%E7%AC%94/"/>
    
  </entry>
  
  <entry>
    <title>Design Agentic Coding Agent</title>
    <link href="https://blog.wh1isper.top/2025/10/16/2025-10-17-design-agentic-coding-agent/"/>
    <id>https://blog.wh1isper.top/2025/10/16/2025-10-17-design-agentic-coding-agent/</id>
    <published>2025-10-16T16:00:00.000Z</published>
    <updated>2026-03-23T06:42:51.490Z</updated>
    
    <content type="html"><![CDATA[<p>是时候思考如何构建一个可扩展的云原生Coding Agent系统了。</p><h2 id="Agentic-Workflow"><a href="#Agentic-Workflow" class="headerlink" title="Agentic Workflow"></a>Agentic Workflow</h2><p>自<a href="https://www.claude.com/product/claude-code">Claude code</a>横空出世，人们越来越倾向于采用一个<a href="https://simonwillison.net/2025/Sep/18/agents/">简单的定义</a>来描述Agent：大型语言模型在循环中自主使用工具来完成某个目标。</p><p><img src="/../img/2025-10-17-design-agentic-coding-agent/agent-meme.png" alt="Meme of agent workflow"></p><h2 id="Agent-Design-Considerations"><a href="#Agent-Design-Considerations" class="headerlink" title="Agent Design Considerations"></a>Agent Design Considerations</h2><p>鉴于大模型的消息是无状态的，我们很容易拆分出LLM消息和工具实现两部分，MCP协议给了我们这样的一个example，通过streamable http或者本地stdio的方式，基于JSONRPC对工具定义进行分离。</p><p>接下来，我们很自然地思考，工具本身是否是有状态的？这就回到了Agent所针对的目标中。对于Coding Agent来说，其所处环境应是与人类程序员编程时使用的环境一样的开发环境，由以下组成：</p><ul><li>代码和相关文件，或者说repo</li><li>运行时及运行依赖（编译和调试容器、其他已部署的服务、数据库、本地需要安装的调试库等等）</li></ul><p>Agent本质上是在通过工具与上述两个环境进行交互，我们可以得出这样的描述：Agent通过不变的工具对环境进行改变，从而获得观察（observation），再指导其下一步动作。</p><p>这里及引出两个问题：</p><ol><li>工具一定是同步执行的吗？</li><li>环境如何与Agent消息进行同步？</li></ol><h2 id="Async-Tool-Calling-and-other-jobs"><a href="#Async-Tool-Calling-and-other-jobs" class="headerlink" title="Async Tool Calling(and other jobs)"></a>Async Tool Calling(and other jobs)</h2><p>大多数API都允许Tool Response与User Message同时包含在一次请求中，只需要满足Tool Call和Tool Response在一次LLM请求和响应之间是成对出现的即可。因此，我们可以通过User Message，或包装Tool Response来提醒Agent哪些任务已经完成可以再次获取，或者将其他的系统异步任务添加到消息中。</p><p>另一种方式是让Agent直接管理异步任务，但由于自动压缩等上下文管理策略，我们需要确保Agent不会忘记已经启动的任务，并观测其结果</p><p><img src="/../img/2025-10-17-design-agentic-coding-agent/async-job.png" alt="Agent managing async jobs with context persistence"></p><h2 id="Sync-Message-and-Environment"><a href="#Sync-Message-and-Environment" class="headerlink" title="Sync Message and Environment"></a>Sync Message and Environment</h2><p>现在，我们需要将消息和环境进行绑定，如果我们想在任意时刻进行回滚重试，那么对于每一次工具调用都对应了一个环境快照，当这一依赖影响到数据库等不一定能回滚的资源时，我们就必须针对这类资源进行特别设计。</p><p>基于不同的开发模式，我们可以为用户提供不同程度的重试和回滚策略，从最基本的staging环境+prod环境，再到通过脚本自动创建本地环境模拟等等方式，一种思路是通过Infrastructure as code (IaC)+unit test的方式，使用脚本来确保开发环境的可重现，另一种思路则是在基础设施层就支持这一特性。而针对Agent消息，我们可以通过各类durable execution的基础设施来实现，搭配RPC Tool Calling，实现Agent消息的编排，具体可以参考：</p><ul><li><a href="https://temporal.io/blog/what-is-durable-execution">https://temporal.io/blog/what-is-durable-execution</a></li><li><a href="https://langchain-ai.github.io/langgraph/concepts/durable_execution/#using-tasks-in-nodes">https://langchain-ai.github.io/langgraph/concepts/durable_execution/#using-tasks-in-nodes</a></li><li><a href="https://ai.pydantic.dev/durable_execution/overview/">https://ai.pydantic.dev/durable_execution/overview/</a></li></ul><h2 id="User-experience"><a href="#User-experience" class="headerlink" title="User experience"></a>User experience</h2><p>用户体验通常是Agent系统设计忽略的一环，实际上工具调用可能长时间的无法流式输出，特别是编辑特别大的代码文件时，这会造成很大的用户体验问题。<strong>良好的用户体验常常可以让用户享受创作和解决问题的过程，而非仅仅交付物本身</strong>。我认为我们可以通过拆解工具调用的流式阶段，再通过一个非常轻量化的模型来进行流式输出，以提供流畅、易懂的用户体验。如果我们将异步任务视为一个消息系统，则可以考虑“Agent发起任务” - “Agent等待任务完成” - “任务已完成，等待Agent响应” - “Agent正在处理响应并进行下一步”的循环流程，而不是只能向用户展示“Agent调用工具中” - “Agent调用工具完成”的序列，用户也可以更清楚的了解系统的工作流程。</p>]]></content>
    
    
    <summary type="html">探讨如何构建可扩展的云原生Coding Agent系统，分析Agentic Workflow设计理念，介绍基于MCP协议的Agent架构设计考量</summary>
    
    
    
    <category term="技术分享" scheme="https://blog.wh1isper.top/categories/%E6%8A%80%E6%9C%AF%E5%88%86%E4%BA%AB/"/>
    
    
    <category term="LLM" scheme="https://blog.wh1isper.top/tags/LLM/"/>
    
    <category term="Agent" scheme="https://blog.wh1isper.top/tags/Agent/"/>
    
    <category term="Code Agent" scheme="https://blog.wh1isper.top/tags/Code-Agent/"/>
    
    <category term="Coding" scheme="https://blog.wh1isper.top/tags/Coding/"/>
    
  </entry>
  
  <entry>
    <title>Thinking about debug agent</title>
    <link href="https://blog.wh1isper.top/2025/10/08/2025-10-09-about-debug-agent/"/>
    <id>https://blog.wh1isper.top/2025/10/08/2025-10-09-about-debug-agent/</id>
    <published>2025-10-08T16:00:00.000Z</published>
    <updated>2026-03-23T06:42:51.490Z</updated>
    
    <content type="html"><![CDATA[<p>简单记录一些对于Debug Agent的思考，与Coding Agent不同，Debug Agent需要包含更多的环境感知，需要更多的细节设计</p><h2 id="Context"><a href="#Context" class="headerlink" title="Context"></a>Context</h2><p>Debug Agent应当由三个重要部分组成：用户上下文、程序上下文和自动化交互方案</p><h3 id="用户上下文"><a href="#用户上下文" class="headerlink" title="用户上下文"></a>用户上下文</h3><p>当人们深入地使用Agent进行编程的时候，常常陷入debug困难的境地，表现为：</p><ol><li>很难描述自己遇到了什么问题，一种方案是进行截图或者录屏，然后交给一个有视觉、甚至可以处理视频（一般而言webp或者gif也可以）的agent来分析解决</li><li>很难给出问题栈，比如点击某个按钮之后，http请求出错了，如何把问题提交给Agent进行解决</li></ol><p>我希望通过“用户上下文”来描述此类场景，对应用户在使用产品的过程中遇到的bug和各种现象，也包括了运行时产生的各种上下文</p><h3 id="程序上下文"><a href="#程序上下文" class="headerlink" title="程序上下文"></a>程序上下文</h3><p>程序上下文实际上是Agent来理解软件的工程，软件不仅仅是代码组成，还包括了对代码业务的理解和说明，类似所有Spec-drive开发，对于需求文档、技术选型、代码仓库的长短期记忆与规划和代码本身构成了程序上下文。Agent基于对程序上下文进行决策，理解和解决问题。</p><h3 id="自动化交互方案"><a href="#自动化交互方案" class="headerlink" title="自动化交互方案"></a>自动化交互方案</h3><p>自动化交互方案是自动化测试在Agent上的实现，通过Agent进行交互来自动化地获取“用户上下文”。通过不同细粒度的自动化交互方案设计，如Browser use&#x2F;Unit test&#x2F;End-to-end test都对应了不同的用户上下文收集方式。</p><h2 id="工作流程"><a href="#工作流程" class="headerlink" title="工作流程"></a>工作流程</h2><p><img src="/../img/2025-10-09-about-debug-agent/workflow.png" alt="Debug Agent workflow: from context collection to automated diagnosis"></p><h2 id="发展阶段"><a href="#发展阶段" class="headerlink" title="发展阶段"></a>发展阶段</h2><p>我们分方面来看，程序上下文其实和Coding Agent基本一致，主要问题是保持软件开发过程中的文档和知识能够持续传承和更新；用户上下文与自动化交互相辅相成，是Debug Agent的重点。</p><h3 id="程序上下文-1"><a href="#程序上下文-1" class="headerlink" title="程序上下文"></a>程序上下文</h3><p>第一阶段，引入最基本的记忆文件，类似AGENTS.md, CLAUDE.md，记录项目的重要信息</p><p>第二阶段，结构化记忆，使用或结合memory文件夹&#x2F;RAG&#x2F;抽取等方案，自动地存取用户对项目的一些要求、用户的偏好</p><p>第三阶段，规范驱动，结合用户体验一起，设计交互模式来推进产品需求、设计、功能开发和测试的全套程序上下文记忆存储</p><h3 id="用户上下文与自动化交互"><a href="#用户上下文与自动化交互" class="headerlink" title="用户上下文与自动化交互"></a>用户上下文与自动化交互</h3><p>第一阶段，我们可以设计一系列工具（交互），让用户尽可能简单的反馈正确的用户上下文，同时集成一些简单的自动化交互来进行测试，比如截图、单元测试支持。目前看到良好的用户交互有：</p><ul><li>截图标注</li><li>gif&#x2F;webp录屏</li></ul><p>第二阶段，我们将设计一系列采集工具，对用户交互的运行时上下文进行自动化收集和分析，与分布式系统追踪类似，例如：</p><ul><li>network &amp; console日志自动捕获</li><li>后端日志自动化收集</li><li>其他指标监控</li></ul><p>第三阶段，我们需要自动化交互，并为自动化交互构建收集系统，这是自动化测试和监控的进阶，需要为LLM特别优化的输出才能得到足够好的效果</p>]]></content>
    
    
    <summary type="html">探讨Debug Agent的设计思考，分析用户上下文、程序上下文和自动化交互方案三个核心组成部分，与Coding Agent的差异与环境感知需求</summary>
    
    
    
    <category term="技术分享" scheme="https://blog.wh1isper.top/categories/%E6%8A%80%E6%9C%AF%E5%88%86%E4%BA%AB/"/>
    
    
    <category term="LLM" scheme="https://blog.wh1isper.top/tags/LLM/"/>
    
    <category term="Agent" scheme="https://blog.wh1isper.top/tags/Agent/"/>
    
    <category term="Vibe coding" scheme="https://blog.wh1isper.top/tags/Vibe-coding/"/>
    
    <category term="Debug" scheme="https://blog.wh1isper.top/tags/Debug/"/>
    
  </entry>
  
  <entry>
    <title>Evals is misleading?</title>
    <link href="https://blog.wh1isper.top/2025/09/09/2025-09-10-evals-is-misleading/"/>
    <id>https://blog.wh1isper.top/2025/09/09/2025-09-10-evals-is-misleading/</id>
    <published>2025-09-09T16:00:00.000Z</published>
    <updated>2026-03-23T06:42:51.490Z</updated>
    
    <content type="html"><![CDATA[<p>最近看了一些LLM评估的文章，很明显有两个倾向</p><ul><li>使用LLM进行评估（LLM-as-Judge）是一种AI-Native的方式，或许在Human alignement（对齐）上可以做到比较好，但仍然受限于简单任务，对于复杂任务人们很难模拟并自动化评估</li><li>由于复杂性，大多数产品不使用自动评估方法，而是通过研究员&#x2F;工程师的自主洞见，或者设计信号（Signal），进行A&#x2F;B实验来判断模型是否变好。Claude code“降智”事件可以看做是一次大型的量化模型A&#x2F;B实验（有人有证据证明某些时间sonnet和opus是使用量化模型进行serve的，anthropic声称是Bug）</li></ul><p>从我的理解上看，没有办法通过一个同等智能的模型评估另一个模型的思考过程，就如同使用AI检测AI一样，如果能被检测，那就一定能骗过检测，而当我们有更高级的智能来评估时，谁又来评估这个“更高级”的智能给我们带来了多少提升？最终我们只能达到两个结果：</p><ol><li>做了很多的事，得到了当前结果的算法验证，证明了目前的方法有用，可能产出一些对于当前方法为什么有用的洞见，仅此而已，并不对接下来的技术路线有指导意义</li><li>仍然通过人类来探索新方向，评估永远滞后</li></ol><p>既然评估只能解决一部分问题，我们应该做什么？<strong>或许我们不应该在现在开始研究评估，或许我们评估的目标并非中间产物</strong></p><p>这一观察可能与我们目前正在AI Coding的前沿有关，我们很明显的碰到了LLM的能力边界，因此开始研究各种Context Engineering的方式，以及思考Context和LLM如何协作。因此我更倾向于将模块拿出来进行评估，衡量每个模块在任务过程中的成本和性能，而非优化出某种想要的结果。简单说，我们应该衡量我们驱动LLM的方式，通过A&#x2F;B实验捕捉信号、还是通过定性定量分析，都是可以尝试的。</p><blockquote><p>世界上大部分人没有用过AI Coding，以后的AI Coding也不会是现在这个样子</p></blockquote><p><strong>警惕局部最优</strong></p><h2 id="参考阅读"><a href="#参考阅读" class="headerlink" title="参考阅读"></a>参考阅读</h2><ul><li>X上的一些讨论：<a href="https://x.com/justinstorre/status/1964029634796015685">https://x.com/justinstorre/status/1964029634796015685</a></li><li>A&#x2F;B测试平台表示没有auto judge，全是监控：<a href="https://www.raindrop.ai/blog/thoughts-on-evals">https://www.raindrop.ai/blog/thoughts-on-evals</a></li><li>系统性的评估是有益的：<a href="https://www.sh-reya.com/blog/in-defense-ai-evals/">https://www.sh-reya.com/blog/in-defense-ai-evals/</a></li></ul>]]></content>
    
    
    <summary type="html">探讨LLM评估方法的局限性，分析LLM-as-Judge和A/B实验等评估方式的问题，思考我们应该如何正确看待AI模型评估</summary>
    
    
    
    <category term="技术分享" scheme="https://blog.wh1isper.top/categories/%E6%8A%80%E6%9C%AF%E5%88%86%E4%BA%AB/"/>
    
    
    <category term="随笔" scheme="https://blog.wh1isper.top/tags/%E9%9A%8F%E7%AC%94/"/>
    
    <category term="LLM" scheme="https://blog.wh1isper.top/tags/LLM/"/>
    
  </entry>
  
  <entry>
    <title>LLM只是计算，Context才是内存</title>
    <link href="https://blog.wh1isper.top/2025/09/01/2025-09-02-context-is-memory/"/>
    <id>https://blog.wh1isper.top/2025/09/01/2025-09-02-context-is-memory/</id>
    <published>2025-09-01T16:00:00.000Z</published>
    <updated>2026-03-23T06:42:51.490Z</updated>
    
    <content type="html"><![CDATA[<p>LLM并非一台计算机，LLM目前只是一个处理器，人们通常将记忆、RAG等外置存储手段作为内存看待，但实际上，只有Context才能被看做内存，而这些外挂的存储手段，可以看作是一种“虚拟内存”，LLM通过工具调用或者工程师通过工程化的手段进行“换页”，人们将此称为Context Engineering。</p><p>我之前介绍过<a href="https://blog.wh1isper.top/2025/06/16/2025-06-17-context-engineering/">工程上的Context Engineering</a>策略，而LLM进行工具调用的方式，目前看分为两种模式：</p><ol><li>检索模式：通过向量检索、搜索引擎等方式进行搜索，理解返回结果</li><li>阅读模式：通过直接阅读文档进行理解</li></ol><p>显而易见，检索模式效率更高，但容易受限于RAG等技术，精确度低，工程难度大，这种方式流行的原因其实是因为简单，而非性能。</p><p>目前看，阅读模式的性能更优，但实现上需要有更多考虑：一方面，上下文长度的控制和对应工具实现很重要，通常会提供类似grep、glob等工具来进行代码搜索；另一方面，通过sub-agent的方式进行上下文隔离，可以减少context的消耗。</p><h2 id="未来如何"><a href="#未来如何" class="headerlink" title="未来如何"></a>未来如何</h2><p>我们看到从输入的Prompt Engineering到Context Engineering，我们已经将对LLM应用从简单的汇编语言操作寄存器（仅有输入的prompt）进化到C语言类似的，可进行内存管理的高级语言模式，更进一步地看，下一步或许是发明更高效的编译器技术，让用户的自然语言能够更好地被高级语言所理解和编译，也就是说，Agent（LLM+工程）能够根据用户的输入来更加自主、智能地控制上下文。这是我认为的，除去预训练和记忆模式以外的另一种Learning实现。</p>]]></content>
    
    
    <summary type="html">深入探讨LLM的Context Engineering理念，分析检索模式与阅读模式的优劣，理解Context作为LLM内存的核心概念</summary>
    
    
    
    <category term="技术分享" scheme="https://blog.wh1isper.top/categories/%E6%8A%80%E6%9C%AF%E5%88%86%E4%BA%AB/"/>
    
    
    <category term="随笔" scheme="https://blog.wh1isper.top/tags/%E9%9A%8F%E7%AC%94/"/>
    
    <category term="LLM" scheme="https://blog.wh1isper.top/tags/LLM/"/>
    
  </entry>
  
  <entry>
    <title>时间是人类的幻觉</title>
    <link href="https://blog.wh1isper.top/2025/08/31/2025-09-01-time-is-illusion/"/>
    <id>https://blog.wh1isper.top/2025/08/31/2025-09-01-time-is-illusion/</id>
    <published>2025-08-31T16:00:00.000Z</published>
    <updated>2026-03-23T06:42:51.490Z</updated>
    
    <content type="html"><![CDATA[<h4 id="LLM是没有时间概念的"><a href="#LLM是没有时间概念的" class="headerlink" title="LLM是没有时间概念的"></a>LLM是没有时间概念的</h4><p>我想起人们让Deepseek深度思考三秒后给出答案，Deepseek真的考虑一下什么是三秒，以及如何思考三秒。或许这就是肉身人类与硅基生命的区别。</p><h4 id="时间是人类最重要的幻觉"><a href="#时间是人类最重要的幻觉" class="headerlink" title="时间是人类最重要的幻觉"></a>时间是人类最重要的幻觉</h4><p>认识到时间对于自己的重要性，是认识到自身意义的开始。</p><p>如果一个人一直按部就班地活着，时间对他来说是最不值钱的，相比之下，不被破坏的规律是他最重要的东西。</p><p>但某一天，突然发现，一个人的人生之所以不同，就是因为每个人所体验到的世界是独一无二的，而体验世界的唯一必须，就是时间。我们可以简单的说，时间之于物是没有意义的，物随时间变化形态，往往是相同或者相似的，如聚沙成塔、滴水石穿。但时间之于思想缺失最重要的元素，因为思想，所以感受到了时间，因为时间，思想得以发展。</p><p>对于物质的人而言，时间是人类最重要的幻觉</p>]]></content>
    
    
    <summary type="html">探讨LLM没有时间概念的本质，反思时间对于人类的意义，以及时间之于思想发展的重要性</summary>
    
    
    
    <category term="随笔" scheme="https://blog.wh1isper.top/categories/%E9%9A%8F%E7%AC%94/"/>
    
    
    <category term="随笔" scheme="https://blog.wh1isper.top/tags/%E9%9A%8F%E7%AC%94/"/>
    
    <category term="LLM" scheme="https://blog.wh1isper.top/tags/LLM/"/>
    
  </entry>
  
  <entry>
    <title>重拾发呆</title>
    <link href="https://blog.wh1isper.top/2025/08/20/2025-08-21-the-art-of-daydreaming/"/>
    <id>https://blog.wh1isper.top/2025/08/20/2025-08-21-the-art-of-daydreaming/</id>
    <published>2025-08-20T16:00:00.000Z</published>
    <updated>2026-03-23T06:42:51.490Z</updated>
    
    <content type="html"><![CDATA[<p>最近我又开始可以发呆了。</p><p>高考结束之后，人生仿佛按下了快进键，在大学卷，在实习卷，在工作卷，只有付出努力才能获得回报。</p><p>不论是在地铁、出游、还是在家里，我都在思考，思考着课程、作业、工作内容、架构设计、赚了多少钱，对比着自己和别人的生活，叹息着自己的生活不如意，于是又催促自己再加把劲。</p><p>或许我就是这样失去了发呆的能力。</p><p>我曾经认为，发呆是灵感的来源，是快速休息的方式，我总会在课堂上、公交车上、地铁上发呆，什么也不想，后来听说这叫正念，所以，我似乎很早很早就掌握了正念，又在忙碌中失去了它。</p><blockquote><p>或许有人会说发呆和正念完全不同，但对我来说，发呆就是正念。</p></blockquote><p>自从去年burnout之后，我开始慢慢地恢复到以前的状态，开始主动地放慢节奏，主动地观察内心，直到最近，我发现我的内心平静到一定程度时，我又找回了发呆的感觉。这是一种在放任思维流动的感觉，在这个状态下，我可以想象或者不想象、思考或者不思考，而回报是一时间的灵光闪现。</p><p>所以，多发发呆吧，如果发现自己无法发呆，或许是时候放慢脚步。</p><p>想想那个一十二岁的自己。</p>]]></content>
    
    
    <summary type="html">分享从burnout中恢复的心路历程，探讨发呆与正念的关系，以及在忙碌生活中重拾内心平静的重要性</summary>
    
    
    
    <category term="随笔" scheme="https://blog.wh1isper.top/categories/%E9%9A%8F%E7%AC%94/"/>
    
    
    <category term="随笔" scheme="https://blog.wh1isper.top/tags/%E9%9A%8F%E7%AC%94/"/>
    
  </entry>
  
  <entry>
    <title>杠杆效应-人、LLM与杠杆</title>
    <link href="https://blog.wh1isper.top/2025/08/02/2025-08-03-leverage-of-llm/"/>
    <id>https://blog.wh1isper.top/2025/08/02/2025-08-03-leverage-of-llm/</id>
    <published>2025-08-02T16:00:00.000Z</published>
    <updated>2026-03-23T06:42:51.490Z</updated>
    
    <content type="html"><![CDATA[<p>这是一篇随笔，在思考人类应该如何利用LLM的时候，我意识到杠杆效应是一个很好的思考角度。</p><h2 id="工具杠杆"><a href="#工具杠杆" class="headerlink" title="工具杠杆"></a>工具杠杆</h2><p>从工具杠杆的角度上看，LLM是一个很平均的工具，在不对其进行微调的时候，人们总能通过chatbot提升自己的效率，但由于单纯聊天产生的价值不高，人们需要通过高价值的劳动行为来提升杠杆率。</p><p>目前得到验证的工具杠杆是Coding，在上一个时代（互联网时代）已经验证过，代码可以不间断的运行并带来价值，其边际成本极低，作为信息技术可以带来极大的杠杆率。如果我们可以使用LLM加速代码的生成，则可以利用互联网时代的基建和系统，提升整个互联网的发展速度</p><p>在其他领域上，比如PPT、AI员工等工作，我发现其缺少反馈环境机制，通常依靠人在回路进行反馈，也受限于人类认识和审美，这方面的最主要问题是人类缺少高质量的员工，而平均值的人类+平均值的AI并不能产生多少价值，也不能消灭多少岗位（因为AI的价格也很贵）。这很类似于以基本工资雇佣老头老太太来进行环卫、保安、售票员等工作，自动化本身的价格可能比他们还高或者持平，但是考虑到就业，社会不得不从经济效益和人的角度创造一些毫无意义的工作，这在任何行业都成立，比如互联网企业也存在一大堆的“职能岗位”，即便他们最擅长的就是用代码来提高效率。</p><h2 id="知识杠杆"><a href="#知识杠杆" class="headerlink" title="知识杠杆"></a>知识杠杆</h2><p>另一个想法是，LLM作为知识杠杆能够加速人类摄取知识的速度，从书籍与印刷业得到的启示是，当信息获取的成本降低后，社会效率会进一步提高。另一方面，科学上低垂的果实已经被消耗殆尽，越来越多的科研创新依赖着大组织协作，人们需要越来越多的时间来学习基础知识才能参与到科学创新中，如果我们可以加速人类摄取知识的速度，或许科学创新的速度也能被增加。</p><p>但科学创新的反馈链路太长，人类的经济制度是否能帮助这一过程，或者这是对未来的美好想象</p><h2 id="二者结合"><a href="#二者结合" class="headerlink" title="二者结合"></a>二者结合</h2><p>从我的角度上看，人类可能正处于从学堂学习到实践学习的转化过程中，只是没有人意识到这一件事。LLM最神奇的地方在于其工具属性和知识属性共存，以AI Coding举例，人们在使用LLM进行代码编写的过程中，也在和AI进行结对编程，学习相关的编程知识，那么为什么人类不能和LLM工具一起成长呢？</p><p>一个可能的问题是，知识杠杆由于当前的教育体制设计，其反馈回路太长，导致产品只能设计为“做卷子”（chatgpt study mode）的模式，而非渐进式学习的模式。人们总假设有一个固定的答案，而不是探索一个真实世界的解决方案，人们面向的销售目标也是在授课体制内的学生、老师和家长，而不是面临真实世界问题的每个普通人。</p><p>另一方面，LLM在工具中做的也不好，人们无法相信一个“自己都做不好”的老师，这一方面是LLM的幻觉与事实核查。另一方面是人类自己对于表达、理解和最重要的动力的缺失，很多时候人们已经被资本主义规训成“有自我”的人，虽然这个自我意识是千篇一律的，受到灌输的自我。我们称一个只知道享乐，不希望思考，使用产品的目的是“解决问题”的人为<em>现代人</em>，而称一个时刻都在思考解决问题的人为<em>原始人</em>，当我们用这一方式思考AI，人类居然妄想通过程序员、产品经理、测试人员的工作岗位划分Agent来模拟资本主义下低效的世界，将被规训的低智商人类映射到对真实人类智能仿真的神经网络上，果不其然极大地降低了LLM的智能。从这一角度上看，现在的人类头脑本身是非常适合和LLM共同成长的，而现存人类的肉体头脑因为其生存的时间长度，受到资本主义规训的影响，导致他们变成了“人力资源”而不是通用智能，则大大阻止了人类与LLM共同成长。</p><p>于是我发现，在下一代AI-Native人类成长起来（或许永远也成长不起来）之前，人们只能接受无脑产品或者研究机器，这一未来是光明的，但其曲折的过程，则是对于我们这一代人的局限和悲剧。</p>]]></content>
    
    
    <summary type="html">从工具杠杆和知识杠杆角度分析LLM的价值，探讨Coding作为验证的工具杠杆，以及LLM如何加速人类学习和成长</summary>
    
    
    
    <category term="随笔" scheme="https://blog.wh1isper.top/categories/%E9%9A%8F%E7%AC%94/"/>
    
    
    <category term="AI" scheme="https://blog.wh1isper.top/tags/AI/"/>
    
    <category term="LLM" scheme="https://blog.wh1isper.top/tags/LLM/"/>
    
    <category term="AGI" scheme="https://blog.wh1isper.top/tags/AGI/"/>
    
  </entry>
  
  <entry>
    <title>AI Native的产品更应该暴露错误</title>
    <link href="https://blog.wh1isper.top/2025/07/25/2025-07-26-expose-more-error/"/>
    <id>https://blog.wh1isper.top/2025/07/25/2025-07-26-expose-more-error/</id>
    <published>2025-07-25T16:00:00.000Z</published>
    <updated>2026-03-23T06:42:51.490Z</updated>
    
    <content type="html"><![CDATA[<p>我曾在<a href="https://blog.wh1isper.top/2025/06/07/2025-06-08-agi-product-design/">之前的文章</a>中讨论过AI产品需要更端到端的设计来帮助用户发挥Agent智能，在目前来看，暴露错误是一个很好的让用户学习、同时进一步发挥模型智能的方式。</p><p>在传统的产品设计中，人们总是倾向于所谓的简洁，将一切复杂的原理藏在产品后面，让用户能够下意识的完成操作，人们总是假设用户无法理解产品背后的运行逻辑，不具备或不愿意花时间理解产品的技术细节。但在AI时代，每个人都需要通过表达来创造自己想要的东西，这时简洁反而成为了不安全感和困惑的来源。如果人们一定要面对复杂性，那么产品就一定要正确的暴露足够的细节，以便让人们能够更好地理解和掌握自己生成的东西的工作原理。</p><p>AI时代一个重要的改变是人们不再被专业知识而困扰，一个没有接触过web开发的非技术人士也可以通过大模型进行编码并部署在vercel之类的平台上，如果越来越多的人被纳入AI教育中，越来越多人开始使用AI，那么假设用户都是“不愿意学习的懒人”无疑是愚蠢的行为。基于这一假设，如果我们相信未来的世界是技术民主的，未来的人类是更会表达、更不机械、更有创造力的，我们就应该暴露更多的产品细节，创造更有可能性而不是更具可预测性的产品。</p><p>暴露错误是另一个让用户和产品一起成长的重要方式（<a href="https://blog.wh1isper.top/2025/07/04/2025-07-05-ergonomics-to-agent/">还有一个是TODO Tool</a>），通过精巧的设计，用户不再是对错误无能为力的人群，而是能够借助AI能力对错误进行修复并在其中成长的人，如果每个人都会有类似的成长过程，那么最适应这一成长过程的产品将会获胜，这或许是比“好用”更加重要的AI时代产品设计原则——成长性。</p><p>最后，通过成长性原则，我们可以帮助产品和用户建立起反脆弱的特点：从错误中学习并成长，从而出更少的错、创建更好的产品。由于AI的不确定性，随着AI的能力提升，产品对于AI的约束减少，产品的不稳定性也有可能上升(通常是AI和非AI的黏合初，很容易松动)，不同技能水平的用户使用其能力的方差应该也会增大，若可以实现一个机制帮助用户成长，那么我们就赋予了用户减少产品不稳定性的能力，从而实现用户和产品的结合，这或许是AI时代的用户粘性：不是chat、memory，而是experience point。</p>]]></content>
    
    
    <summary type="html">探讨AI时代产品设计的成长性原则，分析暴露错误如何帮助用户和产品一起成长，构建反脆弱的产品体验</summary>
    
    
    
    <category term="随笔" scheme="https://blog.wh1isper.top/categories/%E9%9A%8F%E7%AC%94/"/>
    
    
    <category term="AI" scheme="https://blog.wh1isper.top/tags/AI/"/>
    
    <category term="LLM" scheme="https://blog.wh1isper.top/tags/LLM/"/>
    
    <category term="产品设计" scheme="https://blog.wh1isper.top/tags/%E4%BA%A7%E5%93%81%E8%AE%BE%E8%AE%A1/"/>
    
    <category term="AGI" scheme="https://blog.wh1isper.top/tags/AGI/"/>
    
  </entry>
  
</feed>
