<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>工具调用 on MyBrew</title>
    <link>https://aibrew.ai/zh/tags/%E5%B7%A5%E5%85%B7%E8%B0%83%E7%94%A8/</link>
    <description>Recent content in 工具调用 on MyBrew</description>
    <generator>Hugo</generator>
    <language>zh-cn</language>
    <lastBuildDate>Wed, 27 May 2026 00:00:00 +0000</lastBuildDate>
    <atom:link href="https://aibrew.ai/zh/tags/%E5%B7%A5%E5%85%B7%E8%B0%83%E7%94%A8/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>逆向 Claude Code 源码：它的 Agent 架构是怎么设计的，以及我们如何用同样思路给泰拉瑞亚做了个 AI 助手</title>
      <link>https://aibrew.ai/zh/2026/05/%E9%80%86%E5%90%91-claude-code-%E6%BA%90%E7%A0%81%E5%AE%83%E7%9A%84-agent-%E6%9E%B6%E6%9E%84%E6%98%AF%E6%80%8E%E4%B9%88%E8%AE%BE%E8%AE%A1%E7%9A%84%E4%BB%A5%E5%8F%8A%E6%88%91%E4%BB%AC%E5%A6%82%E4%BD%95%E7%94%A8%E5%90%8C%E6%A0%B7%E6%80%9D%E8%B7%AF%E7%BB%99%E6%B3%B0%E6%8B%89%E7%91%9E%E4%BA%9A%E5%81%9A%E4%BA%86%E4%B8%AA-ai-%E5%8A%A9%E6%89%8B/</link>
      <pubDate>Wed, 27 May 2026 00:00:00 +0000</pubDate>
      <guid>https://aibrew.ai/zh/2026/05/%E9%80%86%E5%90%91-claude-code-%E6%BA%90%E7%A0%81%E5%AE%83%E7%9A%84-agent-%E6%9E%B6%E6%9E%84%E6%98%AF%E6%80%8E%E4%B9%88%E8%AE%BE%E8%AE%A1%E7%9A%84%E4%BB%A5%E5%8F%8A%E6%88%91%E4%BB%AC%E5%A6%82%E4%BD%95%E7%94%A8%E5%90%8C%E6%A0%B7%E6%80%9D%E8%B7%AF%E7%BB%99%E6%B3%B0%E6%8B%89%E7%91%9E%E4%BA%9A%E5%81%9A%E4%BA%86%E4%B8%AA-ai-%E5%8A%A9%E6%89%8B/</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt; — 我们逆向了 Claude Code 的 TypeScript 源码，搞清楚了它的 Agent 架构如何处理安全、复杂任务和工具权限。然后把这些模式用到了一个开源项目上——让玩家在泰拉瑞亚游戏里跟 AI 聊天，AI 还能给道具、改天气、传送玩家。以下是我们的发现、实现过程和踩坑总结。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id=&#34;为什么要拆-claude-code-的源码&#34;&gt;为什么要拆 Claude Code 的源码&lt;/h2&gt;
