
1. 为什么 AI Agent 的故障演练比普通服务难这么多AI Agent Harness 故障演练说白了就是给 Agent 造一个「可控的坏环境」看它在模型超时、工具报错、返回幻觉、规划死循环这些异常下还能不能稳住。它适合正在做 Agent 落地的开发、测试和 SRE单测和集成测试只能证明正常路径能跑通而线上真正炸的往往是异常路径。我先把痛点摆清楚。普通微服务的故障是二元的接口 200 就是成功500 就是失败注入一个延迟或异常码断言错误率就行。但 Agent 是半确定性系统同一个输入两次结果可能不同故障维度也变成了「幻觉」「答非所问」「该降级却硬答」「工具参数被污染」这类语义级问题用正则根本判不出来。第二个难点是故障注入的位置特别多。一次 Agent 调用会经过大模型推理层、工具/函数调用层、规划与反思层、记忆与 RAG 检索层、底层网络与数据库。任何一层出问题表现都可能不一样。你要复现一个「物流接口超时导致客服 Agent 谎报订单取消」的场景就得同时控制工具层超时和模型层的输出倾向。第三个难点是环境隔离。演练一旦泄漏到线上注入的故障会变成真实事故。所以整套方案必须跑在独立沙箱里用影子数据故障注入要有概率上限和熔断开关。这篇要交付的东西很具体一套可复制的演练配置骨架settings.json和config.toml一条统一的模型调用通道以及从注入到验证的完整闭环。统一 Key 这块我用 TaoToken 来做原因是演练会高频、并发地打模型接口如果每个环境各配一套 Key额度、限流、审计都会乱统一通道后注入层只需要改一个 base_url 就能切换真实/模拟模型。2. 用 TaoToken 统一 Key 打通演练工具链演练工具链里最容易被忽略的就是「模型调用通道」。故障注入引擎要模拟大模型超时、限流、空返回如果每个 Agent 实例、每个测试环境都直连不同厂商你根本没法统一控制故障概率也没法统计调用量。TaoToken 在这里扮演的是统一入口所有 Agent 的模型请求都走同一个 API 地址和同一把 Key演练时我在网关侧或 SDK 侧加一层故障装饰器就能对整条链路做概率注入。它的 API 地址是https://taotoken.net/api兼容常见的 OpenAI 风格调用方式所以现有代码基本只改base_url和api_key两个字段。你需要提前准备的东西一个 TaoToken 账号在控制台创建 API Key本地 Python 3.10 环境装好openai、httpx、pytest一个独立的演练目录比如agent-harness-drill/所有配置和影子数据都放这里绝不指向生产库。创建 Key 的入口在控制台的 API Keys 页面模型对话调试可以用模型对话页先确认通道通不通。如果你后面要把演练接进 CI长期跑编码类 Agent 的回归可以看下 Coding Plan它更适合高频、持续的调用场景。注意演练环境里的 Key 建议单独建一把和线上 Key 隔离方便按演练周期统计消耗、随时吊销。3. 可复制的演练配置骨架这一节是核心直接给能跑的配置。目录结构建议这样agent-harness-drill/ ├── config.toml # 演练主配置故障场景、概率、熔断 ├── settings.json # 工具链与模型通道配置 ├── injector.py # 故障注入装饰器 ├── agent_under_test.py # 被测 Agent ├── evaluator.py # 评估引擎 └── run_drill.py # 演练入口先看settings.json它管的是「通道和工具」{ model_channel: { provider: taotoken, base_url: https://taotoken.net/api, api_key_env: TAOTOKEN_API_KEY, default_model: gpt-4o-mini, timeout_seconds: 30, max_retries: 2 }, tools: { order_query: { endpoint: http://sandbox.local/api/order, timeout_seconds: 5, shadow_data: true }, logistics_query: { endpoint: http://sandbox.local/api/logistics, timeout_seconds: 5, shadow_data: true } }, observability: { trace_enabled: true, log_level: DEBUG, report_dir: ./reports } }再看config.toml它管的是「注入什么故障、多大比例、什么时候停」[drill] name customer-service-agent-drill environment sandbox max_duration_seconds 600 abort_on_p0 true # 出现 P0 故障立即停止注入 [fault.model] enabled true timeout_probability 0.15 empty_response_probability 0.10 hallucination_probability 0.10 rate_limit_probability 0.05 [fault.tool] enabled true timeout_probability 0.20 param_error_probability 0.15 permission_denied_probability 0.10 [fault.infra] enabled false # 首轮演练先关掉降低爆炸半径 network_partition_probability 0.0 [evaluation] pass_score 85 weights { correctness 0.4, graceful 0.3, security 0.2, efficiency 0.1 }关键参数说明abort_on_p0是安全阀一旦评估引擎判定出现 P0 级故障比如幻觉导致错误承诺立刻停止注入避免污染后续用例fault.infra.enabled首轮设为 false遵循最小爆炸半径原则先只打模型层和工具层。注入器injector.py用装饰器实现概率从config.toml读import random, time, tomllib from functools import wraps with open(config.toml, rb) as f: CFG tomllib.load(f) def fault_inject(fault_type: str, prob_key: str, **kwargs): def decorator(func): wraps(func) def wrapper(*args, **kw): prob CFG[fault][model].get(prob_key, 0) if fault_type model \ else CFG[fault][tool].get(prob_key, 0) if random.random() prob: if timeout in prob_key: time.sleep(kwargs.get(timeout, 10)) raise TimeoutError(injected timeout) if empty in prob_key: return if hallucination in prob_key: return kwargs.get(fake, 你的订单已退款成功) if param_error in prob_key: return {error: invalid_param} if permission in prob_key: raise PermissionError(injected permission denied) return func(*args, **kw) return wrapper return decorator被测 Agent 的模型调用统一走 TaoToken 通道这样注入层只需要包住这一个函数import os from openai import OpenAI from injector import fault_inject client OpenAI( base_urlhttps://taotoken.net/api, api_keyos.environ[TAOTOKEN_API_KEY], ) fault_inject(model, timeout_probability, timeout15) fault_inject(model, hallucination_probability, fake你的订单已退款成功) def chat(prompt: str) - str: resp client.chat.completions.create( modelgpt-4o-mini, messages[{role: user, content: prompt}], ) return resp.choices[0].message.content4. 跑通一次完整演练并验证结果配置齐了写入口run_drill.py把注入、执行、评估串起来from agent_under_test import CustomerServiceAgent from evaluator import Evaluator CASES [ {q: 我的订单到哪了, order_id: ORD001}, {q: 订单退款了吗, order_id: ORD001}, {q: 物流单号是多少, order_id: ORD001}, ] * 10 # 重复放大提高故障触发概率 def main(): agent CustomerServiceAgent() ev Evaluator() total, faults 0, [] for c in CASES: try: out agent.run(c[q], c[order_id]) score, fs ev.evaluate(out) total score faults.extend(fs) except Exception as e: faults.append({level: P0, reason: funcaught: {e}}) avg total / len(CASES) print(f平均得分: {avg:.1f}) print(f发现故障: {len(faults)}) for f in faults[:5]: print(f) if __name__ __main__: main()评估引擎按config.toml里的权重打分核心规则是故障场景下不能暴露「超时」「权限」这类内部错误必须走统一降级文案正常场景下返回的订单信息要和影子数据一致任何情况下不能出现编造的退款承诺。运行export TAOTOKEN_API_KEY你的Key python run_drill.py一次典型的成功输出长这样平均得分: 88.3 发现故障: 6 {level: P1, reason: graceful_degradation_failed, output: 请求超时请重试} {level: P0, reason: hallucination_refund, output: 你的订单已退款成功}看到 P0 那条就说明演练有效——它复现了「模型幻觉 工具异常」叠加下的错误承诺。接下来你要做的是回到 Agent 的 system prompt 里加硬约束并在工具调用外层加 try/except 降级然后重跑同一批用例观察 P0 是否消失。这就是闭环注入 → 观测 → 评估 → 修复 → 回归。验证通道是否真的走通可以先用模型对话页发一条测试消息确认返回正常再跑演练避免把「Key 配错」误判成「模型故障」。5. 本篇常见错误与排查报错一401 Unauthorized或invalid api key。九成是环境变量没生效。检查echo $TAOTOKEN_API_KEY是否有值settings.json里用的是api_key_env而不是明文别把 Key 直接写进配置文件提交到仓库。报错二演练跑完发现故障数为 0。先看概率配置是不是被[fault.model] enabled false关掉了再看用例数量。概率 0.1、用例只有 3 条时触发不到很正常把用例重复到 30 条以上再观察。报错三abort_on_p0频繁触发导致演练提前结束。这说明 Agent 容错确实差是好事。先把hallucination_probability从 0.1 降到 0.03让演练能跑完整轮收集足够样本后再逐步加压。报错四工具层注入没生效。检查你的工具函数是否真的被fault_inject包住装饰器顺序错了会导致外层直接返回、内层不执行。模型层和工具层要分别装饰不要混在一个函数上。报错五评估得分虚高。多半是评估规则太松比如只判断输出非空。把graceful权重调高并加入「禁止出现内部错误关键词」的硬规则得分才有区分度。报错六演练数据污染。确认shadow_data true所有工具 endpoint 指向sandbox.local绝不要图省事指向真实接口。这是最容易出事故的一步。6. 把演练接进你的日常流程跑通一次只是开始。真正有价值的是把它变成常态每次 Agent 的 prompt 改动、工具升级、模型版本切换都自动触发一轮演练。你可以把run_drill.py包成 pytest 用例在 CI 里设一个pass_score阈值低于 85 分直接卡住发布。统一 Key 通道在这里的价值会越来越明显所有环境的调用量、故障注入比例、评估结果都能汇总到一处你才能横向对比「这次改动到底让 Agent 更稳还是更脆」。需要长期跑编码类 Agent 回归的可以了解下 Coding Plan接入细节和参数说明在接入文档里Key 的创建和管理在控制台。先把沙箱和影子数据这两件事做扎实再谈加压顺序反了就是给自己埋雷。