Skip to main content

Agent Loop 解剖:所有主流框架 converge 的核心架构

原文: The Anatomy of an Agent Loop by Steve Kinney (March 19, 2026)

本文为原文的结构化翻译与读书笔记,按现有知识体系重新组织。


这篇文章说了什么

Steve Kinney 花了大量时间读源码(甚至尝试反编译二进制文件),想找出 Claude Code、Codex、Cursor、Vercel AI SDK、LangGraph、smolagents 等框架在 Agent 架构上的"秘方"。结果发现:所有框架 converge 在同一个 6 行循环上。真正的差异不在循环本身,而在围绕循环的工程——上下文管理、安全控制、优雅降级、成本控制。

文章分为两部分:

  1. 各框架如何实现同一个循环(OpenAI Agents SDK、Claude Agent SDK、smolagents、Vercel AI SDK、LangGraph)
  2. 生产环境中真正重要的东西(单 Agent vs 多 Agent、Context Engineering、Tool Design、从零构建)

观后感

这篇文章最核心的结论是:Agent 循环已经是一个 settled problem。过去两年 AI 领域涌现了大量新框架,大家都想标榜自己的架构独特。但当你真正深入源码,会发现所有主流框架 converge 在同一个 while 循环上——这不是巧合,而是 tool-calling LLM 的必然形态。LLM 通过 API 调用时,唯一能影响外部世界的方式就是 tool call;而 tool call 的存在与否天然构成了"继续/停止"的二值信号。因此,无论框架表面包装得多复杂,内核必然是这个 6 行循环。

这意味着什么?

首先,框架选型的重要性被高估了。很多团队在选择 Agent 框架时反复比较 LangChain vs LlamaIndex vs CrewAI vs AutoGen,纠结于谁的工具生态更丰富、谁的 API 更优雅。但文章通过源码对比告诉我们:这些差异都是表层的,循环层是统一的。真正影响 Agent 能力的,是循环之外的工程——上下文管理做得好不好、安全控制是否到位、成本预算是否有、降级策略是否优雅。

其次,ReAct 论文的洞察在 2026 年仍然成立。2022 年 Yao et al. 提出 Reasoning + Acting 的交替模式,在 ALFWorld 上相比纯 chain-of-thought 提升 34%。这个提升的来源不是"更聪明的推理",而是"模型能做事并观察结果"——行动带来了外部反馈,反馈修正了推理。今天的 Agent 框架无论怎么包装,本质上都是在实现这个交替模式。甚至 Anthropic 的 "Building Effective Agents" 报告也用 Agent vs Workflow 的区分来强调这一点:Workflow 是开发者硬编码控制流,Agent 是模型通过 tool call 自主决定下一步。

LangGraph 的图模型是一次有趣的"用复杂度换能力"的尝试。它把 while 循环替换为有向循环图,引入了 superstep、checkpoint、state merge 等概念。这套模型确实解决了 while 循环的固有限制:并行分支执行、故障后的 checkpoint 恢复、human-in-the-loop 审批、time travel。但它也带来了显著的学习成本和心智负担——你需要理解 LangGraph 的 State、Nodes、Edges 三个原语,理解 superstep 执行模型,理解各种 Saver 的 trade-off。文章的评价很公允:"如果你的 agent 是一个带工具的简单循环,LangGraph 是 overkill;如果你需要 durable、resumable、parallelizable 的工作流,它可能是正确选择。"

Multi-agent 的 15x token 成本是一个被严重低估的数字。Anthropic 内部数据显示:标准聊天 1x、单 agent loop ~4x、多 agent 系统 ~15x。15x 不是 typo。每次 agent 间 handoff 都意味着上下文被复制、摘要或重建,每个操作都消耗 token。虽然多 agent 在 Anthropic 内部评估中比单 agent 好 90.2%,但这个提升是否值得 15x 的成本,是每个团队在做技术选型时需要认真计算的问题。文章给出的经验法则是:大多数实际任务,单 agent + 好工具就够用了。需要 genuine specialization(不同部分需要不同的 system prompt、tool set 或 model)时才上多 agent。


一、所有框架 converge 的同一个循环

1.1 伪代码:6 行核心逻辑

while not done:
response = call_llm(messages)
if response.tool_calls:
results = execute_tools(response.tool_calls)
messages.extend(results)
else:
done = True
return response

