Agent 半夜陷入死循环,一夜烧掉上万 token——轨迹测试与熔断

举报
霍格沃兹测试学社 发表于 2026/08/18 10:38:45 2026/08/18
【摘要】 AI测试开发高频题:测Agent≠测接口,核心是验证“决策链是否失控”。本文以深夜烧万元的真实事故切入,详解如何通过轨迹记录、循环检测与三重熔断(步数/Token/时间)保障Agent可靠性,并将“不失控”写入自动化测试用例。

AI 测试开发面试高频题:“测 Agent 和测普通接口有什么本质区别?”
普通接口测的是"一次调用对不对",Agent 测的是"一条决策链会不会失控"。这篇用一次半夜烧钱的真实事故,讲轨迹测试和熔断怎么设计。

一、真实事故:两个工具的结论打架,Agent 在中间荡了一夜秋千

某电商公司的售后 Agent 负责自动处理物流异常:先调 query_order 查订单状态,再调 check_logistics 核实物流,确认异常就创建工单。上线第一周风平浪静,第八天早上,财务同学看着 token 账单找上门:一晚上一个任务烧掉了平时一个月的量,粗算上万元。

拉出那个任务的完整调用日志,画面非常荒诞:

00:12  query_order      → {"status": "物流异常"}
00:12  check_logistics  → {"status": "正常"}
00:13  query_order      → {"status": "物流异常"}
00:13  check_logistics  → {"status": "正常"}
……(重复 1,700 多次,直到 07:40 被值班同学手动停掉)

两个工具的结论互相矛盾,模型每次都"认真"地想 reconcile 这个矛盾:信 A 就去问 B,信 B 就去问 A,永远对不上,永远不放弃。没有步数上限、没有预算上限、没有循环检测——三个保险一个都没装,于是它兢兢业业地荡了一夜秋千。

复盘结论很明确:Agent 的失控不是模型"变笨了",而是系统没给它的决策链设边界。 模型可以犹豫,系统必须兜底。

二、思路:给决策链做三件事——记录、检测、熔断

轨迹(trajectory)就是 Agent 的"作案录像":每一步打算调什么工具、传什么参数、观察到什么。有了录像才能做三件事:

  1. 记录:每步落盘,事后可回放;
  2. 检测:识别"重复调用"和"A-B-A-B 振荡"两类典型死循环;
  3. 熔断:步数、token 预算、墙钟时间,任何一条越线立即终止。

三、核心代码:轨迹记录 + 循环检测 + 三重熔断

第 1 步:轨迹记录与循环检测

import hashlib, time
from dataclasses import dataclass, field

def _h(x):  # 参数/观察结果归一化取哈希,忽略 dict 顺序差异
    return hashlib.md5(repr(sorted(x.items()) if isinstance(x, dict) else x)
                       .encode()).hexdigest()

@dataclass
class Trajectory:
    steps: list = field(default_factory=list)

    def record(self, tool, args, observation):
        self.steps.append({"tool": tool, "args": _h(args),
                           "obs": _h(observation), "ts": time.time()})

    def detect_loop(self):
        sig = [(s["tool"], s["args"], s["obs"]) for s in self.steps]
        # 类型一:同一步原地踏步,连续 3 次
        if len(sig) >= 3 and len(set(sig[-3:])) == 1:
            return "stuck"
        # 类型二:A→B→A→B 振荡,连续 4 步两两相同
        if len(sig) >= 4 and sig[-4] == sig[-2] and sig[-3] == sig[-1] \
                and sig[-4] != sig[-3]:
            return "oscillate"
        return None

注意 detect_loop 把观察结果的哈希也算进签名:事故里那种"参数相同、返回相同、还在重复问"的情况,obs 哈希一致才能精准命中;如果模型每次换着花样问但观察结果不变,同样会被 stuck 规则抓住。

第 2 步:带熔断的执行器

