核心摘要

Agent 可观测性有三个不同任务:

  1. Trace 记录足以重建一次运行的有界事件;
  2. Eval 判断结果是否满足契约;
  3. Debug 定位导致失败的状态转移、策略决策、依赖或预算。

目标不是捕获每个 Token,而是在最小化敏感数据的同时保留回答运维问题所需的证据,并让质量信号独立于模型自己的解释。

为什么 Agent 可观测性不同

传统 APM 可以把 HTTP 成功作为技术信号,但 Agent 返回 200 时,仍可能选错 Tool、越过租户边界、引用没有依据的资料,或花费超过预算。反过来,下游超时是技术失败,即使 Agent 正确拒绝了危险操作。

问题 技术遥测 Agent 证据
请求完成了吗? 状态码、耗时、异常 最终结果与取消原因
运行时做了什么? Span 树与依赖调用 状态转移、Tool 调用、策略决策
回答质量如何? APM 通常不可见 契约、证据、人工或模型评估
系统安全吗? 认证错误与网络事件 拒绝操作、跨租户尝试、外发行为
花费多少? CPU、内存 Token、厂商价格、Tool 与重试预算

不要从状态码、模型解释或单一分数推断语义质量。每种决策都应记录对应的独立证据。

先定义事件契约

在选择 LangSmith、Langfuse、Phoenix、其他平台或自建存储之前,先定义由应用拥有的事件契约:

json
{
  "event": "tool_call_completed",
  "schema_version": 3,
  "run_id": "run_7f...",
  "trace_id": "4bf92f...",
  "step": 4,
  "tenant_hash": "h:...",
  "subject_hash": "h:...",
  "tool": "invoice.list",
  "tool_version": "2026-05-1",
  "argument_digest": "sha256:...",
  "policy": "allow",
  "state_version": 12,
  "duration_ms": 184,
  "result_class": "bounded_page",
  "error_code": null,
  "usage": {
    "input_tokens": 812,
    "output_tokens": 164,
    "estimated_cost_usd": 0.0019
  }
}

建议的事件包括 run_startedmodel_call_completedtool_call_requestedpolicy_decisiontool_call_completedstate_transitionbudget_exhaustedrun_completedrun_failed。每个事件都应有稳定的 Schema 版本、Run ID 和脱敏策略。

应用事件契约让后端可以迁移。Trace 平台可以负责存储传输元数据,但即使更换 SDK 或模型厂商,业务事件仍然有意义。

Trace 边界与隐私

OpenTelemetry 是厂商无关的 Trace、Metric 和 Log 传输基础。它不替应用决定应该记录什么。使用 Span 表达耗时和因果关系,使用事件或日志表达结构化业务结果。

OpenTelemetry GenAI 当前状态

截至 2026-07-28,OpenTelemetry 的 GenAI 语义约定已从核心 Semantic Conventions 文档迁移到独立的 semantic-conventions-genai 仓库。该仓库覆盖 GenAI Client、Agent、MCP、Span、Metric 和 Event,但尚未发布正式 Release。因此应把 Agent 属性视为持续演进的约定:固定实现所用的 Commit 或生成 Schema,保留应用自己的事件版本,并在升级约定前测试 Exporter。OpenTelemetry 核心 Trace、Metric、Log 与 Context API 可以稳定,而 GenAI 专用属性集仍可能变化。

各类信号的职责

信号 负责表达 不能证明
Trace/Span Agent Run、Model Call、Retrieval、Tool Call 和 Handoff 的因果与耗时 任务质量或授权
Span Event/Log 策略拒绝、重试、缓存决策或状态转移等有界事件 时间序列服务目标
Metric 聚合的速率、延迟、Token、成本、队列、错误与策略计数 某次运行为何失败
Evaluation 对可观察产物做契约、证据、安全和任务质量判断 运行时因果或权限