这就是全部。但这段伪代码背后有一个关键机制需要理解:tool calls 是 LLM 的"继续信号",文本响应是"终止信号"

当你通过 API 调用 LLM 时,可以传入一个 tools 列表——定义模型允许调用的函数,每个函数有名称、描述和参数 schema。如果模型判断需要使用某个工具,它不会返回纯文本,而是返回一个结构化对象:"以这些参数调用这个函数"。你的代码执行这个函数,把结果作为新消息发回,模型继续处理。

这个机制的意义是深远的:它把"模型能做什么"从纯推理扩展到了"模型能影响世界"。模型不再是只能输出文本的聊天机器人,而是可以通过工具读写文件、执行代码、查询数据库、发送消息。Tool call 的存在与否天然构成了二值控制流——有 tool call 就继续循环,没有就返回最终答案。

Barry Zhang 进一步把这个循环压缩为两行:

env = Environment()
while True:
action = llm.run(system_prompt + env.state)
env.state = tools.run(action)

环境 mutate → 模型观察 → 模型行动 → 重复。其余全是 orchestration。

为什么这个循环是必然的? 因为 tool-calling LLM 的 API 设计决定了:模型每次调用只能返回"文本"或"tool call 请求"两种结果之一。不存在第三种选择。因此,任何 Agent 框架的内核必然是一个"调用 LLM → 检查是否有 tool call → 执行或停止"的循环。框架能做的是在这个循环周围添加上下文管理、安全控制、流式输出、checkpoint 恢复等工程能力。

1.2 ReAct 论文背景

Yao et al. (Princeton & Google Research, 2022) 的 ReAct 论文形式化了 Reasoning + Acting 的交替模式。相比纯 chain-of-thought,在 ALFWorld benchmark 上提升 34%。核心 insight:能做事并观察结果的模型,比只会冥想的模型表现更好。


二、各框架源码级实现对比

2.1 OpenAI Agents SDK

  • 核心:while (true) + runSingleTurn()
  • 决策树用 discriminated union 表示 4 种结果:
类型含义
final_outputLLM 返回文本,无 tool call → 停止
handoff调用 transfer_to_<agent_name> → 切换 agent,继续
run_again有 tool calls → 执行工具,继续
interruption工具需要人工审批 → 暂停,返回部分结果

设计洞察:这个 discriminated union 的设计非常优雅。它不是用多个 if-else 散落各处,而是把"每次 LLM 调用之后可能发生的所有事情"统一建模为一个类型。这样做的好处是:任何新增的循环行为(比如新增一种暂停条件、新增一种特殊的 tool call 类型)只需要加一个新的 union member,不需要改动循环控制逻辑。这是典型的"用类型系统管理复杂度"的思路。

  • 默认 max_turns=10,turn 指一次 LLM 调用(工具执行不计入)
  • Handoff 机制复用 tool 基础设施,而非单独的路由层。这意味着 agent-to-agent 委托不需要额外的序列化/反序列化层,只需要定义一个名字叫 transfer_to_<agent_name> 的工具。这个设计的巧妙之处在于:它证明了"agent 间的通信"本质上和"agent 与外部世界的通信"是同构的——都是 tool call + result。
  • Guardrails 在三个点拦截:input(首轮并行,作为延迟优化)、output(最终响应后)、tool(每次工具执行前后),返回 tripwire_triggered boolean。这里的关键洞察是:guardrails 不是循环的一部分,而是循环的过滤器。这种分离保证了核心循环的简洁性,同时允许安全策略在不改动核心逻辑的情况下被注入。

2.2 Claude Agent SDK

架构哲学差异:循环不在你的进程里跑,而是跑在 bundled 的 Claude Code CLI 二进制文件里。你的应用通过 stdin/stdout (NDJSON) 通信:

Your Application → stdin (NDJSON) → Claude Code CLI → HTTP → Anthropic API

这个设计的哲学意义非常深远。大多数框架(包括 OpenAI Agents SDK、Vercel AI SDK)都把 agent 循环实现为应用进程内的一个函数——你是控制者,框架是被调用的库。但 Claude Agent SDK 反过来了:你是被调用者,Claude Code CLI 是控制者。你的应用只是往 CLI 的 stdin 写 NDJSON,从 stdout 读结构化消息。这意味着循环的完整控制权(包括上下文管理、工具执行、错误恢复)都不在你手里,而是在一个你无法直接修改的二进制里。

