云计算百科
云计算领域专业知识百科平台

Agent系统的“可观测性”攻坚战:从LLM调用到业务链路的全栈监控实践

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需要回答的问题
服务是否存活? 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的实践和阿里云的方案,实施路径可归纳为:

  • 先解决“能看到”:建立基础的Tracing,记录每次LLM调用和工具调用
  • 再解决“能分析”:构建Trace Analyzer,从数据中诊断失败模式
  • 再解决“能优化”:基于分析结果迭代Harness,提升Agent质量
  • 最后解决“能治理”:建立成本归因、权限审计、安全合规体系
  • 六、总结

    Agent系统的可观测性正经历一场从“可选”到“必须”的转变。当LangChain团队用Tracing数据推动66.5%的性能突破,当阿里云用LoongCollector实现Agent全链路可观测,当OBI将观测能力下沉到内核实现零侵入采集——这些实践共同指向一个结论:

    模型是黑盒,但Tracing把黑盒的输入输出变成了可分析的数据。没有Tracing数据,Agent的迭代优化就无从谈起。

    Agent可观测性的核心价值,不在于“看到更多数据”,而在于“能回答更复杂的问题”——从“服务是否挂了”到“Agent是否真的做了它该做的事”,从“响应有多快”到“成本为什么超标”,从“哪个接口报错了”到“哪一步推理走偏了”。这正是从“系统可用”走向“结果可信、行为可控”的关键一步。

    赞(0)
    未经允许不得转载:网硕互联帮助中心 » Agent系统的“可观测性”攻坚战:从LLM调用到业务链路的全栈监控实践
    分享到: 更多 (0)

    评论 抢沙发

    评论前必须登录!