跳转到内容

Agent Loop:推理、行动、观察与停止条件

一个 Coding Agent 说“修复完成”,下一秒却又调用同一条失败命令;一个天气助手查到有雨,却没再去确认日历是否已经改期。它们缺的不是工具,是让工具结果真正改变下一步的循环。

查一次天气只是函数调用。查到有雨,再读取日历、判断冲突、请求改期、验证结果,才是 Agent Loop。它看上去只是一个 while,工程难题却都藏在里面:动作怎样表示,错误怎样反馈,状态怎样更新,重复怎样识别,以及谁有资格让循环结束。本讲只讨论一次 Agent Run 内部的循环,跨小时、跨天的外部任务循环留到第 11、12 讲。

ReAct 的教学写法和它的工程版本

Section titled “ReAct 的教学写法和它的工程版本”

2022 年公开的 ReAct 把 reasoning 与 acting 交替组织:模型不再只在内部生成推理,也不再盲目发出动作,而是根据环境返回的 Observation 修正后续选择。[1] 最常见的教学写法是:

Thought -> Action -> Observation -> Thought -> ... -> Final Answer

这个写法直观,落到工程实现时要补两件事。Thought 不必等同于完整、可见的内部思维过程,生产系统更适合保存简短计划、动作理由或结构化决策,不应依赖记录模型的隐藏推理。而 Final Answer 只是模型的一种动作,任务是否真的完成,仍要由测试、数据库状态、文件工件或用户确认来决定。

所以工程版 Agent Loop 更接近下面这组可检查的动作:

读取状态
-> 请求模型选择候选动作
-> 校验动作
-> 执行动作
-> 记录环境观察
-> 更新状态
-> 检查终态与预算
-> 继续或停止

很多框架的核心循环都能压缩成类似伪代码:

while steps < max_steps:
decision = model(messages, tools)
if decision.is_final:
return verify_or_return(decision)
action = validate(decision.tool_call)
observation = execute(action)
messages.append(observation)
return budget_exhausted()

mini-swe-agent 的源码很适合观察这种骨架。它围绕模型查询、命令执行、Observation 回填和退出条件组织循环,代码量不大,却已经包含 Agent 的主要运动方式。[2] 看图的时候只盯这条顺序:先 query,再 execute,接着把环境结果放回去。

mini-swe-agent 的最小循环源码

图 1:mini-swe-agent 的 query-execute-observe 循环。来源见文末。

代码短不代表问题简单,每一行背后都有一份需要写清楚的契约。

1. Messages:模型看见的运行上下文

Section titled “1. Messages:模型看见的运行上下文”

Messages 可能包含用户目标、系统规则、工具结果和先前动作。它们帮助模型决策,却不应该承担全部业务状态。订单金额、审批状态或剩余预算如果只藏在自然语言消息里,系统很难可靠恢复,也难防止模型误读。

Decision 通常有两类:调用工具,或者给出最终回复。成熟一点的接口还会表达拒绝、请求澄清、转交其他 Agent、等待审批等状态。把所有情况都塞进一段自由文本,后续控制会非常脆弱。

3. Action:通过校验后的实际动作

Section titled “3. Action:通过校验后的实际动作”

模型输出不应直接等于动作。Harness 至少要检查工具是否存在、参数是否符合 Schema、当前身份是否有权限,以及动作是否需要审批。只有通过检查的候选决策,才会成为 Action。

Observation 不能只写“工具执行成功”。它要给模型和程序足够的结构,让下一步知道发生了什么:

{
"status": "success",
"order_id": "order_demo_1024",
"new_state": "refunded",
"transaction_ref": "txn_demo_88"
}

失败也要结构化:是参数错误、权限不足、网络超时、业务拒绝,还是结果未知?不同错误需要不同处理。

State 记录目标、当前步骤、工件、预算、重试和终态。Messages 可以从 State 生成,但 State 不应只能从 Messages 猜回来。

Trace 保存每次模型、工具、Guardrail 和状态变化的关联。它用来调试和评估,不是把所有敏感内容原样记录下来。

Agent 的常见失败,很多时候不在模型不会推理,而在环境反馈不完整。比如命令执行失败,只返回:

Something went wrong.

模型无法判断应该修改参数、换工具、稍后重试还是请求人工处理,它只能猜。更有用的返回是:

{
"status": "failed",
"error_type": "permission_denied",
"operation": "write_file",
"path": "/protected/config.yml",
"retryable": false,
"next_allowed_action": "request_approval"
}

这不是为了迁就模型,是正常的接口设计–程序员调用 API 同样需要错误码和可恢复语义。一份 Tool Result 应该回答四件事:发生了什么;环境现在是什么状态;这个错误能否重试;下一步允许做什么。

一个可靠的 Loop 不是只有 success 和“继续”。至少要事先写清下面六类出口。

目标由可信证据满足。比如测试通过并且补丁存在,订单状态已经更新,报告文件已生成且引用检查通过。

出现不可恢复错误,例如身份无效、工具不存在、输入违反业务规则。

达到最大轮次、Token、费用或总时长。预算耗尽是正常终态,不应伪装成成功。

等待用户补充信息、人工批准或外部事件。暂停必须保存恢复所需状态。