这个设计的代价是灵活性降低,但收益是开箱即用的生产级能力:上下文压缩、CLAUDE.md 持久化、sub-agent 隔离、cost 追踪——这些在别的框架里需要自己配置或自己实现的东西,Claude Agent SDK 直接给你了。

  • 权限三层体系:allowed_tools(自动允许)、disallowed_tools(阻止,覆盖 allow)、permission_mode(兜底)
  • 可精确到命令模式:"Bash(npm:*)"
  • 遇到权限拒绝时,agent 把它当 tool result 接收,尝试替代方案——从访问限制中自愈。这个设计的巧妙之处在于:权限拒绝不是抛出异常打断循环,而是变成工具结果的一部分返回给模型。模型收到"这个命令被拒绝了"的信息后,会自己想办法绕过或替代。这是一种非常优雅的错误处理模式。
  • 上下文自动压缩,到达 limit 时发出 SystemMessage(subtype="compact_boundary")
  • CLAUDE.md 文件中的指令每次请求重新注入,survive compaction。这意味着你可以把最重要的系统级指令(比如"永远不要删除原始文件")放在 CLAUDE.md 里,即使对话历史被压缩,这些指令也不会丢失。
  • Sub-agent(Task 工具)用 fresh context window,返回 1,000-2,000 token 摘要(来自 10,000+ token 的内部工作)。这是解决长上下文问题的一个实用方案:不是把所有内容塞进一个 context window,而是让子 agent 在自己的窗口里工作,只把最终摘要传回主 agent。
  • 每次 ResultMessage 包含 total_cost_usd、token 用量、num_turnssession_id,支持 run 恢复。这些元数据字段对于生产环境至关重要——你不知道一个 agent run 花了多少钱、跑了多少步,就无法优化成本。

2.3 smolagents(HuggingFace)

核心差异:code-as-action 而非 JSON tool calls

这个设计的根本动机是什么? 文章引用了一段非常尖锐的话:"代码语言被专门设计成表达计算机动作的最佳方式。如果 JSON snippets 更好,JSON 就会成为顶级编程语言,编程就会是人间地狱。" 这段话的深层含义是:JSON tool call 的本质是把"程序逻辑"压缩成"数据结构",然后由框架来解析和执行。这种压缩是有损的——你失去了代码的组合性、可读性、可调试性。而让模型直接生成代码,相当于把"编排逻辑"的编写权交还给模型,模型可以用循环、条件、变量、函数调用来表达复杂的多步操作,而不需要框架预先定义好每一步的 JSON schema。

  • 终止条件:生成的代码调用 final_answer() → 引发 FinalAnswerException
  • 达到 max_steps 未调用 final_answer() 时,从历史合成响应(graceful degradation,多数框架做错的地方)。这里的关键洞察是:大多数框架在达到 max_steps 时只是 silently stop,不告诉用户发生了什么。smolagents 的做法是:即使 agent 没来得及调用 final_answer(),也会从它已有的历史中合成一个响应,让用户至少得到一些东西,而不是一个空的 or 不完整的输出。
  • 15,724 条 trace 分析:首次调用解析错误把成功率从 51.3% 降到 42.3%,因此推出结构化 CodeAgent 变体(JSON schema + "thoughts" + "code" 字段)达到 100% 解析可靠性。这个案例说明了一个重要原则:理论上的优雅(纯代码生成)和工程上的可靠性(100% 解析率)之间需要权衡。smolagents 没有固执地坚持纯代码模式,而是在发现解析错误影响成功率后,引入了结构化 JSON 包装层来保证可靠性。

2.4 Vercel AI SDK

TypeScript-first,为 Web 开发者设计,composable 架构。

const agent = new ToolLoopAgent({
model: 'anthropic/claude-sonnet-4.5',
instructions: 'You are a helpful assistant.',
tools: {
weather: tool({
description: '...',
inputSchema: z.object({ city: z.string() }),
execute: async ({ city }) => getWeather(city),
}),
},
stopWhen: stepCountIs(20),
});

