Harness Engineering 笔记(四):可观测性——Harness 的反馈循环基础

0 阅读5分钟

系列第四篇。OpenAI 在 Harness Engineering 文章中强调了一个关键原则:让应用对 Agent 可见——他们把 Chrome DevTools 接入运行时,让 Agent 能看到 UI、日志和指标。可观测性不只是调试工具,更是 Harness 反馈循环的基础设施。

前面三篇已经介绍了如何让一个 Agent 在 Harness 的框架上运行起来,但是运行起来遇到的各种问题要怎么感知到也是一个难题,需要有一种机制能够去观测到 Agent 运行过程,这才引入本篇的内容。

一个 Agent 在运行的时候是很难去调试的,与传统的软件相比,Agent 运行存在了很多的不确定性,我们会见到很多 bug 都是偶现的,很难去复现问题;有下面几个原因会让 Agent 的调试变得非常困难:

  1. 模型的输出具有不确定性:给模型的同个 query,可能走了不同的路径,调用了不同的 tool,通常上无法通过“重跑”复现一个 bug。
  2. Agent 一般是长链路:Agent 的一个 query 任务可能 10+ 步,问题可能出现在第 6 步,但是在第 10 步的时候才显现出来。
  3. 模型内部的状态不可见:LLM 为什么选择工具 A,不选择工具 B,这些我们都是不知道的,除非用了 extended thinking,否则其实我们是看不到具体的推理过程的。

因此我们可以得到一个核心的认识:我们不能调试 Agent,我们只能观测 Agent,用事后的分析取代实时的调试。

可观测性的三根支柱

Trace(追踪)— "发生了什么"

用于记录的 Agent 的执行过程,是 Agent 的行车记录仪。它需要记录 Agent 执行过程中每一步操作:输入了什么、LLM 输出了什么(含 thinking)、工具执行了什么、工具输出了什么,总共花了多少 Token,每步执行的耗时是怎么样的。

其通常有一些系列 span 组成,每个 span 都是一个原子操作,例如一次 agent 调用,一次工具的调用等等。span 的定义和 trace 的定义可能如下:


// SpanKind classifies what a span represents.
type SpanKind string
const (
	SpanKindLLM   SpanKind = "llm"
	SpanKindTool  SpanKind = "tool"
	SpanKindAgent SpanKind = "agent"
)

// Span represents a single timed operation within a trace.
type Span struct {
	SpanID       string                 `json:"span_id"`
	ParentID     string                 `json:"parent_id,omitempty"` // 用于记录父节点的 span 是什么
	TraceID      string                 `json:"trace_id"` // 当前 traceID,用于串起一次 query 的执行
	Kind         SpanKind               `json:"kind"`
	Name         string                 `json:"name"`
	StartTime    time.Time              `json:"start_time"`
	EndTime      time.Time              `json:"end_time"`
	DurationMS   float64                `json:"duration_ms"`
	StopReason   string                 `json:"stop_reason,omitempty"`
	InputTokens  int                    `json:"input_tokens,omitempty"`
	OutputTokens int                    `json:"output_tokens,omitempty"`
	ToolName     string                 `json:"tool_name,omitempty"`
	ToolInput    map[string]interface{} `json:"tool_input,omitempty"`
	ToolOutput   string                 `json:"tool_output,omitempty"`
	ToolError    string                 `json:"tool_error,omitempty"`
	Error        string                 `json:"error,omitempty"`
}

// Trace is the complete record of an agent run.
type Trace struct {
	TraceID    string          `json:"trace_id"`
	AgentName  string          `json:"agent_name"`
	StartTime  time.Time       `json:"start_time"`
	EndTime    time.Time       `json:"end_time"`
	DurationMS float64         `json:"duration_ms"`
	Spans      []Span          `json:"spans"` // 一个 trace 由一系列 span 组成
	Result     *ResultSnapshot `json:"result,omitempty"`
}

举个例子:

Step 0 [context]  消息列表: 2条 | 预估 token: 1,850
Step 1 [llm]      stop_reason: tool_use → search({query: "React 18"}) | 1,240ms
Step 2 [tool]     search: success | 3,200 chars → 截断至 2,000
Step 3 [llm]      stop_reason: end_turn → 最终回答 | 2,100ms
Step 4 [done]     总计: 6,509 tokens | $0.011 | 4,160ms

进一步的我们可以通过结构化 span 的内容,将一个 trace 给可视化出来(但是实际的生产应用中用户的敏感信息是一个待解决的问题,一般情况下要有用户的授权才能够去查看用户的敏感信息。)

Log(日志)— "出了什么问题"

结构化事件流,可搜索可索引。记录错误、重试、循环检测、Compaction 等异常事件。

Metrics(指标)— "表现怎么样"

可聚合的数值:Token 消耗、延迟、工具成功率、每任务步数。用于监控整体健康度。

类比:Trace = 行车记录仪,Log = 故障灯,Metrics = 仪表盘。

Trace 的用途

当我们记录一次 query 的执行过程时候可以将 Trace 用于“回放”,这在可观测性中起到了非常重要的作用。有下面几个重要的用途:

用途 1:事后调试

用户投诉"Agent 给了错误答案"→ 拉出 Trace → 逐步回放 → 发现 Step 1 的 SQL 把"上个季度"错误解析为 Q3(实际是 Q4)→ 根因:LLM 不知道当前日期 → 修复:system prompt 注入日期。

没有 Trace,你根本无法追溯到 SQL 里那个时间范围的错误。

用途 2:回归测试

修复后用旧 Trace 验证:重用旧 Trace 的用户输入和工具返回值(mock 掉真实工具),只让 LLM 用新 prompt 重新推理,对比新旧输出。不需要重新查数据库。

用途 3:批量评估

积累几百条 Trace 后,用它们批量测试新版 Harness,LLM-as-judge 自动打分,得到统计显著的评估结果。这就是 Agent 的"单元测试"——不是测代码逻辑,而是测 Agent 行为。

可观测性的重要性

观测代码是架构的一等公民——不是事后加的,是和业务逻辑同等重要的。每次 LLM 调用和工具执行前后都应该有 Trace 记录。如果你在搭 Harness 时没有从第一天就植入可观测性,后面加的成本会非常高。

Extended Thinking 是填补"LLM 内部不可见"的关键。 Claude 的 extended thinking 让你能看到模型的推理过程,把 thinking 内容记入 Trace,调试效率大幅提升。

生产级工具推荐:LangSmith、Langfuse(开源)、Braintrust、OpenTelemetry。

小结

  1. Agent 不能"调试",只能"观测"——事后分析取代实时调试
  2. Trace 是最重要的支柱——调试、回归测试、批量评估的基础
  3. 可观测性是 Harness 反馈循环的基础设施——跨越 Context Engineering、Architectural Constraints、Entropy Management 三个支柱
  4. 观测代码是一等公民——从 Harness 构建的第一天就应该植入他

下一篇是最后一个章节:多 Agent 编排。