Skip to content

第 3 章 · Agent Loop 怎么真正转起来

3.1 ReAct:思考 + 行动

第 2 章的伪代码里,循环每转一轮,LLM 做两件事:思考(决定干什么)和行动(调工具)。这个模式有个名字——ReAct(Reason + Act)。

ReAct 是 2022 年的一篇论文提出的,现在是几乎所有 agent 的基础模式。每一轮循环的格式是:

Thought: 我需要先查一下今天北京天气
Action: weather_tool(location="北京")
Observation: 25度,晴
Thought: 天气不错,用户问的是明天,我再查明天
Action: weather_tool(location="北京", date="tomorrow")
Observation: 28度,多云
Thought: 信息齐了,可以回复用户了
Final Answer: 北京明天 28 度多云。
  • Thought:LLM 的内心独白,决定下一步。
  • Action:LLM 选择调用的工具 + 参数。
  • Observation:工具执行返回的结果,反馈给 LLM。
  • Final Answer:LLM 觉得任务完成,给出最终答案,循环结束。

现代 agent 框架(包括 Anthropic 的 tool use API)不再用 "Thought: / Action: / Observation:" 这种文本格式,而是用结构化的 JSON,但底层逻辑完全一样:思考 → 行动 → 观察 → 思考 → ... → 结束。

3.2 停止条件:循环什么时候停

agent loop 最容易出问题的地方不是"转不起来",而是"停不下来"。常见的停止条件:

条件触发例子
LLM 主动停止stop_reason == "end_turn"LLM 觉得任务完成,返回最终答案
步数上限循环计数器达到 N防止 LLM 陷入死循环
超时总耗时超过 T 秒防止工具调用卡死
用户中断用户主动按 ESC 或发送停止信号用户看到方向不对,主动叫停
错误阈值连续工具调用失败超过 K 次防止 LLM 反复试同一个失败的工具

经验法则:永远设置步数上限。否则 agent 可能因为"再查一次试试"反复循环,导致 token 和时间成本失控。

3.3 上下文管理:agent 的工作记忆

agent 每轮循环都会往 messages 列表里加东西:

[user_task, assistant_msg_1, tool_result_1, assistant_msg_2, tool_result_2, ...]

这个列表就是 agent 的工作记忆。问题在于:context window 是有限的。Claude Sonnet 4.5 的 context 是 200K tokens,看起来很大,但一个复杂 agent 跑 20 轮、每轮工具结果几千 token,很快就爆。

Anthropic 在 Context Engineering 博客里提出一个核心概念——Context Rot(上下文腐烂)

随着 context 里 token 数量增加,模型准确回忆信息的能力会下降。

也就是说,不是"塞得下就记得住",而是"塞得越满,记得越差"。所以 agent 工程的关键不是"塞满 context",而是"用最小的高信号 token 集最大化期望结果"。

实操建议:

  • 工具结果要精简:不要把整个网页 HTML 塞进 context,只塞提取后的关键信息。
  • 长内容用文件中转:工具把大结果写文件,context 里只放文件路径和摘要。
  • 定期压缩:每 N 轮把历史对话压缩成一段摘要,替换原始 messages。
  • Subagent 隔离:把复杂子任务交给 subagent 在独立 context 里跑,主 agent 只拿结论。

3.4 错误处理:agent 怎么面对失败

工具调用会失败。网络超时、文件不存在、API 限流、权限不足——这些都会发生。agent 的处理方式决定了它能不能跑稳。

错误返回给 LLM,而不是吞掉

python
def execute_tool(tool_call):
    try:
        return tool.run(tool_call.args)
    except Exception as e:
        # 关键:把错误信息作为 tool_result 返回给 LLM
        return {"error": f"工具执行失败: {type(e).__name__}: {str(e)}"}

LLM 看到 {"error": "..."} 后,会自己决定怎么办——换工具、换参数、放弃任务、或问用户。不要吞错误,否则 LLM 会以为成功了,继续往下走,最后给你一个错误结果。

重试 vs 放弃

  • 网络超时、限流 → 重试 1-2 次
  • 文件不存在、权限不足 → 不重试,直接告诉 LLM
  • 工具本身有 bug → 重试也没用,告诉 LLM 让它换办法

循环检测:如果 LLM 连续 3 次调同一个工具用同样的参数都失败,强制停止——它卡住了。

3.5 一个能跑的最小 Python agent

把上面的概念合起来,写一个真能跑的 agent。这个 agent 能查天气(用 mock 数据)+ 计算,基于 Anthropic SDK。

python
import anthropic
import json

client = anthropic.Anthropic()  # 自动读 ANTHROPIC_API_KEY

