本页目录

评估与可观测

agent 难评估:输出开放式、过程非确定、链路多步。但没有评估就没法迭代——评估(离线衡量质量)和可观测(在线看清运行时)两者互补,才能把 agent 从"看着还行"变成可度量、可改进。

概述

agent 难评估,难在三点:输出开放式⁠(没有唯一正确答案)、过程非确定⁠(同 prompt 两次结果不同)、链路多步⁠(一个任务中间走了二十次工具调用)。但没有评估就没法迭代⁠——改了 prompt、换了模型、加了工具,凭什么说"变好了"而不是"换了个错法"?这篇讲两件互补的事:⁠评估(eval)——离线、可重复地衡量质量;⁠可观测(observability)——在线看清 agent 运行时到底在干嘛。前者回答"整体变好没",后者回答"哪一步出了问题"。两者接进迭代循环,agent 才能从"看着还行"变成"可度量、可改进"。

评估:离线衡量质量

flowchart LR
    D["eval 集<br/>代表性任务 + 期望/rubric"] --> R["跑当前版本"]
    R --> J["打分"]
    J -->|可编程?| P["代码断言"]
    J -->|开放式?| L["LLM-as-judge"]
    J -->|有完成标准?| O["outcome 评分"]
    P --> I["聚合 & 对比"]
    L --> I
    O --> I
    I -->|改 prompt/模型/工具| R

打分方式按可靠性从高到低用,能用前者就别用后者:

1. 可编程校验(最可靠)

能用代码判定的,绝不交给模型。"返回 JSON 通过 schema""CSV 有数值列 price""测试通过""含某关键字段"——直接断言。确定、零成本、无偏差。结构化输出(output_config.format / strict: true)能让这类校验稳定可做(见 提示工程与结构化输出)。

2. LLM-as-judge(开放式才用)

摘要质量、回答是否切题这类无法编程判定的,用一个模型当裁判。要点全在降偏差⁠:

  • 给明确 rubric:把"好不好"拆成可独立判定的分条⁠("是否引用了来源""是否回答了全部三个子问题"),而不是让裁判凭感觉打一个总分。模糊 rubric → 噪声分。
  • 裁判与被测分离⁠:别让同一个模型评自己的输出(自我背书)。用不同模型、或至少独立上下文当裁判。
  • 警惕已知偏差⁠:裁判偏爱更长的答案、偏爱自己家模型的风格、受选项顺序影响——rubric 越具体,这些偏差影响越小。
  • 把打分本身也评一评⁠:抽样人工核对裁判判分,确认裁判靠谱再规模化。

3. outcome-based(有完成标准时)

Managed Agents 把"结果导向评估"做成了原语。发一个 user.define_outcome 事件,带:

  • description(任务)、rubric({type:"text"|"file"},⁠必填⁠,写可独立判定的标准)、max_iterations(默认 3,最大 20)。

平台用一个独立上下文的 graderiterate → grade → revise 循环,每轮发 span.outcome_evaluation_start/ongoing/end,end.result ∈ {satisfied, needs_revision, max_iterations_reached, failed, interrupted},直到达标或到上限。这等于把"评估 + 自动改进"内建进运行。适合产出有明确"完成长什么样"的任务(报告、模型、文档)。

4. regression(回归)

每发现一个失败,把它固化成一条 eval case。这是 eval 集随时间变强的方式——线上每翻一次车,就在离线多一道防线。

最重要的实践⁠:⁠先有一个小而真实的 eval 集再开始调⁠。哪怕只有 20 条代表性任务(含已知失败/边界,不只 happy path),也远胜凭单条手感拍脑袋。

可观测:在线看清运行时