用户或上游系统取消任务。取消后还要处理已经发生的副作用。

系统不断重复相同工具、相同错误或等价方案。即使预算还有剩余,也应触发 Circuit Breaker。

假设 Coding Agent 回复:“修复完成,所有测试通过。”Harness 不应直接接受这句话,它至少要检查:

  1. 工作区是否真的有改动;
  2. 运行的是哪组测试;
  3. 测试退出码是否为零;
  4. 测试对应的源码版本是否就是当前版本;
  5. 是否存在未跟踪的失败或跳过项;
  6. 修改是否超出允许范围。

模型的 Final Answer 是 Claim,测试记录、环境终态和工件才是 Evidence。

No evidence, no success.
没有证据,不进入成功终态。

这条规则会贯穿后面的 Loop Engineering、Evaluation 和可靠性章节。

SDK 内置 Agent Loop,处理模型调用、工具调用、Handoff、Guardrail 和最终输出。开发者可以使用 Runner,也可以在需要确定性控制时用普通代码编排。[3]

下面这张仓库页只能证明框架入口存在,权限、状态和成功证据仍然要留在应用里定义。

OpenAI Agents SDK 仓库页面

图 2:框架可以封装循环,但应用仍要定义工具权限、业务状态和成功证据。来源见文末。

smolagents 提供 CodeAgentToolCallingAgent。前者让模型生成代码组合动作,表达能力更强;后者用结构化 Tool Call,边界更容易检查。[4] 两种方式没有绝对优劣,动作越通用,沙箱和审计要求越高。

LangGraph 不把所有循环隐藏在 Runner 里。节点返回状态更新,条件边决定继续、转向或结束,Checkpoint 又让循环可以暂停和恢复。[5] 这种设计牺牲了一些轻量性,换来显式控制。

Google ADK:Agent Loop 与 Workflow Agent 并存

Section titled “Google ADK:Agent Loop 与 Workflow Agent 并存”

ADK 允许 LLM Agent 自主选择,也提供 Sequential、Parallel、Loop 等确定性 Workflow Agent。[6] 这是一种常见的生产思路:需要开放判断的地方交给模型,必须经过的步骤交给程序。

动手:两个 Loop,只差一个停止规则(15 分钟)

Section titled “动手:两个 Loop,只差一个停止规则(15 分钟)”

本讲附带一个不调用外部模型的可运行实验:minimal_agent_loop.py。脚本用固定决策模拟模型,避免 API 和模型版本干扰。进入本讲的 experiments/ 目录后运行:

Terminal window
python3 minimal_agent_loop.py

先别急着改代码。先确认一个案例在两步进入 success,另一个案例在第三步因为 max_steps 进入 budget_exhausted,再去看两条 Trace 有什么不同。

成功案例先调用天气工具,再给出答案:

{
"terminal": "success",
"trace_steps": 2
}

第二个案例不断重复天气工具,Harness 在第三步触发最大步数:

{
"terminal": "budget_exhausted",
"reason": "max_steps",
"trace_steps": 3
}

完整结果见 experiment-result.json。我第一次跑的时候还撞到一个问题:系统 Python 3.9 不支持 str | None 这种联合类型语法,脚本直接起不来,修正过程记在 validation-notes.md。运行环境也算 Harness 的一部分,示例代码看起来正确,不等于在目标环境里能跑。

确认基线以后,可以按下面顺序修改实验:

  1. 加入未知工具,观察 invalid_action
  2. 让工具返回 retryable: true,实现最多一次重试;
  3. 检测连续相同动作,提前触发 no_progress
  4. 把 Trace 中的敏感字段替换成摘要。

四条规则可以先记住:动作先校验再执行;Observation 要能指导恢复,不能只有一句自然语言抱怨;模型可以申请成功,验证器决定是否成功;每个循环都必须有预算和失败出口。

本讲实验用固定决策模拟模型,只验证 Loop 控制,不比较模型能力;Thought 也是教学标签,不要求记录或展示模型的隐藏推理。不同框架对循环和终态的命名会不同,共同问题没有变:动作要可校验,反馈要可恢复,成功要有证据。

下一讲会把 Agent Loop 放回更大的系统,区分 Workflow、Agent、Harness 与 Loop Engineering。先分清这四层,后面讲状态图、长任务和多 Agent 才不会绕成一团。


以下链接均为本章已引用的原始论文、官方文档或源码。建议先读 [1] 理解 ReAct 的起点,再对照 [2] 的源码和 [3]-[6] 的框架实现。

[1] Yao et al., ReAct. https://arxiv.org/abs/2210.03629

[2] SWE-agent, mini-swe-agent default loop. https://github.com/SWE-agent/mini-swe-agent/blob/main/src/minisweagent/agents/default.py

[3] OpenAI, Running agents. https://openai.github.io/openai-agents-python/running_agents/

[4] Hugging Face, smolagents. https://huggingface.co/docs/smolagents/index

[5] LangChain, LangGraph Graph API. https://docs.langchain.com/oss/python/langgraph/graph-api

[6] Google, ADK Workflow Agents. https://google.github.io/adk-docs/agents/workflow-agents/