结果评估 vs 轨迹评估

E2
运维 · 评估与可观测性

结果评估 vs 轨迹评估——给目的地、路线,还是两者打分。

对一次智能体运行,你可以问两个根本不同的问题:它是否抵达了一个可接受的终态(结果),以及它抵达的方式是否可接受(轨迹)。它们捕获不同的缺陷、花费不同的成本,在不同的情形下各为正解。把二者混为一谈——或因为更便宜而默认只看结果——会发布出靠运气正确、过程却危险的智能体。本文定义两者,展示各自何时是对的工具,以及如何用部分给分和工具调用断言把它们结合起来。

STEP 1

结果评估:一个对最终世界状态的谓词。

结果(或终态)评估无视智能体做过的一切,事后只对世界问一个问题:后置条件满足了吗?黄金标准是一个可执行检查器,而非字符串匹配:跑测试套件、查数据库、打 API、对文件系统做 diff。

# outcome check: never compare the agent's prose, check the world
def check_outcome(env) -> bool:
    return (
        env.run_tests() == "pass"
        and env.db.query("select count(*) from orders where state='shipped'") == 1
        and not env.fs.exists("/tmp/scratch.lock")
    )

长处:客观、打分便宜、对一个正确智能体可以用无穷多种措辞或路径抵达目标这件事稳健。盲区:它看不见目标是怎样抵达的。它会放过那个走运的猜测者,放过那个经由"先灾难后回滚"的副作用达成终态的智能体,并且在智能体已完成 90% 而你想知道这一点时只给你一个比特。

STEP 2

轨迹评估:给决策序列打分。

轨迹评估给序列打分——步骤、工具调用、参数、观察,以及把它们串起来的推理。它回答结果评估在结构上无法回答的问题:智能体是否采取了被禁止的动作、是否用对的参数调了对的工具、是否从注入的错误中恢复、是否避开了不可逆操作、是否在合理步数内收尾?

  • 参考轨迹匹配——与一条或多条已知良好路径比对。脆弱:会惩罚有效的替代路线。仅在路径确实就是规格时才用。
  • 对轨迹的性质断言——不是"匹配这条路径",而是"这些不变量成立":从未在没有先确认的情况下调 delete_*,从未把 PII 送给外部工具,最多重试 3 次。这是稳健的形态,也是你应当默认伸手去拿的那个。
  • 逐步评判——一个 LLM 评判者在上下文中给每个决策打分("在此状态下调 search 合理吗?")。昂贵且嘈杂;留给诊断,而非 CI 关卡。

精确参考路径匹配是轨迹评估的经典错误:它把"做了不同的事"和"做错了事"混为一谈,并把你的智能体训练成一个脆弱的路径回放器。断言不变量与被禁动作,而非那唯一真路径。

STEP 3

各自何时是对的工具。

  • 只看结果是对的,当后置条件完全可验证、路径对价值无关紧要、且没有不可逆副作用(或它们被沙箱隔离)时:必须让测试通过的代码、必须返回正确行的查询。
  • 轨迹要紧,当动作有现实后果(动了钱、发了邮件、碰了生产数据)时,当必须捕获"答案对、理由错"时,当你在调试结果为何失败时,或当任务没有干净可检查的终态、过程是你唯一能审视之物时。
  • 对任何你真要部署的东西,永远两者都要。结果是头条的通过/失败;轨迹是叠加其上的安全与质量关卡。一个会动钱的智能体必须同时满足"转账到账了"和"它从未尝试向一个未验证账户转账"。

决策规则:结果评估告诉你是否该庆祝;轨迹评估告诉你明天是否还能再信任它。生产智能体对后者的需要远甚于排行榜。

STEP 4

部分给分:当一个比特太粗时。

二元结果打分丢掉了"什么有用的都没做"和"完成了 8 个子目标中的 7 个然后绊倒"之间的差别。对长的多阶段任务,这毁掉你的信号——每次迭代都读作 0%,直到某天读作 100%,你无法把进步和噪声区分开。把任务分解为可独立检查的子目标,给已达成的比例打分,最好带一个依赖感知的加权,使解锁后续阶段计入更多权重。

# partial credit over checkable subgoals (weighted)
SUBGOALS = [
    ("repo cloned",        0.1, lambda e: e.fs.exists("/work/.git")),
    ("bug reproduced",     0.2, lambda e: e.ran("pytest -k repro")),
    ("fix applied",        0.3, lambda e: e.diff_touches("core/")),
    ("tests pass",         0.4, lambda e: e.run_tests() == "pass"),
]
score = sum(w for _, w, ok in SUBGOALS if ok(env))

在部分给分之上保留一个严格的二元判定。部分给分用于跟踪两次发布之间的进展;二元的"完全解决"才是你报告并据以设关卡的东西。单独优化部分分会培育出在容易子目标上拿满分、却永远不闭合那个难子目标的智能体。

STEP 5

工具调用断言:杠杆最高的轨迹检查。

多数与生产相关的轨迹性质都可归约为对工具调用日志的断言,而这些断言便宜、确定,且恰是生产中真正伤人的那些缺陷。它们是轨迹版的单元测试——每次改动都在 CI 里跑它们。

  • 存在/不存在——必需工具被调用了;被禁工具从未被调用。
  • 参数约束——transfer(amount) 从未超过上限;破坏性调用在评估环境上始终带 dry_run=True
  • 顺序与前置条件——每个 delete 前都有 confirm;任何数据读取前都先鉴权。
  • 预算——工具调用总数/令牌/墙钟时间在上限内;没有病态重试循环。
trajectory assertions  (task: refund-flow, 1 run)
  outcome: refund recorded            PASS
  assert: called verify_identity      PASS
  assert: never called delete_*       PASS
  assert: refund.amount <= order.total FAIL  ← refunded 120 on an 80 order
  assert: steps <= 15                  PASS (9)
  verdict: OUTCOME-PASS / TRAJECTORY-FAIL  -> do not ship

那一行就是本文的全部论点:一个只看结果的评估框架会报告一个干净的通过,并发布一个超额退款的智能体。那条轨迹断言只是便宜的一行,却正是救你的那一行。

STEP 6

诚实的取舍。

轨迹评估严格地更有信息量,也严格地更昂贵、更脆弱、更带主观判断——每一条断言都是你如今得维护的一个判断,而过度规定路径会把你的评估变成一件紧身衣,因为智能体有创意就判它失败。给结果打分,以知道智能体是否能用;断言轨迹不变量,以知道是否能放心让它去做——只在两者都通过时发布,并且永远别让二者中更便宜的那个去顶替另一个。