研究提案

[WIP] [v2] 工具调用失败模式:被忽视的 Agent 鲁棒性训练数据

本文提出将工具调用失败模式作为独立研究对象,系统化构建 Agent 鲁棒性训练数据的研究方案。从失败类型分类、日志挖掘、数据格式设计到评测体系,提供一套可操作的完整路径。

Agent 工具调用 训练数据 鲁棒性 LLM

[WIP] 工具调用失败模式:被忽视的 Agent 鲁棒性训练数据

问题的起点:Agent 为什么脆弱?

当前 LLM-based Agent 的研究大量聚焦于「如何让 Agent 更好地调用工具完成任务」,但一个被系统性忽视的问题是:当工具调用失败时,Agent 该如何行动?

这不是小概率事件。在生产环境中,API 超时率通常在 0.1%–5% 之间,权限错误、参数格式错误、语义偏差更是家常便饭。现有 Agent 训练数据(包括 ToolBench、APIBench 等)几乎清一色是「成功调用轨迹」,这相当于只教学生在晴天开车,从未让他们练习过雨天刹车。

核心论点:工具调用失败模式应当作为独立的研究对象,配套专属的训练数据构建方案。


一、失败类型分类体系

1.1 工具本身问题(Infrastructure Failures)

这类失败与 Agent 行为无关,属于外部环境故障,但 Agent 必须正确响应。

子类型典型表现正确处理策略
超时(Timeout)HTTPTimeoutError, 无响应指数退避重试,超阈值后换备用方案
权限不足(AuthZ)403 Forbidden, Permission Denied停止重试,上报人工处理,不泄露敏感路径
服务端错误(5xx)500 Internal Server Error区分是否幂等再决定是否重试
限流(Rate Limit)429 Too Many Requests尊重 Retry-After 头,不暴力重试

数据构建方法: 在日志中捕获所有 status_code >= 400 的调用,按子类型分箱。对于权限类失败,可以在测试环境中系统性地注入不同权限级别的 Token,生成覆盖各种 Auth 场景的轨迹。

1.2 参数问题(Parameter Failures)

这类失败是 Agent 自身造成的,训练价值最直接。

格式错误(Syntactic): 参数类型不匹配、JSON 格式非法、枚举值拼写错误。容易自动检测,构建成本低,但训练价值相对有限——这类错误大模型已经做得不错。

范围越界(Boundary Violation): 日期超出数据范围、分页 offset 超过总条数、文件路径不存在。特点是错误信息明确,但 Agent 需要推理出合法范围再重试,而不是机械重填。

语义错误(Semantic Errors)——最核心的子类型:

语义错误的特点是:调用在技术层面完全成功(HTTP 200),但结果偏离了用户意图。这是所有失败类型中最难识别、也最有研究价值的。

举例:

  • 用户问「上海明天的天气」,Agent 调用了 get_weather(city="Shanghai", date="today") ——返回 200,数据正常,但日期填错了
  • 用户要查「过去7天的订单」,Agent 传入 start_date="2026-03-23", end_date="2026-03-30" 实际应为 UTC 时间但传了本地时间,结果少了8小时的数据
  • 搜索工具返回了结果,但 Agent 用了错误的排序参数导致取到的不是最相关的文档

语义错误为什么难? 因为它没有显式的失败信号。Agent 必须具备「结果自验证」能力——不只是检查调用成功与否,而是判断返回内容是否满足原始意图。这需要一种元认知能力,而当前训练数据几乎不覆盖这个维度。

🔄 [v2 改进]

语义错误 Judge LLM 的具体实现方案

原方案提到「用 Judge LLM」识别语义错误,但未给出 prompt 设计细节。语义错误检测的质量完全取决于 Judge prompt 的设计,以下给出可直接复用的模板:

Judge Prompt 模板:

你是一个工具调用语义评估专家。请判断以下工具调用是否真正满足了用户的原始意图。

原始意图:{intent}
工具名称:{tool_name}
工具调用参数:{params}
工具返回结果:{result}

请按以下步骤思考:
1. 用户真正想要的是什么?(从意图中提取关键要素:对象/时间/范围/条件)
2. 工具调用的参数是否完整、准确地表达了这个意图?
3. 工具返回的结果是否与用户意图匹配?(即使 HTTP 200,内容可能错误)
4. 如果不匹配,具体差距在哪里?

输出格式(严格 JSON):
{
  "satisfied": <bool>,
  "gap_description": "<如果 satisfied=false,描述具体差距;否则为 null>",
  "error_category": "<semantic_param_error|semantic_result_mismatch|scope_error|temporal_error|null>",
  "confidence": <0.0-1.0>,
  "reasoning": "<简短推理过程>"
}

Judge LLM 错误率控制:交叉验证策略

