一、引言
AI Agent 的关键能力来自一个持续运行的反馈循环:系统先理解任务和约束,再选择下一步行动;如果行动需要外部能力,就调用工具并读取环境返回的结果;如果结果不符合预期,就修正计划或操作;直到任务完成、达到停止条件,或者需要把决策权交还给用户。
本文将这个过程抽象为六个阶段:
lua
输入理解 → 任务规划 → 工具执行 → 结果观察 → 错误修正 → 最终响应
↑ |
+-------------------------+
这个六阶段模型不一定对应六次模型调用,一个模型响应可能同时完成输入理解、局部规划和工具选择;一个框架节点也可能把观察与纠错合并。这里划分的是逻辑职责,而不是固定的代码模块。
它是一种工程抽象,不是行业统一标准。它的价值在于提供共同语言:无论系统采用 OpenAI Agents SDK、LangGraph、Google ADK,还是自研 Runtime,都可以用同一组问题分析其执行链路。
二、输入理解:把自然语言转成可执行任务
用户输入通常只描述期望,没有完整描述执行条件。例如:
帮我分析线上支付失败的原因,并给出修复建议。
Agent 在执行前至少需要提取以下信息:
arduino
Task
├── Goal:定位支付失败原因并提出修复建议
├── Scope:线上支付链路
├── Constraints:不能直接修改生产环境
├── Required evidence:日志、指标、发布记录、错误栈
├── Completion criteria:原因有证据支持,建议能对应原因
└── Missing information:时间范围、服务名称、环境权限
1.输入理解不是关键词分类
可靠的输入理解至少包含四个维度:
| 维度 | 需要识别的内容 | 识别错误的后果 |
|---|---|---|
| 目标 | 用户最终希望得到什么 | 完成了步骤,却没有完成任务 |
| 对象 | 操作哪个文件、服务或数据集 | 工具作用于错误对象 |
| 约束 | 权限、时间、格式和安全边界 | 产生越权或不可逆操作 |
| 完成标准 | 什么证据可以说明任务完成 | Agent 过早停止或持续循环 |
2.事实与假设需要分开
可以把 Agent 初始状态表示为:
yaml
State = {
observations: 已知事实,
assumptions: 当前假设,
constraints: 操作边界,
open_questions: 尚未解决的问题,
artifacts: 已获取的外部结果
}
"支付失败发生在数据库层"在读取错误栈之前只能算假设。如果 Agent 把假设当作事实,后续规划会沿错误方向持续展开。
3.歧义处理也是一种行动
遇到关键信息缺失时,Agent 有三个选择:
rust
低风险、可撤销 -> 采用显式假设后继续
可通过只读工具获得 -> 调用工具补充信息
高风险或不可推断 -> 请求用户澄清或批准
请求澄清并不意味着 Agent 失败。对于删除数据、发送消息、部署生产环境等高影响操作,把控制权交还给用户是正确的运行时决策。
三、任务规划:规划的是下一步,不一定是完整路线
任务规划容易被理解成"一开始生成一份完整步骤列表"。这种做法只适合环境稳定、路径可预测的任务。
真实环境会持续暴露新信息。Agent 更适合使用滚动规划:
yaml
初始目标
|
v
规划当前最合理的一步
|
v
执行并获取新信息
|
v
更新状态和剩余任务
|
+----> 规划下一步
1.三种常见规划粒度
- 反应式规划:每轮只确定下一步。
rust
观察日志为空
-> 检查日志时间范围
发现时区错误
-> 修正查询参数
发现数据库超时
-> 查询数据库指标
优点是能适应环境变化,缺点是容易短视或重复探索。
-
分层规划:先生成里程碑,再动态选择每个里程碑内的行动。
目标:定位支付失败
├── 1. 确认故障窗口
├── 2. 定位异常服务
├── 3. 验证候选原因
└── 4. 形成修复建议
分层规划兼顾方向稳定和局部灵活性,适合多数工程 Agent。
- 固定工作流:由程序预先定义执行路径。
rust
输入分类 -> 参数校验 -> 查询数据库 -> 生成报告
它的行为容易测试,适合步骤明确的业务流程。路径已知时应优先考虑 workflow;无法预知步骤、需要模型动态决策时才使用 agent。
2.好计划需要包含验证动作
弱计划只描述"做什么":
markdown
1. 搜索代码
2. 修改代码
3. 返回结果
可验证计划同时描述"如何知道做对了":
markdown
1. 搜索错误码的定义和调用路径
验证:找到错误码的产生位置和传播链路
2. 根据失败条件修改最小范围代码
验证:变更只影响目标分支
3. 运行相关测试
验证:原失败用例通过,相关回归用例未失败
缺少验证步骤时,Agent 很容易把"工具调用成功"误判为"任务成功"。
四、工具执行:模型提出调用,运行时负责落地
模型通常不会直接执行外部操作。它生成结构化的工具调用请求,由 Runtime 校验并分派。
json
LLM 输出
{
"tool": "search_logs",
"arguments": {
"service": "payment-api",
"start_time": "2026-10-26T09:00:00+08:00",
"query": "status:error"
}
}
|
v
Runtime 校验
├── 工具是否存在
├── 参数是否符合 Schema
├── 当前身份是否有权限
├── 是否需要人工批准
└── 是否达到调用预算
|
v
工具适配器
|
v
日志平台 / 数据库 / 浏览器 / Shell / 内部 API
1.工具接口是 Agent 的行动空间
对 Agent 来说,工具名称、描述和参数 Schema 共同定义了它可以采取哪些行动。下面两个接口实际能力相近,但对模型的可用性不同:
php
//第一种
run(command: string)
//第二种
search_logs(
service: string,
start_time: datetime,
end_time: datetime,
query: string
)
后者明确表达了参数边界,运行时也能进行结构化校验。工具接口也称为 Agent-Computer Interface,其工具文档与测试会直接影响 Agent 表现。
2.工具错误需要机器可读
只有一段模糊文本:
javascript
Error: operation failed
模型很难决定下一步。结构化错误更有利于恢复:
json
{
"status": "error",
"code": "TIME_RANGE_TOO_LARGE",
"message": "单次查询最多支持 24 小时",
"retryable": true,
"suggestion": "缩小时间范围或拆分查询"
}
其中最关键的是:
- code :稳定的错误分类;
- retryable :是否值得重试;
- suggestion :允许怎样调整;
- details :供诊断使用的原始信息;
- side_effect :失败前是否已经产生部分副作用。
3.有副作用的工具需要不同控制
工具可以按风险分层:
L0 纯计算:格式转换、数学计算
L1 只读访问:搜索、读取文件、查询日志
L2 可撤销写入:创建草稿、修改工作区文件
L3 外部副作用:发消息、创建工单、调用付费服务
L4 高影响操作:删除数据、生产部署、资金操作
风险越高,越需要参数约束、幂等键、权限隔离、审批和审计记录。Agent 的智能水平不能替代这些系统控制。
五、结果观察:工具返回值还不是结论
ReAct 的核心贡献之一,是让模型交错地产生推理和行动,并将环境观察用于后续决策。在工程实现中,"观察"是把工具原始返回转成可以更新 Agent 状态的信息。
yaml
工具原始结果
|
v
语法检查:返回格式是否完整
|
v
语义检查:结果是否回答当前子问题
|
v
状态更新:新增了哪些事实和证据
|
v
进度判断:目标是否更接近完成
假设日志工具返回了 200 条记录。Agent 不应只记录"查询成功",而应提取:
erlang
Observation
├── 事实:09:31 后失败率由 0.2% 升至 18.4%
├── 事实:错误集中为 DB_CONNECTION_TIMEOUT
├── 关联:09:28 有数据库配置变更
├── 缺口:尚未证明配置变更导致连接超时
└── 下一验证:对照连接池指标和变更内容
工具成功描述的是调用状态,任务成功描述的是目标状态。两者必须分开。
另外,观察信息需要进入共享状态。LangGraph 把 state 描述为所有节点都能访问的共享记忆,用来保存跨步骤仍需保留的信息;其图节点读取状态并返回状态更新,这种设计可以防止执行逻辑依赖零散字符串拼接。
一个实用状态结构可以包含:
css
AgentState
├── user_goal
├── constraints
├── plan
├── observations[]
├── tool_calls[]
├── verified_facts[]
├── hypotheses[]
├── unresolved_questions[]
├── retry_budget
└── status
状态不应无限增长。长任务需要摘要、裁剪或外部存储,否则上下文成本会持续增加,旧信息也可能干扰当前判断。
六、错误修正:从失败中获得可用信号
Agent 的错误修正可以分成四层。
- 调用级错误:包括参数缺失、类型错误、超时、限流和权限不足,这类错误通常不需要重新规划整个任务。
rust
参数错误 -> 修正参数后重试
临时超时 -> 有上限地退避重试
权限不足 -> 请求授权或更换只读路径
工具不存在 -> 选择可用工具
-
结果级错误:工具正常返回,但结果不可用,这要求 Agent 检查结果语义,而不是只看 HTTP 状态码或进程退出码。
搜索结果为空
测试命令退出码为 0,但没有运行任何测试
网页请求成功,但内容是登录页
数据库查询成功,但使用了错误时间范围 -
计划级错误:连续几个步骤都没有减少不确定性,说明当前路线可能错误,有效修正需要改变假设、工具、查询范围或任务分解。如果 Agent 使用相同参数机械重试,它只是在消耗预算。
css
假设 H1
|
+-> 查询 A:无证据
|
+-> 查询 B:无证据
|
+-> 查询 C:仍无证据
|
v
放弃或降低 H1 优先级,生成 H2/H3
- 目标级错误:Agent 发现原目标无法在当前条件下完成,例如:用户要求访问未授权系统、数据源缺失而无法得出确定结论、两个约束互相冲突、执行风险超过允许范围等,此时正确行为是停止,说明证据、缺口与可选路径,而不是编造结果。
反思并不等于重新训练模型,错误修正可以发生在推理时状态和记忆层,通过语言形式的反馈,让 Agent 总结错误并把反思文本保存在情景记忆中,以影响后续尝试,而不需要更新模型权重。
不过,自我反思不能代替外部验证。模型可能生成听起来合理、实际错误的批评。工程系统应优先使用测试结果、API 响应、规则校验器和人工反馈等外部信号。
七、最终响应:用证据证明任务结束
Agent 不应因为"感觉已经完成"而停止。停止条件通常来自四类信号:
┌─────────────────┬──────────────────────────────────────┐
│ 停止类型 │ 条件 │
├─────────────────┼──────────────────────────────────────┤
│ 成功结束 │ 完成标准满足,必要证据已获得 │
│ 受控失败 │ 关键条件缺失,继续执行无法推进 │
│ 等待用户 │ 需要澄清、授权、审批或主观判断 │
│ 预算终止 │ 达到最大轮数、时间、费用或调用上限 │
└─────────────────┴──────────────────────────────────────┘
一个可靠的最终响应通常包括:
- 完成了什么;
- 关键结论是什么;
- 使用了哪些证据;
- 哪些内容尚未确认;
- 是否产生了外部副作用;
- 用户接下来是否需要做决定。
diff
结论:
支付失败由连接池上限配置错误引起。
证据:
- 09:28 发布将 max_connections 从 200 调整为 20;
- 09:31 起连接等待时间和支付失败率同步上升;
- 回滚配置后,测试环境的失败用例恢复通过。
已执行:
- 修改测试环境配置;
- 运行支付服务集成测试,42 个用例全部通过。
未执行:
- 未修改生产环境,生产回滚仍需审批。
这种响应保留了结果、证据和操作边界,比一句"问题已经修复"更适合审计。
八、把六阶段闭环写成最小 Runtime
下面的伪代码省略了模型供应商和工具协议细节,保留 Agent Runtime 的核心结构:
ini
def run_agent(user_input, tools, limits):
state = initialize_state(user_input)
turns = 0
while turns < limits.max_turns:
turns += 1
model_input = build_context(
goal=state.goal,
constraints=state.constraints,
plan=state.plan,
observations=state.observations,
available_tools=tools.schemas(),
)
decision = call_model(model_input)
if decision.type == "final":
if completion_check(decision.output, state):
return success(decision.output, state)
state.observations.append({
"type": "validation_error",
"message": "最终输出尚未满足完成标准",
})
continue
if decision.type == "ask_user":
return waiting_for_user(
question=decision.question,
resumable_state=state,
)
if decision.type == "tool_call":
validation = validate_tool_call(decision, tools, state)
if not validation.ok:
state.observations.append(validation.error)
state = revise_plan(state)
continue
result = tools.execute(
name=decision.tool_name,
arguments=decision.arguments,
)
observation = normalize_tool_result(result)
state.observations.append(observation)
if observation.indicates_failure:
state = repair_or_replan(state, observation)
else:
state = update_progress(state, observation)
return stopped(
reason="max_turns_exceeded",
state=state,
)
这个循环包含几个不可省略的控制点:
模型调用前:构建受控上下文
工具执行前:校验工具、参数、权限和审批
工具执行后:规范化结果并更新状态
最终回答前:检查完成条件
每轮结束后:检查预算和停止条件
真实系统还需要增加持久化、并发控制、幂等性、事件流、可观测性和人工介入机制。它们不会改变闭环本身,只会让闭环在生产环境中更可靠。
九、用统一模型分析不同 Agent 框架
| 统一阶段 | OpenAI Agents SDK | LangGraph | Google ADK |
|---|---|---|---|
| 输入理解 | Agent instructions、input、context | 初始 state、node | user message、session |
| 任务规划 | 模型输出下一行动 | Agent node、条件边 | Agent/LLM 产生 event |
| 工具执行 | Runner 执行 tool calls | ToolNode 或自定义 node | Runner 处理 FunctionCall |
| 结果观察 | 工具结果追加到输入 | 节点更新共享 state | FunctionResponse/event 写入 session |
| 错误修正 | 再次运行 loop、工具错误策略 | 条件边、重试、interrupt | callback、event loop |
| 最终响应 | final output / RunResult | 到达 END | final response event |
| 控制机制 | max_turns、guardrails、approval | persistence、interrupt、HITL | session、callbacks、plugins |
十、分析一个 Agent 系统的七个问题
面对一个新的 Agent 框架或产品,可以沿以下顺序分析:
markdown
1. Goal
用户目标怎样进入系统?
2. State
哪些信息会跨步骤保存?
3. Policy
谁决定下一步,模型、规则还是两者共同决定?
4. Action
Agent 可以调用哪些工具,工具怎样描述?
5. Observation
工具结果怎样返回并更新状态?
6. Recovery
参数错误、工具失败和计划失败分别怎样处理?
7. Termination
谁判断任务完成,最大预算是多少?
进一步可以增加两个生产问题:
markdown
8. Control
哪些操作需要审批、隔离或人工接管?
9. Observability
能否追踪模型输入、工具参数、结果、状态变化和停止原因?
如果一个系统只能回答 Action,却无法清楚解释 State、Recovery 和 Termination,它通常只是"支持工具调用的聊天应用",还没有形成可靠的 Agent Runtime。