# 定义工具
tools = [
    {
        "name": "get_weather",
        "description": "查询指定城市的天气",
        "input_schema": {
            "type": "object",
            "properties": {
                "city": {"type": "string", "description": "城市名"}
            },
            "required": ["city"]
        }
    },
    {
        "name": "calculate",
        "description": "执行数学计算",
        "input_schema": {
            "type": "object",
            "properties": {
                "expression": {"type": "string", "description": "数学表达式,如 2+3*4"}
            },
            "required": ["expression"]
        }
    }
]

# 工具实现
def execute_tool(name, args):
    if name == "get_weather":
        return f"{args['city']} 今天 25 度晴"  # mock 数据
    elif name == "calculate":
        try:
            return str(eval(args["expression"]))  # 仅演示,生产环境别用 eval
        except Exception as e:
            return f"计算失败: {e}"

# Agent Loop
def agent(user_task, max_steps=10):
    messages = [{"role": "user", "content": user_task}]
    
    for step in range(max_steps):  # 步数上限
        response = client.messages.create(
            model="claude-sonnet-4-5-20250929",
            max_tokens=1024,
            tools=tools,
            messages=messages
        )
        
        # 检查停止条件
        if response.stop_reason == "end_turn":
            return response.content[0].text  # LLM 主动停止
        
        # 处理工具调用
        if response.stop_reason == "tool_use":
            messages.append({"role": "assistant", "content": 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
                    })
            messages.append({"role": "user", "content": tool_results})
    
    return "达到步数上限,agent 停止"

# 试一下
print(agent("北京今天多少度?算一下 (25 + 7) * 2 等于几"))

跑起来你会看到:

  1. LLM 先调 get_weather(city="北京") → 拿到 "25 度晴"
  2. LLM 再调 calculate(expression="(25+7)*2") → 拿到 "64"
  3. LLM 给出最终答案:"北京今天 25 度晴,(25+7)*2 = 64"

这就是一个真能跑的 agent,不到 50 行 Python。第 4 章我们详细讲 Tool Use 的细节,第 5 章讲怎么用 MCP 让工具标准化。

前人智慧 / Prior Art

本章把 Agent Loop 落到 50 行能跑的 Python 代码,并讲了停止条件、上下文管理、错误处理三个工程细节。这些都是经典论文和工程博客的实证结论。

ReAct 论文的循环结构验证

ReAct 论文(Yao et al., 2022)不仅提出了 Thought/Action/Observation 格式,更重要的是用实验证明了循环结构优于单次推理

论文在 HotpotQA 上的对照:

方法EM 准确率关键观察
Standard prompt28.7%直接答,常错
CoT (只推理)29.4%推理但易幻觉
Act (只行动)25.7%调工具但不知为何
ReAct35.1%推理+行动交替

ReAct 把循环结构化:每一轮必须先 Thought 再 Action,Observation 必须回灌到下一轮 Thought。本章 3.1 节的格式就是这个结构。

现代 Anthropic / OpenAI 的 tool use API 不再用 "Thought: / Action:" 文本格式,改用结构化 JSON(tool_use block)。但底层逻辑完全一样--这是 ReAct 的工程化升级,不是替代。

Context Rot 的实证

Anthropic Context Engineering 博客 提出的 Context Rot 不是概念,是实测结论。博客里展示了 context 使用率和召回准确率的关系曲线:context 用到 80% 时,召回率比 20% 时下降 20+ 个百分点。

这条曲线直接支撑了本章 3.3 节"上下文管理"的几条建议:

  • 工具结果要精简 -- 减少 context 增量
  • 长内容用文件中转 -- 不进 context
  • 定期压缩 -- 减少 context 累积
  • Subagent 隔离 -- 不污染主 context

Context Engineering 现在有独立章节深入讲(第 5 章),本节只点结论。

"Lost in the Middle" 现象

Liu et al. 2023 的论文实证了长 context 的一个具体失败模式:关键信息放在 context 中间时,召回率显著下降

实验设计很巧妙:让模型从 20 个文档里找一个事实,事实位置从开头扫到结尾。召回率呈 U 型曲线--开头和结尾高(90%+),中间低(~50%)。

对本章的工程意义:context 不是"塞得下就行",重要信息要放开头或结尾。具体到 Agent 实现:

  • system prompt 放最前(指令)
  • 原始任务放最前(目标)
  • 最近工具结果放最后(最新观察)
  • 中间放压缩后的历史

这条规则在第 5 章 Context Engineering 会详细展开。

错误返回给 LLM 的工程智慧

本章 3.4 节强调"错误返回给 LLM,不要吞"。这条经验来自多个工程实践:

  1. AutoGPT 失败教训:AutoGPT 早期吞错误,导致 LLM 以为成功继续往下走,最后给错误结果。这是 AutoGPT 翻车的原因之一。
  2. Claude Code 设计:Claude Code 的 Bash 工具明确把 stderr 和非零退出码作为 tool_result 返回给 LLM,让 LLM 自己决定怎么办。
  3. OpenAI Function Calling 文档:官方文档示例里,工具失败时返回 {"error": "..."} 而不是抛异常。

