☰
Agent开发必备:LangSmith可观测性平台实现链路追踪与调试
2026/10/7 6:41:28 网站建设 项目流程

做 Agent 开发的人,大概率都经历过这种状况:本地跑得好好的多步推理链路,一放到线上就开始出现各种奇怪行为。翻日志只能看到一串零散的 LLM 调用记录,根本说不清是哪一步决策出了问题、哪个工具返回了错误数据、又是哪一个中间结果把 Agent 带偏了。这也是我为什么现在所有 Agent 项目都要先接上 LangSmith——它解决的核心问题就是链路可追踪,让你把一次 Agent 运行从收到消息、中间决策、工具调用、再到最终回答的全部过程,像看调用拓扑图一样看得清清楚楚。

LangSmith 是 LangChain 团队推出的 LLM 可观测性平台,作用相当于给 Agent 装上行车记录仪,但它并不只对 LangChain 友好。OpenAI、Anthropic、LlamaIndex,甚至你自己裸写 Prompt 循环,都能通过 langsmith SDK 接入。无论你是刚开始接触 Agent 开发的新手,还是负责线上 Agent 服务的后端同学,这篇文章都值得花十分钟看一下:我会从概念讲起,带你把一个 Agent 项目完整接进 LangSmith,再整理我在生产环境中踩过的坑和排查思路。

1. 为什么做 Agent 的人都在补「可观测性」这堂课

1.1 Agent 链路和普通接口日志的本质区别

先说一个事实:传统后端观测是「请求级」的。一个 REST 接口收到请求、处理、返回,你靠日志加指标加链路追踪(比如 OpenTelemetry),就能把一次请求的过程完整拼出来。因为逻辑是确定的,分支是有限的,错误大致是可枚举的,所以「排查问题 = 定位分支」就够了。

但 Agent 完全不是这个逻辑。一个 Agent 项目里,LLM 是自由的决策者:它可能第一步就决定调用某个工具,也可能先反问用户再决定下一步;同样的输入,两次运行的路径可能完全不一样;同样的工具,在不同上下文里被调用的参数也可能千奇百怪。你没法用「先 A 后 B 再 C」的固定流程图去描述一次运行。

我举个自己的例子。之前做一个信息收集类 Agent,用户问「帮我查一下上海这周适合户外活动的天气」,Agent 的规划是好的,但工具调用时把城市参数解析成了「上海中心城区」,结果数据源一直匹配不上,Agent 就反复重试同一把工具,白白消耗了三次 LLM 调用,最后还给了个含糊答案。如果没有链路追踪,这种问题基本只能靠猜。有了追踪,一眼就能看到那一环的工具入参出了偏差,修复时间从两小时缩短到五分钟。

这就是 Agent 可观测性的本质需求:你要关注的不只是「这接口通不通」,而是「这个自主决策的流程每一步到底做了什么、为什么这么做」。

1.2 LangSmith 到底解决了什么问题

LangSmith 的核心价值,拆开看其实有四块:

  • 调试(Debug):把一次 Agent 运行的完整决策树可视化,包括每一步的 Prompt、工具入参和出参、中间结果、Token 消耗。
  • 评测(Eval):沉淀数据集,对同样的输入跑不同模型、不同 Prompt,批量打分。
  • 监控(Monitor):把线上 Agent 的 Token 消耗、延迟、错误率做成实时面板。
  • 运营(Feedback):接收集成用户反馈(点赞、点踩),用真实数据和主观评价一起优化 Agent。

对刚入门的朋友,你最该先吃透的是第一项「调试」,也就是链路可追踪。后面几项基本都建立在良好 Trace 的基础上,链路都没有,谈别的都是空中楼阁。

另外很重要的一点:LangSmith 不是 LangChain 的附属品。官方提供了 langsmith SDK,你可以直接拿它去包装任意的 Python 或 JS 函数。换句话说,哪怕你完全不用 LangChain、LangGraph,而是自己写了一套 Agent 循环——自己调模型、自己管工具列表、自己写反思逻辑——一样可以把完整链路送进 LangSmith 看板。这也是我认为它在当下的 Agent 可观测性工具里最值得先学的原因。

2. 接入前必须搞懂的四个概念:Trace、Span、Run、Observation

2.1 名词拆解,一张表讲清楚

打开 LangSmith 控制台,你看到的界面其实围绕几个固定名词组织起来的。先记四个:

概念一句话解释类比
Project一个项目,存放一批相关的 Trace一个业务系统的日志目录
Trace一次完整请求运行的记录一次会话的回放视频
Span / RunTrace 里的一个步骤片段,可嵌套视频里的一个镜头
Observation某个步骤的具体观测数据,如 Token、延迟、错误镜头的参数信息

