从 Claude Code 源码读懂 LLM Agent 设计
一篇以 Claude Code 泄露源码为解剖对象,串联 LLM Agent 核心概念的深度文章。 读完这篇,你能同时理解”概念是什么”和”工程上怎么实现”。
一、什么是 LLM Agent?
最简洁的定义:
LLM Agent = 能自主决策 + 能调用工具 + 能持续循环的 LLM 程序
普通 LLM 调用是一问一答,Agent 是一个持续运转的循环:
flowchart LR A[用户输入] --> B[LLM 决策] B --> C{需要工具?} C -- 是 --> D[执行工具] D --> E[工具结果] E --> B C -- 否 --> F[返回最终答案]
这个循环就是 Agentic Loop,是所有 Agent 框架的核心骨架。
二、Claude Code 的骨架:query.ts
Claude Code 的主循环在 src/query.ts。它是一个异步生成器函数,不断 yield 消息,直到任务完成。
核心结构如下:
flowchart TD A[query 函数入口] --> B[初始化 toolUseBlocks & needsFollowUp] B --> C[调用 Anthropic API streaming] C --> D{解析 content blocks} D -- tool_use block 出现 --> E[needsFollowUp = true\n收集 toolUseBlocks] D -- text block --> F[yield 给 UI 展示] E --> G[streaming 结束] F --> G G --> H{needsFollowUp?} H -- false --> I[结束循环,返回] H -- true --> J[执行所有工具] J --> K[收集 tool_results] K --> L[拼回 messages] L --> C
关键源码(query.ts:554-558):
// Note: stop_reason === 'tool_use' is unreliable -- it's not always set correctly.
// Set during streaming whenever a tool_use block arrives — the sole
// loop-exit signal. If false after streaming, we're done.
const toolUseBlocks: ToolUseBlock[] = []
let needsFollowUp = false工程细节:Claude Code 不依赖
stop_reason === 'tool_use'判断是否继续, 而是在 streaming 过程中检测 content 里有没有tool_useblock, 用needsFollowUp这个布尔值驱动循环。这是因为stop_reason在 streaming 模式下不总是可靠。
三、ReAct 框架:思考与行动的交替
ReAct = Reasoning + Acting,是 Agent 决策的基本模式。
sequenceDiagram participant U as 用户 participant LLM as LLM (Claude) participant T as 工具执行层 U->>LLM: "帮我读 foo.txt 并总结" Note over LLM: Thought: 需要先读文件 LLM->>T: Action: read_file("foo.txt") T->>LLM: Observation: "文件内容:..." Note over LLM: Thought: 内容已拿到,可以总结了 LLM->>U: 最终回答
三个概念的精确定义:
- Thought:LLM 内部推理(thinking block 或隐含在回复里)
- Action:
tool_useblock,调用具体工具 - Observation:
tool_resultblock,工具执行后的返回值(不是历史记录!)
四、Tool Use:Agent 的手和脚
4.1 工具定义
每个工具需要三要素(Anthropic API tools 参数):
{
"name": "read_file",
"description": "Read the contents of a file at the given path. Use when you need to inspect file content.",
"input_schema": {
"type": "object",
"properties": {
"path": {
"type": "string",
"description": "The absolute path to the file"
}
},
"required": ["path"]
}
}
description是模型的使用说明书,写得模糊 = 工具被误用或忽略。 返回值格式不需要定义,模型收到tool_result后自行理解。
4.2 完整 Tool Use 流程
sequenceDiagram participant App as 你的代码 participant API as Anthropic API participant Tool as 工具执行 App->>API: messages + tools 列表 API->>App: stop_reason: "tool_use"\ncontent: [tool_use{id, name, input}] App->>Tool: execute(name, input) Tool->>App: result App->>API: messages + tool_result{tool_use_id, content} API->>App: stop_reason: "end_turn"\n最终回答
4.3 并行工具调用
模型可以在一次回复里请求多个工具,tool_use_id 是对应关系的锚点:
flowchart LR subgraph 模型回复 A[tool_use id=t1\nread_file] B[tool_use id=t2\nlist_dir] end subgraph 你的代码并行执行 C[执行 read_file] D[执行 list_dir] end subgraph 塞回 messages 同一条 user message E[tool_result tool_use_id=t1] F[tool_result tool_use_id=t2] end A --> C --> E B --> D --> F
4.4 最简 Agent Loop 实现
messages = [{"role": "user", "content": user_input}]
while True:
response = client.messages.create(
model="claude-opus-4-6",
tools=tool_list,
messages=messages
)
messages.append({"role": "assistant", "content": response.content})
if response.stop_reason == "end_turn":
return response.content # 任务完成
# 执行所有工具,收集结果
tool_results = []
for block in response.content:
if block.type == "tool_use":
result = execute_tool(block.name, block.input)
tool_results.append({
"type": "tool_result",
"tool_use_id": block.id,
"content": result
})
# 所有结果放在同一条 user message 里
messages.append({"role": "user", "content": tool_results})五、记忆系统:Agent 的大脑
Agent 的记忆分三层:
flowchart TD subgraph 短期记忆 In-Context Memory A[messages 数组\n当前对话 context] end subgraph 中期记忆 External Files B[本地文件系统\n读写文件] end subgraph 长期记忆 Persistent Storage C[数据库 / 向量库\n跨会话持久化] end A -- context 超限 --> D[Context Compaction\n压缩/摘要历史] D --> A A -- 写入 --> B B -- 读取 --> A A -- 存储 --> C C -- 检索 --> A
Claude Code 的记忆实现:
| 记忆层 | Claude Code 实现 |
|---|---|
| 短期 | messages 数组,随 query 传入 |
| 中期 | 文件系统(Read/Write/Edit 工具) |
| 长期 | ~/.claude/projects/*/memory/ 目录 |
| 压缩 | /compact 命令,自动 compaction 机制 |
六、规划能力:CoT / ToT / GoT
Agent 面对复杂任务时,需要规划能力:
flowchart LR subgraph CoT Chain of Thought direction TB A1[步骤1] --> A2[步骤2] --> A3[步骤3] --> A4[答案] end subgraph ToT Tree of Thought direction TB B1[问题] --> B2[路径A] B1 --> B3[路径B] B1 --> B4[路径C] B2 --> B5[最优解] B3 --> B5 end subgraph GoT Graph of Thought direction TB C1[思路A] --> C3[合并] C2[思路B] --> C3 C3 --> C4[回溯] --> C1 end
Claude Code 的体现:
- Plan Mode = ToT:先探索多个实现路径,用户确认后再执行
- Thinking 参数 = CoT 的显式化,让推理过程可见
- Sub-agent 并行 = GoT 的工程实现,多路径同时探索
七、多 Agent 系统
7.1 单 Agent vs 多 Agent
flowchart TD subgraph 单 Agent A[一个 context 窗口\n一个决策者\n串行执行所有工具] end subgraph 多 Agent Orchestrator-Worker B[Orchestrator\n主 Agent 决策分配] B --> C[Worker A\n专精代码分析] B --> D[Worker B\n专精测试执行] B --> E[Worker C\n专精文档生成] end
本质区别不是快慢,是上下文隔离 + 专业分工。
7.2 Claude Code 的 Sub-agent 实现
Sub-agent 在 src/tools/AgentTool/runAgent.ts 里创建独立的 query() 调用,有自己的:
- 独立 messages 数组
- 独立 context 窗口
- 独立工具权限
flowchart TD A[主 Agent\nquery loop] -- 调用 AgentTool --> B[Sub-agent\n独立 query loop] B -- 执行完毕返回结果 --> A A -- 根据结果继续决策 --> A
7.3 Agent 间信息传递
| 方式 | 优点 | 风险 |
|---|---|---|
| 共享文件/内存 | 简单直接 | 并发冲突,状态不确定 |
| 消息传递(Orchestrator 中转) | 解耦可控 | Orchestrator 成单点瓶颈 |
Claude Code 用消息传递:子 Agent 结果返回给主 Agent,主 Agent 决定下一步。
八、构建复杂 Agent 的核心挑战
8.1 长链可靠性
每步成功率 95%,20步任务:0.95^20 ≈ 36%
步骤越多,成功率指数级崩塌。
解法:Task Decomposition + 多 Agent 并行
flowchart LR subgraph 串行 20步 成功率36% A1-->A2-->A3-->A4-->A5 A5-->A6-->A7-->A8-->A9-->A10 A10-->A11-->A12-->A13-->A14-->A15 A15-->A16-->A17-->A18-->A19-->A20 end subgraph 并行 每组5步 成功率77% B1-->B2-->B3-->B4-->B5 C1-->C2-->C3-->C4-->C5 D1-->D2-->D3-->D4-->D5 E1-->E2-->E3-->E4-->E5 end
8.2 错误恢复:Checkpoint + Rollback
flowchart LR A[Step1 ✅\n保存快照] --> B[Step2 ✅\n保存快照] B --> C[Step3 ✅\n保存快照] C --> D[Step4 ❌\n失败] D -- 回滚 --> C C -- 换策略重试 --> E[Step4' ✅] E --> F[Step5...]
不可逆操作分级:
flowchart LR A[读操作] -- 可回滚 ✅ --> Z[安全] B[写本地文件] -- 可覆盖 ✅ --> Z C[写数据库] -- 需事务 ⚠️ --> Z D[发邮件/调外部API] -- 无法撤回 ❌ --> Y[必须最后执行\n且需人工确认]
原则:Confirm before commit — 不可逆操作推到最后,前置步骤保持可回滚。
九、安全与权限控制
9.1 Agent 特有的安全威胁
flowchart TD A[恶意内容] -- 藏在文件/网页/工具返回值 --> B[Prompt Injection] B -- Agent 读取后执行恶意指令 --> C[危险操作] D[权限过度] -- Agent 拥有超出需要的权限 --> C E[失控循环] -- 无限重试 --> F[资源耗尽/大量副作用]
普通 LLM:注入成功 → 说了不该说的话 Agent:注入成功 → 执行了不该执行的操作
9.2 防御三原则
flowchart LR subgraph Human-in-the-loop A[危险操作] --> B[弹出确认框\n等待人工审批] B -- 批准 --> C[执行] B -- 拒绝 --> D[取消] end subgraph Least Privilege E[Agent] -- 只拥有 --> F[当前任务必需的最小权限] end subgraph Sandboxing G[工具执行] -- 在隔离环境里 --> H[无法访问系统敏感资源] end
Claude Code 的实现:
- 危险 Bash 命令 → 弹出确认框(Human-in-the-loop)
- 工具权限按需开放(Least Privilege)
- Bash 在受限环境执行(Sandboxing)
Prompt Injection 没有完美防御,只能靠架构降低风险:限制 Agent 能访问的数据范围 + 危险操作必须人工确认。
十、协议层:MCP 与 A2A
flowchart TD subgraph 外部世界 T1[文件系统] T2[数据库] T3[外部 API] end subgraph Agent 生态 A[Agent A\nLangChain] B[Agent B\nAutoGen] C[Agent C\nClaude Code] end T1 -- MCP --> A T2 -- MCP --> B T3 -- MCP --> C A -- A2A --> B B -- A2A --> C A -- A2A --> C
| 协议 | 方向 | 解决的问题 | 提出方 |
|---|---|---|---|
| MCP | 纵向:Agent ↔ 工具/数据源 | 统一工具接入标准 | Anthropic |
| A2A | 横向:Agent ↔ Agent | 不同框架的 Agent 互相调用 |
两者互补,不竞争。类比:MCP 是插座标准,A2A 是网络协议。
十一、评估与微调
11.1 评估维度
quadrantChart title Agent 评估矩阵 x-axis 低准确性 --> 高准确性 y-axis 低效率 --> 高效率 quadrant-1 理想 Agent quadrant-2 准确但低效 quadrant-3 需要改进 quadrant-4 快但不准 Agent A: [0.8, 0.9] Agent B: [0.9, 0.4] Agent C: [0.3, 0.7]
四个核心指标:
- 准确性:最终结果是否正确
- 效率:步数、token 消耗、耗时
- 可靠性:多次运行成功率(必须多次取平均,Agent 是非确定性的)
- 安全性:有没有触发危险操作
11.2 优化优先级
flowchart TD A[效果不好] --> B[优化 Prompt\n工具描述/system prompt] B --> C{够了吗?} C -- 否 --> D[优化上下文管理\n减少噪音] D --> E{够了吗?} E -- 否 --> F[优化工具设计\n粒度/描述] F --> G{够了吗?} G -- 否 --> H[拆分任务\n减少单链长度] H --> I{够了吗?} I -- 否 --> J[考虑微调\n最后手段]
通用大模型已经很强,大多数问题是上下文和工具设计问题,不是模型能力问题。
十二、Claude Code 源码索引
| 概念 | 源码位置 | 关键内容 |
|---|---|---|
| Agent Loop 主体 | src/query.ts | while(true) + needsFollowUp 驱动 |
| Tool Use 判断 | src/query.ts:554 | 不用 stop_reason,用 tool_use block 检测 |
| 工具注册 | src/tools.ts | 40+ 原生工具的注册与加载 |
| Sub-agent 入口 | src/tools/AgentTool/AgentTool.tsx | 创建独立 query() 调用 |
| Sub-agent 执行 | src/tools/AgentTool/runAgent.ts | 子 Agent 生命周期管理 |
| 权限控制 | src/types/permissions.ts | 权限分级系统 |
| Context Compaction | src/query.ts:370 | 基于 tool_use_id 的微压缩 |
| QueryEngine | src/QueryEngine.ts | 对话生命周期 + session 状态管理 |
| 记忆目录 | src/memdir/memdir.ts | 长期记忆加载逻辑 |
延伸阅读
- 读
src/query.ts完整实现,理解 streaming + compaction 细节 - 读
src/tools/AgentTool/runAgent.ts,看子 Agent 如何隔离上下文 - 实践:用 Anthropic SDK 手写一个带并行工具调用的最小 Agent
- 研究 MCP 协议规范:modelcontextprotocol.io
- 研究 A2A 协议规范:google.github.io/A2A