Agent系统的“可观测性”攻坚战:从LLM调用到业务链路的全栈监控实践
2026年2月,LangChain团队做了一件让社区震动的事:他们的编程Agent deepagents-cli在Terminal Bench 2.0基准测试中,得分从52.8%跃升至66.5%,排名从Top 30以外冲进Top 5。整个过程中,模型没换(固定使用gpt-5.2-codex),他们只改了一样东西:Harness。而驱动这一切的,是Agent系统的“可观测性”。
引言:Agent的“真相”在运行轨迹里
Agent系统有一个与传统软件截然不同的特点:你不运行它,就不知道它会做什么。
传统软件的真相在代码里——你阅读源码就能理解其行为。Agent的真相却在运行轨迹里——当Agent执行了200步、耗时两分钟完成一个任务,而在第23步走错了方向,没有Tracing,你根本无从知晓。
Agent系统的可观测性不是“锦上添花”,而是“没有就活不下去”的基础设施。本文将系统拆解Agent可观测性的核心挑战、技术方案与生产实践,覆盖从LLM调用到业务链路的全栈监控。
一、Agent可观测性 ≠ 传统软件可观测性
1.1 本质差异:从“调试代码”到“调试推理”
传统软件出问题时,你检查错误日志、看堆栈追踪、找到失败的代码行。Agent出问题时,没有堆栈——因为没有代码失败,失败的是推理。
LangChain在概念指南中精辟地总结了这一转变:
“当Agent耗时两分钟、执行200步完成一个任务,在中间某处犯了错误时,这是一种不同类型的错误。没有堆栈追踪——因为没有代码失败。失败的是Agent的推理。”
传统可观测性回答的问题是:“哪个服务挂了?响应多慢?错误率多少?”Agent可观测性需要回答的问题完全不同:
| 服务是否存活? | Agent是否真的做了它声称做的事? |
| 响应延迟多少? | 为什么这次任务成本比平时高10倍? |
| 错误率多少? | 为什么Agent在第23步选了edit_file而不是read_file? |
| CPU/内存使用率? | Agent是在学习,还是在重复犯同样的错误? |
1.2 Agent可观测性的三大核心挑战
无论采用何种Agent形态,规模化使用后均会遭遇三大共性难题:
1. 执行过程黑盒化
Agent的执行过程涉及LLM调用、工具执行、多轮推理、记忆检索等环节。传统方案面对一轮包含10次ReAct推理的Agent任务,仅能识别出10条独立HTTP请求,无法还原分层、有序的完整决策流程。
2. 行为轨迹难追溯
Agent具备较高的自主操作权限,可读写本地文件、执行系统命令、调用第三方API。缺少专项审计能力,无法完整追溯Agent的全部操作行为,在企业安全、合规场景中存在极大风险。
3. 成本难度量
大模型Token消耗是Agent的主要成本来源,多轮迭代和工具调用会指数级放大消耗。缺少按Agent、用户、任务维度的精细化成本拆分能力,企业将无法开展预算管控与投入产出评估。
二、Tracing:从服务调用链到推理链路
2.1 Agent Tracing的独特价值
Agent Tracing记录的是每一次Think → Act → Observe的完整链路。每个Span不仅记录“调了什么”,还要记录“推理了什么”——完整的prompt、模型返回的结果、工具调用的参数和结果、每一步的耗时和token消耗。
LangChain团队正是靠Tracing数据驱动了66.5%的突破:他们在每轮实验中用LangSmith记录完整的Tracing数据,然后构建了一个Trace Analyzer Skill——从LangSmith拉取追踪数据,并行启动多个错误分析Agent各自诊断失败原因,主Agent综合所有发现提出Harness改进建议。
2.2 基于OpenTelemetry的Agent Tracing实现
以下是一个基于OpenTelemetry的Agent Tracing埋点实现:
from opentelemetry import trace
from opentelemetry.trace import SpanKind, Status, StatusCode
import json
import time
tracer = trace.get_tracer(__name__)
class AgentTracer:
"""Agent全链路追踪器"""
def trace_agent_run(self, agent_name: str, session_id: str):
"""装饰器:追踪Agent完整运行"""
def decorator(func):
def wrapper(*args, **kwargs):
# 创建根Span – Agent级别
with tracer.start_as_current_span(
f"agent.{agent_name}.run",
kind=SpanKind.CLIENT,
attributes={
"agent.name": agent_name,
"session.id": session_id,
"gen_ai.operation.name": "agent.run"
}
) as root_span:
# 在Span中记录Agent的System Prompt
root_span.set_attribute("agent.system_prompt", self._get_system_prompt(agent_name))
# 执行Agent的推理循环
result = func(*args, **kwargs)
# 记录最终结果
root_span.set_attribute("agent.result_summary", result.get("summary", ""))
root_span.set_status(Status(StatusCode.OK))
return result
return wrapper
return decorator
def trace_llm_call(self, model: str, messages: list, response: dict):
"""追踪LLM调用"""
with tracer.start_as_current_span(
"llm.call",
kind=SpanKind.CLIENT,
attributes={
"gen_ai.request.model": model,
"gen_ai.request.max_tokens": response.get("usage", {}).get("completion_tokens", 0),
"gen_ai.usage.input_tokens": response.get("usage", {}).get("prompt_tokens", 0),
"gen_ai.usage.output_tokens": response.get("usage", {}).get("completion_tokens", 0),
"gen_ai.operation.name": "chat"
}
) as span:
# 记录输入消息(脱敏)
span.set_attribute("gen_ai.request.messages", self._safe_serialize(messages))
# 记录输出
span.set_attribute("gen_ai.response.content", response.get("content", ""))
return response
def trace_tool_call(self, tool_name: str, arguments: dict, result: any, duration_ms: float):
"""追踪工具调用"""
with tracer.start_as_current_span(
f"tool.{tool_name}",
kind=SpanKind.CLIENT,
attributes={
"tool.name": tool_name,
"tool.arguments": json.dumps(arguments, ensure_ascii=False),
"tool.duration_ms": duration_ms,
"tool.success": result is not None
}
) as span:
span.set_attribute("tool.result", self._safe_truncate(str(result), 1000))
return result
2.3 Trace Analyzer:从数据到改进
LangChain的Trace Analyzer Skill是对Tracing数据价值的最好诠释:
class TraceAnalyzer:
"""从Tracing数据中诊断Agent失败原因"""
def __init__(self, trace_data: list):
self.traces = trace_data
def analyze_failures(self):
"""并行分析失败模式"""
failures = []
for trace in self.traces:
if trace.get("status") != "success":
failure = self._diagnose_single_trace(trace)
failures.append(failure)
# 聚合失败模式
return self._aggregate_patterns(failures)
def _diagnose_single_trace(self, trace):
"""诊断单条Trace的失败原因"""
# 检查:是否在第N步选错了工具?
# 检查:是否某次工具调用返回了错误?
# 检查:是否上下文溢出导致模型遗忘关键信息?
# 检查:是否成本超标?
patterns = []
for step in trace.get("steps", []):
if step.get("tool_choice") != step.get("expected_tool"):
patterns.append({
"type": "wrong_tool_selection",
"step": step.get("step_index"),
"selected": step.get("tool_choice"),
"expected": step.get("expected_tool")
})
return {"trace_id": trace.get("id"), "patterns": patterns}
三、零侵入观测:让Agent“自带”可观测性
3.1 为什么零侵入至关重要
传统可观测性方案需要在代码中埋点,但在Agent场景中面临巨大挑战:每个Provider的SDK完全不同,开发者要为每一个编写tracing wrapper;GenAI语义约定还在快速演进;多语言适配是乘法问题。
更根本的问题是:很多AI Agent应用压根不用官方SDK,直接用通用HTTP客户端拼接JSON请求体调用大模型API——所有基于SDK monkey-patch的方案全部失效。
3.2 OBI:将观测下沉到内核
OBI(OpenTelemetry eBPF Instrumentation)的做法是把观测能力下沉到Linux内核——不改一行业务代码,自动识别并解析所有AI相关网络调用,把关键证据完整记录进OpenTelemetry标准的trace和metrics。
#mermaid-svg-g3XkEKdpBWlwVfSP{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-g3XkEKdpBWlwVfSP .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-g3XkEKdpBWlwVfSP .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-g3XkEKdpBWlwVfSP .error-icon{fill:#552222;}#mermaid-svg-g3XkEKdpBWlwVfSP .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-g3XkEKdpBWlwVfSP .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-g3XkEKdpBWlwVfSP .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-g3XkEKdpBWlwVfSP .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-g3XkEKdpBWlwVfSP .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-g3XkEKdpBWlwVfSP .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-g3XkEKdpBWlwVfSP .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-g3XkEKdpBWlwVfSP .marker{fill:#333333;stroke:#333333;}#mermaid-svg-g3XkEKdpBWlwVfSP .marker.cross{stroke:#333333;}#mermaid-svg-g3XkEKdpBWlwVfSP svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-g3XkEKdpBWlwVfSP p{margin:0;}#mermaid-svg-g3XkEKdpBWlwVfSP .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-g3XkEKdpBWlwVfSP .cluster-label text{fill:#333;}#mermaid-svg-g3XkEKdpBWlwVfSP .cluster-label span{color:#333;}#mermaid-svg-g3XkEKdpBWlwVfSP .cluster-label span p{background-color:transparent;}#mermaid-svg-g3XkEKdpBWlwVfSP .label text,#mermaid-svg-g3XkEKdpBWlwVfSP span{fill:#333;color:#333;}#mermaid-svg-g3XkEKdpBWlwVfSP .node rect,#mermaid-svg-g3XkEKdpBWlwVfSP .node circle,#mermaid-svg-g3XkEKdpBWlwVfSP .node ellipse,#mermaid-svg-g3XkEKdpBWlwVfSP .node polygon,#mermaid-svg-g3XkEKdpBWlwVfSP .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-g3XkEKdpBWlwVfSP .rough-node .label text,#mermaid-svg-g3XkEKdpBWlwVfSP .node .label text,#mermaid-svg-g3XkEKdpBWlwVfSP .image-shape .label,#mermaid-svg-g3XkEKdpBWlwVfSP .icon-shape .label{text-anchor:middle;}#mermaid-svg-g3XkEKdpBWlwVfSP .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-g3XkEKdpBWlwVfSP .rough-node .label,#mermaid-svg-g3XkEKdpBWlwVfSP .node .label,#mermaid-svg-g3XkEKdpBWlwVfSP .image-shape .label,#mermaid-svg-g3XkEKdpBWlwVfSP .icon-shape .label{text-align:center;}#mermaid-svg-g3XkEKdpBWlwVfSP .node.clickable{cursor:pointer;}#mermaid-svg-g3XkEKdpBWlwVfSP .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-g3XkEKdpBWlwVfSP .arrowheadPath{fill:#333333;}#mermaid-svg-g3XkEKdpBWlwVfSP .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-g3XkEKdpBWlwVfSP .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-g3XkEKdpBWlwVfSP .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-g3XkEKdpBWlwVfSP .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-g3XkEKdpBWlwVfSP .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-g3XkEKdpBWlwVfSP .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-g3XkEKdpBWlwVfSP .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-g3XkEKdpBWlwVfSP .cluster text{fill:#333;}#mermaid-svg-g3XkEKdpBWlwVfSP .cluster span{color:#333;}#mermaid-svg-g3XkEKdpBWlwVfSP div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-g3XkEKdpBWlwVfSP .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-g3XkEKdpBWlwVfSP rect.text{fill:none;stroke-width:0;}#mermaid-svg-g3XkEKdpBWlwVfSP .icon-shape,#mermaid-svg-g3XkEKdpBWlwVfSP .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-g3XkEKdpBWlwVfSP .icon-shape p,#mermaid-svg-g3XkEKdpBWlwVfSP .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-g3XkEKdpBWlwVfSP .icon-shape .label rect,#mermaid-svg-g3XkEKdpBWlwVfSP .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-g3XkEKdpBWlwVfSP .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-g3XkEKdpBWlwVfSP .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-g3XkEKdpBWlwVfSP :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}
Agent应用
HTTP请求
Linux内核
OBI探针
OTel Collector
观测后端
解析请求
提取模型名
提取Token
提取tool_calls
提取SSE事件
3.3 零侵入的代码实践
# 传统方式:需要手动为每个Provider埋点
# from opentelemetry.instrumentation.openai import OpenAIInstrumentor
# OpenAIInstrumentor().instrument()
# OBI方式:无需任何代码改动
# 只需设置环境变量,启动时自动注入
# export OBI_ENABLED=true
# export OBI_OTEL_ENDPOINT=http://collector:4318
# python my_agent.py
# 所有LLM调用(无论用哪个SDK,甚至用requests直接调用)
# 都会被自动捕获并生成符合GenAI语义约定的OTel Span
四、成本可观测:从“猜”到“算”
4.1 Token成本的三维归因
Agent系统需要建立按Agent、用户、任务维度的精细化成本拆分能力。以下是一个成本归因实现:
class CostTracker:
"""Agent成本追踪器"""
def track_cost(self, agent_name: str, session_id: str, user_id: str, llm_usage: dict):
"""按Agent、用户、会话归因Token成本"""
cost = {
"agent_name": agent_name,
"session_id": session_id,
"user_id": user_id,
"prompt_tokens": llm_usage.get("prompt_tokens", 0),
"completion_tokens": llm_usage.get("completion_tokens", 0),
"total_tokens": llm_usage.get("total_tokens", 0),
"estimated_cost_usd": self._calculate_cost(
llm_usage.get("prompt_tokens", 0),
llm_usage.get("completion_tokens", 0),
llm_usage.get("model", "unknown")
),
"timestamp": time.time()
}
self._emit_cost_metric(cost)
return cost
def get_cost_summary(self, agent_name: str, time_range: tuple):
"""按Agent维度的成本汇总"""
# 返回该Agent在指定时间内的总Token消耗和成本
pass
五、生产实践:开箱即用的可观测性
5.1 三层可观测性架构
EdgeOne Makers等平台提供了内置的三层可观测性能力:
- 日志(Logging):所有Agent调用自动记录,包含请求、响应、工具调用、模型输出
- 指标(Metrics):实时性能指标,包括延迟、吞吐量、错误率、Token消耗
- 链路追踪(Tracing):完整的Agent调用链路,从用户请求到每个工具执行
5.2 Agent可观测的实施路径
基于LangChain的实践和阿里云的方案,实施路径可归纳为:
六、总结
Agent系统的可观测性正经历一场从“可选”到“必须”的转变。当LangChain团队用Tracing数据推动66.5%的性能突破,当阿里云用LoongCollector实现Agent全链路可观测,当OBI将观测能力下沉到内核实现零侵入采集——这些实践共同指向一个结论:
模型是黑盒,但Tracing把黑盒的输入输出变成了可分析的数据。没有Tracing数据,Agent的迭代优化就无从谈起。
Agent可观测性的核心价值,不在于“看到更多数据”,而在于“能回答更复杂的问题”——从“服务是否挂了”到“Agent是否真的做了它该做的事”,从“响应有多快”到“成本为什么超标”,从“哪个接口报错了”到“哪一步推理走偏了”。这正是从“系统可用”走向“结果可信、行为可控”的关键一步。
网硕互联帮助中心






评论前必须登录!
注册