agent 上线后要能回答:它走了哪几步?哪步慢/失败?烧了多少 token?为什么停?

  • trace / span:把一次运行拆成可见步骤——每个 tool_use/tool_result/thinking/模型调用是一个 span。Managed Agents 直接给事件流,span.model_request_end 还带 model_usage(本次推理的 token 明细),用于逐步归因延迟与成本。

  • usage(成本 + 缓存健康):每次响应的 usage 给:

    字段看什么
    input_tokens未缓存输入(全价)
    output_tokens输出
    cache_read_input_tokens命中缓存(~0.1x);⁠长期为 0 = 有静默失效器⁠(见 上下文工程)
    cache_creation_input_tokens写缓存(~1.25x)

    注意:input_tokens 只是未缓存的余量,总量 = 三者之和——别只看一个字段就以为没花 token。

  • stop_reason 分布⁠:end_turn / tool_use / max_tokens / refusal 的占比是健康仪表盘。max_tokens 偏高 = 输出被截断(调大或流式);refusal 异常升高 = prompt 触发了拒绝;pause_turn 多 = 服务端工具循环在续。

  • OpenTelemetry:把 trace/metric 导到标准后端统一看。

真实例子:本站点的静态生成器构建时就初始化了 OTel——构建日志里那行 OTel tracer initialised, exporting to ... 就是。同一套"trace 每一步、量化每一次"的思路,LLM 应用照搬即可。

把两者接进循环

build run(带 trace) eval(打分) 看 trace 定位失败步 再 run —— 可观测答"哪步出了问题",评估答"整体变好没"

可观测回答"⁠哪步出了问题",评估回答"⁠整体变好没"。缺前者,不知道为什么错;缺后者,不知道改动是不是真改进。两者缺一,迭代就是盲改。

最佳实践

  • 先有小而真实的 eval 集再开始调。 哪怕 20 条代表性任务(含已知失败/边界),也远胜凭单条手感拍脑袋。
  • 打分按可靠性从高到低用。 可编程校验 > LLM-as-judge > outcome 评分;能 assert 的绝不交给模型(结构化输出 让这类校验稳定可做)。
  • 裁判与被测分离,给可独立判定的 rubric。 别自评自背书;把"好不好"拆成可逐条判定的分项,降噪降偏差。
  • 每翻一次车,固化成一条回归 eval。 线上每个失败变离线一道防线,eval 集随时间变强。
  • trace 每一步,量化每一次。 一次运行拆成 span,usage/stop_reason 逐步归因延迟与成本;OTel 导标准后端统一看。
  • 缓存命中率进监控。 cache_read_input_tokens 长期为 0 = 有静默失效器,成本会悄悄翻倍。
  • 非确定系统看聚合,别被单次手感带偏。 通过率、token 分布、stop_reason 占比;批量结果按 custom_id/trace id 归因,绝不靠顺序。

权衡与失败模式

  • 只测 happy path:线上一遇边界就崩 → eval 集必须含失败/边界,并随事故增长(回归)。
  • 裁判 = 被测⁠:自评自背书 → 裁判与被测分离;对应 agent 工程的"⁠编写与审查分两条 lane"(见 多 Agent 编排)。
  • rubric 含糊⁠:"看起来不错"无法稳定打分 → 写可独立判定的分条。
  • 只看单条不看分布⁠:非确定系统要看聚合(通过率、token 分布、stop_reason 占比),别被单次手感带偏。
  • 不归因⁠:批量/并行结果对不上是哪条 → 用 custom_id / trace id 归因,绝不靠顺序(Batches 结果本就乱序)。
  • 缓存命中率不监控⁠:成本悄悄翻几倍才发现 → 把 cache_read_input_tokens 进监控面板。

参考

  • Anthropic 官方文档⁠: Building Evals、Managed Agents Define Outcomes & Observability、Handling Stop Reasons(platform.claude.com)

Keywords: eval, eval set, programmatic check, structured outputs, LLM-as-judge, rubric, judge bias, outcome-based, user.define_outcome, grader, span.outcome_evaluation, max_iterations, regression, observability, trace, span, model_usage, usage, input_tokens, output_tokens, cache_read_input_tokens, stop_reason 分布, OpenTelemetry, OTel, authoring vs review lane, custom_id