最值得关注的设计决策Agentinterface 而非 class。在 TypeScript 中,interface 定义了一组行为契约,任何实现了这组契约的对象都可以被称为 Agent。这意味着第三方可以不依赖 Vercel AI SDK 的源码,直接实现自己的 Agent 类型,并与 SDK 的其余部分无缝协作。Temporal 基于这个接口构建了 DurableAgent,让 agent 工作流能够跨进程重启恢复——这是一个典型的"接口驱动扩展"的案例。

  • 默认停止条件 stepCountIs(1) = 不循环,必须显式 opt-in。这个默认值的哲学含义是:框架作者认为"不循环"比"无限循环"更安全。如果你忘记设置 stopWhen,agent 至少不会陷入无限循环消耗你的 API 配额。这是一个把"安全默认"放在"方便默认"之上的设计选择。
  • 停止条件可组合:stopWhen: [stepCountIs(20), yourCustomCondition()]。这允许你把多个停止条件叠加在一起——比如"最多 20 步" AND "用户说 stop" AND "总费用超过 $5"。
  • prepareStep hook:每次 LLM 调用前可动态修改 model、tools、messages、tool choice。这是所有框架中 per-iteration 控制能力最强的设计。比如你可以让 agent 在前 5 步用便宜的小模型做探索,后面用强模型做精炼;或者在发现某个工具返回错误后,临时切换到另一个工具。
  • Done tool pattern:强制 toolChoice: 'required',定义一个无 execute 函数的工具。模型调用它时循环停止——这是一种结构化输出终止信号。传统方式让模型"决定何时停止"(返回纯文本),但纯文本的判断容易受 prompt 影响。Done tool 把"停止"从隐式判断变成显式工具调用,让模型必须主动调用一个特殊的工具来表示"我完成了"。这种设计把终止条件从"模型说什么"变成了"模型做什么",减少了歧义。

2.5 LangGraph

有向循环图 替代 while 循环。不是 DAG——cycle 是重点。

  • 三个原语:State(TypedDict/Pydantic model)、Nodes(Python 函数变换 state)、Edges(路由函数决定下一步)
  • Loop = cycle:llm_call node → conditional edge (should_continue) → tool_nodeEND
  • 执行模型借鉴 Google Pregel:supersteps——每 tick 所有 scheduled nodes 并行运行,state merge,checkpoint 写入
  • Checkpoint 是 killer feature:InMemorySaverSqliteSaverPostgresSaver + 社区 Redis/Couchbase 实现
  • 支持 while 循环难以实现的能力:并行分支、fault tolerance、interrupt/resume、human-in-the-loop、time travel(加载 prior checkpoint,修改 state,fork execution)

权衡:复杂度高。简单 loop + tools 的场景 overkill;需要 durable、resumable、parallelizable 的工作流时,可能是正确选择。

2.6 其他框架

  • CrewAI:确定性编排(Flows + @start()/@listen()) + 自主推理(Crews),ReAct loop 在 CrewAgentExecutor._invoke_loop()
  • AutoGen:万物建模为 agent 间对话——loop 就是 agent 间的消息交换。v0.4 采用 actor model,Magentic-One 使用 dual-loop ledger planning system

三、生产环境中真正重要的东西

循环本身是简单的。难的是让循环在真实用户面前不失控。

3.1 单 Agent vs 多 Agent

Anthropic 内部 token scaling 数据

场景Token 成本
标准聊天1x
单 Agent loop~4x
多 Agent 系统~15x

15x 不是 typo。每次 agent 间 handoff 都意味着 context 被复制、摘要或重建,每个操作都烧 token。

多 Agent 在 Anthropic 内部评估中比单 Agent 好 90.2%,能力提升是真实的,但代价昂贵。

四种多 Agent 模式

模式描述适用场景
Pipeline顺序执行,输出传给下一个research → draft → review
Manager一个 orchestrator 委派 specialist职责清晰的团队
HandoffsAgent 间直接转移控制去中心化、灵活但难 debug
Fan-out多 Agent 并行子任务,结果合并任务可真正分解时

结论:大多数实际任务,单 agent + 好工具就够用了。需要 genuine specialization(不同部分需要不同 system prompt、tool set 或 model)时才上多 agent。如果因为"单 agent 太简单"而选多 agent,你大概率在花 15x 的 token 换边际改善。

3.2 Context Engineering(新前沿)

不是 prompt engineering——context engineering。模型在每个 loop iteration 看到什么,比启动时告诉它什么更重要。

Anthropic Context Engineering Guide 提出四种策略:

Write context:把信息存到 context window 外(scratchpads、memory files、progress notes)。模型生成后续需要的信息并持久化到 tool-accessible 位置, survive compaction。

