Harness Engineering-第19章 可观测性与调试

《Harness Engineering --- AI Agent 工程方法论》完整目录

第19章 可观测性与调试

"You can't debug what you can't see."

:::tip 本章要点

  • Agent 可观测性的三大支柱:日志(Logging)、追踪(Tracing)、指标(Metrics)
  • Trace 是 Agent 调试的核心------还原从用户输入到最终输出的完整决策链路
  • LangSmith/LangFuse 等工具提供了可视化的 Agent Trace 查看器
  • 生产环境必须监控的指标:成功率、延迟、token 消耗、错误率 :::

19.1 为什么 Agent 需要专门的可观测性

传统 Web 服务的可观测性关注请求延迟、错误率、吞吐量。Agent 系统有本质不同:

  • 决策链路长------一个用户请求可能触发 20+ 次工具调用和多次 LLM 交互
  • 行为非确定性------同样的输入可能产生不同的执行路径
  • 失败模式复杂------不只是"报错了",还有"做错了但没报错"
  • 成本可变------一个请求可能消耗 1K token 或 100K token
graph TD subgraph Pillars["可观测性三大支柱"] L["📝 Logging\n记录每个事件"] --> T["🔗 Tracing\n还原决策链路"] T --> M["📊 Metrics\n聚合指标告警"] end L -->|"事件流"| Store["日志存储"] T -->|"Trace 树"| Viz["可视化查看器\nLangSmith / LangFuse"] M -->|"时间序列"| Alert["仪表盘 + 告警"] style L fill:#dbeafe,stroke:#3b82f6 style T fill:#fef3c7,stroke:#f59e0b style M fill:#dcfce7,stroke:#22c55e

19.2 三大支柱

日志(Logging)

结构化日志记录每个关键事件:

typescript 复制代码
// Agent 循环中的关键日志点
logger.info('agent.loop.start', {
  sessionId,
  userMessage: truncate(message, 200),
  contextTokens: countTokens(messages),
})

logger.info('agent.tool.call', {
  sessionId,
  tool: toolName,
  params: sanitizeParams(params),  // 去除敏感信息
  iteration: loopIteration,
})

logger.info('agent.tool.result', {
  sessionId,
  tool: toolName,
  success: !error,
  durationMs,
  resultTokens: countTokens(result),
})

logger.info('agent.loop.end', {
  sessionId,
  iterations: totalIterations,
  totalTokens,
  totalDurationMs,
  toolsCalled: toolCallSummary,
})

追踪(Tracing)

Trace 把一次完整的 Agent 交互组织成树状结构:

flowchart TD Root["Trace: 修复登录 bug\n总耗时 45s | 85K tokens"] Root --> L1["LLM Call #1 (3s, 2K t)\n决定先读代码"] L1 --> T1a["Tool: Read auth.ts\n0.1s"] L1 --> T1b["Tool: Read auth.test.ts\n0.1s"] Root --> L2["LLM Call #2 (5s, 8K t)\n分析并修改"] L2 --> T2a["Tool: Edit auth.ts\n0.2s"] L2 --> T2b["Tool: Bash npm test\n12s ❌ 失败"] Root --> L3["LLM Call #3 (4s, 6K t)\n修复测试失败"] L3 --> T3a["Tool: Edit auth.ts\n0.2s"] Root --> L4["LLM Call #4 (2s, 3K t)\n再次验证"] L4 --> T4a["Tool: Bash npm test\n15s ✅ 通过"] Root --> L5["LLM Call #5 (2s, 1K t)\n生成总结"] style T2b fill:#fee2e2,stroke:#ef4444 style T4a fill:#dcfce7,stroke:#22c55e style Root fill:#dbeafe,stroke:#3b82f6,stroke-width:2px

一眼就能看出:Agent 在第一次修改后测试失败(红色),自动修复后成功(绿色)。这种可视化对调试至关重要。

Trace 的数据模型

一个 Trace 由嵌套的 Span 组成。每个 Span 记录一个操作单元:

typescript 复制代码
interface Span {
  id: string
  parentId?: string      // 父 Span(构成树结构)
  name: string           // "llm.call" | "tool.execute" | "agent.task"
  startTime: number
  endTime?: number
  metadata: {
    model?: string       // LLM 调用时记录模型名
    tool?: string        // 工具调用时记录工具名
    inputTokens?: number
    outputTokens?: number
    error?: string       // 失败时的错误信息
    [key: string]: any
  }
  children: Span[]
}