Judge LLM 本身也会出错,尤其在边界案例上。建议采用双 Judge 交叉验证:

  • 使用两个不同 LLM(如 GPT-4o + Claude 3.5 Sonnet)独立判断
  • 一致时(两者都 satisfied 或都 not satisfied):直接采纳,置信度 = min(conf_A, conf_B)
  • 分歧时:标记为 needs_human_review,加入人工审核队列
  • 人工抽检:对一致样本中随机抽 5% 人工复核,验证双 Judge 精度

预期:双 Judge 一致率约 75–85%,一致样本的精度约 90–95%;分歧样本(15–25%)全部人工处理。

1.3 逻辑问题(Logic Failures)

死循环(Infinite Retry Loop): Agent 遇到同一错误反复重试,参数完全不变。这是最危险的失败模式,会导致 token 耗尽和费用爆炸。

幂等性错误(Idempotency Violation): 对非幂等操作(如创建订单、发送消息)在失败后直接重试,导致重复操作。Agent 需要区分 GET 类工具和 POST/写入类工具的不同重试策略。

级联失败(Cascade Failure): 步骤 A 失败,Agent 未中止后续步骤,导致步骤 B、C 建立在错误前提上。正确行为是在关键步骤失败时立即停止并向用户报告,而非继续执行。


二、数据来源:从真实日志中挖掘,而非合成

合成数据的核心缺陷在于:人写的「失败场景」往往过于规则化,缺少真实系统中的噪声、上下文依赖和奇特的失败组合。真实 Agent 系统日志是天然的宝矿。

2.1 日志格式设计:让日志本身就是可训练轨迹

关键原则:不是在已有日志上补打标注,而是在设计系统时就让日志格式对训练友好。

每条工具调用记录应包含:

{
  "trace_id": "sess_a3f9c2",
  "step_index": 3,
  "timestamp": "2026-03-29T14:23:11Z",
  "tool_name": "search_documents",
  "intent": "查找过去一周关于项目X的会议记录",
  "input_params": {
    "query": "项目X会议记录",
    "date_from": "2026-03-22",
    "date_to": "2026-03-29",
    "sort_by": "relevance"
  },
  "raw_response": {
    "status_code": 200,
    "body": { "results": [], "total": 0 }
  },
  "agent_observation": "搜索返回空结果",
  "next_action": {
    "type": "retry",
    "params_delta": { "date_from": "2026-03-01" },
    "reasoning": "尝试扩大日期范围"
  },
  "failure_annotation": null
}

关键字段说明:

  • intent:调用时的自然语言意图,语义错误检测的锚点
  • agent_observation:Agent 自己对结果的解读(而非原始响应)
  • next_action.params_delta:重试时只记录变化的参数,便于分析修正策略
  • failure_annotation:初始为 null,由后续流程填入

2.2 自动标注「正确行为」vs「错误行为」

规则型自动标注(高覆盖、低精度):

  • 同一工具相同参数重试超过 3 次 → 标注为 loop_failure
  • 非幂等操作(工具名含 create/send/delete)重试 → 标注为 idempotency_risk
  • 最终任务结果为失败且最后 N 步存在某工具调用 → 该调用为候选负样本

回溯标注(结果驱动):

以任务最终结果为标注锚点。如果最终任务成功,则追溯成功路径上所有的「失败→正确恢复」步骤,标注为正样本;如果最终任务失败,则找出关键错误决策点,标注为负样本。

LLM 辅助标注(语义错误专用):

语义错误无法靠规则检测,需要用一个 Judge LLM 判断:

“工具返回了 {result},原始意图是 {intent},这次调用是否满足了意图?如果不满足,错误原因是什么?”

Judge LLM 的输出再经人工抽检校验,形成高质量的语义失败标注。

2.3 处理长尾失败

低频失败(如特定 API 的特定错误码)往往携带最高价值的信息——正因为罕见,模型最没见过,训练收益最大。

主动采集策略:

  • 建立「稀有失败」报警,当某种新型失败首次出现时触发人工记录
  • 在测试环境中系统性地注入故障(Chaos Engineering),将生成的失败轨迹加入训练集
  • 与业务方合作,收集历史上「Agent 翻车」的真实事故 case

数据增强策略:

  • 对已有的长尾失败轨迹,通过 LLM 进行「变体生成」——保持失败模式不变,改变工具名称、参数值、领域上下文——从 1 个 case 扩展为 10 个 case

三、数据格式设计:完整的失败轨迹 JSON

