#33 Agent 的可观测性:日志、追踪、监控与性能分析(LangSmith、Wandb)
一、一个让我熬夜到凌晨三点的 Bug
去年秋天,我负责的一个多Agent协作系统在生产环境出了怪事:用户反馈说“机器人回答越来越慢,最后直接卡死”。我登录服务器,CPU和内存看起来都正常,日志里也没有报错。但Agent就是像中了邪一样,每次调用LLM后要等十几秒才返回结果。
我怀疑是LLM API超时,但检查了调用记录,每次请求都在2秒内完成。又怀疑是工具调用链出了问题,但代码逻辑看起来天衣无缝。折腾到凌晨两点,我决定把每个Agent的每一步执行时间都打出来——手动加了一堆time.time()和print,重启服务,复现问题,盯着终端看输出。
真相让我哭笑不得:某个子Agent在调用一个外部搜索工具时,工具本身返回很快,但Agent的解析器在处理返回结果时,因为一个JSON字段格式变化,触发了Python的re模块回溯爆炸——一个正则表达式在极端输入下从O(n)退化成了O(2^n)。这个Bug在单次调用时只慢几百毫秒,但多个Agent串行执行时,延迟被指数级放大。
如果当时我有完善的可观测性系统,这个Bug可能在第一次出现时就被自动捕获,而不是让我手动插桩排查到天亮。这就是为什么我后来在团队里立了规矩:没有可观测性的Agent,不准上线。
二、Agent 可观测性为什么比传统后端更难
传统后端服务,你关心的是请求延迟、错误率、吞吐量。但Agent系统多了一层“智能”的黑盒——LLM调用。一个Agent请求可能包含:
- 多次LLM调用(每次调用可能几秒到几十秒)
- 多个工具调用(每个工具可能有自己的延迟和错误)
- 复杂的循环和条件分支(Agent可能自己决定下一步做什么)
- 上下文窗口管理(Token消耗、截断策略)
- 非确定性输出(同样的输入,每次输出可能不同)
这意味着你不能只盯着“接口响应时间”这一个指标。你需要知道:LLM调用花了多少钱?哪个工具调用失败了?Agent在哪个步骤陷入了死循环?上下文窗口被撑爆了吗?
三、日志:别只记“发生了什么”,要记“为什么”
很多团队写Agent日志,就记一句"Agent started"和"Agent finished"。这种日志在出问题时毫无价值。
我现在的做法是:每个Agent实例生成一个唯一的trace_id,贯穿整个请求生命周期。日志记录点包括:
# 这里踩过坑:别用简单的uuid,要包含时间戳和随机数,方便按时间范围检索trace_id=f"agent_{int(time.time())}_{uuid.uuid4().hex[:8]}"# Agent启动时记录完整输入logger.info({"event":"agent_start","trace_id":trace_id,"input":user_input,# 注意脱敏!别把用户密码记进去"agent_config":{"model":"gpt-4-turbo","temperature":0.7,"max_tokens":4096,"tools":["search","calculator","code_interpreter"]}})# 每次LLM调用前后记录logger.info({"event":"llm_call_start","trace_id":trace_id,"step":current_step,"prompt_tokens":len(tokenizer.encode(prompt)),"call_id":call_id})# LLM返回后记录logger.info({"event":"llm_call_end","trace_id":trace_id,"step":current_step,"call_id":call_id,"duration_ms":duration_ms,"completion_tokens":completion_tokens,"total_tokens":total_tokens,"cost_usd":calculate_cost(model,prompt_tokens,completion_tokens)})关键点:日志要结构化。用JSON格式而不是纯文本,这样后续可以导入到ELK、Datadog等系统做分析。纯文本日志就像用记事本写代码——能看,但没法自动化处理。
四、追踪:把Agent的思考过程可视化
日志是离散的事件点,追踪是把这些点串成一条线。对于Agent系统,追踪尤其重要,因为Agent的执行路径是非线性的——它可能循环、回溯、并行调用。
我推荐使用OpenTelemetry标准来做追踪。LangSmith底层也是基于这个标准。一个典型的Agent追踪应该包含:
- 根Span:整个Agent请求
- 子Span:每次LLM调用
- 子Span:每个工具调用
- 事件:Agent的思考过程(比如“决定调用搜索工具”)
用代码实现大概是这样:
fromopentelemetryimporttracefromopentelemetry.exporter.otlp.proto.http.trace_exporterimportOTLPSpanExporterfromopentelemetry.sdk.traceimportTracerProviderfromopentelemetry.sdk.trace.exportimportBatchSpanProcessor# 别这样写:每次请求都new一个tracer,会导致span丢失# tracer = trace.get_tracer(__name__)# 正确做法:全局初始化一次provider=TracerProvider()processor=BatchSpanProcessor(OTLPSpanExporter(endpoint="http://localhost:4318/v1/traces"))provider.add_span_processor(processor)trace.set_tracer_provider(provider)tracer=trace.get_tracer("agent_tracer")# 在Agent执行时withtracer.start_as_current_span("agent_execution")asroot_span:root_span.set_attribute("trace_id",trace_id)root_span.set_attribute("user_input",user_input[:100])# 截断,别记太多withtracer.start_as_current_span("llm_call")asllm_span:llm_span.set_attribute("model","gpt-4")llm_span.set_attribute("tokens",1500)# 执行LLM调用...llm_span.set_status(trace.StatusCode.OK)withtracer.start_as_current_span("tool_call")astool_span:tool_span.set_attribute("tool_name","search")tool_span.set_attribute("query",search_query)# 执行工具调用...tool_span.set_status(trace.StatusCode.ERRORiferrorelsetrace.StatusCode.OK)有了追踪,你可以在Jaeger或Grafana Tempo里看到每个Agent请求的完整调用链。哪个步骤慢、哪个步骤出错,一目了然。
五、LangSmith:专为LLM应用设计的可观测性平台
LangSmith是LangChain团队推出的产品,但它不绑定LangChain——你可以用在任何Agent框架上。我目前的生产环境就是自己写的Agent框架,照样接入了LangSmith。
接入方式很简单:
fromlangsmithimportClientfromlangsmith.run_helpersimporttraceable# 初始化客户端langsmith_client=Client(api_key="ls_xxx",# 从LangSmith后台获取api_url="https://api.smith.langchain.com"# 或者自部署的地址)# 用装饰器标记需要追踪的函数@traceable(client=langsmith_client,run_type="llm",# 可选: llm, chain, tool, retrievername="my_llm_call",project_name="agent_production"# 按项目分组)defcall_llm(prompt,model="gpt-4"):# 这里踩过坑:@traceable会自动捕获输入输出,但如果你在函数内部修改了输入字典,追踪会记录修改后的值response=openai_client.chat.completions.create(model=model,messages=[{"role":"user","content":prompt}])returnresponse.choices[0].message.contentLangSmith最让我喜欢的功能是对比实验。你可以把同一个Prompt发给不同的模型,或者同一个模型的不同参数,然后对比输出质量和延迟。这在调优Agent时太有用了——以前我只能靠感觉判断“GPT-4比GPT-3.5好多少”,现在有数据支撑了。
另一个杀手功能是自动反馈收集。你可以让用户对Agent的回答点赞/点踩,这些反馈会关联到具体的追踪记录。然后你可以分析:被点踩的回答,是LLM调用出了问题,还是工具调用返回了错误数据?
六、Wandb:不只是实验管理,还能做性能分析
很多人知道Wandb(Weights & Biases)是做深度学习实验管理的,但它对Agent系统同样有用。我主要用它来做三件事:
1. 记录Token消耗和成本
importwandb wandb.init(project="agent_monitoring",config={"model":"gpt-4-turbo","temperature":0.7})# 每次LLM调用后记录wandb.log({"prompt_tokens":prompt_tokens,"completion_tokens":completion_tokens,"total_tokens":total_tokens,"cost_usd":cost,"latency_ms":latency_ms,"step":current_step})2. 可视化Agent执行路径
Wandb支持自定义图表。我写了个小工具,把Agent的每一步执行记录(LLM调用、工具调用、决策点)转换成Wandb的表格,然后按trace_id分组。这样我可以在Wandb的UI里直接看到每个请求的完整执行路径,比看日志舒服多了。
3. 监控性能退化
我在Wandb里设了监控规则:如果某个模型的平均延迟超过5秒,或者Token消耗突然增加20%,就发告警到Slack。这帮我抓到了好几次因为Prompt工程改动导致的性能退化——有时候加一句“请详细解释”会让Token消耗翻倍。
七、性能分析:找到瓶颈的精确位置
有了日志和追踪,你知道了“哪个步骤慢”,但你还想知道“为什么慢”。性能分析(Profiling)就是干这个的。
对于Agent系统,性能瓶颈通常出现在这几个地方:
LLM调用:这是最明显的瓶颈。但别只看延迟,还要看Token消耗。有时候模型返回很快,但返回了大量无用Token,导致后续处理变慢。
工具调用:外部API的延迟不可控。我遇到过搜索API偶尔超时10秒的情况。解决方案是给每个工具调用加超时和重试机制,并在追踪里记录每次重试。
上下文管理:Agent的上下文窗口会随着对话增长。当上下文超过某个阈值(比如8K Token),LLM的推理速度会明显下降。我习惯在每次LLM调用前记录当前上下文大小,并在Wandb里画一条趋势线。
正则表达式和字符串处理:开头那个Bug就是例子。Agent经常需要解析LLM返回的文本,如果解析逻辑写得不好,可能成为隐藏的性能杀手。建议对解析逻辑做基准测试,特别是当输入文本长度变化时。
我常用的性能分析工具是py-spy,一个无需修改代码的采样分析器:
# 采样30秒,生成火焰图py-spy record-oagent_profile.svg--pid12345--duration30火焰图能直观地看到CPU时间花在哪里。有一次我发现Agent有30%的CPU时间花在JSON解析上——因为LLM返回的JSON格式不规范,我用了json.loads加try-except循环解析。后来改用pydantic做严格校验,CPU占用降到了5%。
八、告警:别等人报Bug才发现问题
可观测性的最终目的是主动发现问题。我设了三级告警:
P0(立即处理):Agent完全不可用,比如LLM API返回401错误,或者所有请求都超时。通过PagerDuty打电话。
P1(当天处理):性能退化,比如平均延迟从2秒涨到5秒,或者错误率超过5%。发Slack消息到on-call频道。
P2(本周处理):趋势性问题,比如Token消耗每周增长10%,或者某个工具的错误率缓慢上升。记录到Jira工单。
告警规则示例(用Prometheus + Alertmanager):
groups:-name:agent_alertsrules:-alert:HighLatencyexpr:histogram_quantile(0.95,rate(agent_llm_latency_seconds_bucket[5m]))>5for:2mlabels:severity:p1annotations:summary:"LLM调用P95延迟超过5秒"-alert:HighErrorRateexpr:rate(agent_tool_errors_total[5m]) / rate(agent_tool_calls_total[5m])>0.05for:5mlabels:severity:p1九、个人经验:别追求完美,先跑起来
如果你刚开始搭建Agent的可观测性,我的建议是:
先做日志,再做追踪,最后做性能分析。日志最简单,改几行代码就能看到效果。追踪需要引入OpenTelemetry SDK,稍微复杂一点,但值得。性能分析是锦上添花,等系统稳定了再搞。
别想着一步到位。我见过团队花两周搭建完美的Grafana仪表盘,结果Agent本身还没跑通。先让Agent能跑,能记录关键事件,能出问题时有迹可循。后面再慢慢加细节。
关注成本。LLM调用很贵,可观测性系统本身也会产生成本(存储日志、追踪数据)。我设了日志保留7天,追踪数据保留30天,超过的自动归档到冷存储。Wandb的免费额度够小团队用,但生产环境建议买付费版。
让可观测性成为开发流程的一部分。每次提交代码,CI/CD流水线里自动检查:新代码是否添加了必要的日志和追踪?如果没有,构建失败。这听起来严格,但能避免“上线后才发现没日志”的尴尬。
最后,记住一个原则:可观测性不是为了监控而监控,而是为了让你在凌晨三点被叫醒时,能快速定位问题,然后回去睡觉。