def run_agent_with_guard(task, max_steps=8, token_budget=20_000, timeout_s=120):
    traj, total_tokens, start = Trajectory(), 0, time.time()

    for step in range(max_steps):                       # 熔断一:步数上限
        if time.time() - start > timeout_s:             # 熔断二:墙钟超时
            return finish(traj, total_tokens, "timeout")
        if total_tokens > token_budget:                 # 熔断三:token 预算
            return finish(traj, total_tokens, "token_budget")

        action, tokens = agent.plan(task, traj.steps)   # 模型决策
        total_tokens += tokens
        observation = execute(action)                   # 执行工具
        traj.record(action["tool"], action["args"], observation)

        if loop := traj.detect_loop():                  # 循环检测提前终止
            return finish(traj, total_tokens, f"loop:{loop}")
        if agent.is_done(observation):
            return finish(traj, total_tokens, "success")

    return finish(traj, total_tokens, "max_steps")

事故任务如果跑在这个执行器里,第 4 步就会命中 oscillate 熔断,成本从一夜上万元变成几分钱。熔断之后不是简单报错,而是降级:把轨迹和矛盾点打包升级给人工(“query_order 与 check_logistics 结论冲突,请人工介入”)——Agent 允许放弃,不允许空转。

第 3 步:把"不失控"写成测试用例

def test_conflicting_tools_never_loop():
    """两个工具结论矛盾时,Agent 必须在预算内终止并升级人工"""
    with mock_tools({
        "query_order":     {"status": "物流异常"},
        "check_logistics": {"status": "正常"},
    }):
        r = run_agent_with_guard("核查订单 A1002 的物流异常并处理")

    assert r.reason != "max_steps", "跑满步数上限,说明循环检测没生效"
    assert r.steps <= 6, f"决策步数 {r.steps} 超出预期"
    assert r.total_tokens < 20_000, "单任务成本失控"
    assert r.escalated_to_human, "矛盾场景必须升级人工,不允许猜"

def test_normal_task_still_succeeds():
    """熔断不能误伤正常任务:黄金任务集成功率不降"""
    with mock_tools(happy_path_tools):
        r = run_agent_with_guard("查询订单 A1001 状态并回复用户")
    assert r.reason == "success" and r.steps <= 4

第二条用例同样重要:熔断是刹车,刹车太灵车就没法开。 每次收紧阈值,必须用黄金任务集验证成功率没有掉。

四、沉淀成方法:Agent 测试三层 + 线上监控

层级 测什么 核心指标
单步契约层 每次工具调用参数合法吗(见第 4 篇) 参数非法率、重试收敛率
轨迹断言层 决策链合理吗 任务成功率、步数 P95、循环率、单任务 token 成本
熔断与监控层 线上失控能秒级发现吗 熔断触发率、token 消耗曲线告警、轨迹全量可回放

三条工程纪律:一是轨迹全量落日志,工具名 + 参数 + 观察结果 + token 数,出事能像这次一样完整回放;二是熔断阈值从数据来:用黄金任务集跑出步数分布,取 P95 × 1.5 作为 max_steps,token 预算同理,拍脑袋的阈值要么漏报要么误伤;三是熔断触发率本身是发布指标,新版本熔断率明显升高,说明模型决策质量退化,和成功率下降同等严重。

五、面试追问,你答得上来吗

  1. 测 Agent 和测普通接口的本质区别是什么?——答:接口是"一次调用一个契约",断言输入输出即可;Agent 是"一条不确定的决策链",要断言轨迹行为——目标是否达成、路径是否合理(步数、有无循环)、成本是否受控,并且必须给系统装熔断,因为 Agent 的失败模式不是"返回错误",而是"停不下来"。
  2. 怎么定义"Agent 完成了任务"?——答:把目标翻译成可验证的终态谓词:比如"工单已创建且字段正确"直接查工单系统,不靠模型自评;主观目标用 LLM 裁判 + 人工抽检验证。终态能查系统的查系统,查不了的才用裁判。
  3. 循环检测会不会误判"合理的重复"(比如分页翻查同一工具)?——答:会,所以签名里带参数和观察结果哈希:分页调用参数不同,不会命中 stuck;真正该抓的是"参数相同、观察相同、还在重复"。再配合黄金任务集回归误伤率,检测规则和熔断阈值一样,都是要被测试的组件。

下一篇预告:《流式输出只显示半句话——SSE 流式接口的测试点》

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。