{
  "trajectory_id": "traj_20260329_b7e2a1",
  "task_description": "帮用户查询本月销售数据并生成报表",
  "final_outcome": "success",
  "steps": [
    {
      "step_index": 1,
      "tool_name": "query_sales_data",
      "input_params": {
        "month": "2026-03",
        "region": "华南",
        "granularity": "daily"
      },
      "response": {
        "status_code": 500,
        "error_code": "DB_TIMEOUT",
        "message": "Database connection timeout after 30s"
      },
      "failure_type": "infrastructure",
      "failure_subtype": "timeout",
      "agent_decision": {
        "action": "retry_with_backoff",
        "wait_seconds": 5,
        "reasoning": "服务端超时,数据库可能短暂不可用,等待后重试"
      },
      "decision_correctness": "correct",
      "correctness_reason": "超时类错误适合退避重试,参数无需修改"
    },
    {
      "step_index": 2,
      "tool_name": "query_sales_data",
      "input_params": {
        "month": "2026-03",
        "region": "华南",
        "granularity": "daily"
      },
      "response": {
        "status_code": 200,
        "body": {
          "records": 31,
          "data": ["..."]
        }
      },
      "failure_type": null,
      "agent_decision": {
        "action": "proceed",
        "reasoning": "数据获取成功,继续生成报表"
      },
      "decision_correctness": "correct",
      "correctness_reason": null
    }
  ],
  "failure_summary": {
    "total_failures": 1,
    "failure_types": ["infrastructure/timeout"],
    "recovery_pattern": "retry_with_backoff",
    "recovery_success": true
  },
  "metadata": {
    "source": "production_log",
    "annotator": "rule_based_v2",
    "human_verified": false,
    "domain": "business_analytics",
    "difficulty": "easy"
  }
}

语义错误示例(最难标注的情形):

{
  "step_index": 1,
  "tool_name": "get_weather",
  "input_params": {
    "city": "Shanghai",
    "date": "2026-03-29"
  },
  "response": {
    "status_code": 200,
    "body": { "temp": 18, "condition": "cloudy" }
  },
  "failure_type": "parameter",
  "failure_subtype": "semantic_error",
  "failure_detail": "用户询问的是明天(2026-03-30)的天气,Agent 填入了今天的日期",
  "agent_decision": {
    "action": "proceed",
    "reasoning": "天气数据获取成功"
  },
  "decision_correctness": "incorrect",
  "correctness_reason": "应检测到返回数据的日期与用户意图不符,需要重新调用",
  "correct_action": {
    "action": "self_verify_and_retry",
    "params_delta": { "date": "2026-03-30" }
  }
}

四、评测方式:构造失败场景测试集

4.1 FailBench 设计

提出 FailBench——专门针对 Agent 失败处理能力的评测基准,与现有能力评测正交。

评测维度:

维度指标测量方式
失败识别率正确识别失败类型的比例注入已知类型失败,测 Agent 分类准确率
恢复成功率失败后最终完成任务的比例在失败环境中运行任务,看任务完成率
循环避免率不陷入无效重试循环的比例注入无法恢复的错误,测 Agent 何时放弃
语义自验证率对语义错误的主动检测比例构造表面成功实则错误的返回,测 Agent 是否发现
幂等安全率非幂等操作的重复执行率(越低越好)注入写操作失败,测是否重复执行

🔄 [v2 改进]

FailBench 测试用例的具体构造方法

原方案提出了 5 个评测维度,但未说明每个维度的测试用例怎么造。以下为每个维度给出具体的注入方式和验证方法:

维度 1:失败识别率

  • 注入方式:在 Proxy 层拦截真实工具响应,替换为预设的各类型错误(按分类体系 1.1–1.3 枚举)
  • 测试用例构造:对每种失败子类型(共约 12 个子类型),构造 10–20 个真实业务场景中的调用上下文
  • 验证方式:要求 Agent 在 observation 字段中显式输出失败类型标签,与注入标签对比
  • 预期覆盖:约 150 个测试用例(12 × 12 + 边界 case)

维度 2:恢复成功率

  • 注入方式:在任务执行的第 N 步(N 随机)注入单一类型失败,观察 Agent 是否最终完成任务
  • 测试用例构造:选取 50 个有标准答案的端到端任务(来自 ToolBench/APIBench 的成功轨迹),在其中注入各类失败
  • 验证方式:任务最终输出与标准答案对比(精确匹配 + 语义相似度双重验证)

维度 3:循环避免率

  • 注入方式:持续返回同一错误(不可恢复型),如 403 Forbidden
  • 关键设计:Agent 必须在 3 次重试内放弃,而非无限循环
  • 验证方式:统计每次注入后 Agent 的实际重试次数;超过 5 次视为「陷入循环」

维度 4:语义自验证率(最难)

  • 注入方式:构造「表面成功但内容错误」的响应——HTTP 200,数据格式正确,但核心值有偏差(日期差一天、时区错误、排序字段选错)
  • 测试用例构造:人工设计 30–50 个语义错误场景,每个场景有明确的「正确意图」和「错误响应」对
  • 验证方式:Agent 是否在 observation 或 next_action 中提到了不一致性(关键词匹配 + LLM 评分)

