· ARJUN · WRITING

在构建一个复杂的 Agent 时,会遇到哪些挑战?

一个复杂 Agent 往往要连续规划、调用工具、处理失败并验证结果。只要其中一环缺少边界,任务即使跑完也可能无法交付。本文从一次跑偏的 CI 排障开始,拆解开发中最常见的五类挑战。

你给编程 Agent 一个任务:

找出昨晚 CI 失败的原因,完成修复并创建 Pull Request。不要修改部署配置。

它先读取失败日志,再检索代码,修改测试超时时间,重新运行测试,最后创建 PR。每一次工具调用都返回成功,PR 里也有一段完整的原因说明。

看上去,任务做完了。

但日志接口默认只返回最后 500 行。Agent 看到的是结尾的 test timed out after 60s,没有看到前面更早出现的连接池耗尽。它把超时从 60 秒改成 120 秒,本地重跑恰好通过,随后把「测试时间不足」写成了根因。

工具没有报错,执行链也没有中断,结果仍然不能交付。

这类问题很难靠一句更长的 Prompt 解决。模型、计划、工具、外部环境和停止条件共同组成了一条执行链。把这条链拆开,复杂 Agent 的工程难点主要集中在五处:怎样定义可靠,计划如何跟随环境调整,工具失败后怎样恢复,错误发生后如何复盘,以及怎样控制整条链的成本和延迟。

挑战一:一次跑通,离可靠还很远

先从最容易被忽略的地方说起:到底怎样才算完成?

「修复 CI」对人来说像一个明确目标,对 Runtime 来说却缺少很多东西:什么算根因,哪些文件不能改,哪组测试可以证明修复有效,什么情况必须停下来问人。

如果这些内容只藏在自然语言里,Agent 很容易把「成功创建 PR」当成「成功修复问题」。更稳妥的做法,是在执行前把任务整理成一个可检查的 Task Contract:

goal: 找到 CI 失败的根因并提交最小修复
constraints:
  - 不修改部署配置
  - 不写入生产环境
evidence_required:
  - 覆盖完整失败时间窗口的日志
  - 能复现原始失败
  - 相关回归测试通过
deliverable:
  - 包含根因、改动和验证证据的 Pull Request
stop_when:
  - 日志不完整
  - 需要超出权限的操作
  - 证据之间互相冲突

这不是为了把自然语言全部变成配置文件,而是把最容易在多步执行里丢掉的内容固定下来:交付物、边界、证据和停止条件。

模型可以决定下一步怎么走,但不能在执行到一半时悄悄改掉「什么算完成」。

复杂 Agent 从任务请求、Task Contract、计划与状态、工具执行到证据检查的闭环;证据不足时返回补查或重规划
Task Contract 固定目标、边界、证据和停止条件。证据不完整时,系统应该回到补查或重规划,而不是直接交付。

同一个任务要经得住重复运行

同一个任务重复运行,可能得到不同的工具选择和执行路径。原因不只来自模型采样:检索结果会变化,外部 API 会超时,上下文可能被裁剪,仓库状态也可能已经更新。

τ-bench 把 Agent 放进带有用户、工具和业务规则的任务中,不只检查最终回答,而是比较任务结束后的数据库状态。它还用重复运行衡量同一个任务能否持续成功。这个设计说明了一个容易被单次 Demo 掩盖的问题:跑通一次只能证明这次成功,不能证明下一次仍会遵守同样的规则。

工程上不必追求每条自然语言轨迹完全一致。需要稳定的是这些结果:

稳定的结果,可以建立在不同的执行路径上。

挑战二:计划会被真实环境打断

复杂任务通常需要规划,但「先列五个步骤,然后照着做」仍然太脆弱。日志查不到、测试无法复现、工具权限不足,都会让原计划失效。