有一点要特别注意:随着 LangSmith API 升级,旧文档里经常出现 Run 和 Span 混用的情况。v2 API 里官方更强调 Tree(Trace)和 Span 这对概念;在 SDK 代码和旧资料里,又能看到 run_type、parent_run_id 这类字段。理解它们本质是同一件事就够了——一次 Trace 是一棵树,每个 Span 是树上的一层节点,可以在自己的父 Span 下面继续开子 Span。

顺带说一句,最近很多人问 agent harness 和 agent 框架有什么区别,其实放到可观测性这个语境里也说得通:框架负责组织 Agent 的执行流程,harness 更像「怎么把这套流程安全地跑起来、管起来」;LangSmith 则服务于这两者的共同需求——不管你是用 LangGraph、CrewAI 还是自研框架,观测层都是独立的一层,不要绑死在具体框架上。

2.2 数据是怎么一层层「串」起来的

一个典型的 Agent 调用,在 LangSmith 里会长成这样的树形结构:

Trace:run_agent("上海周六适合户外活动吗?") ├── Span:plan(LLM 调用,生成工具调用计划) │ ├── Observation:prompt、completion、token usage │ └── Observation:model 名称、温度 ├── Span:call_weather_tool(调用天气工具) │ ├── Observation:工具入参 { city: "上海", date: "2025-12-06" } │ └── Observation:工具出参 { temperature: 8, condition: "小雨" } ├── Span:read_result(LLM 调用,根据工具结果生成回复) │ └── Observation:输出文本

关键在读树的方式:每一个 Span 都是独立的计时单元,你可以看到它耗时多久、消耗了多少 Token、返回了什么内容;如果某个 Span 抛异常,LangSmith 会标红,并把堆栈和错误信息带出来。排查问题时不用去猜,直接在树上点开可疑节点看细节就行。

这棵树是怎么来的?原理其实很朴素——LangSmith SDK 会拿到当前运行上下文里的 Trace ID 和父 Span ID,用它们把每个节点串成树。这也是为什么后面你会看到,接入 LangSmith 本质上就是「给函数套装饰器、加上下文」,让 SDK 知道每一步都属于哪条链路。

3. 十分钟接入:环境配置与最小可运行示例

3.1 创建账号、拿 Key、配环境变量

接入的第一步是去 LangSmith 官网注册账号,创建一个 Workspace,然后在 Settings 里生成 API Key。API Key 的格式一般是ls__开头,这个 Key 用于识别你是哪个团队的请求,所以要保管好,别 commit 到公开仓库里。

安装依赖很简单,一条命令:

pip install langsmith openai python-dotenv

关键环境变量有三组,我直接列成表格:

环境变量作用
LANGCHAIN_TRACING_V2=true开启 v2 协议上报
LANGCHAIN_API_KEY=ls__xxx认证身份
LANGCHAIN_PROJECT=my-agent指定项目名

还有一个经常被忽略的:LANGCHAIN_ENDPOINT。默认指向 LangSmith 官方云服务;如果你用的是自托管或内网部署版,需要改成对应地址。生产环境如果走内网,建议把它显式写进配置,避免默认域名连不通。

关于 Key 管理,我建议不要在代码里写死,用一个 .env 文件配合 python-dotenv 读取,或者直接注入到 CI/CD 环境变量里。别小看这一步,我见过好几个项目因为 Key 写在测试代码里泄露出去,被迫重置。

3.2 用 @traceable 装饰器完成最小接入

假设你现在有一个最朴素的 Agent:一个函数调模型,一个函数调工具,一个函数把两者串起来。最小可运行示例是这样:

import os from dotenv import load_dotenv from openai import OpenAI load_dotenv() # 接入 LangSmith 的最小配置 os.environ.setdefault("LANGCHAIN_TRACING_V2", "true") os.environ.setdefault("LANGCHAIN_API_KEY", os.getenv("LANGCHAIN_API_KEY")) os.environ.setdefault("LANGCHAIN_PROJECT", "quickstart-agent") client = OpenAI()

然后用 @traceable 装饰器包装函数:

from langsmith import traceable @traceable(run_type="llm", name="call_llm") def call_llm(prompt: str) -> str: resp = client.chat.completions.create( model="gpt-4o-mini", messages=[{"role": "user", "content": prompt}], ) return resp.choices[0].message.content @traceable(run_type="tool", name="weather_tool") def get_weather(city: str) -> dict: return {"city": city, "temperature": 8, "condition": "小雨"} @traceable(run_type="chain", name="run_agent") def run_agent(query: str) -> str: plan = call_llm(f"用户提问:{query}。请判断是否需要查天气,并返回要查询的城市名。") city = plan.strip() weather = get_weather(city) answer = call_llm(f"根据天气信息 {weather} 回答用户问题:{query}") return answer

