一、引言:Agent最大的风险是"你不知道它在干什么"
我参与构建过多个面向行业的Agent应用------财务对账、合规审查、能源异常分析。这些系统有一个共同点:上线初期运行得"挺好",直到某天业务方拿着一个错误结果来质问,而我们在日志里什么都找不到。
传统应用的日志里,一行请求就有明确的调用栈:哪个接口、哪个参数、哪个返回值、哪次异常。但Agent完全不同------一次任务可能包含数十次LLM调用、十几次工具调用、若干次重试和计划修订,每一步的"推理"是模型内部的概率过程,输出受Prompt、上下文窗口、随机性影响,同样的输入可能得到完全不同的执行路径。它本质是一个黑盒里的随机游走,而传统观测手段是为确定性调用链设计的。
这就是行业Agent对可观测性提出更高要求的根本原因。本文记录我在实践中沉淀的一套方案:用Trace追踪每一次推理与动作,用Metrics量化系统的健康度与成本,用Feedback闭环让结果质量可度量、可回灌,三者共同构成一条"看得见、可度量、能改进"的完整链路。
二、行业Agent的观测需求与传统方案的差距
先明确问题。我把行业Agent的可观测性需求归纳为四类,而传统监控(APM、日志平台)在这四类上都有明显缺口:
| 观测需求 | 传统方案 | 缺口 |
|---|---|---|
| 链路追踪 | 按HTTP调用链追踪 | Agent内部是LLM调用+工具调用+重试的复合图,不是简单请求链 |
| 性能监控 | 接口延迟、错误率 | 缺Token消耗、上下文长度、工具调用耗时等Agent特有指标 |
| 质量评估 | 靠断言与告警 | 输出"正确性"无法自动判定,需结合业务校验与用户反馈 |
| 成本归因 | 按服务/接口 | 一次任务跨多个模型、多个工具,成本需按任务维度归集 |
最关键的差距在最后两项:传统可观测性回答"系统坏没坏",而行业Agent还要回答"结果对不对、贵不贵"。这决定了Agent可观测性的设计必须从"监控请求"升级为"度量一次智能决策的全过程"。
三、Trace层:一次任务的完整"旅行记录"
Trace解决"看得见"的问题:一次Agent任务从收到请求到返回结果,期间发生的每一次LLM调用、工具调用、重试、计划修订,都要有迹可循,且能按任务关联成一颗树。
3.1 Span模型设计
我定义Agent特有的Span类型,而不是复用HTTP Span了事:
python
from dataclasses import dataclass, field
from datetime import datetime, timezone
from typing import Any, Optional
@dataclass
class AgentSpan:
span_id: str
trace_id: str
parent_id: Optional[str]
kind: str # llm_call | tool_call | plan_revise | retry | guard_check
name: str # 如 "llm:gpt-4o", "tool:query_balance"
start_ts: datetime = field(default_factory=lambda: datetime.now(timezone.utc))
end_ts: Optional[datetime] = None
status: str = "running" # running/ok/error/timeout
attributes: dict = field(default_factory=dict)
def finish(self, status: str, **attrs):
self.end_ts = datetime.now(timezone.utc)
self.status = status
self.attributes.update(attrs)
emit(self) # 异步上报到Trace后端
@property
def duration_ms(self) -> float:
if not self.end_ts:
return 0.0
return (self.end_ts - self.start_ts).total_seconds() * 1000
每个Span的attributes按kind承载关键信息:LLM调用 记录模型名、输入输出Token数、温度、Prompt前若干字符(注意脱敏,见后文);工具调用 记录工具名、参数摘要、返回值摘要、错误码;计划修订记录修订前后计划差异、修订原因。
3.2 上下文传播:Tracer贯穿整个执行
为了让所有Span挂在同一棵树上,需要一个上下文感知的Tracer------Agent执行到哪里,当前Span就自动成为新Span的父节点:
python
import contextvars
class Tracer:
_current = contextvars.ContextVar("agent_span", default=None)
@classmethod
def start(cls, kind: str, name: str, **attrs) -> AgentSpan:
parent = cls._current.get()
span = AgentSpan(
span_id=new_id(),
trace_id=parent.trace_id if parent else new_id(),
parent_id=parent.span_id if parent else None,
kind=kind, name=name, attributes=attrs,
)
cls._current.set(span)
return span
@classmethod
def end(cls, span: AgentSpan, status: str = "ok", **attrs):
span.finish(status, **attrs)
# 恢复父Span为当前上下文,保证并发/嵌套正确
parent = find_span(span.parent_id)
cls._current.set(parent)
# 用法:包装LLM与工具调用
def traced_llm_call(messages, **kwargs):
span = Tracer.start("llm_call", "llm:gpt-4o",
model=kwargs.get("model"),
prompt_chars=len(json.dumps(messages)))
try:
resp = llm_chat(messages, **kwargs)
Tracer.end(span, "ok",
in_tokens=resp.usage.prompt_tokens,
out_tokens=resp.usage.completion_tokens)
return resp
except Exception as e:
Tracer.end(span, "error", error=str(e))
raise
用contextvars而非全局变量,是为了保证并发执行多个子任务(能源分析里并行跑多个台区)时,每个执行流各自的Span树互不串扰------这是Agent并行化后最容易踩的坑:并发一开,Span全串到同一棵树上。
3.3 结构化日志与语义事件
Span是"链路骨架",但链路里的"肉"是推理过程。Thought(推理)、Action(动作)、Observation(观察)这些ReAct循环中的中间产物,必须以结构化事件落盘。我把它们设计成与Span绑定的语义事件:
python
@dataclass
class AgentEvent:
trace_id: str
span_id: str
seq: int # 事件序号,还原执行顺序
event_type: str # thought | action | observation | guard | feedback
content: dict # 结构化内容,禁止自由文本
def log_event(trace_id: str, span_id: str, event_type: str, **content):
event = AgentEvent(trace_id=trace_id, span_id=span_id,
seq=next_seq(trace_id), event_type=event_type,
content=content)
append_events(trace_id, event) # JSON Lines 落盘/上送
三个纪律:内容必须结构化(自由文本没法检索与统计)、必须脱敏(见后文)、必须带序号(Agent执行顺序对还原推理至关重要)。
四、Metrics层:系统的"仪表盘"与"温度计"
Trace回答"这一单发生了什么",Metrics回答"整体健康吗"。行业Agent的Metrics不是简单照搬RED(请求率、错误率、延迟),而是围绕任务生命周期重新定义。
4.1 核心指标集
我按四类维护指标,全部按任务维度打点:
python
class AgentMetrics:
def __init__(self, registry):
self.registry = registry # Prometheus等注册表
def observe_task(self, task_id: str, result: dict, meta: dict):
# 1. 流量与成功率
self.registry.counter("agent_tasks_total",
labels={"agent": meta["agent"]}).inc()
ok = 1 if result.get("ok") else 0
self.registry.counter("agent_tasks_success",
labels={"agent": meta["agent"]}).inc(ok)
# 2. 延迟分布(含"首Token延迟"与"全任务延迟"两个维度)
self.registry.histogram("agent_task_duration_seconds",
labels={"agent": meta["agent"]}).observe(
meta["duration_s"])
# 3. Token与成本(按模型维度归因)
for model, usage in meta["token_usage"].items():
self.registry.counter("agent_tokens_total",
labels={"agent": meta["agent"], "model": model}).inc(usage)
self.registry.counter("agent_cost_usd_total",
labels={"agent": meta["agent"], "model": model}).inc(
usage * PRICE[model])
# 4. 质量代理指标:重试率、修订率、守卫拦截率
self.registry.counter("agent_retries_total",
labels={"agent": meta["agent"]}).inc(meta.get("retries", 0))
self.registry.counter("agent_plan_revisions_total",
labels={"agent": meta["agent"]}).inc(meta.get("revisions", 0))
4.2 指标的告警语义必须"面向业务"
行业Agent的告警不能只写"错误率>5%"。我沉淀了一套业务语义化的告警规则,举三个真实案例:
- "同一条任务连续N次修订仍失败" → 告警"该业务场景Agent能力不足",触发的是能力复盘,而不是简单重试;
- "平均每任务Token消耗环比+30%" → 告警"成本异常",往往对应Prompt膨胀或上下文泄漏,需要检查是否把历史对话全量塞进了每次调用;
- "守卫层拦截率骤升" → 告警"输入质量恶化或新注入手法出现",需要人工检查被拦截样本,更新注入规则。
这类告警的共同点是:指标只是引线,背后要挂一个明确的"人工动作"。没有动作的告警只会变成告警疲劳。
五、Feedback闭环:让"结果对不对"可度量、可回灌
Trace和Metrics回答"发生了什么、整体如何",但行业场景最尖锐的问题它们回答不了:这个结果到底对不对? 对账Agent说"差异已处理",它处理对了吗?预测Agent说"明日峰值8500kW",它预测准了吗?
答案是Feedback闭环------把"事后才知道的对错"转化为"可度量的信号",再回灌进系统改进质量。我把它拆成四个环节。
5.1 自动校验反馈:结果在返回前先自证
能自动判定的反馈,绝不等到用户来点。我把业务校验器注册进闭环,结果生成后立即校验:
python
@dataclass
class Feedback:
trace_id: str
task_id: str
source: str # auto_check | user_rating | business_outcome
score: float # 0.0 ~ 1.0
comment: str
created_at: datetime
class FeedbackLoop:
def __init__(self, validators: list[Callable], store):
self.validators = validators
self.store = store
def collect_auto(self, task: dict, result: dict) -> list[Feedback]:
fb = []
for v in self.validators:
ok, reason, score = v(task, result) # 业务校验器
fb.append(Feedback(
trace_id=task["trace_id"], task_id=task["id"],
source=f"auto:{v.__name__}",
score=score if ok else 0.0,
comment=reason))
return fb
校验器示例:对账任务校验"差异数是否为0、调整单是否都有关联凭证";预测任务校验"预测值与真实值的MAPE"(真实值到达后延迟触发)。
5.2 用户反馈:结构化而非"点个赞"
行业场景下,"点赞/点踩"远远不够。我用的反馈表单是结构化的:结果可读性、数据准确性、处理完整性、是否需人工兜底,以及一条必填或选填的文本说明。结构化的价值在于可统计、可聚类------"处理完整性"连续两周得分低于0.6,就能定位到某个Agent的某个步骤,而不是靠用户吐槽才知道。
5.3 业务结果反馈:终极裁判是业务指标
最硬核的反馈来自业务结果本身。能源预测Agent的反馈,不是用户打分,而是"第二天真实负荷出来后,预测的MAPE是多少";对账Agent的反馈,是"财务复核时发现的漏单数"。这类反馈需要延迟关联------任务早就结束了,真实结果在几天后才产生,闭环必须支持"按trace_id补记反馈":
python
def record_delayed_feedback(trace_id: str, source: str, score: float, comment: str):
store.upsert(Feedback(trace_id=trace_id, task_id="",
source=source, score=score,
comment=comment, created_at=now()))
5.4 反馈回灌:从信号到改进
收集反馈不是目的,回灌才是。我做了三层回灌,由近及远:
第一层,实时纠错。 单条反馈得分过低(如自动校验不通过),立即触发重试、修订或转人工,不等用户发现。
第二层,质量画像。 反馈按Agent、按Skill、按任务类型聚合,形成"质量热力图":哪个Agent、哪个步骤、哪类输入最常出问题。每月生成一份质量报告,直接驱动下个月的优化排期。
第三层,数据与模型回灌。 低分样本经脱敏与人工复核后,进入两类资产:一是Few-shot示例库 ,改进Prompt;二是评测集,任何Prompt、模型、技能升级都必须先过评测集------分数不降才允许上线。这是闭环的最终形态:系统每天产生数据,数据反过来改进系统。
python
def build_eval_set(feedback: list[Feedback], min_score: float = 0.3,
sample_size: int = 200) -> list[dict]:
"""低分样本 → 脱敏 → 人工复核 → 评测集"""
bad = [f for f in feedback if f.score < min_score]
sampled = random.sample(bad, min(sample_size, len(bad)))
return [redact_and_review(f) for f in sampled] # 人工复核后入库
六、贯穿三层的关键横切:脱敏与成本
三根支柱之上,还有两个必须贯穿始终的横切关注点。
脱敏。 Trace里的Prompt片段、工具参数、反馈内容,几乎必然包含业务敏感信息。我的纪律是:Span属性与事件内容在写入前先过脱敏层(PII正则+关键词),保证观测系统自身不成为泄密源。宁可损失一点可读性,也不能让监控日志成为安全事件。
成本。 Token消耗天然是Agent最重要的Metrics之一,但它的价值不止于"省钱":Token消耗异常往往是质量问题的最早信号------同一任务Token翻倍,通常意味着Agent在绕圈子、上下文在膨胀、或计划在反复修订。我把"成本异常"与"质量下降"绑定告警,而不是单独看数字。
七、工程实践的五个关键决策
1. 观测先于上线,而不是补课。 Trace埋点、指标打点、反馈通道,在Agent框架层就内置好,而不是等出事后从日志里捞。框架层统一埋点,业务代码零侵入------这是"能坚持观测"的前提,靠自觉埋点是坚持不下去的。
2. 一个trace_id贯穿到底。 从用户请求、到Agent编排、到每次LLM与工具调用、再到延迟反馈,共享同一个trace_id。行业排查问题的标准动作就是"拿着trace_id查全链路",没有它,跨系统对账就是噩梦。
3. 采样要有策略,但不能丢尾部。 高流量下全量Trace成本太高,我用"头部全采样+尾部全采样":常规任务按比例采样,但失败、超时、高成本任务100%采样------你永远要为最坏情况保留完整现场。
4. 指标要能"下钻"到单次任务。 仪表盘上的聚合数字只是入口,每个指标都必须能点进对应的一批trace_id。聚合给你趋势,下钻给你真相,两者缺一不可。
5. Feedback是观测的最终交付物。 如果Trace和Metrics最后没有汇聚成"结果质量可度量、改进有数据支撑"的闭环,那观测就只是"更贵的日志"。把Feedback闭环当作系统的一等公民来设计,而不是事后追加的功能。
八、总结
行业Agent的可观测性,本质上是在回答三个递进的问题:它做了什么(Trace)、系统健康吗(Metrics)、结果对吗(Feedback)。前两个是传统可观测性工程的延伸,第三个才是Agent带来的新命题------它要求观测系统从"监控基础设施"升级为"度量智能决策质量的工程基础设施"。
这套体系上线后,我们第一次能做到:任何一条业务投诉,都能在五分钟内还原任务的完整执行路径与推理过程;每个Agent的质量与成本都有月度画像;每次Prompt或模型升级,都有评测集把关。Agent不再是一个"感觉还行"的黑盒,而是一个可度量、可审计、可改进的工程系统。
可观测性不会让Agent变得更聪明,但它让Agent变得可以被信任。在行业落地的语境里,后者比前者更重要。