ReAct 的核心思路是交错进行推理与行动:执行一个动作,读取环境返回的 Observation,再决定下一步。Plan-and-Execute 则先形成较完整的计划,再由执行器逐步完成,并在必要时重新规划。它们解决的是不同粒度的控制问题,没有一种模式适合所有任务。

无论采用哪种方式,计划最好进入可持久化 State,而不是只留在模型生成的一段文字里:

{
  "step": "collect-ci-evidence",
  "status": "blocked",
  "depends_on": ["locate-failed-job"],
  "evidence": ["job:9182/log-page:1"],
  "missing": ["earlier_log_pages"],
  "retry_budget": 1,
  "next": "request-next-log-cursor"
}

这样 Runtime 才能回答几个关键问题:当前卡在哪一步,依据是什么,还缺什么,是否允许重试,以及恢复后从哪里继续。

上一篇关于 Agent Planning 的文章讨论了不同规划方法。这一篇更关心计划落到生产系统后怎样被保存、验证和修订。

挑战三:工具成功了,任务仍可能失败

日志接口返回 200,证据仍可能少一半。

工具调用至少有四层不同的「成功」:

  1. Transport: 请求发出并收到响应;
  2. Schema: 字段和类型符合接口定义;
  3. Semantic: 结果完整、时效正确,并能回答当前问题;
  4. Effect: 有副作用的操作只发生了一次,而且发生在允许的对象上。
工具调用从 Transport、Schema、Semantic 到 Effect 的四层成功,以及 truncated 标记为何意味着证据仍不完整
HTTP 200 和 Schema 正确只说明请求与结构没有出错;证据是否完整、写操作是否只发生一次,还需要更高一层的检查。

开头的日志接口只满足了前两层。更有用的返回结构应该把完整性也暴露出来:

{
  "status": "ok",
  "lines": ["..."],
  "window": {
    "from": "2026-09-16T23:48:10Z",
    "to": "2026-09-16T23:52:31Z"
  },
  "truncated": true,
  "next_cursor": "log-page-2"
}

Schema 校验能阻止 next_cursor 被写成一个对象,却不能替 Agent 判断「这 500 行是否足以定位根因」。工具契约还需要说明数据范围、来源、更新时间、是否截断,以及这次调用产生了什么副作用。

工具描述也不该只写「查询日志」。它应该告诉模型什么时候使用、不能用来证明什么、可能返回哪些不完整状态,以及遇到这些状态后应该怎么处理。

写操作超时以后,先别急着重试

外部工具失败后,最容易写出的策略是「最多重试三次」。它对部分读取请求有效,对写操作却可能制造新问题。

假设 Agent 调用 Git 平台创建 PR,客户端等待超时。此时有两种可能:服务器没有收到请求,或者 PR 已经创建,只是响应没有返回。直接重试可能产生重复 PR。

更稳妥的顺序是:

发送创建请求
  → 响应超时,结果未知
  → 按分支名或操作标识查询现有 PR
  → 已存在:记录结果并继续
  → 不存在:确认请求可安全重放后再重试
创建 Pull Request 响应超时后,先对账外部状态,再决定继续、重试或请求人工确认
写操作超时后,结果处于未知状态。先按业务标识对账,再决定继续、带幂等键重试,还是请求人工确认。

不同错误需要不同策略:

失败类型 处理方式
临时网络错误、无副作用读取 有上限的重试与退避
参数不符合 Schema 修复参数后重新调用
权限不足或认证失败 停止,不用重复请求掩盖问题
写操作超时、结果未知 先 Reconcile 当前状态,再决定是否重放
返回成功但证据不完整 补充查询或标记 Blocked,不进入下一阶段

Idempotency Key 能帮助服务识别重复操作,但前提是服务端实现了对应语义。没有这个能力时,Runtime 至少要保存操作标识、调用前状态和可用于对账的业务键。

失败恢复往往比第一次调用成功更难,因为系统必须知道:什么已经发生,什么只是准备发生,什么仍然无法确认。