Select context:在合适的时间通过工具拉取相关信息(grep、glob、RAG、database queries)。模型不需要在 window 里携带所有东西。

Compaction:当 context 接近 limit 时,早期消息压缩成摘要。关键指令放在不会丢失的地方。

Offload context:让 agent 写代码来批量处理数据,而不是在对话里一步步来。

3.3 Tool Design

工具设计是 loop 能跑多好的关键。

  • 工具必须 :崩溃可以容忍,卡住(hang)致命
  • 错误信息要对 agent 友好——防住 "LLM chaos monkey" 式的错误用法
  • 日志即工具:好的日志设计能让 agent 自助解决问题
  • 代码即工具:能生成代码的场景,优先让 agent 写代码执行,而不是定义一堆预定义函数(Armin Ronacher 的 "Tools: Code Is All You Need" 论点)

四、从零构建一个 Minimal Agent

4.1 最简实现

import anthropic

client = anthropic.Anthropic()
messages = [{"role": "user", "content": "What's the weather in SF?"}]

tools = [{
"name": "get_weather",
"description": "Get the current weather in a given location",
"input_schema": {
"type": "object",
"properties": {
"city": {"type": "string", "description": "The city to get weather for"}
},
"required": ["city"]
}
}]

while True:
response = client.messages.create(
model="claude-sonnet-4-5",
max_tokens=1024,
messages=messages,
tools=tools,
)

# Check if the model wants to call a tool
if response.stop_reason == "tool_use":
tool_use = next(block for block in response.content if block.type == "tool_use")
result = get_weather(tool_use.input["city"])

# Append the tool result and continue the loop
messages.append({"role": "assistant", "content": response.content})
messages.append({
"role": "user",
"content": [{
"type": "tool_result",
"tool_use_id": tool_use.id,
"content": result,
}]
})
else:
# No tool calls - we're done
print(response.content[0].text)
break

4.2 添加 Streaming

with client.messages.stream(
model="claude-sonnet-4-5",
max_tokens=1024,
messages=messages,
tools=tools,
) as stream:
for text in stream.text_stream:
print(text, end="", flush=True)

4.3 Safety Controls

  • Max turns:防止无限循环(默认 10)
  • Human-in-the-loop:对高风险工具(Bashsend_email)要求审批
  • Input/Output guardrails:在循环前后过滤内容

4.4 Context Management

  • 当消息历史超过 context window 70% 时触发摘要压缩
  • 早期消息 → 摘要文本,释放空间
  • 关键指令放在 CLAUDE.md 或 system prompt 顶部( survive compaction)

4.5 Observability

  • 记录每次 LLM 调用的 latency、token 用量、cost
  • 记录 tool call 的成功/失败率
  • LangSmith / Phoenix / Braintrust 等工具可开箱即用

五、Agent Loop 的死亡陷阱

5.1 循环本身的陷阱

陷阱表现解法
无限循环Agent 反复调用同一个工具Max turns + loop detection(检测连续相同 tool call)
上下文爆炸超过 10-20 步后 forget 初始目标外部记忆 + 自动摘要
工具幻觉调用不存在的工具或参数错误JSON schema 强制校验 + 重试
静默失败达到 max_steps 后 silently stop从历史合成响应,而不是沉默

5.2 工程周围的陷阱

  • 成本失控:多 agent 系统 15x token 成本,必须有 budget 和 alert
  • 延迟累积:每次 tool call 都是网络往返,串行调用放大延迟
  • 状态不一致:并行 tool calls 的 race condition
  • 权限蔓延:agent 拥有的权限应遵循最小特权原则

六、核心结论

The loop is settled

所有主流框架 converge 在同一个 6 行 while 循环上。这不是巧合——这是 tool-calling LLM 的必然形态。未来不会有人重新发明这个循环,竞争点会转移到:

  1. Context Engineering:如何在每个 iteration 给模型呈现最相关的信息
  2. Tool Design:如何设计让 agent 高效使用的工具接口
  3. Safety & Cost Controls:如何让 loop 在生产环境不失控
  4. Observability:如何 debug 一个跑了 20 步、调了 15 个工具的 agent

最小 viable agent 的标准

while not done:
response = llm(messages)
if tool_calls in response:
messages.append(tool_results)
else:
done = True

加一个 system prompt、一组工具、一个 max_turns 限制。这就是 MVP。剩下的全是工程。


参考链接