---
title: 评估与可观测
url: https://doc.liz6.com/ai/02-Agent/05-evaluation-and-observability
locale: zh
area: ai
tags:
- ai
- Agent
date: 2026-06-30
modified: 2026-07-16
description: agent 难评估:输出开放式、过程非确定、链路多步。但没有评估就没法迭代——评估(离线衡量质量)和可观测(在线看清运行时)两者互补,才能把 agent 从"看着还行"变成可度量、可改进。
---

# 评估与可观测

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

## 概述

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

## 评估:离线衡量质量

```mermaid
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`)能让这类校验稳定可做(见 [提示工程与结构化输出](/ai/03-applications-and-production/02-prompt-engineering-and-structured-output.md))。

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

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

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

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

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

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

平台用一个**独立上下文的 grader** 跑 **iterate → 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 = 有静默失效器**(见 [上下文工程](/ai/01-models-and-context/01-context-engineering.md)) |
  | `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 应用照搬即可。

## 把两者接进循环

<svg viewBox="0 0 780 150" xmlns="http://www.w3.org/2000/svg" font-family="-apple-system,'Source Han Sans CN','Microsoft YaHei',sans-serif" role="img" aria-label="可观测与评估接入循环:build 到 run 再到 eval,定位失败后修改再跑">
  <rect width="780" height="150" fill="#ffffff"/>
  <rect x="20" y="30" width="70" height="40" rx="6" fill="#4f46e5"/>
  <text x="55" y="55" text-anchor="middle" font-size="12" font-weight="700" fill="#ffffff">build</text>
  <line x1="90" y1="50" x2="120" y2="50" stroke="#475569" stroke-width="1.6" marker-end="url(#loopArrow)"/>
  <rect x="124" y="30" width="112" height="40" rx="6" fill="#0d9488"/>
  <text x="180" y="55" text-anchor="middle" font-size="12" font-weight="700" fill="#ffffff">run(带 trace)</text>
  <line x1="236" y1="50" x2="266" y2="50" stroke="#475569" stroke-width="1.6" marker-end="url(#loopArrow)"/>
  <rect x="270" y="30" width="96" height="40" rx="6" fill="#0d9488"/>
  <text x="318" y="55" text-anchor="middle" font-size="12" font-weight="700" fill="#ffffff">eval(打分)</text>
  <line x1="366" y1="50" x2="396" y2="50" stroke="#475569" stroke-width="1.6" marker-end="url(#loopArrow)"/>
  <rect x="400" y="30" width="152" height="40" rx="6" fill="#f0fdfa" stroke="#99f6e4"/>
  <text x="476" y="55" text-anchor="middle" font-size="12" font-weight="700" fill="#0f766e">看 trace 定位失败步</text>
  <line x1="552" y1="50" x2="582" y2="50" stroke="#475569" stroke-width="1.6" marker-end="url(#loopArrow)"/>
  <rect x="586" y="30" width="60" height="40" rx="6" fill="#4f46e5"/>
  <text x="616" y="55" text-anchor="middle" font-size="12" font-weight="700" fill="#ffffff">改</text>
  <path d="M616,70 C616,110 260,110 180,72" fill="none" stroke="#94a3b8" stroke-width="1.6" stroke-dasharray="4,3" marker-end="url(#loopArrowGray)"/>
  <text x="398" y="122" text-anchor="middle" font-size="11" fill="#94a3b8">再 run —— 可观测答"哪步出了问题",评估答"整体变好没"</text>
  <defs>
    <marker id="loopArrow" markerWidth="10" markerHeight="8" refX="8" refY="3" orient="auto"><path d="M0,0 L8,3 L0,6 Z" fill="#475569"/></marker>
    <marker id="loopArrowGray" markerWidth="10" markerHeight="8" refX="8" refY="3" orient="auto"><path d="M0,0 L8,3 L0,6 Z" fill="#94a3b8"/></marker>
  </defs>
</svg>

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

## 最佳实践

- **先有小而真实的 eval 集再开始调。** 哪怕 20 条代表性任务(含已知失败/边界),也远胜凭单条手感拍脑袋。
- **打分按可靠性从高到低用。** 可编程校验 > LLM-as-judge > outcome 评分;能 `assert` 的绝不交给模型([结构化输出](/ai/03-applications-and-production/02-prompt-engineering-and-structured-output.md) 让这类校验稳定可做)。
- **裁判与被测分离,给可独立判定的 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 编排](/ai/02-Agent/04-multi-agent-orchestration.md))。
- **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*