跑一次run_agent("上海周六适合户外活动吗?"),然后打开 LangSmith 控制台,你会立刻看到一条 Trace:根节点是 run_agent,下面两个 LLM Span 和一个工具 Span 清清楚楚。

这里有几个细节值得说:

  • run_type参数:llm、chain、tool、retriever、embedding 等。它决定在 UI 里显示的图标和着色,方便你一眼分辨哪一步是模型调用、哪一步是工具执行。
  • name参数:不写的话默认取函数名。建议显式命名,因为生产环境下函数名可能被混淆或缩写。
  • 装饰器默认记录函数的入参和出参。如果 Prompt 里可能带敏感信息,建议用 metadata 或参数排除来控制哪些数据进 Trace。

这段演示用的是最朴素的循环,没有引入 LangChain。实际你如果用 LangChain 的 AgentExecutor、LangGraph 的状态图,LangSmith 的集成会更自动化——框架会在关键位置自动埋点,不用你每个函数都手动加装饰器。但对自建 Agent,@traceable 就是最好的接入方式。对于异步函数它也支持,直接装饰 async def 即可,调用时照常 await,注意异常和取消事件要处理干净,否则容易上报不完整。

4. Agent 场景的关键埋点:多步推理、工具调用、费用统计

4.1 把 Agent 的每一步都变成 Span

接完最小示例后,你可能会问:我的 Agent 不止三步,有反思、记忆、多个工具循环,怎么让链路更清晰?

我的做法是坚持一个原则:一个「动作」就是一个 Span。具体来说:

  • 每次 LLM 调用单独形成一个 llm 类型的 Span
  • 每次工具执行单独形成一个 tool 类型的 Span
  • 把「规划」「反思」「写摘要」这类逻辑块包一层 chain 类型的 Span
  • 用父 Span 把一组相关动作串成「阶段」

比如一个典型 ReAct 循环:

@traceable(run_type="chain", name="react_loop") def react_loop(query: str, max_iterations: int = 3): messages = [{"role": "user", "content": query}] for i in range(max_iterations): thought = call_llm(messages) action = parse_action(thought) if action["type"] == "finish": return thought tool_result = execute_tool(action) messages.append({"role": "assistant", "content": thought}) messages.append({"role": "tool", "content": str(tool_result)}) return "迭代超限"

在这个结构里,LangSmith 的树会显示每一轮循环的完整过程。你不仅能看出第几轮出了问题,还能对比「Agent 是在哪一轮开始重复同一个工具」「哪一轮的 Token 消耗突然变大」。

再给一个非常实际的操作建议:在每轮循环开始时,把轮次号写进 Span 名。做法是动态传给 name,或通过 run 对象更新 display name。这样在 UI 展开时,你一眼就能看到「第 1 轮」「第 2 轮」,排查多轮 Agent 效率提升非常明显。

4.2 用 Metadata 和 Feedback 做精细化追踪

默认情况下,Trace 只有入参、出参和耗时。但线上定位问题,往往需要更多上下文。LangSmith 允许你给每次运行附加 metadata 和 feedback。

Metadata 适合放和这次运行相关的业务字段:

from langsmith import traceable @traceable( metadata={ "user_id": "U_12345", "channel": "app", "agent_version": "v2.1.0", "region": "cn-east", } ) def run_agent(query: str): ...

这样在 Trace 列表页,你可以直接按 user_id 或 agent_version 过滤,比手动一个个点开看方便太多。对于多租户系统,这个字段几乎是必需品。

Feedback 则适合做线上质量信号采集。把用户的点赞、点踩、复制回答等行为回传给 LangSmith:

