给Agent上线前加一道自动化稳定性闸门

给Agent上线前加一道自动化稳定性闸门

大模型应用最危险的时刻,不是开发时,而是上线后。模型会漂移,接口会超时,返回格式会悄悄变了样,而你直到用户投诉才知道。本文给 Agent 项目加一道"稳定性闸门":用 pytest 做回归测试、用 mock 模拟真实接口、统计失败率与超时,再配一份灰度发布清单。整套东西能接进 CI,每次发版前自动跑,拦住八成的线上事故。所有代码可直接抄进你的项目。

一、为什么要给 Agent 加稳定性闸门

传统软件测试测的是"逻辑对不对",Agent 测试多了一层"模型行为稳不稳"。同一个 prompt,今天返回 JSON,明天可能返回一段自然语言;上游工具接口从 200ms 变成 8 秒,Agent 会卡死在等待;第三方模型临时降级,输出质量断崖。这些问题靠人工点测根本发现不了,必须靠自动化回归。

我见过最典型的事故:据行业公开复现案例报道,一个客服 Agent 上线两周一切正常,某天模型供应商把 temperature 默认值改了,回答从简洁变啰嗦,触发下游字符长度校验失败,整条工单链路瘫痪六小时。如果上线前有断言"输出必须能在 3 秒内返回且是合法 JSON",这道事故在灰度阶段就会被拦下。

稳定性闸门的三个目标很明确:第一,格式不变形------断言输出结构;第二,延迟不失控------统计每个 case 的耗时;第三,行为不漂移------用固定用例集持续比对。

二、用 mock 模拟真实接口

Agent 依赖的大模型、工具接口、数据库都不该在单元测试里真调,否则测试慢、贵、且不稳定。正确做法是用 mock 把外部依赖替掉,专注测 Agent 自身的编排逻辑。

以 Python 为例,用 unittest.mock 替换模型调用:

python 复制代码
# agent.py
def run_agent(query, llm_client):
    resp = llm_client.complete(prompt=build_prompt(query))
    return parse_tool_call(resp)

# test_agent.py
from unittest.mock import MagicMock
from agent import run_agent

def test_agent_returns_valid_tool_call():
    fake_llm = MagicMock()
    fake_llm.complete.return_value = '{"action": "search", "query": "北京天气"}'
    result = run_agent("查天气", fake_llm)
    assert result["action"] == "search"
    assert "query" in result

比如你的 Agent 会调用搜索工具,fake_llm 返回固定的工具调用 JSON,测试就能稳定验证"解析逻辑"是否正确,而不受模型随机性影响。再比如要测"模型返回自然语言而非 JSON"的降级分支,只要让 return_value 改成一段散文,断言系统能正确报错而不是崩溃。

三、统计失败率与超时

光断言对错不够,还要量化"稳不稳"。用 pytest 的钩子收集每个用例的耗时,超阈值就标红:

python 复制代码
# conftest.py
import pytest, time

@pytest.hookimpl(hookwrapper=True)
def pytest_runtest_call(item):
    start = time.time()
    outcome = yield
    elapsed = time.time() - start
    item.user_properties.append(("latency_s", round(elapsed, 3)))
    if elapsed > 3.0:
        print(f"[SLOW] {item.nodeid} took {elapsed:.2f}s")

配合一个汇总测试,统计整体失败率:

python 复制代码
def test_failure_rate_within_threshold(results):
    failed = sum(1 for r in results if r.failed)
    rate = failed / len(results)
    assert rate < 0.05, f"失败率 {rate:.1%} 超过 5% 阈值"

把这组用例固定成一个"黄金集"(golden set),每天跑、每次发版跑。一旦失败率从 0 跳到 3%,说明模型或依赖有漂移,先别全量发。

四、灰度发布清单

闸门过了,不等于能一把梭全量。Agent 的失败往往不是"全错",而是"对大多数、错一小撮",全量发布会把那一小撮放大成事故。所以发布本身也要有节奏,用流量逐步验证,而不是赌一把。给 Agent 的发布加一道灰度:

  1. 先在内部环境跑完整回归,失败率、超时全部达标。
  2. 小流量放行:接 5% 真实用户流量,监控错误率、平均延迟、人工抽检输出质量。
  3. 观察窗口:至少 30 分钟无异常,再把流量提到 25%、50%、100%。
  4. 熔断兜底:错误率超 2% 或 P99 延迟翻倍,自动回滚到上一版。

灰度阶段最该盯的是"软指标"------比如用户重试率、会话中断率,这些比成功率更早暴露体验退化。

