假设我们请求 LLM Agent 创建一个 Issue。回答中写着“已创建 Issue”,但项目中并没有新的 Issue。再次询问时,Agent 解释称其已使用工具。

在更换模型或添加“绝对不要撒谎”这样的 Prompt 之前,我们需要先确认一件事:执行过程实际上进行到了哪个阶段?

仅凭 Agent 的文本,我们无法得知答案。必须区分以下情况:是否生成了工具调用请求、执行器是否处理了该请求、外部服务中是否留下了结果。

1. “调用了”这一表述混杂了不同的事件

使用工具的 Agent 的工作大致经过以下过程。

  1. 模型选择要使用的工具和参数。
  2. 运行时解析调用请求并验证权限。
  3. 执行器实际调用函数或外部 API。
  4. 工具结果传递给模型。
  5. 判断是否达到用户请求的状态。

每个步骤都可能单独失败。

即使模型正确生成了调用请求,执行器也可能抛出异常。即使 API 已受理请求,异步任务可能仍在执行中。即使执行已结束,也可能在错误的项目中创建了 Issue。

因此,在调查故障时,最好不要用“工具调用成功”这一单一状态来覆盖所有步骤。

2. 区分普通文本和调用事件

以 OpenAI Responses API 中的自定义函数调用为例。以下是调用项主要字段的简化示例。

json
{
  "type": "function_call",
  "call_id": "call_42",
  "name": "create_ticket",
  "arguments": "{\"title\":\"로그인 오류 점검\"}"
}

应用程序接收此项并执行相应函数,将结果关联到同一个 call_id。

json
{
  "type": "function_call_output",
  "call_id": "call_42",
  "output": "{\"status\":\"created\",\"ticket_id\":\"T-731\"}"
}

这里的 status 和 ticket_id 是示例应用程序定义的结果格式。它们并非由 OpenAI 保证 Issue 创建成功的标准字段。

这一结构与出现在普通回答中的以下句子有明显的不同。

我调用了 create_ticket 创建了 Issue。

这句话是模型生成的文本。即使代码块中写有工具名称或调用形式,如果执行器没有将其作为调用请求进行处理的实现,执行就不会发生。

自定义函数由应用程序执行,但由提供商运行的服务器端工具,其执行位置可能不同。因此,首先必须确认所使用的工具在哪里执行,以及会留下哪些事件。OpenAI 函数调用文档和 Claude 工具使用文档描述了这些执行边界。

3. 症状相同,原因不同

没有调用请求,只生成了完成句

如果模型输出中只有普通消息而没有调用事件,需确认模型是否在没有执行证据的情况下声称已完成。

可能的原因包括:该请求未暴露该工具、工具描述含糊、未选择调用等。首先必须比较实际请求中包含的工具定义与模型的原始输出。

但是,不能仅因为追踪界面中没有记录就确定未执行。还需检查采集缺失、采样情况以及独立的执行路径。

有调用请求,但执行器漏掉了

在这种情况下,需要查看应用程序的控制流,而不仅仅是模型。

  • 是否只读取了响应中的文本而丢弃了调用项?
  • 是否只处理了多个调用中的第一个?
  • 工具名称与实际处理程序名称是否匹配?
  • 执行循环是否在等待批准状态下结束?

流式传输也是检查对象。如果将参数片段处理为完成的请求,或漏掉调用完成事件,可能会导致执行遗漏。response.function_call_arguments.done 是参数生成结束的信号,而不是外部函数执行结束的信号。

执行失败被转换为成功并传递

异常处理代码有时也会抹去失败。

python
try:
    result = execute_tool(arguments)
except Exception:
    result = {"message": "처리했습니다"}

这段代码甚至消除了调查原因的线索。传递给模型的结果中也应保留失败及其范围。

在 MCP 中,收到响应的事实与工具操作的成功也是区分开的。例如,以下响应表示工具错误。

json
{
  "jsonrpc": "2.0",
  "id": 17,
  "result": {
    "content": [
      {
        "type": "text",
        "text": "프로젝트에 이슈를 생성할 권한이 없습니다."
      }
    ],
    "isError": true
  }
}

如果适配器仅传递 content 而丢弃错误标志,关键信息将会丢失。MCP 工具规范分别处理协议错误和工具执行错误。