&lt;p&gt;Claude Code 不只是个编程助手。底层它是一个 Agent 运行时——会 spawn 子 Agent、管理文件权限、跑 bash 命令、判断什么时候该问用户什么时候该直接做。我们想搞清楚它的内部机制，然后把这些想法用到一个完全不同的场景：泰拉瑞亚游戏服务器。&lt;/p&gt;
&lt;p&gt;我们的项目 &lt;a href=&#34;https://github.com/d99sfrmdbz-debug/terra_llm_bridge&#34;&gt;terra_llm_bridge&lt;/a&gt; 把泰拉瑞亚 TShock 服务器接到了一个 LLM 上。玩家在聊天框打 &lt;code&gt;@ai&lt;/code&gt; 就能跟 AI 对话——但 AI 不止能聊天，还能&lt;strong&gt;做事&lt;/strong&gt;：给道具、改天气、传送玩家，甚至能切换困难模式。最后那条就是我们翻车的地方。&lt;/p&gt;
&lt;p&gt;第一次有玩家让 AI 设成雨天，LLM 自作主张调了 &lt;code&gt;terra_world_hardmode(confirm=True)&lt;/code&gt;——把整个服务器的世界&lt;strong&gt;不可逆&lt;/strong&gt;地切成了困难模式。没人要求它这么做。模型自己觉得该做就做了。&lt;/p&gt;
&lt;p&gt;我们需要一个真正的权限系统。于是去翻 Claude Code 的源码。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;claude-code-的-7-层权限架构&#34;&gt;Claude Code 的 7 层权限架构&lt;/h2&gt;
&lt;p&gt;通读 &lt;code&gt;src/utils/permissions/permissions.ts&lt;/code&gt; 的约 1500 行代码，加上 Agent 工具的基础设施（约 3800 行），一套清晰的架构浮现出来。Claude Code 不是靠单点检查做安全——它有&lt;strong&gt;七层&lt;/strong&gt;：&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;Layer 1a: 拒绝规则   →  &amp;#34;永远不允许 Bash(git push --force)&amp;#34;
Layer 1b: 询问规则   →  &amp;#34;Bash(curl *) 总是弹窗确认&amp;#34;
Layer 1c: 工具自检   →  每个工具 checkPermissions() 自己的逻辑
Layer 1d: 工具自拒   →  Read 工具白名单特定路径
Layer 1f: 内容规则   →  &amp;#34;就算 bypass 模式，npm publish 也要弹窗&amp;#34;
Layer 1g: 安全检查   →  &amp;#34;.git/、.claude/ 永远不能绕过用户确认&amp;#34;
Layer 2:  模式旁路   →  bypassPermissions / auto / acceptEdits / dontAsk
Layer 3:  YOLO 分类器 →  AI 读全文 transcript，判断是否安全
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;最有意思的是 &lt;strong&gt;YOLO 分类器&lt;/strong&gt;——一个独立的小模型，读取完整对话记录，把每次工具调用分类为安全或危险。两阶段系统：快速分类器处理明显 case，深度思考分类器处理边界情况。&lt;/p&gt;</description>
      <content:encoded><![CDATA[<blockquote>
<p><strong>TL;DR</strong> — 我们逆向了 Claude Code 的 TypeScript 源码，搞清楚了它的 Agent 架构如何处理安全、复杂任务和工具权限。然后把这些模式用到了一个开源项目上——让玩家在泰拉瑞亚游戏里跟 AI 聊天，AI 还能给道具、改天气、传送玩家。以下是我们的发现、实现过程和踩坑总结。</p>
</blockquote>
<hr>
<h2 id="为什么要拆-claude-code-的源码">为什么要拆 Claude Code 的源码</h2>
<p>Claude Code 不只是个编程助手。底层它是一个 Agent 运行时——会 spawn 子 Agent、管理文件权限、跑 bash 命令、判断什么时候该问用户什么时候该直接做。我们想搞清楚它的内部机制，然后把这些想法用到一个完全不同的场景：泰拉瑞亚游戏服务器。</p>
<p>我们的项目 <a href="https://github.com/d99sfrmdbz-debug/terra_llm_bridge">terra_llm_bridge</a> 把泰拉瑞亚 TShock 服务器接到了一个 LLM 上。玩家在聊天框打 <code>@ai</code> 就能跟 AI 对话——但 AI 不止能聊天，还能<strong>做事</strong>：给道具、改天气、传送玩家，甚至能切换困难模式。最后那条就是我们翻车的地方。</p>
<p>第一次有玩家让 AI 设成雨天，LLM 自作主张调了 <code>terra_world_hardmode(confirm=True)</code>——把整个服务器的世界<strong>不可逆</strong>地切成了困难模式。没人要求它这么做。模型自己觉得该做就做了。</p>
<p>我们需要一个真正的权限系统。于是去翻 Claude Code 的源码。</p>
<hr>
<h2 id="claude-code-的-7-层权限架构">Claude Code 的 7 层权限架构</h2>
<p>通读 <code>src/utils/permissions/permissions.ts</code> 的约 1500 行代码，加上 Agent 工具的基础设施（约 3800 行），一套清晰的架构浮现出来。Claude Code 不是靠单点检查做安全——它有<strong>七层</strong>：</p>
<pre tabindex="0"><code>Layer 1a: 拒绝规则   →  &#34;永远不允许 Bash(git push --force)&#34;
Layer 1b: 询问规则   →  &#34;Bash(curl *) 总是弹窗确认&#34;
Layer 1c: 工具自检   →  每个工具 checkPermissions() 自己的逻辑
Layer 1d: 工具自拒   →  Read 工具白名单特定路径
Layer 1f: 内容规则   →  &#34;就算 bypass 模式，npm publish 也要弹窗&#34;
Layer 1g: 安全检查   →  &#34;.git/、.claude/ 永远不能绕过用户确认&#34;
Layer 2:  模式旁路   →  bypassPermissions / auto / acceptEdits / dontAsk
Layer 3:  YOLO 分类器 →  AI 读全文 transcript，判断是否安全
</code></pre><p>最有意思的是 <strong>YOLO 分类器</strong>——一个独立的小模型，读取完整对话记录，把每次工具调用分类为安全或危险。两阶段系统：快速分类器处理明显 case，深度思考分类器处理边界情况。</p>
<p>但对我们最有用的不是 AI 分类器。而是 Claude Code <strong>如何在结构上防止某些工具在错误的上下文中被调用</strong>——通过工具白名单、黑名单和子 Agent 特化。</p>
<hr>
<h2 id="agent-模式不是多-agent-协作而是专项-worker">Agent 模式：不是多 Agent 协作，而是专项 Worker</h2>
<p>Claude Code 用的不是&quot;Agent 之间协商谈判&quot;的多 Agent 协作。它是一个<strong>主协调器 + 专项 Worker</strong>：</p>
<pre tabindex="0"><code>主 Agent（Tool Calling，全部工具）
  │
  ├─ 简单: &#34;读文件 X&#34; → Read 工具
  │
  └─ 复杂: &#34;审计这个分支&#34; → Agent(&#34;Explore&#34;)
                              │
                              ├─ 工具: [Read, Grep, Glob]  ← 白名单
                              ├─ 禁止: [Edit, Write]        ← 黑名单
                              ├─ 系统提示: &#34;你是文件搜索专家&#34;
                              └─ 返回结果 → 主 Agent 行动
</code></pre><p>每个子 Agent 类型由三要素定义：</p>
<ol>
<li><strong>工具权限</strong>（白名单 + 黑名单）——能碰什么</li>
<li><strong>系统提示</strong>——角色专属指令</li>
<li><strong>模型</strong>——Explore Agent 用 Haiku（便宜），Plan Agent 用 Sonnet（推理强）</li>
</ol>
<p>核心洞察：<strong>主 Agent 不会变更复杂</strong>。它保持简单，只有一个 <code>Agent</code> 工具让它把复杂任务委派出去。子 Agent 就是另一个 Tool Calling 循环，只是工具受限 + 提示词不同。</p>
<p>这套架构的可组合性是关键：每个零件简单，但组合起来能处理单个 prompt 消化不了的复杂度。</p>
<hr>
<h2 id="我们怎么把这个模式用到-terra_llm_bridge">我们怎么把这个模式用到 terra_llm_bridge</h2>
<p>我们的泰拉瑞亚桥接比 Claude Code 简单——46 个工具而非几百个，&ldquo;安全问题&quot;是&quot;别在玩家问天气时切 hardmode&quot;而不是&quot;别让 AI rm -rf /&quot;。但模式是直接可以搬的。</p>
<h3 id="问题">问题</h3>
<p>改之前：LLM 同时看到所有 46 个工具。当玩家问&quot;给我最强套装&rdquo;，LLM 会<strong>并行</strong>调 <code>wiki_search</code> 查资料 + <code>give_item</code> 给东西——一边查 wiki 一边已经预判了 Solar Flare 套装。有时候猜对，有时候给召唤师玩家塞了一套战士装备。</p>
<h3 id="解决方案两阶段工具开放">解决方案：两阶段工具开放</h3>
<p>我们没有加子 Agent——46 个工具不需要。但我们在 graph 层面用了<strong>工具限制模式</strong>：</p>
<pre tabindex="0"><code>route → llm(研究)  ⇄  tool      →  escalate  →  llm(行动)  ⇄  authorize  ⇄  tool  →  output
        17 个只读工具                          46 全工具      关键词 gate
        wiki、lookup、状态                       give、kick、spawn
</code></pre><p>图有两个阶段：</p>
<p><strong>研究阶段</strong>——LLM 只拿到 17 个只读工具（wiki_search、item_lookup、player_list、world_info 等）。它<strong>不能</strong>调 give_item、kick、spawn 或任何破坏性工具。先查资料。</p>
<p><strong>升级（escalate）</strong>——当 LLM 输出文本（没有更多 tool_call），图自动切到行动模式，注入提示：&ldquo;你现在可以访问全部工具了。&rdquo;</p>
<p><strong>行动阶段</strong>——LLM 拿到全部 46 个工具，可以对研究发现做出行动。</p>
<p>这是结构层面强制执行的，不是 prompt 建议。LLM 在研究阶段根本调不了 <code>give_item</code>，因为这个工具没绑定。</p>
<h3 id="权限-gate">权限 Gate</h3>
<p>在两阶段拆分之前，我们还加了 <code>authorize_node</code>——LLM 和 ToolNode 之间的硬拦截，检查玩家聊天最近的消息是否包含该工具领域的关键词：</p>
<div class="highlight"><div style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">2
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">3
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">4
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">5
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">6
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span>GATED_TOOLS <span style="color:#ff79c6">=</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#f1fa8c">&#34;terra_world_hardmode&#34;</span>: {<span style="color:#f1fa8c">&#34;hardmode&#34;</span>, <span style="color:#f1fa8c">&#34;hard mode&#34;</span>, <span style="color:#f1fa8c">&#34;肉山&#34;</span>, <span style="color:#f1fa8c">&#34;困难模式&#34;</span>},
</span></span><span style="display:flex;"><span>    <span style="color:#f1fa8c">&#34;terra_player_kick&#34;</span>:    {<span style="color:#f1fa8c">&#34;kick&#34;</span>, <span style="color:#f1fa8c">&#34;踢出&#34;</span>, <span style="color:#f1fa8c">&#34;踢了&#34;</span>},
</span></span><span style="display:flex;"><span>    <span style="color:#f1fa8c">&#34;terra_server_stop&#34;</span>:    {<span style="color:#f1fa8c">&#34;stop server&#34;</span>, <span style="color:#f1fa8c">&#34;关服&#34;</span>, <span style="color:#f1fa8c">&#34;停服&#34;</span>},
</span></span><span style="display:flex;"><span>    <span style="color:#6272a4"># ... 还有 8 个</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></td></tr></table>
</div>
</div><p>如果玩家说&quot;设个雨天试试&quot;而 LLM 想调 <code>world_hardmode</code>，authorize_node 检查：玩家最近的消息里有 hardmode 相关的关键词吗？没有？<strong>拦截。</strong> 这个工具调用被替换成 BLOCKED 消息，ToolNode 根本看不到。</p>
<p>这是粗过滤器——它检查的是玩家<strong>提到了</strong>什么，而不是玩家<strong>请求了</strong>什么。&ldquo;上次打肉山的时候&quot;会通过关键词检查，尽管玩家没要求开 hardmode。但粗够了：目标是拦截灾难性的跨界调用（天气 → hardmode），不是完美理解意图。</p>
<hr>
<h2 id="我们选择不做的">我们选择不做的</h2>
<h3 id="没有-yolo-分类器">没有 YOLO 分类器</h3>
<p>Claude Code 的 AI 分类器读完整 transcript，用另一个模型判断工具调用是否安全。我们没做，因为：</p>
<ul>
<li>增加延迟——每次 gated 工具调用前多一次 LLM 请求</li>
<li>泰拉瑞亚聊天风险低——给错套装可以补救</li>
<li>关键词匹配已经能拦住灾难性 case</li>
</ul>
<h3 id="没有子-agent-派生">没有子 Agent 派生</h3>
<p>Claude Code 为复杂任务 spawn 子进程。我们不需要：</p>
<ul>
<li>泰拉瑞亚工具面小（46 个）</li>
<li>多轮工具调用已经能处理我们实际面对的场景</li>
<li>给游戏聊天机器人 spawn 子进程是过度工程</li>
</ul>
<h3 id="没有-react-模式">没有 ReAct 模式</h3>
<p>经典的 Thought → Action → Observation 循环会增加 token 消耗，但不改变我们的核心能力。DeepSeek 的 thinking tokens 已经承担了推理，而两阶段工具访问比基于 prompt 的 ReAct 更可靠地强制了&quot;先研究再行动&rdquo;。</p>
<hr>
<h2 id="一张图看清架构">一张图看清架构</h2>
<pre tabindex="0"><code>┌──────────────────────────────────────────────────────────┐
│  泰拉瑞亚服务器（TShock + C# 插件，24 个游戏 Hook）        │
│  玩家输入 &#34;@ai 给我最好的套装&#34;                             │
└──────────────────────┬───────────────────────────────────┘
                       │ JSON webhook
┌──────────────────────▼───────────────────────────────────┐
│  Python aiohttp 监听器 (:9876)                            │
└──────────────────────┬───────────────────────────────────┘
                       │
┌──────────────────────▼───────────────────────────────────┐
│  LangGraph StateGraph                                     │
│                                                           │
│  route  →  llm(研究)  ⇄  tool      17 只读工具           │
│               │                                           │
│          escalate  →  llm(行动)  ⇄  authorize  ⇄  tool   │
│                          46 全工具      关键词拦截         │
│               │                                           │
│             output  →  广播到游戏聊天                      │
│                                                           │
│  记忆: AsyncSqliteSaver 按玩家（thread_id）持久化          │
└──────────────────────────────────────────────────────────┘
                       │
         ┌─────────────┴──────────────┐
         ▼                            ▼
   TShock REST API              Terraria Wiki API
   (give / kick / spawn)        (terraria.wiki.gg)
</code></pre><hr>
<h2 id="源码分析的启示">源码分析的启示</h2>
<p>读 Claude Code 源码教会我们三件事，适用于任何 Agent 项目：</p>
<p><strong>1. 安全是分层的，不是二元的。</strong> 一个 <code>confirm</code> 参数对 LLM 来说只是软建议。真正的安全需要结构性约束——LLM 不该能调用它无权使用的工具，就像 Web 服务器不该让你访问没有权限的端点，不管你怎么礼貌地请求。</p>
<p><strong>2. 工具限制是最便宜也最可靠的安全形式。</strong> Claude Code 的 Explore Agent 之所以&quot;只读&quot;，不是因为 prompt 写了——是因为 Edit 和 Write 不在它的工具列表里。我们的研究阶段之所以&quot;先查资料&quot;，不是 prompt 建议——是因为 give_item 根本没绑定。你不能通过 prompt injection 绕过不存在的工具。</p>
<p><strong>3. 特化胜过复杂化。</strong> Claude Code 的子 Agent 不比主 Agent 更聪明——只是更受约束。更少的工具 + 聚焦的 prompt = 更可靠的行为。我们的两阶段系统同理：先限制，准备就绪再扩展。</p>
<hr>
<h2 id="关于这个项目">关于这个项目</h2>
<p><code>terra_llm_bridge</code> 是一个连接泰拉瑞亚游戏服务器与 LLM 的开源项目。功能包括：</p>
<ul>
<li><strong>24 个游戏 Hook</strong>——自研 C# TShock 插件捕获聊天、Boss 击杀、死亡、登录等 24 种事件</li>
<li><strong>46 个管理工具</strong>——给道具、管玩家、控天气、召 NPC、管区域和权限</li>
<li><strong>两阶段 Agent</strong>——研究（17 工具）→ 行动（46 工具）</li>
<li><strong>硬权限 Gate</strong>——基于关键词的 authorize_node 拦截未授权工具调用</li>
<li><strong>MCP 服务端</strong>——同 46 工具暴露给 Claude Code 做服务器管理</li>
<li><strong>持久化记忆</strong>——通过 LangGraph AsyncSqliteSaver 按玩家保持对话历史</li>
</ul>
<p>项目目前处于<strong>活跃测试阶段</strong>，尚未发布到 GitHub。我们在私有泰拉瑞亚服务器上运行，迭代 Agent 架构后再开源。如果对代码感兴趣或想提前体验，欢迎联系。</p>
<hr>
<p><em>技术栈：Python 3.14, LangGraph 1.x, DeepSeek（Anthropic 兼容 API）, C# .NET 9, TShock v6.1.0, aiohttp, httpx.</em></p>
]]></content:encoded>
    </item>
  </channel>
</rss>