Span 的层级关系自然映射了 Agent 的执行结构:

  • 顶层 Span = 整个任务
  • 二级 Span = 每次 LLM 调用
  • 三级 Span = LLM 调用中的工具执行

OpenTelemetry 集成

越来越多的 Agent 框架开始支持 OpenTelemetry (OTel) 协议------这是可观测性领域的事实标准。使用 OTel 的好处:

  1. 统一协议 --- 同一套采集器可以同时收集 Agent Trace、HTTP 请求 Trace、数据库查询 Trace
  2. 生态丰富 --- Jaeger、Grafana Tempo、Datadog 等都支持 OTel 数据
  3. 标准化属性 --- gen_ai.systemgen_ai.request.modelgen_ai.usage.input_tokens 等已有标准定义
python 复制代码
# OpenTelemetry 集成示例
from opentelemetry import trace

tracer = trace.get_tracer("agent-service")

with tracer.start_as_current_span("agent.task") as span:
    span.set_attribute("task.description", user_message)

    with tracer.start_as_current_span("llm.call") as llm_span:
        response = await llm.chat(messages)
        llm_span.set_attribute("gen_ai.request.model", "claude-opus-4-6")
        llm_span.set_attribute("gen_ai.usage.input_tokens", response.usage.input)

    with tracer.start_as_current_span("tool.execute") as tool_span:
        tool_span.set_attribute("tool.name", "Read")
        result = await read_tool.execute(params)

指标(Metrics)

聚合的数值指标用于监控和告警:

typescript 复制代码
// 关键指标
const METRICS = {
  // 质量
  'agent.task.success_rate': Gauge,        // 任务成功率
  'agent.task.user_satisfaction': Gauge,   // 用户满意度

  // 性能
  'agent.task.duration_ms': Histogram,     // 任务耗时分布
  'agent.llm.ttft_ms': Histogram,          // 首 token 延迟
  'agent.tool.duration_ms': Histogram,     // 工具执行耗时

  // 成本
  'agent.tokens.input': Counter,           // 输入 token 总量
  'agent.tokens.output': Counter,          // 输出 token 总量
  'agent.llm.calls': Counter,              // LLM 调用次数
  'agent.tool.calls': Counter,             // 工具调用次数

  // 错误
  'agent.tool.errors': Counter,            // 工具错误次数
  'agent.llm.errors': Counter,             // LLM 错误次数
  'agent.task.timeout': Counter,           // 任务超时次数
}

19.3 实现 Trace

一个最小的 Trace 实现:

typescript 复制代码
class Trace {
  private spans: Span[] = []
  private currentSpan: Span | null = null

  startSpan(name: string, metadata?: Record<string, any>): Span {
    const span: Span = {
      id: crypto.randomUUID(),
      parentId: this.currentSpan?.id,
      name,
      startTime: Date.now(),
      metadata: metadata || {},
      children: [],
    }
    if (this.currentSpan) {
      this.currentSpan.children.push(span)
    } else {
      this.spans.push(span)
    }
    this.currentSpan = span
    return span
  }

  endSpan(result?: Record<string, any>): void {
    if (!this.currentSpan) return
    this.currentSpan.endTime = Date.now()
    this.currentSpan.duration = this.currentSpan.endTime - this.currentSpan.startTime
    if (result) Object.assign(this.currentSpan.metadata, result)
    // 回到父 span
    this.currentSpan = this.findParent(this.currentSpan.parentId)
  }

  toJSON(): object {
    return { spans: this.spans, totalDuration: this.getTotalDuration() }
  }
}

// 使用
const trace = new Trace()

trace.startSpan('agent.task', { task: userMessage })
  trace.startSpan('llm.call', { model: 'claude-opus-4-6' })
  trace.endSpan({ tokens: 5000 })

  trace.startSpan('tool.execute', { tool: 'Read' })
  trace.endSpan({ success: true })
trace.endSpan({ result: 'success' })

19.4 LangSmith / LangFuse

生产级的 Agent 可观测性平台:

LangSmith(LangChain 官方):

  • 自动捕获 LangChain/LangGraph 的每一步
  • 可视化 Trace 树
  • 支持评估和人工标注
  • 对比不同版本的 Agent 表现

LangFuse(开源替代):

  • 兼容 OpenTelemetry 协议
  • 自托管或 Cloud
  • 支持 cost tracking 和 user feedback