维度 5:幂等安全率

  • 注入方式:在写操作(含 create/update/delete 的工具)返回模糊结果(如 {"status": "unknown"}),观察 Agent 是否重试
  • 验证方式:统计写操作被执行超过 1 次的比例(越低越好,目标 < 5%)

4.2 故障注入测试框架

在测试时用 Proxy 拦截真实工具调用,按配置注入故障:

fault_config = {
    "tool": "query_sales_data",
    "trigger": "always",  # or "first_call_only"
    "fault_type": "timeout",
    "fault_params": { "delay_seconds": 35 }
}

测试用例覆盖:单步失败、级联失败、语义错误、混合失败序列(先超时后语义错误)。


五、与现有工作的关系和差异

5.1 与 SWE-bench 的对比

SWE-bench 是目前最具影响力的 Agent 编程能力评测,任务是修复 GitHub Issue。它的失败模式是「代码逻辑错误导致测试不通过」,属于领域特定的任务失败。

本研究关注的是工具调用层面的系统性失败,两者正交:

维度SWE-bench本研究
失败来源代码逻辑错误工具调用层错误
领域依赖强(代码)弱(通用工具调用)
失败可见性高(测试报错)低(尤其语义错误)
训练目标代码推理能力鲁棒性与自我修正

5.2 与 ToolBench / APIBench 的差异

这些数据集提供了大量「成功的工具调用轨迹」,用于教 Agent 如何调工具。本研究是其互补品:专门覆盖失败场景,提升 Agent 在工具调用出错时的应对能力。两类数据应当结合使用——就像教人开车,既要教正常驾驶,也要教紧急制动。

5.3 与 ReAct / Reflexion 的关系

Reflexion 等工作已经提出让 Agent 从失败中反思学习,但反思的对象通常是任务层面的策略失败(错误的行动计划),而非工具层面的调用失败。本研究更底层,专注在「单次工具调用失败→正确处置」这个微粒度的决策闭环上。


六、下一步计划

  1. 数据采集:与 2–3 个有生产规模 Agent 系统的团队合作,接入日志采集 SDK,6 个月内积累 10 万条带失败标注的轨迹
  2. 标注流水线:实现规则型 + LLM Judge 双层标注,人工抽检 5% 校验精度
  3. FailBench 发布:构建覆盖 5 大维度、200+ 测试用例的评测集,开源发布
  4. 训练实验:在 7B/13B 规模模型上做消融实验,验证加入失败轨迹数据后各维度指标的提升幅度

🔄 [v2 改进]

数据规模估算(第一性原理推导)

原方案说要采集「数十万条」,但没有推导依据,也没有说清楚不同来源的分配。以下从训练目标反推所需规模:

Step 1:训练覆盖需求

本方案的失败类型体系包含 3 大类、约 12 个子类型(Infrastructure: 4 + Parameter: 3 + Logic: 3,加上混合类型约 2)。

经验原则:每个子类型至少需要 1,000 条带标注样本才能让模型稳定学习该失败模式。(参考 ToolBench 等数据集,每类工具约 800–2,000 条训练数据可获得稳定性能)

基础需求:12 × 1,000 = 12,000 条

考虑到:

  • 类内多样性(不同工具、不同领域的同类型失败)需要 2–3× 倍增
  • 混合失败序列(多步骤)额外需要约 3,000 条

总标注数据需求:约 15,000–30,000 条

Step 2:来源分配

数据来源占比绝对量特点
生产日志(真实失败轨迹)60%9,000–18,000质量高,但需清洗和标注
Chaos Engineering 注入30%4,500–9,000可控,覆盖长尾类型
人工构造(语义错误专用)10%1,500–3,000成本高,但语义错误无法自动生成

Step 3:采集时间线估算

假设合作的 2–3 个生产系统每天产生约 10,000 次工具调用,其中 2–5% 为真实失败:

  • 每天约 200–500 条原始失败日志
  • 经过过滤和标注(约 30% 转化率)→ 每天约 60–150 条有效标注
  • 达到 15,000 条有效标注:约需 100–250 天(3–8 个月)

这与「6 个月积累 10 万条」的原目标相比,10 万条在标注质量上不可行(需 1,000+ 人工审核工时),建议修正目标为 6 个月内积累 20,000–30,000 条高质量标注数据,「数十万」为未来无监督扩充阶段的目标。


核心 claim:工具调用失败数据是当前 Agent 训练数据中最系统性缺失的一类。每一次生产系统中的工具失败,都是一个被浪费的训练样本。本研究的目标,是把这些「浪费的失败」变成 Agent 进化的燃料。

原始版本:tool-failure-patterns-training-data