五、端到端冒烟与监控看板

单元测试和 mock 再全,也替代不了"真刀真枪跑一次"。所以我建议在灰度前再加一道端到端冒烟:用一份受限的真实 key,挑三五个覆盖核心路径的真实 query,连真实模型和真实工具跑一遍,断言"能返回、能解析、能完成工具调用"。这一道不是为了测逻辑,是为了测"链路通不通"------mock 永远模拟不出供应商临时把接口从 v1 换成 v2 这种破事。

冒烟之外,还要有监控看板。闸门拦住的是"发版前",但线上漂移是"发版后"的事。我习惯盯四个指标:第一,成功率,即完整跑完不报错的比例,低于 98% 就要警觉;第二,平均延迟和 P99 延迟,重点看尾部的长请求有没有变慢;第三,输出格式合规率,比如要求 JSON 的接口里,实际返回合法 JSON 的比例;第四,用户侧软指标,比如重试率、会话中断率、人工接管率,这些比成功率更早暴露体验退化。把这四个指标做成看板,每天扫一眼,比等故障工单主动得多。

值得强调的是,看板的价值不在"漂亮",在"有基线、有对比"。没有历史基线的指标没有意义------今天 P99 是 1.2 秒,你看不出好坏;只有当你知道上周同一时间它是 0.8 秒,才会意识到"慢了 50%"是个问题。所以每版发完,记得把当天的指标快照存下来,作为下一版的对照锚点。

六、三类典型事故复盘

复盘过几十次 Agent 线上事故后,我发现八成集中在三类,正好对应闸门能拦的方向。

第一类是格式退化。模型升级后,原本稳定输出 JSON 的接口开始夹杂解释性文字,下游解析直接报错。这类事故,只要闸门里有"断言输出是合法 JSON"的用例,灰度阶段立刻会变红。教训是:不要把解析器写得"太宽容",能容错不等于该容错,格式契约必须显式断言。

第二类是延迟雪崩。某个工具接口从几百毫秒退化到十几秒,Agent 在等待里堆积,连接池耗尽,连锁拖垮整条链路。这类事故的早期信号是 P99 延迟缓慢爬升,看板比对能提前发现;闸门里对每个 case 统计耗时、超 3 秒告警,就是为此设计的。

第三类是行为漂移。模型没报错、格式也对,但回答质量肉眼可见地变差------比如客服 Agent 突然开始建议用户"重启路由器"而不是查订单。这种最阴险,因为硬指标全绿。应对办法是保留一小批"人工精标黄金用例",定期让人抽看输出,或者用另一个强模型做"裁判"比对语义相似度。闸门拦得住前两类,第三类要靠人和裁判模型补位。

七、工程取舍与踩坑

任何测试策略都是"用今天的成本换明天的安稳",闸门也不例外。落地时最容易踩的坑,不是技术不会写,而是尺度没拿准------太松拦不住事故,太严拖慢发布还养出虚假安全感。下面这几点是我带团队踩过一遍后沉淀下来的。

  1. mock 不能太"理想"。如果 mock 永远返回完美 JSON,测试永远绿,但线上一碰脏数据就崩。比如一定要加几个"畸形返回"用例:空字符串、超长文本、半截 JSON、带思考链的包裹格式,验证解析器的健壮性。曾经有个项目 mock 只返回标准 JSON,上线后模型某次返回了带 Markdown 代码围栏的 JSON,解析器直接抛异常,而测试全程没发现,因为没人造过这种样例。

  2. 黄金集要有人维护。模型升级后,旧用例可能集体失效,但这未必是 Agent 的错,而是预期该更新。建议每个季度复核一次黄金集,删掉过时断言、补新边界。

  3. CI 超时别设太松。Agent 测试本身慢,CI 单步超时设成 10 分钟看似安全,实则掩盖了"某用例悄悄变慢 3 倍"的问题。更稳的做法是记录每次耗时基线,偏离 20% 就告警。

  4. 别用真实 API key 跑测试。一旦把密钥写进测试,等于给每次 CI 打卡烧钱,还可能触发供应商限流。统一走 mock,只有"端到端冒烟"才用受限的真实调用,且单独开关。

  5. 测试报告要能追溯到具体用例。很多团队的 CI 只输出一个总分,红了却不知道红在哪。建议每次跑完把每个用例的耗时、断言结果、失败原因都落盘成可读报告,发版前扫一眼就知道"是哪一类问题在变多"。当某类畸形输入的失败率连续三次上升,那就是模型供应商在偷偷改行为的信号,比任何监控都早。

