从 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_use block, 用 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 或隐含在回复里)
  • Actiontool_use block,调用具体工具
  • Observationtool_result block,工具执行后的返回值(不是历史记录!)

四、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 互相调用Google

两者互补,不竞争。类比: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.tswhile(true) + needsFollowUp 驱动
Tool Use 判断src/query.ts:554不用 stop_reason,用 tool_use block 检测
工具注册src/tools.ts40+ 原生工具的注册与加载
Sub-agent 入口src/tools/AgentTool/AgentTool.tsx创建独立 query() 调用
Sub-agent 执行src/tools/AgentTool/runAgent.ts子 Agent 生命周期管理
权限控制src/types/permissions.ts权限分级系统
Context Compactionsrc/query.ts:370基于 tool_use_id 的微压缩
QueryEnginesrc/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