Agent 上线后最怕的不是 prompt 不好,是你根本不知道它在干什么。线上跑了一周,用户投诉回答质量下降,你打开日志一看--全是 print("done")。哪一步慢了?哪次工具调用失败了?哪个用户的回答花了 $0.3?一概不知。没有可观测性的 Agent 就是黑盒里的黑盒:LLM 本身是黑盒,Agent 的多步工具调用是黑盒上叠黑盒,出问题时你只能猜。
可观测性不是"加日志"这么简单,它是一套系统工程:先定好要观测哪些指标(延迟、token 成本、工具成功率、错误率),再用链路追踪把每一步串起来,然后做线上评测(LLM-as-judge + 用户反馈),最后设告警自动发现问题。这篇 SOP 走一遍完整流程,每步附可复制的 Python 代码。工具选型以开源为主:Langfuse(开源 LLM 可观测性平台)、Arize Phoenix(开源 tracing + 评测)、OpenTelemetry(通用可观测性标准),LangSmith 作为 LangChain 生态的 SaaS 选项一并提及。和本站《LangGraph 生产级 Agent 搭建 SOP》(langgraph-production-agent-sop) 配套使用--那篇讲怎么搭 Agent,这篇讲怎么监控它。
一、量化基线:要观测什么
写任何 tracing 代码之前,先定义清楚要观测什么。Agent 可观测性的四个核心指标:
- 延迟(latency):端到端响应时间,按 p50/p95/p99 分位数统计。平均值会掩盖尾部延迟--p99 高意味着部分用户等很久。
- token 成本(cost):每次调用的 input/output token 数和对应费用。Agent 多步调用容易成本失控。
- 工具调用成功率(tool-call success rate):每次工具调用的成功/失败比例。工具失败是 Agent 翻车的高频原因。
- 错误率(error rate):整个 Agent run 失败的比例。区分可重试错误(API 5xx)和不可重试错误(代码 bug)。
这四个指标构成了一个完整的"健康度仪表盘":延迟反映用户体验,成本反映可持续性,工具成功率反映外部依赖稳定性,错误率反映系统可靠性。少任何一个都有盲区--只看延迟不看错误率,可能速度快是因为失败请求直接返回了;只看成本不看工具成功率,可能省了 token 但工具一直失败在空转。生产环境必须四个一起看,交叉对比才能发现真问题。
把这个四指标体系用代码定义出来:
from dataclasses import dataclass, field
@dataclass
class AgentMetrics:
latencies: list = field(default_factory=list)
token_costs: list = field(default_factory=list)
tool_success: int = 0
tool_total: int = 0
errors: int = 0
total_runs: int = 0
def record_latency(self, seconds: float) -> None:
self.latencies.append(seconds)
def record_cost(self, cost_usd: float) -> None:
self.token_costs.append(cost_usd)
def record_tool_call(self, success: bool) -> None:
self.tool_total += 1
if success:
self.tool_success += 1
def record_run(self, error: bool = False) -> None:
self.total_runs += 1
if error:
self.errors += 1
@staticmethod
def percentile(values: list, p: float) -> float:
if not values:
return 0.0
sorted_vals = sorted(values)
idx = min(int(len(sorted_vals) * p / 100), len(sorted_vals) - 1)
return sorted_vals[idx]
def summary(self) -> dict:
return {
"latency_p50": self.percentile(self.latencies, 50),
"latency_p95": self.percentile(self.latencies, 95),
"latency_p99": self.percentile(self.latencies, 99),
"cost_avg": sum(self.token_costs) / len(self.token_costs)
if self.token_costs else 0,
"tool_success_rate": self.tool_success / self.tool_total
if self.tool_total else 0,
"error_rate": self.errors / self.total_runs
if self.total_runs else 0,
}这个 AgentMetrics 是所有后续步骤的基础--tracing 采集数据,metrics 聚合数据,评测和告警消费数据。
二、链路追踪:OpenTelemetry + Langfuse/Phoenix
指标告诉你"出了什么问题",tracing 告诉你"在哪一步出的"。Agent 一次 run 可能调 3-5 次工具、2-3 次 LLM,没有 trace 你无法定位是哪一步慢了或错了。
Langfuse:@observe 装饰器
Langfuse 的 @observe() 装饰器是最轻量的 tracing 方式--给函数加一行注解,自动捕获输入、输出、耗时和异常:
from langfuse import observe
@observe(name="agent_run")
def run_agent(query: str) -> str:
research = search_tool(query)
answer = draft_answer(research)
return answer
@observe(name="search_tool")
def search_tool(query: str) -> str:
# 替换为你的搜索 API 调用
return f"搜索结果:{query}"
@observe(name="draft_answer")
def draft_answer(context: str) -> str:
# 替换为你的 LLM 调用
return f"基于 {context} 的回答"外层 run_agent 自动成为一个 trace,内层 search_tool 和 draft_answer 成为它的子 span。在 Langfuse UI 里能看到完整调用树--每一步的输入输出、耗时、是否出错。
Phoenix:register + OpenTelemetry spans
Phoenix 基于 OpenTelemetry,用 register() 一行初始化 tracer:
from phoenix.otel import register
from opentelemetry import trace
# 需先 pip install arize-phoenix-otel
tracer_provider = register(
project_name="my-agent",
endpoint="http://localhost:6006/v1/traces",
auto_instrument=True,
)
tracer = trace.get_tracer("agent")
def run_agent_phoenix(query: str) -> str:
with tracer.start_as_current_span("agent_run") as span:
span.set_attribute("input.value", query)
result = f"回答:{query}"
span.set_attribute("output.value", result)
return resultauto_instrument=True 会自动给 OpenAI、LangChain 等常用库加 tracing。手动 span 用 start_as_current_span,通过 set_attribute 记录输入输出。Phoenix 遵循 OpenInference 语义约定(input.value、output.value 等属性名),详见 OpenInference 规范。
Langfuse 和 Phoenix 的核心区别:Langfuse 更偏"全生命周期管理"--tracing + prompt 管理 + 评测 + 用户反馈一体化,自托管简单(Docker 一键起);Phoenix 更偏"分析和评测"--强在 trace 分析视图、漂移检测和 embedding 可视化,适合需要深度下钻的场景。如果你用 LangChain/LangGraph 生态,LangSmith 是 SaaS 里集成最深的,但数据要出你的网络。三者不互斥:Langfuse 做主 tracing 后端,Phoenix 做评测分析,是常见组合。
@observe的参数(as_type、name)和register()的返回值在不同 SDK 版本可能有差异,以官方文档为准。
三、token 与成本追踪
Agent 多步调用 LLM,成本很容易失控--一次 run 调 5 次 LLM,每次 2000 token,跑 1000 次就是 1000 万 token。不追踪成本,月底账单会吓你一跳。
先定义定价表和成本计算函数:
# 定价表(USD per 1K tokens),按你的模型更新
PRICING = {
"gpt-4o": {"input": 0.0025, "output": 0.01},
"gpt-4o-mini": {"input": 0.00015, "output": 0.0006},
}
def compute_cost(model: str, input_tokens: int, output_tokens: int) -> float:
rates = PRICING.get(model)
if rates is None:
return 0.0
return (input_tokens * rates["input"]
+ output_tokens * rates["output"]) / 1000然后给 LLM 调用加 tracing,记录 token 用量。Langfuse 在 as_type="generation" 时会解析 usage 字段:
from langfuse import observe
@observe(name="llm_call", as_type="generation")
def call_llm(model: str, messages: list) -> dict:
# 替换为你的 LLM SDK 调用(如 openai.chat.completions.create)
response = {
"content": "模型回复",
"usage": {"prompt_tokens": 500, "completion_tokens": 200},
}
usage = response["usage"]
cost = compute_cost(model, usage["prompt_tokens"],
usage["completion_tokens"])
return {
"content": response["content"],
"cost_usd": cost,
"input_tokens": usage["prompt_tokens"],
"output_tokens": usage["completion_tokens"],
}更深入的成本优化策略(前缀缓存、模型路由、batch API)见本站《大模型 API 成本优化 SOP》(llm-api-cost-optimization-sop)。这里补充一个生产实践:成本追踪要按"每次 Agent run"聚合,而不是只看单次 LLM 调用。一次 run 可能调 5 次 LLM,单次每次都便宜,但加起来可能 $0.15--跑 10000 次就是 $1500。在 trace 里记录 cost_usd 字段后,在 Langfuse dashboard 按 trace 聚合,看 p50/p95 的单次 run 成本,这才是用户视角的真实成本。
四、工具调用与 artifact 日志
Agent 的工具调用是出问题的高发区--搜索 API 返回空、代码执行报错、数据库查询超时。每个工具调用的输入输出都应该作为 artifact 记录下来,方便事后排查。
import json
from langfuse import observe
@observe(name="web_search_tool")
def web_search(query: str) -> dict:
# 替换为实际搜索 API 调用
raw_result = {
"title": "示例结果",
"snippet": "相关内容摘要",
"url": "https://example.com",
}
return {
"result": raw_result["snippet"],
# artifact 存原始返回,方便排查
"artifact": json.dumps(raw_result, ensure_ascii=False),
}
@observe(name="code_executor_tool")
def execute_code(code: str) -> dict:
# 替换为沙箱化代码执行
return {
"stdout": "执行输出",
"exit_code": 0,
"artifact": code, # 存执行的代码
}@observe 自动把函数返回值记录到 trace 里。如果返回值包含 artifact 字段,在 Langfuse UI 里能看到完整的工具原始输出。排查"为什么这次搜索没找到结果"时,直接看 artifact 就行。
五、线上评测:LLM-as-judge + 用户反馈
离线评测在上线前跑固定测试集(见本站《AI agent 评测与基准测试 SOP》(ai-agent-evaluation-sop)),线上评测在真实流量上持续跑。两种方式互补:离线评测挡回归,线上评测发现真实世界的漂移和边缘 case。线上评测的关键设计是采样策略--不是每条 trace 都跑 judge(成本太高),而是按规则采样:100% 采错误 trace(必看),10% 采正常 trace(随机抽样),100% 采用户点踩的 trace(最真实的负反馈信号)。这样既控制了 judge 成本,又覆盖了最需要关注的 case。
LLM-as-judge:自动打分
用一个 LLM 给另一个 LLM 的输出打分,覆盖线上 trace 的一个采样(如 10%):
from langfuse import observe
@observe(name="llm_judge")
def evaluate_answer(query: str, answer: str) -> float:
judge_prompt = (
"请给以下回答打分(1-5 分),评估准确性和有用性。\n"
f"问题:{query}\n"
f"回答:{answer}\n"
"只回复一个 1-5 的数字。"
)
# 替换为你的 judge LLM 调用
# 建议 judge 用比生产更强的模型,避免自我偏好偏差
score_text = "4" # judge_response 解析后
return float(score_text)用户反馈:thumbs up/down
显式用户反馈是最真实的质量信号。用 Langfuse 的 create_score API 把反馈关联到 trace:
from langfuse import get_client
def submit_user_feedback(trace_id: str, thumbs_up: bool) -> None:
langfuse = get_client()
langfuse.create_score(
trace_id=trace_id,
name="user_feedback",
value=1 if thumbs_up else 0,
data_type="NUMERIC",
comment="用户点赞/点踩",
)前端收集到 thumbs up/down 后调这个函数,把分数写回 Langfuse。在 dashboard 里按 user_feedback 分数筛选 trace,对比"被踩的"和"被赞的"回答特征差异,定位质量瓶颈。
create_score的参数签名在不同 SDK 版本可能有差异,以官方文档为准。
六、告警与迭代:阈值与仪表盘
有了数据还要能自动发现问题。定义告警规则,当指标越过阈值时触发通知:
from dataclasses import dataclass
@dataclass
class AlertRule:
metric: str
threshold: float
comparator: str # "gt"(大于触发)或 "lt"(小于触发)
def check(self, value: float) -> bool:
if self.comparator == "gt":
return value > self.threshold
return value < self.threshold
RULES = [
AlertRule("latency_p95", 10.0, "gt"), # p95 延迟 > 10s
AlertRule("error_rate", 0.05, "gt"), # 错误率 > 5%
AlertRule("tool_success_rate", 0.90, "lt"), # 工具成功率 < 90%
AlertRule("cost_avg", 0.50, "gt"), # 平均成本 > $0.50/run
]
def check_alerts(snapshot: dict) -> list:
"""检查指标快照,返回触发的告警列表。"""
triggered = []
for rule in RULES:
value = snapshot.get(rule.metric, 0.0)
if rule.check(value):
triggered.append(
f"ALERT: {rule.metric}={value:.4f} "
f"({rule.comparator} {rule.threshold})"
)
return triggeredsnapshot 就是前面 AgentMetrics.summary() 的返回值。生产环境把这个检查放到定时任务里(每 5 分钟跑一次),触发告警后发 Slack/钉钉/邮件。Langfuse 和 Phoenix 都内置了 dashboard,可以直接在 UI 里看 p95 延迟、错误率、成本趋势等图表,不用自己搭 Grafana。告警迭代的核心循环是:设阈值 -> 触发告警 -> 人肉排查 -> 根因修复 -> 调整阈值。前两周阈值会频繁调整,趋于稳定后改为月度 review 一次。
七、踩坑记录
坑一:只看平均值不看分位数。 平均延迟 2 秒看起来没问题,但 p99 可能是 30 秒--每 100 个用户就有 1 个等半分钟。平均值会被大量快请求拉低,掩盖尾部问题。生产环境必须看 p95 和 p99。
坑二:tracing 开了但没传 usage。 Langfuse/Phoenix 的自动捕获依赖 SDK 集成。如果你直接用 requests.post 调 LLM API 而不是用官方 SDK,token 用量不会被自动记录。要么换用 instrumented 的 SDK,要么手动在 trace 里设置 token 属性。
坑三:LLM-as-judge 不校准就上线。 Judge 模型有自己的偏好--可能偏向长回答、偏向某种格式。不拿人工标注的样本校准,你可能在优化"judge 喜欢"而不是"用户喜欢"。先跑 50-100 条人工标注,算 judge 和人工的一致率(如 Cohen's kappa),低于 0.6 不能上线。
坑四:artifact 日志太大撑爆存储。 搜索结果、网页 HTML、API 返回的 JSON 可能几十 KB 甚至几 MB。全量记录 artifact 会让 trace 存储成本飙升。对超过 4KB 的 artifact 截断,只保留前 2000 字符加省略号。
坑五:告警阈值拍脑袋设。 没有基线数据就设阈值,要么告警风暴(阈值太低)要么漏报(阈值太高)。先跑 1-2 周收集数据,看指标的 p95 在哪,然后设在 p95 的 1.5-2 倍处。
坑六:Langfuse 和 Phoenix 同时开互相干扰。 两者都基于 OpenTelemetry,如果同时注册 tracer provider 且不隔离,span 可能被重复导出或路由到错误后端。选一个作为主 tracing 后端,另一个用单独的 OTEL_EXPORTER_OTLP_ENDPOINT 配置,或通过 resource attribute 区分。
FAQ
Q1:Langfuse、Phoenix、LangSmith 怎么选? A:Langfuse 是开源 LLM 可观测性平台,可自托管,适合要求数据内网的团队。Phoenix(Arize)也是开源的,强项在评测和漂移检测,适合评测密集型场景。LangSmith 是 LangChain 的 SaaS,与 LangChain/LangGraph 集成最深但有厂商锁定。自托管加框架无关选 Langfuse;评测为主选 Phoenix;纯 LangChain 生态选 LangSmith。配合本站《LangGraph 生产级 Agent 搭建 SOP》(langgraph-production-agent-sop) 使用效果最佳。
Q2:@observe 装饰器影响性能吗?
A:开销很小--每个 span 几毫秒的序列化和异步批量上传。Langfuse v4 用异步 batch 上传,trace 不阻塞主循环。但高吞吐场景(1000+ calls/sec)需调 batch 参数(flush_at、flush_interval),否则内存堆积。
Q3:LLM-as-judge 用哪个模型? A:建议 judge 用比生产模型更强的模型(如生产用 GPT-4o-mini,judge 用 GPT-4o),避免自我偏好偏差。成本敏感时对线上 trace 采样 10% 跑 judge,而不是全量。更多评测方法见本站《AI agent 评测与基准测试 SOP》(ai-agent-evaluation-sop)。
Q4:用户反馈(thumbs)数据量太少怎么办? A:显式反馈天然稀疏--大多数用户不会点赞或点踩。补充隐式信号:回答被复制(满意)、用户重新问同一问题(不满意)、会话长度突变。隐式信号权重低于显式反馈,但能覆盖更多 case。
Q5:线上评测和离线评测什么关系? A:离线评测在部署前跑固定测试集,是上线门禁(挡回归)。线上评测在真实流量上持续跑,是运行时监控(发现漂移和边缘 case)。两者互补:离线测的是"已知场景",线上测的是"真实世界"。缺了离线评测回归没人挡,缺了线上评测真实问题没人发现。成本优化的预算分配见本站《大模型 API 成本优化 SOP》(llm-api-cost-optimization-sop)。
看法
可观测性的核心矛盾是:你恨不得记录一切,但记录一切的成本(存储、性能、噪音)会拖垮系统。好的可观测性不是"全量记录",而是"分层记录"--trace 记全链路但采样 10%,metrics 记聚合值但全量,artifact 记摘要但不存原始大对象。Langfuse 和 Phoenix 的价值不只是工具本身,而是它们定义了一套 LLM 可观测性的语义约定(什么该记、怎么记、记到什么粒度),让你不用从零设计 schema。最后一句:可观测性是 Agent 能上生产的必要条件,不是可选功能。一个你看不见的系统,你不敢让它自动决策。
参考来源
- Langfuse 官方文档 - @observe 装饰器与 Instrumentation
- Langfuse 官方文档 - LLM-as-a-Judge 评测
- Langfuse 官方文档 - Scores(评分 API)
- Arize Phoenix GitHub 仓库 - arize-ai/phoenix
- Phoenix 官方文档 - Tracing Setup(register)
- Phoenix 官方文档 - arize-phoenix-otel SDK
- OpenTelemetry 官方文档 - Python SDK
- OpenInference 语义约定规范 - Arize-ai/openinference
- LangSmith 官方文档