from langsmith import Client ls_client = Client() ls_client.create_feedback( run_id=trace_id, key="user_rating", score=1, # 1 表示点赞,0 表示点踩 comment="回答信息过时", )

这里的 run_id 怎么拿?在你调用 run_agent 后,可以通过 langsmith 的上下文工具拿到当前 run 的 ID,或者在装饰器内部直接读取。很多团队觉得接反馈是「事后再说」的功能,但我建议产品一上线就接。有了 feedback 加 metadata,你就可以回答「v2.1.0 版本在 app 渠道的用户里,被点踩的比例是不是比 v2.0.9 高了」,这种数据对 Agent 迭代特别有价值。

费用统计也要提一句。LangSmith 的 Trace 详情页会展示每个 LLM Span 的 Token 用量和预估费用,但前提是模型提供商返回的 usage 字段被正确记录。用 OpenAI SDK 默认会带;如果你用的是自部署模型或其他兼容网关,记得把 usage 信息透传出来,否则 UI 上看到的费用会是 0。这也是一个高频踩坑点。

4.3 沉淀数据集,把 Trace 用于批量评测

链路可追踪的上层玩法,是把你跑出来的 Trace 沉淀成评测数据集。LangSmith 里可以直接把某条 Trace 的输入输出保存成 example,也可以手动导出一批问题作为测试集。

日常操作我一般这样做:

from langsmith import Client client = Client() dataset_name = "weather_agent_test" client.create_examples( inputs=[{"query": "上海周六适合户外活动吗?"}], outputs=[{"answer_contains": "小雨"}], dataset_name=dataset_name, )

然后用这个数据集批量跑 Agent,再通过 LangSmith 的 Evaluator 给回答打分。这样每次改 Prompt、换模型,都能快速跑一遍回归,而不是靠感觉判断「好像变好了」。注意不同版本 SDK 的评测 API 细节略有差异,依赖版本以官方文档为准,但核心思路是一样的:没有数据集,就没有回归;没有回归,Agent 迭代就是裸奔。

5. 常见问题与排查技巧实录

5.1 Trace 没上报的几类典型原因

接入 LangSmith 后,最常见的挫败感来自「代码跑完了,控制台什么都没有」。我按出现频率排序,整理一下典型原因:

一是环境变量没生效。最常见的是 LANGCHAIN_TRACING_V2 没设成字符串 "true",或者 Key 设置位置和代码执行顺序不对。python-dotenv 的 load_dotenv() 必须在读取环境变量的代码之前执行,否则你用 os.environ 读到的还是旧值。

二是项目名拼写不一致。控制台里的 Project 名区分大小写。如果 LANGCHAIN_PROJECT 设置了 "MyAgent",而你在 UI 里打开的是 "myagent",看起来就像丢数据了,实际只是换了个项目存储。

三是装饰器没作用于异步函数或流式场景。@traceable 对 async 函数支持没问题,但如果你在流式输出里直接套装饰器,有时只能拿到最终内容,拿不到中间增量。对流式场景,建议每次都查一下官方对 stream 的专门说明,别凭感觉处理。

四是上报被采样或网络不通。某些部署环境默认对 Trace 做了采样,或者公司内网屏蔽了 LangSmith 域名。排查方法是在本地跑一次最小示例:本地能上报,那基本就是网络或代理问题;本地也不行,回到前三条检查。

5.2 排查技巧与日常调优心得

最后分享几个实践经验。

第一,先建一个「最小链路」沙箱项目。我习惯在 LangSmith 里单独建一个 playground 项目,专门用来跑最小示例。排查问题时先在 playground 里复现,避免和线上流量混在一起,也避免污染正式项目的指标。

第二,善用 Trace 详情页的对比和标注功能。同一输入、不同版本跑出来的 Trace 可以并排对比,你才能理解一次改动到底产生了什么影响。这个功能在调 Prompt、换模型时特别好用——肉眼对比两个 Trace 的每一步差异,往往能直接找到性能瓶颈或逻辑漂移点。

第三,生产环境建议开启采样,而不是全量上报。Agent 的 Trace 数据量不小,每个 Span 都带完整 Prompt,全量上报既费流量又费配额。LangSmith 支持按比例采样,通过 LANGCHAIN_TRACING_SAMPLING_RATE 设置,服务稳定后我一般把采样率压到 10% 左右,但保留错误 Trace 全量上报。这样既保证排障能力,又不会让成本失控。

第四,也是最重要的一条:把 LangSmith 当「事实来源」,而不是「事后查阅工具」。养成每次 Agent 改动后都去翻 Trace 的习惯,就像后端同学改完接口要看监控一样。链路可追踪这件事,价值不只在出问题时才体现,更在日常迭代里每一次「哦,原来它这一步是这么走的」的顿悟。

我个人用下来的体会是,LangSmith 的学习成本其实很低,真正有门槛的是「链路思维」——你能不能把一个自由决策的过程拆成可观测的节点。这个思维一旦建立,不只是工具好用,还会反向促使你把 Agent 代码写得更模块化。毕竟一个函数一个 Span,代码结构不好,Trace 也不会好看。下一步有空的话,我打算再把 LangSmith 的在线评测和多版本对比单独写一篇,那部分对换模型、调 Prompt 的帮助更大。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询