整条运行使用统一关联模型:以 AgentRun 为根,为 Model Call、Retrieval、Tool Call 和 Handoff 建立有界子 Span。Evaluation 作为独立版本化记录,通过 run_id、Evaluator 版本、数据切片和证据 ID 关联。生产级 RAG 评估指南定义检索和答案质量门禁;Agent 工具安全指南定义运行时权限,遥测只能观察它,不能替代它。

不要采用以下默认做法:

  • 在每个 Span 中写入原始 Prompt 或完整 Tool 参数;
  • 保存完整模型回答、隐藏推理或私有检索文档;
  • 保存 Bearer Token、API Key、Authorization Header 或原始用户 ID;
  • 把无界 Tool Result 放进 Span 属性;
  • 用一个 success=true 隐藏策略拒绝和语义失败。

只有在存在明确关联目的时才使用 Hash 或稳定伪名。Hash 并不会自动使数据匿名:输入空间很小时,映射可能被反推。遥测存储也必须按租户做访问控制,并将删除请求传播到导出物和评估数据集。

python
from dataclasses import dataclass
from hashlib import sha256
from typing import Any

from opentelemetry import trace


def digest(value: str) -> str:
    return "sha256:" + sha256(value.encode("utf-8")).hexdigest()


@dataclass(frozen=True)
class RunContext:
    run_id: str
    tenant_id: str
    subject_id: str


def record_tool_event(
    tracer: trace.Tracer,
    context: RunContext,
    *,
    tool_name: str,
    tool_version: str,
    arguments: dict[str, Any],
    policy_decision: str,
    state_version: int,
    duration_ms: int,
    result_class: str,
    error_code: str | None,
) -> None:
    with tracer.start_as_current_span("ToolCall") as span:
        span.set_attribute("agent.run_id", context.run_id)
        span.set_attribute("agent.tenant_hash", digest(context.tenant_id))
        span.set_attribute("agent.subject_hash", digest(context.subject_id))
        span.set_attribute("agent.tool.name", tool_name)
        span.set_attribute("agent.tool.version", tool_version)
        span.set_attribute("agent.argument_digest", digest(repr(sorted(arguments.items()))))
        span.set_attribute("agent.policy.decision", policy_decision)
        span.set_attribute("agent.state.version", state_version)
        span.set_attribute("agent.duration_ms", duration_ms)
        span.set_attribute("agent.result.class", result_class)
        if error_code:
            span.set_attribute("agent.error.code", error_code)

该片段记录摘要和结果类别,而不是参数内容。若法律或运维目的确实需要保留少量字段,应放进独立的访问受控审计记录。

Span 命名与上下文传播

使用 AgentRunModelCallToolCallPolicyCheckStateTransition 等稳定、低基数 Span 名称。不要把用户输入、资源 ID 或任意 Tool 参数放入名称。跨网关和可信 Tool 服务传播 W3C Trace Context;跨越租户或安全边界时,创建新的内部 Run ID。

区分以下标识:

  • trace_id:传输层因果关联;
  • run_id:一次 Agent 执行及其重试/回放身份;
  • request_id:外部请求或幂等边界;
  • argument_digest:不暴露内容的精确输入摘要。

Tool 被拒绝时,记录策略结果和原因类别,但不要把 Trace 后端当作授权系统。

质量评估是独立平面

评估必须针对明确问题使用明确 Oracle:

评估层 示例 Oracle 适用场景
Contract Schema、枚举、引用、预算、策略 每次运行或 CI
Scenario 预期 Tool、Resource、状态和结果 回归测试
Replay 固定模型、Prompt、Fixture 和 Tool Double 版本比较
Abuse 注入、跨租户、外泄、重复副作用 安全门禁
Human Review Rubric 与复议流程 高影响或歧义场景
Model Judge 校准 Rubric 与标注集 可扩展语义信号

不要让被评估的模型给自己的隐藏推理打分。评估可观察的输出、证据支持、策略决策和副作用。Judge 返回的置信度不是校准概率,除非已经用标注样本完成校准。