八、辩证:闸门也会误伤

必须说清代价。其一,维护测试集有成本,小团队可能觉得"写测试比写功能还累",但 Agent 一旦上线出错,修复成本远高于前期投入。我见过一个三人团队为了赶版本,把回归用例从八十个砍到十个,结果上线当天就因一个工具返回格式变化崩了四小时,事后补测试花的时间比当初写测试还多。所以测试集不是负担,是 insurance。

其二,mock 越强,离真实越远。mock 永远模拟不出模型"偶尔抽风"的长尾------比如某个罕见输入触发了模型输出一段带表情符号的回复,mock 里你根本不会想到要造这种样例。这就是为什么端到端冒烟测试不能省:它用真实接口跑,专门抓 mock 抓不到的脏数据。两者是互补关系,不是替代关系。

其三,过度严格的断言会让发布变慢,错失时效。如果你的断言连"回答里多了一个空格"都判失败,那每次模型供应商做个无害的格式化更新,你的发布就被卡住。正确做法是核心路径严、边缘路径松,分级设阈值:涉及金额、权限、安全的断言零容忍,纯表达层面的差异容忍。

其四,闸门会产生"虚假安全感"。绿色不代表没问题,只代表"没触发你设的那几道断言"。 Agents 最危险的失败往往不是报错,而是"答非所问但格式完美"。所以闸门之外,永远保留人工抽检和裁判模型比对,把它们当成第二道防线。

结论很朴素:稳定性闸门不是为了不出错,是为了把错挡在灰度里、挡在用户看到之前。它降低的是"低级事故"的概率,而不是"所有事故"的概率;把它当成的不是万能锁,而是一道该层层设防的第一道门。

九、互动提问

稳定性闸门不是银弹,但它把"靠运气上线"变成了"靠流程上线"。下面几个问题,欢迎结合实际项目聊聊:

  1. 你的 Agent 项目现在有线上的自动化回归吗,还是靠人工点测?
  2. 如果只能加一个测试,你会先断言"格式正确"还是"延迟达标",为什么?
  3. 欢迎在评论区聊聊:你踩过最贵的那次 Agent 线上事故,最后是怎么定位的?

数据与事件来源:

以下为本方案涉及的工具与经验依据,便于读者在自己的项目里复现与进一步查证;阈值类数值均为通用经验起点,请按业务实际容忍度调整:

  • pytest 官方文档与钩子机制(docs.pytest.org)
  • Python unittest.mock 标准库说明(docs.python.org/3/library/unittest.mock)
  • 本文 CI 配置与测试代码为作者工程实践总结,灰度阈值(5%/2%/30分钟)为通用经验值,请按业务实际调整
  • "模型默认值变更导致链路瘫痪"为行业中已多次公开复现的典型事故模式,非特指单一厂商
相关推荐
deepseek233 小时前
erdosproblems 冻结评论与证明声明拆解:机器证明泛滥后,一个数学网站关掉了评论区
人工智能·数学·大模型
跟我学机器学习3 小时前
基于两阶段索引的RAG Agent:检索增强与 Agent 结合实践
人工智能·深度学习·语言模型
Keep_Trying_Go3 小时前
MobileSAM 论文方法详解
人工智能·深度学习·计算机视觉·分割算法
LaughingZhu4 小时前
Product Hunt 每日热榜 | 2026-10-07
人工智能·深度学习·神经网络·搜索引擎·百度
朝朝辞暮i4 小时前
VLA 系统学习第 12 课:为什么机器人不能只看一帧?——时间序列、Observation History 与 Action Chunk
人工智能·python·深度学习·神经网络·vla
朝朝辞暮i4 小时前
VLA 系统学习第 8 课:Optimizer 到底在干什么?——从 Learning Rate 到 SGD、Momentum 和 Adam
人工智能·python·深度学习·神经网络·vla
EatFan4 小时前
2026 后端 AI 集成三条落地路径:Spring AI 2.0、MCP 协议与旁路服务怎么选
java·人工智能·spring boot·spring·大模型·spring ai·mcp
做cv的小昊5 小时前
【大模型算法自学笔记01】NLP基础知识(1.1 自注意力)
人工智能·笔记·算法·自然语言处理·大模型·llm
夏文强5 小时前
国产开源反攻海外:GLM-5.3 进 Cursor,CursorBench 开放权重第一
人工智能·开源·大模型·glm·智谱