python 复制代码
# LangFuse 集成示例
from langfuse import Langfuse

langfuse = Langfuse()

trace = langfuse.trace(name="fix-login-bug", user_id="user-123")

# LLM 调用
generation = trace.generation(
    name="analyze-code",
    model="claude-opus-4-6",
    input=messages,
    output=response,
    usage={"input": 5000, "output": 2000},
)

# 工具调用
span = trace.span(name="read-file", input={"path": "src/auth.ts"})
span.end(output={"lines": 150, "success": True})

19.5 调试策略

重放调试

保存完整的 Trace 后,可以在本地重放 Agent 的决策过程:

typescript 复制代码
// 加载历史 Trace
const trace = await loadTrace(traceId)

// 查看每一步的输入和输出
for (const span of trace.spans) {
  console.log(`Step: ${span.name}`)
  console.log(`Input: ${JSON.stringify(span.metadata.input)}`)
  console.log(`Output: ${JSON.stringify(span.metadata.output)}`)
  console.log(`Duration: ${span.duration}ms`)
  console.log('---')
}

对比调试

同一任务运行两次,对比 Trace 差异:

bash 复制代码
Trace A (成功):                    Trace B (失败):
├── Read src/auth.ts               ├── Read src/auth.ts
├── Read src/types.ts              ├── Edit src/auth.ts  ← 没读 types.ts
├── Edit src/auth.ts               │   (修改不完整)
├── npm test (pass)                ├── npm test (fail)
└── Done                           └── 放弃

一眼看出问题:Trace B 跳过了读取 types.ts,导致修改不完整。

渐进式调试

当 Agent 行为异常时,逐步缩小问题范围:

markdown 复制代码
1. 查看 Trace 概览 → 在第 3 步出了问题
2. 查看第 3 步的 LLM 输入 → system prompt + 前两步结果
3. 查看第 3 步的 LLM 输出 → 模型选择了错误的工具
4. 分析原因 → 第 2 步的工具结果太长,关键信息被截断
5. 修复 → 改进工具结果的截断策略

19.6 告警与仪表盘

生产环境需要自动告警:

yaml 复制代码
alerts:
  - name: agent-success-rate-drop
    condition: agent.task.success_rate < 0.85
    for: 10m
    severity: warning
    message: "Agent 任务成功率低于 85%"

  - name: agent-token-spike
    condition: rate(agent.tokens.input[5m]) > 1000000
    severity: critical
    message: "Token 消耗速率异常,可能存在无限循环"

  - name: agent-error-rate
    condition: rate(agent.tool.errors[5m]) / rate(agent.tool.calls[5m]) > 0.1
    severity: warning
    message: "工具错误率超过 10%"

仪表盘应该展示:

  • 实时任务成功率和趋势
  • Token 消耗的分布(P50/P95/P99)
  • 最常调用的工具和最常失败的工具
  • 平均任务耗时和工具调用次数

19.7 本章小结

Agent 可观测性让黑盒变成灰盒:

  1. 三大支柱------日志记录事件,Trace 还原链路,指标驱动告警
  2. Trace 是核心------树状结构可视化整个决策过程
  3. 工具支持------LangSmith/LangFuse 提供生产级方案
  4. 调试策略------重放、对比、渐进式缩小范围
  5. 持续监控------告警和仪表盘确保问题被及时发现

下一章讨论 Agent 系统的成本控制与性能优化。

相关推荐
杨艺韬4 小时前
Harness Engineering-第17章 Human-in-the-Loop:人机协作设计
agent
杨艺韬4 小时前
Harness Engineering-第6章 工具编排与并发执行
agent
杨艺韬4 小时前
Harness Engineering-第21章 设计模式与架构决策
agent
杨艺韬4 小时前
Harness Engineering-第5章 Tool Design:给 Agent 造趁手的兵器
agent
杨艺韬4 小时前
vLLM内核探秘-第4章 PagedAttention:虚拟内存的启示
agent
杨艺韬4 小时前
vLLM内核探秘-第15章 多模态推理
agent
杨艺韬4 小时前
vLLM内核探秘-第1章 架构总览
agent
杨艺韬4 小时前
vLLM内核探秘-第6章 Worker 与 Executor:GPU 军团
agent
杨艺韬4 小时前
vLLM内核探秘-第11章 分块预填充与混合批处理
agent