更安全的 Judge 契约

只传入 Rubric 需要的最少数据。把检索文本和模型回答视为不可信输入,使用清晰边界防止它们改变评估指令:

typescript
type JudgeInput = {
  question: string;
  answer: string;
  evidence: Array<{ id: string; text: string }>;
};

type JudgeResult = {
  supported: "yes" | "no" | "uncertain";
  relevant: "yes" | "no" | "uncertain";
  missingEvidence: string[];
  rationale: string;
};

function validateJudgeResult(value: unknown): JudgeResult {
  if (!value || typeof value !== "object") throw new Error("invalid_judge_result");
  const result = value as Record<string, unknown>;
  const allowed = new Set(["yes", "no", "uncertain"]);
  if (!allowed.has(String(result.supported)) ||
      !allowed.has(String(result.relevant)) ||
      !Array.isArray(result.missingEvidence) ||
      !result.missingEvidence.every((item) => typeof item === "string")) {
    throw new Error("invalid_judge_result");
  }
  return {
    supported: result.supported as JudgeResult["supported"],
    relevant: result.relevant as JudgeResult["relevant"],
    missingEvidence: result.missingEvidence as string[],
    rationale: typeof result.rationale === "string" ? result.rationale : "",
  };
}

权限、Tool 参数、结构化输出、证据 ID、预算和幂等性使用确定性检查。模型评估分数应与校准集比较,持续监控评估者一致性;高影响分歧进入人工复核。

能解释结果的指标

不要只做平均延迟和一个质量分数的仪表盘。指标应映射到具体动作:

  • 运行完成、取消和预算耗尽率;
  • 每次运行的 Model Call、Tool Call、重试和状态转移数量;
  • 按策略版本统计 allow/confirm/deny,包括跨租户拒绝;
  • Tool 参数校验失败和结果超限;
  • 抽样评估中的证据覆盖率和无依据声明率;
  • 按模型、Tool、租户类别和结果统计 p50/p95/p99 延迟;
  • 输入/输出 Token、估算厂商成本和每个成功任务成本;
  • 排队时间、下游错误类别和取消传播。

指标标签应使用有界枚举。包含用户问题或任意资源 ID 的标签会造成高基数和隐私问题。

不保存思维链也能调试循环

Agent 循环可以通过状态和动作事件观测:

python
from collections.abc import Iterable
from dataclasses import dataclass


@dataclass(frozen=True)
class StepEvent:
    step: int
    tool: str | None
    argument_digest: str | None
    state_version: int
    policy: str
    outcome: str


def detect_stall(events: Iterable[StepEvent], window: int = 4) -> bool:
    sequence = list(events)
    if len(sequence) < window * 2:
        return False
    recent = sequence[-window:]
    previous = sequence[-window * 2:-window]
    signature = lambda item: (
        item.tool,
        item.argument_digest,
        item.state_version,
        item.policy,
        item.outcome,
    )
    return [signature(item) for item in recent] == [
        signature(item) for item in previous
    ]

重复签名还必须结合最大步数、状态版本不变、重复幂等键和依赖超时。重复调用同一个 Tool 不一定是循环,分页和轮询可能是合法行为。最大步数和取消必须由运行时执行,而不是由遥测后端代替。

回放时保存脱敏事件 Fixture 和确定性 Tool Double,使用固定模型或已记录的模型响应重跑,再比较事件契约和业务结果。如果会再次执行外部副作用,就不能把它称为安全的“时间旅行”;应使用 Dry Run 或补偿语义。

采样与成本控制

采样是策略决策,不存在统一比例:

信号 可采用的策略
高影响操作或明确事故 保留所需审计字段
策略拒绝、超时或预算超限 保留扩展的脱敏 Trace
普通低风险运行 保留元数据和有界样本
聚合健康指标 保留不含内容的计数和直方图