将请求受理解释为工作完成

在部署、文件转换、电子邮件发送等具有多个阶段的工作中,尤其需要小心。

如果部署 API 返回了作业 ID,可能仅确认了“部署请求已受理”。即使邮件服务接受了消息,也不能确认其已到达收件人的收件箱。

必须为每个工具定义产品将哪个状态称为“完成”。

4. 区分 Prompt 和 Schema 解决的范围

“在收到执行结果之前,不要说已完成”这样的指示可能会有所帮助。但它无法恢复执行器的异常处理或中断的工作流。

配置也有其应用范围。

  • tool_choice: "required" 要求工具调用。
  • strict: true 有助于生成符合支持 Schema 约束的参数。

两者都不能保证外部操作的成功。格式正确的项目 ID 是否实际存在、用户是否有权限在该项目中工作,也需要单独检查。

强制调用的配置应应用于必要步骤,并能在描述结果的步骤中终止。如果在所有回合中无条件强制调用,可能会导致不必要的重复。

5. 完成状态由执行记录生成

在实际操作中,独立于模型回答管理工作状态的方法很有用。例如,可以区分以下状态。

  • requested:收到了调用请求。
  • waiting_for_approval:等待执行批准。
  • running:开始执行。
  • succeeded:工具返回了成功结果。
  • verified:确认了定义的完成条件。
  • failed:确认了失败。
  • unknown:尚无法判断执行结果。

这些名称是设计示例。重要的是,不要让模型任意声明状态,而是由执行器和验证代码基于实际事件进行更新。

Issue 创建的完成条件可以定义如下。

  1. 创建 API 返回了 Issue ID。
  2. 通过该 ID 查询到的 Issue 存在于请求的项目中。
  3. 标题等必要字段与请求一致。

并非所有工具都需要额外查询。这取决于原始响应保证了什么,以及工作风险和成本有多大。如果服务在存储后立即查询时存在延迟,也需要考虑这一特性。

为了安全起见,诸如完成徽章之类的重要 UI 最好直接从这些状态渲染。模型应描述经过验证的结果,但不应成为决定完成与否的唯一主体。

6. 遇到超时需在重试前确认

假设外部 API 请求以超时结束。服务器可能已经创建了 Issue,只是响应丢失了。

如果将此状态立即视为失败并重新创建,就会产生重复 Issue。首先需要记录为 unknown,并需要通过查询或提供商的请求标识符来验证结果。

如果服务支持幂等性键,则在同一逻辑操作的多次尝试中使用相同的键。实际的保证范围和保留时间需遵循服务文档。Stripe 的幂等请求文档是一个具体的例子。

模型的 call_id 和业务操作的幂等性键也需要区分。如果模型再次请求相同的操作,可能会生成新的调用 ID,因此仅依靠调用 ID 来防止重复执行的设计可能不够充分。

7. 在回答评估中附加执行证据

在 Agent 测试中,必须同时验证回答文本和实际状态。

至少可以构造以下情况。

  • 在未调用工具时,是否不声称已完成?
  • 是否不将权限拒绝描述为成功?
  • 工具错误是否原样传递给模型?
  • 是否将异步任务标记为执行中?
  • 是否在超时后未验证结果就直接重复执行?
  • 是否将多个调用的结果正确关联到相应的请求?
  • 是否不将错误目标上生成的结果确认为完成?

追踪日志中最好有能将模型调用、工具请求、执行开始与结束、错误、验证结果关联起来的标识符。可以利用如 OpenAI Agents SDK 中的 tracing 这样的功能来收集这些信息。

但是,不应无条件地在操作日志中留下密钥值或个人信息。应优先记录追踪问题所需的标识符和状态,并对参数和结果应用掩码和访问控制。

总结

当 Agent 说“已调用”时,请确认以下三点。

  1. 实际是否存在调用请求?
  2. 执行器记录了什么结果?
  3. 该结果是否满足用户请求的完成条件?

区分这三个问题,可以更准确地诊断模型错误的主张和运行时的执行失败。

值得信赖的 Agent 不仅仅由流畅的完成句构成。必须设计支撑这些句子的执行证据和验证路径。