Skip to main content
BCC
s03

Agent Loop

Minimal Agent

核心循环

~100 lines of code1 toolswhile(stop_reason === 'tool_use') 循环 + BashTool
整个 AI Agent 的秘密就是一个 while 循环

One loop is all you need

One loop is all you need

[ Phase 1: 最小 Agent ] · 工具数: 1 · 代码量: ~100 行


前置知识

  • 需要完成: s00 [AI API 入门] + s02 [子进程与安全执行]

你将学到

  • AI Agent 的核心本质——一个 while 循环
  • 模型如何通过 tool_use 请求执行工具
  • 如何将工具结果作为 tool_result 送回模型
  • 循环退出的判断条件
  • 为什么这个循环能让 AI "自主行动"

问题场景

到 s02 为止,你有了两个独立的能力:

  • s00: 能调用 AI 模型,得到文本回答
  • s02: 能安全地执行 shell 命令

但这两个能力是割裂的。用户必须手动把命令结果复制给 AI,再把 AI 的建议手动执行。

一个真正的 AI Agent,应该能自动决定何时使用工具、自动执行工具、自动理解执行结果并继续推理。

这堂课,我们用一个 while 循环把这两个能力连起来。这个循环,就是 AI Agent 的全部秘密。

设计决策

循环条件:stop_reason vs content 检测

方案优点缺点
while(stop_reason === "tool_use")直观,API 设计的本意Claude Code 发现它偶尔不可靠
while(true) + 检测 content 中的 tool_use block100% 可靠代码稍复杂

教学版选择 stop_reason,因为它最好理解。Claude Code 生产版选择了 content 检测,因为在万级用户量下,任何不可靠都会被放大。

最大轮次限制

Agent 循环必须有一个安全阀:

策略优点缺点
无限制简单AI 可能死循环,烧掉大量 token
固定上限 (MAX_TURNS=10)安全可能在复杂任务中过早停止
Token 预算精确控制成本实现复杂

我们用固定上限 10 轮。Claude Code 使用 token 预算(s07 课会实现),但 10 轮对入门足够了。

动手实现

步骤 1: 定义工具(Tool Definition)

告诉模型有哪些工具可用。每个工具是一个 JSON Schema

const TOOLS: Anthropic.Tool[] = [
  {
    name: "bash",
    description: "Execute a shell command...",
    input_schema: {
      type: "object",
      properties: {
        command: { type: "string", description: "The shell command to execute" },
      },
      required: ["command"],
    },
  },
];

完整的工具定义和 system prompt 见 源码 标签页

工具描述建议用英文书写——模型主要用英文语料训练,英文描述通常能让模型更准确地理解工具的用途。

步骤 2: 核心循环

这是整课的核心——4 步循环:

while (turnCount < MAX_TURNS) {
  // 1. 发送 messages 给模型(带工具定义)
  const response = await client.messages.create({
    model: "claude-sonnet-4-20250514",
    tools: TOOLS,
    messages,
  });

  // 2. 把模型回复加入 messages
  messages.push({ role: "assistant", content: response.content });

  // 3. 检查是否需要执行工具
  if (response.stop_reason !== "tool_use") break;

  // 4. 执行工具,把结果作为 user message 送回
  const toolResults = await executeTools(response.content);
  messages.push({ role: "user", content: toolResults });
}

整个 Agent 就是这 4 步的无限循环。模型通过 stop_reason: "tool_use" 告诉你"我需要用工具";当它觉得任务完成了,就返回 stop_reason: "end_turn",循环自然结束。

以"看看当前目录有什么文件"为例,循环展开如下:

Round 1:
  messages = [user①]
                       ↓ API 调用(带 tools 参数)
  response = [text: "让我看看目录内容", tool_use: bash("ls -la")]
  messages += [assistant①]
  stop_reason = tool_use → 继续循环

  执行 bash("ls -la") → "total 32\ndrwxr-xr-x ..."
  messages += [user: [tool_result: "total 32\n..."]]

Round 2:
  messages = [user①, assistant①, user②]
                       ↓ API 调用(带 tools 参数)
  response = [text: "这是一个 TypeScript 项目"]
  messages += [assistant②]
  stop_reason = end_turn → 任务完成,退出循环

步骤 3: 工具执行与结果格式

每个 tool_use block 都必须有对应的 tool_result——这是 API 的强制约定:

toolResults.push({
  type: "tool_result",
  tool_use_id: block.id,   // 必须匹配 tool_use 的 id
  content: output,
  ...(exitCode !== 0 ? { is_error: true } : {}),
});

关键细节:is_error: true 不会导致 Agent 崩溃。相反,模型会看到错误,尝试修正或换一种方式。这就是 Agent 的"自我修正"能力。

完整实现见 源码 标签页的 agent.ts

运行验证

npm run dev "帮我看看当前目录有什么文件"
npm run dev   # 进入交互模式

点击 模拟器 标签页查看 Agent 循环的完整动画演示

对照 Claude Code 架构

概念我们的实现Claude Code
循环方式stop_reason !== "tool_use"while(true) + content 检测
工具注册硬编码数组assembleToolPool() 动态注册
工具执行串行 for 循环StreamingToolExecutor 并行
轮次限制MAX_TURNS=10token 预算 + maxTurns
错误处理传 is_error分级恢复策略栈

更详细的架构对照见 深入 标签页

深入思考

Q: 为什么 tool_result 必须作为 user message 发送?

A: Anthropic Messages API 要求严格的 user/assistant 交替。模型的回复是 assistant message(包含 tool_use),所以工具结果必须包装成 user message(包含 tool_result)。这不是实现细节,而是 API 协议。

Q: 如果模型一直请求使用工具,永远不停呢?

A: 这就是 MAX_TURNS 存在的原因。没有这个安全阀,一个出错的 prompt 可能让 Agent 无限循环,消耗大量 token 和计算资源。Claude Code 用了更精细的方式:token 预算(到了上限自动压缩上下文继续,而不是直接停止)。

Q: 模型怎么知道有哪些工具可用?

A: 你在 API 调用中传入的 tools 数组会被注入到模型的上下文中。模型根据工具描述决定何时使用、使用哪个、传什么参数。所以工具描述的质量直接决定了 Agent 的智能程度

练习

  1. 修改 runAgent,在每轮循环开始时打印当前 messages 数组的长度,观察它如何增长
  2. 给 Agent 添加第二个工具 read_file(读取文件内容),让模型能在 bash 和 read_file 之间选择
  3. 尝试让模型完成一个需要多步的任务:"创建一个 hello.txt,写入 Hello World,然后读取并确认内容"

下一课预告

Agent 跑起来了,但你有没有注意到 messages 数组的结构?每轮对话,它里面都在积累越来越复杂的数据。下一课 s04 消息管理 将深入这个数组,给它一套类型系统和管理策略。