run_id 做确定性采样,确保子 Span 一致。Tail Sampling 可以等待结果信息后执行,但隐私过滤必须在导出前完成。成本估算应包含事件大小、流量、后端留存、评估调用和删除要求。

如何选择技术栈

按照契约和数据控制选择组件,而不是使用通用排行榜:

需求 能力候选
厂商无关传输 OpenTelemetry SDK 与 Collector
Trace 查询 兼容 OTel 的后端
Prompt/Eval 工作流 满足数据驻留、留存和访问要求的平台
回放与回归 版本化 Fixture、数据集和确定性 Tool Double
看板与告警 使用有界标签的指标后端

托管和自托管产品的 SDK、数据驻留、删除、导出和价格都会变化。选型前应核对厂商当前文档。某个平台深度集成 LangChain,不代表它支持你的 Agent 运行时或授权模型。

生产控制

访问与留存

  • 分离运维、开发、评估和租户对遥测的访问;
  • 加密传输和存储,轮转导出器凭证;
  • 将原始内容、事件元数据、聚合指标和评估产物放进不同留存类别;
  • 将主体删除传播到 Trace 后端、缓存、导出物和评估数据集;
  • 记录脱敏版本和策略版本,使 Schema 变更后仍可解释事件。

告警

围绕工作负载基线告警:

  • 拒绝或跨租户尝试突然增加;
  • 重复状态且没有进展;
  • p99 延迟或排队时间超过服务目标;
  • 每个成功任务成本超出预算;
  • 校准样本中无依据证据或契约失败增加;
  • 导出器失败或遥测丢失。

不要把 90%80%15% 或固定“生产就绪等级”写成通用阈值。正确阈值取决于影响、流量、模型和业务容忍度。

发布门禁

启用新 Tool 或模型前,至少需要确定性契约测试、场景测试、滥用测试、回放对比和隐私评审。高影响操作需要人工审批或独立授权工作流;Trace 或 Judge 分数不能授予权限。

常见失败模式

失败 需要的证据 修复动作
重复循环 状态版本、动作摘要、预算事件 停止运行,修复转移或预算
Tool 选错 候选集合、选中 Tool、Schema 结果 改善边界并增加场景用例
无依据回答 证据 ID 与声明检查 要求证据或拒答
跨租户尝试 主体摘要、租户策略结果 拒绝、告警并检查调用路径
成本失控 Token、重试、模型路由 执行预算和路由策略
遥测泄露 脱敏审计和访问日志 撤销访问、删除导出物并修过滤器

常见问题

Agent Trace 应该记录什么?

记录有界标识、耗时、版本、用量、策略结果、结果类别和最终业务结果。按用途脱敏或省略内容。Trace 不是隐藏推理逐字稿。

OpenTelemetry 是 Agent 专用标准吗?

不是。它是厂商无关的可观测性基础。应用仍需定义 Agent 事件、隐私规则和业务语义,再把事件映射到 Span、Log 和 Metric。

LLM Judge 可以直接作为发布门禁吗?

不能单独使用。确定性检查、校准的模型评估、回放、滥用测试和高影响人工复核应共同组成门禁。

不保存思维链,如何排查 Agent 循环?

使用有界 Step Event、状态版本、参数摘要、策略结果和预算;用脱敏 Fixture 与 Tool Double 回放。

遥测应该保留多久?

根据用途、敏感度、法律义务、事故需求和删除传播选择,不存在统一期限或采样率。

总结

好的 Agent 可观测性是一套受控的证据系统。OpenTelemetry 可以承载因果上下文,但应用事件契约才定义什么重要;评估应检查可观察契约,而不是相信模型解释;调试应重建状态和动作转移,而不是把私有 Prompt 或隐藏思维变成永久日志。把 Trace、评估、策略、隐私和回放放在一起设计,才能回答“发生了什么、应该改变什么”,同时避免制造第二个数据治理问题。

一手来源