这条经验的本质:LLM 需要真相才能做对决策。吞错误等于喂假数据,必然导致错误决策。

LangGraph 的停止条件设计

LangGraph 文档 把停止条件做成 first-class concept:

  • END 节点:图执行到此停
  • 条件边:根据 state 决定继续或停
  • recursion_limit:最大步数硬上限

本章 3.2 节的停止条件表格(LLM 主动停止 / 步数上限 / 超时 / 用户中断 / 错误阈值)就是 LangGraph 设计的归纳。LangGraph 的工程贡献是把这些停止条件显式化--不是隐式行为,是可配置可观察的图节点。

Reflexion 对"循环检测"的启发

本章 3.4 节提到"连续 3 次同样工具+参数强制停止"。这条规则的论文支撑来自 Reflexion:Reflexion 的设计就是"检测到失败轨迹后,不要机械重试,要反思换方法"。

机械重试(同样工具+同样参数)是 Agent 最蠢的失败模式。Reflexion 启发的工程做法是:

  1. 检测重复(同工具+同参数 N 次)
  2. 强制停止
  3. 生成反思("为什么这个工具反复失败")
  4. 把反思写入 context,让 LLM 换方法

这套流程在第 8 章评估与安全会详细讲。

性能平台期与外部反馈的突破 07-09

吴恩达 Agentic AI 课程(Bilibili BV1DfrdByE2H,Module 4 外部反馈)提出一个对 Agent 工程很有指导意义的观察:单纯优化 prompt 很快会遇到平台期,反射和外部反馈是突破平台期的关键

性能曲线大致这样:

text
收益

  │  ★ 加反射 / 外部反馈后突破
  │   ╱─────── 平台期
  │  ╱
  │ ╱
  │╱
  └────────────-> 迭代次数

含义:

  • 前 3-5 轮 prompt 优化收益明显
  • 第 5-10 轮边际收益骤降(平台期)
  • 这时继续调 prompt 不如加反射、加工具、加外部反馈

外部反馈的四种类型

当 LLM 自己判断输出对不对不可靠时,可以用外部反馈来校准。吴恩达课程总结了四类常见的外部反馈:

类型怎么做例子
代码执行让模型生成代码,运行,拿真实结果print(2**0.5) -> 1.414...
规则检查用正则 / 代码检查输出格式文案词数 ≤ 10、输出是合法 JSON
搜索验证搜外部知识库 / 网页对照论文标题是否真实存在
工具结果调工具拿真实数据查数据库、读文件、调 API

外部反馈 vs 自反射的区别

  • 自反射:LLM 自己看自己的输出,判断好不好 -- 容易自我偏好,不可靠
  • 外部反馈:用代码 / 规则 / 搜索 / 工具拿真实结果 -- 客观,可复现

平台期突破的关键就在这里:当 LLM 自反射已经提不动时,外部反馈能给出 LLM 自己看不到的信号。比如 LLM 写的代码有 bug,自反射可能觉得"逻辑没问题",但代码执行报错就是客观事实--这个反馈比 LLM 自评更可靠。

对 Agent Loop 设计的影响

  1. Agent Loop 里应该优先用外部反馈(代码执行、工具结果),不是让 LLM 自评
  2. 错误返回(3.4 节)就是外部反馈的一种--工具执行失败是客观信号
  3. 评估时如果 eval 分数卡住,先别继续调 prompt--检查是不是该加反射或外部反馈

这条洞察和本章 3.4 节"错误返回给 LLM"的工程经验一致:LLM 需要真相才能做对决策。外部反馈就是给 LLM 真相的最可靠通道。

50 行代码的工程意义

本章 3.5 节的 50 行 Python agent 不是玩具,是教学最小集。它包含了所有 agent 的核心:

  • 工具 schema 定义
  • tool_use / tool_result 处理
  • 停止条件(end_turn / 步数上限)
  • 错误返回

后续所有框架(Claude Agent SDK / LangGraph / CrewAI)底层都是这个循环,只是封装了不同抽象。理解这 50 行,就能理解所有 agent 框架在做什么--它们都在帮你避免手写这 50 行,同时加上权限、subagent、hook 等生产化能力。

  • Agent loop 的核心模式是 ReAct:Thought → Action → Observation 循环。
  • 停止条件必须设置:步数上限 + 超时 + 错误阈值。
  • 上下文管理是 agent 工程的核心:Context Rot 让塞满 context 反而变差。
  • 错误返回给 LLM,不要吞掉——LLM 会自己决定怎么办。
  • 50 行 Python 就能跑一个真能用的 agent。

下一章我们深入 Tool Use——工具 schema 怎么设计、Anthropic 的 tool use API 怎么用、工具设计的经验法则。

基于 CC BY-SA 4.0 发布