挑战四:错误发生后,必须能复盘和评估

Trace 要留下能复盘的现场。

普通应用日志经常只留下「请求开始」「请求结束」和错误堆栈。对 Agent 来说,这不足以解释为什么它从查日志走到了修改超时。

一条可用的 Trace 至少应该串起:

LangSmith 和 Langfuse 都能记录模型调用、工具步骤、延迟和 Token 等信息。它们解决的是采集与查看问题,不会自动告诉你哪条业务规则写错了。字段怎么设计、敏感数据怎样脱敏、哪些证据需要长期保留,仍然属于应用架构。

无需把模型隐藏的 Chain of Thought 全部保存下来。调试所需的是应用能够观察的输入、输出、状态转换和外部动作。把这些信息串起来,已经足以定位大部分「从哪一步开始偏了」。

评估不只给最终回答打分

如果评估只问「最终回答读起来怎么样」,一个错误诊断也可能因为表达完整而得到高分。Agent 的评估至少需要三层:

1. 最终状态

用确定性检查回答:目标文件是否按预期修改,测试是否通过,数据库是否达到目标状态,禁止操作是否从未发生。

2. 执行轨迹

检查 Agent 是否选错工具、重复调用、忽略截断标记、超出步数预算,或者在证据不足时继续执行高影响动作。

3. 语义质量

判断根因说明是否准确、引用证据是否支持结论、PR 描述是否足够让审阅者理解。这里可以使用带 Rubric 的 LLM-as-a-Judge,但它不该单独充当事实裁判。确定性结果、人工标注样本和定期抽查仍然必要。

评估集也不能只放正常路径。至少要覆盖:工具超时、空结果、截断结果、权限不足、Schema 变化、重复写入和互相冲突的证据。

还要把同一批关键任务重复运行。单次成功率告诉你 Agent 有没有能力完成,重复成功则更接近用户真正感受到的可靠性。

Agent 可靠性评估分为最终状态、执行轨迹与语义质量三层,并结合延迟、Token、错误和重试等 Trace 指标
最终状态回答有没有完成,执行轨迹检查过程是否越界,语义质量判断结论是否被证据支持;延迟、Token、错误与重试来自同一条 Trace。

挑战五:每一步都在消耗时间和预算

每增加一次模型调用、检索或工具往返,都会增加成本和等待时间。更复杂的 Agent 有时能完成更开放的任务;如果只是多绕几轮再得到同一个结果,那些步骤只是在消耗预算。

Anthropic 在 Building effective agents 中建议从最简单的方案开始:路径稳定、规则明确的任务更适合由代码编排的 Workflow;只有当步骤无法提前写死、确实需要模型根据环境决定下一步时,才增加 Agent 自主性。

常见优化需要带着边界使用:

优化的目标不是让 Agent 少想几步,而是让每一步都值得。

上线前检查表

检查项 要回答的问题
完成条件 什么最终状态才算交付,而不只是流程结束?
权限边界 哪些动作可自动执行,哪些必须确认,哪些禁止?
工具契约 返回值是否暴露来源、时间范围、截断和副作用?
状态保存 中断后能否知道最后确认成功的是哪一步?
失败恢复 读失败、写超时和权限错误是否使用不同策略?
执行预算 是否限制步数、时间、Token 和重复调用?
Trace 能否从最终结果追到每条证据和状态转换?
评估集 是否同时检查终点、轨迹、异常路径和重复运行?

回到开头那次 CI 排障。一个可以交付的 Agent,不只是创建了 PR。它还应该说清楚:哪段完整日志支持根因,哪项修改对应这个原因,哪些测试证明问题已经消失,以及它遵守了哪些不能触碰的边界。

如果这些都没有,剩下的只是一条执行成功的工具链,和一段听起来很像答案的总结。

创建了 PR 只是流程结束;找到真实原因并提供完整证据,任务才算真正完成。

参考资料