世界上有一种技术叫 Observability,它就像给Agent装了一堆监控探头——每次调用工具、每次推理、每次出错,都被记录下来。
凌晨4点16分,我发现某个Agent的响应时间从2秒变成了20秒,一查日志才发现:它在循环调用同一个工具,因为没检测到上次已经成功。那一刻我感谢那些日志——没有它们,我可能还在瞎猜。
📚 定义
Agent Observability(Agent可观测性) 是指通过日志、追踪、指标三种数据,了解Agent内部运行状态的能力。
- Logs(日志):记录事件、错误、调试信息
- Traces(追踪):记录请求完整链路和耗时
- Metrics(指标):量化性能数据(响应时间、成功率等)
🔬 核心维度
维度1: 推理可观测 ├── LLM调用记录(模型、token消耗、成本) ├── Prompt和Response日志 └── 推理延迟和失败率 维度2: 工具调用可观测 ├── 工具名称、参数、返回值 ├── 执行时间、成功/失败 └── 错误堆栈和重试次数 维度3: 上下文可观测 ├── 上下文大小(token数) ├── 历史消息数量 └── 摘要次数和压缩率 维度4: 业务指标 ├── 任务完成率 ├── 平均响应时间 ├── 用户满意度 └── 成本(API调用费用)
🚀 OpenClaw 实战
# OpenClaw 可观测性配置
observability:
# 日志配置
logging:
level: info # debug | info | warn | error
format: json # json | text
destination: /var/log/openclaw/agent.log
rotation:
max_size: 100mb
max_files: 10
# 敏感数据脱敏
redact:
- "api_key"
- "password"
- "token"
# 追踪配置 (OpenTelemetry)
tracing:
enabled: true
exporter: otlp # console | otlp | jaeger
endpoint: "http://localhost:4317"
sample_rate: 1.0 # 采样率
# 追踪内容
record:
- llm_calls # LLM调用
- tool_calls # 工具调用
- context_changes # 上下文变化
- errors # 错误
# 指标配置 (Prometheus)
metrics:
enabled: true
endpoint: "/metrics"
port: 9090
# 自定义指标
custom_metrics:
- name: task_completion_rate
type: gauge
description: "任务完成率"
- name: token_consumption
type: counter
description: "总token消耗"
- name: tool_call_latency
type: histogram
description: "工具调用延迟"
# 告警配置
alerting:
- condition: "error_rate > 0.1"
action: "发送飞书通知"
- condition: "latency_p95 > 5000"
action: "记录警告日志"
💻 代码示例:简易可观测系统
// observability.js
export class AgentObservability {
constructor() {
this.logs = [];
this.traces = new Map();
this.metrics = {
llmCalls: 0,
toolCalls: 0,
errors: 0,
totalTokens: 0
};
}
// 记录日志
log(level, message, metadata = {}) {
const entry = {
timestamp: new Date().toISOString(),
level,
message,
...metadata
};
this.logs.push(entry);
console[level](`[${entry.timestamp}] ${message}`, metadata);
}
// 开始追踪
startTrace(name, metadata = {}) {
const traceId = `trace_${Date.now()}_${Math.random().toString(36).slice(2)}`;
this.traces.set(traceId, {
name,
startTime: Date.now(),
spans: [],
metadata
});
return traceId;
}
// 添加Span
addSpan(traceId, spanName, duration, metadata = {}) {
const trace = this.traces.get(traceId);
if (!trace) return;
trace.spans.push({
name: spanName,
duration,
...metadata
});
}
// 结束追踪
endTrace(traceId) {
const trace = this.traces.get(traceId);
if (!trace) return;
trace.totalDuration = Date.now() - trace.startTime;
this.log('info', `Trace ${trace.name} completed`, {
traceId,
duration: trace.totalDuration,
spans: trace.spans.length
});
}
// 记录指标
recordMetric(name, value) {
if (this.metrics[name] !== undefined) {
if (typeof this.metrics[name] === 'number') {
this.metrics[name] += value;
}
}
}
// 获取指标快照
getMetrics() {
return { ...this.metrics };
}
// 导出为OpenTelemetry格式
exportTraces() {
return Array.from(this.traces.values()).map(trace => ({
traceId: trace.traceId,
name: trace.name,
startTime: trace.startTime,
duration: trace.totalDuration,
spans: trace.spans
}));
}
}
// 使用示例
const obs = new AgentObservability();
// 记录LLM调用
obs.log('info', 'LLM调用开始', { model: 'claude-opus-4' });
obs.recordMetric('llmCalls', 1);
obs.recordMetric('totalTokens', 1500);
// 追踪工具调用
const traceId = obs.startTrace('web_search');
const startTime = Date.now();
// ... 执行工具调用
obs.addSpan(traceId, 'tool_execution', Date.now() - startTime);
obs.endTrace(traceId);
🎯 最佳实践
✅ 推荐做法:
- 日志分级:DEBUG用于调试,INFO用于常规,ERROR用于问题
- 敏感数据脱敏:API密钥、密码不记录明文
- 追踪采样:高流量时采样10-20%即可
- 指标聚合:用Prometheus/Grafana做可视化
⚠️ 踩坑:
- 日志爆炸:Agent每次循环都打印,一天能写几十GB
- 上下文泄露:日志里记录了完整对话历史
- 追踪开销:过多的追踪也会影响性能