LangSmith实践:Agent链路追踪与可观测性调试指南 做 Agent 开发最大的痛点往往不是“写不出来”而是“跑起来之后看不透”。我自己接手过好几个 Agent 项目早期调试基本靠 print 大法东一个 print、西一个 print到最后连自己都分不清哪行日志是哪个工具打的。明明报了错却不知道是模型编错了参数还是工具返回了脏数据明明多轮对话表现飘忽却说不出是哪一轮开始崩的。直到把 LangSmith 接进项目这些问题才算真正有了答案。LangSmith 是 LangChain 官方推出的 LLM 可观测平台核心能力就是链路追踪一次 Agent 请求从入口到最后输出中间每个 LLM 调用、每个工具执行、每一步用了多少 token、花了多少毫秒、烧了多少成本全部摊开成一条可视化的时间线。这篇文章我会讲清楚它的核心概念、接入方式和排查技巧并给出一份能直接跑的示例代码适合正在做 Agent 开发、尤其是用 LangChain 生态的读者。1. 为什么 Agent 链路必须可追踪1.1 Agent 不是普通函数出问题你根本不知道卡在哪传统函数是确定性的输入固定输出固定报错位置也固定。但 Agent 完全不同。一个典型的 Agent 调用链可能包含用户输入 → 意图判断 → 多轮 LLM 推理 → 选择工具 → 工具返回结果 → 再次推理 → 最终输出。每一步都是模型生成的结果天然带有随机性。同一个问题今天跑和明天跑走的路径可能完全不同。这种不确定性带来的第一个问题就是“不可复现”。用户报了一个 bug你本地一跑发现好了再看日志又啥都没有——因为你根本不知道线上那次完整调用链里模型的哪个中间输出导致了后面的错误。我遇到过最典型的一次Agent 在调用天气工具时把参数 city 传成了北京 天气工具端没匹配到城市返回了空结果Agent 却自己脑补出一个“晴25℃”的回答。如果只看最终输出你甚至觉得它答得不错只有看到工具调用那一步的入参和出参才知道整条链路是从哪里开始造假的。所以对 Agent 来说可观测性不是加分项而是基本盘。你需要把一次请求里的所有子步骤拆开、按时间线排好、把每一层的输入输出都留下来才能在出问题时快速定位“崩在哪一层、为什么崩、是谁的锅”。1.2 可观测性要解决的四类问题我把 Agent 调试中遇到的需求归纳成四类LangSmith 基本都能覆盖逻辑正确性每一步的输入输出是否符合预期。尤其是工具调用模型有没有选对工具、传对参数、正确处理返回值。性能瓶颈一条链路里哪一步最慢。很多时候慢的并不是 LLM 本身而是某个工具接口超时或者是检索步骤拉了个大文档。成本构成一次对话烧了多少 token哪些步骤是大头。模型选型、上下文裁剪、工具返回长度都会直接反映在成本上。回归风险改了 prompt 或换了模型之后以前能过的用例是否还能过。没有评估体系的 Agent 项目改一次崩一片是常态。这四类需求靠普通日志系统很难满足。普通日志是“平铺”的没有父子层级没有 token 统计也没有跨步骤的关联。LangSmith 这类 LLM 可观测平台的思路是把一次 Agent 执行建模成一棵树——根节点是一次完整请求每个子节点是请求里的一个具体步骤然后在这棵树上挂时间、token、输入输出、cost 等元数据。这和我熟悉的分布式链路追踪比如 SkyWalking、Jaeger是同一个思想只不过应用对象变成了“模型调用 工具调用”的组合链。2. LangSmith 核心概念与设计思路2.1 一条 Trace 背后的数据结构Run 树LangSmith 里最基本的追踪单元叫 Run也可以理解为链路里的一个节点。Run 有类型区分llm、chain、tool、retriever、embedding、parser 等。一次完整的执行过程叫 Trace本质上是一棵由多个 Run 组成的多叉树。举个例子。Agent 收到“北京天气怎么样”这个问题实际执行可能是根 Runchain 类型代表整个 Agent 执行子 Run 1llm 类型模型第一次推理“需要调用天气工具”子 Run 2tool 类型调用 get_weather 工具入参 city北京子 Run 3llm 类型模型拿到工具结果生成最终回答LangSmith 控制台里这条 Trace 会以瀑布图的形式展开每个 Run 一行显示名称、类型、耗时、token 数。你可以点开任意一个 Run查看它的完整输入、输出、模型配置甚至是当时的 temperature 等参数。理解这棵树是会用 LangSmith 的前提。因为排查问题本质上就是“在这棵树里找异常节点”哪个 Run 耗时异常、哪个 Run 输出为空、哪个 Run 的入参和预期不符。树形结构天然适合这种定位方式。2.2 Project、Dataset、Evaluator 各司其职除了 Trace 和 RunLangSmith 还有几个高频概念初次上手容易混淆我一起说清楚Project项目Trace 的逻辑分组。你可以按“环境dev/staging/prod”“业务线客服/写作/数据分析”或“实验批次”来建项目。接入了LANGSMITH_PROJECT环境变量后所有 trace 会默认进入对应项目。项目之间隔离数据方便对比不同版本的表现。Dataset数据集一组测试用例每个用例包含 inputs输入和可选的 outputs期望输出。Dataset 是评估的地基用来批量跑回归。Evaluator评估器一个打分函数接收一次 Run 的结果和对应的期望输出返回一个分数或布尔判断。可以写规则匹配也可以让另一个 LLM 当裁判。Feedback反馈真实用户在界面上对某条回答点的“赞/踩”或者你通过 API 埋点采集的满意度数据。Feedback 能直接挂在某条 trace 上把用户反馈和内部链路对应起来。这套设计思路其实和单元测试很像Dataset 就是测试用例Evaluator 就是断言Experiment用评估器跑完一批 Dataset 的结果就是一次测试报告。只不过被测对象从普通函数换成了 Agent。3. 五分钟接入环境准备与最小示例3.1 拿到 API Key 并规划项目隔离第一步是去 smith.langchain.com 注册账号在 Settings 里生成 API Key。Key 通常是ls__开头的一长串字符生成后只会完整显示一次记得马上保存到本地配置里。我个人习惯是每个 Agent 项目固定开三个 projectagent-dev、agent-staging、agent-prod通过部署脚本注入不同的LANGSMITH_PROJECT。这样本地调试的脏数据不会污染线上监控线上出了问题也能快速按项目过滤。如果你只有一个项目所有环境的数据混在一起后面做对比分析会非常痛苦。注册后默认有免费额度个人开发、日常调试基本够用具体限额以官方页面为准。如果你们公司有私有化需求LangSmith 也提供自托管方案数据不出内网这个按需评估就好。3.2 安装 SDK 和设置环境变量接入方式很简单先安装 SDKpip install -U langsmith # 如果用 LangChain 做 Agent顺带装上 pip install -U langchain langchain-openai langsmith然后设置环境变量。推荐在启动脚本或.env里统一管理而不是写死在代码里export LANGSMITH_API_KEYls__你的key export LANGSMITH_TRACINGtrue export LANGSMITH_PROJECTagent-dev注意两个细节。第一LANGSMITH_TRACINGtrue是新版的标准开关如果你看老文档会看到LANGCHAIN_TRACING_V2那是历史写法新项目直接用LANGSMITH_TRACING即可。第二LANGSMITH_API_KEY是必须的少了它即使 tracing 开关打开SDK 也只是静默跳过上报不会报错——这也是很多人“设置了半天却没 trace”的主要原因。3.3 最小可追踪示例裸调 OpenAI 也能追踪先来个最轻量的。即使你不用 LangChain只是裸调 OpenAI SDKLangSmith 也提供了封装函数wrap_openai包一层之后所有补全请求都会自动上报链路import os from openai import OpenAI from langsmith.wrappers import wrap_openai os.environ[LANGSMITH_API_KEY] ls__你的key os.environ[LANGSMITH_TRACING] true os.environ[LANGSMITH_PROJECT] hello-trace client wrap_openai(OpenAI()) resp client.chat.completions.create( modelgpt-4o-mini, messages[{role: user, content: 用一句话介绍杭州}], ) print(resp.choices[0].message.content)跑完这段代码打开 LangSmith 控制台切到hello-trace项目按时间排序就能看到刚才那次调用的 trace。点进去能看到消息内容、token 数、耗时、模型名连请求头里的 parametrized 信息都记录得清清楚楚。这里有个新手常犯的误区以为必须用 LangChain 才能接入 LangSmith。实际上 LangSmith 的追踪能力是独立于框架的只要是用 Python 调 LLM 的服务都能通过wrap_openai或后面的traceable方式接入。理解这一点很重要因为它意味着你用 Dify、CrewAI、LangGraph 甚至自研框架都能保留可观测性能力。4. 实操给一个真实 Agent 挂上追踪4.1 构造一个带工具调用的 Agent光看 hello world 不过瘾我们直接上一个带工具调用的 Agent。下面这个示例包含两个工具一个查天气模拟返回一个算数学表达式。完整的链路会涉及“选工具 → 传参 → 拿结果 → 再推理”非常适合观察追踪效果。import os from langchain_openai import ChatOpenAI from langchain.agents import create_tool_calling_agent, AgentExecutor from langchain_core.tools import tool os.environ[LANGSMITH_API_KEY] ls__你的key os.environ[LANGSMITH_TRACING] true os.environ[LANGSMITH_PROJECT] agent-demo tool def get_weather(city: str) - str: 查询指定城市的实时天气 # 真实场景这里会调天气服务这里模拟返回 return f{city} 今日多云转晴气温 24℃空气质量良。 tool def calc(expression: str) - str: 计算简单的数学表达式例如 23*47 # 仅用于演示生产环境请勿直接 eval return str(eval(expression)) llm ChatOpenAI(modelgpt-4o, temperature0) tools [get_weather, calc] agent create_tool_calling_agent(llm, tools) executor AgentExecutor(agentagent, toolstools, verboseFalse) resp executor.invoke({input: 北京天气怎么样顺便帮我算 23*47}) print(resp[output])注意calc里用了eval纯粹为了演示方便生产环境一定要换成安全的表达式解析库或者直接用专门的数学引擎。跑完这段到控制台里找到最新一条 trace你会看到一条典型的 agent 链路瀑布图。随着大版本迭代链路形态会略有差异但大致结构是固定的根节点是 AgentExecutor 的 chain 类型 Run底下挂着若干 llm 节点和 tool 节点。因为问题同时涉及天气和计算你会看到模型先调用get_weather再调用calc然后汇总结果输出每一步都有独立的 token 统计和耗时。4.2 在控制台里看懂一条完整链路拿到 trace 之后重点看四个地方每个 Run 的输入输出。点开工具节点检查get_weather的入参是不是模型正确提取出的北京而不是北京天气这种带杂质的字符串。Agent 翻车最常见的原因就是工具参数提取错误。父子层级关系。确认工具调用发生在预期的位置而不是被模型跳过或重复调用。比如模型本应先查天气再计算结果它先算了数学题或者一个工具被调了三次这些异常在树形结构里一眼就能看出来。token 和耗时分布。哪一层最贵、哪一层最慢。很多 Agent 变慢的元凶不是模型本身而是某个工具返回了一大坨 JSON 塞进上下文导致后续 LLM 调用 token 暴涨。错误信息。如果某一步报错对应 Run 会用红色标出堆栈信息直接挂在节点上不用再翻本地日志。我第一次用 LangSmith 排查线上问题时就是靠“对比正常 trace 和异常 trace”定位的正常时模型调用工具的入参是city上海异常时入参变成了city上海 的天气源头是上游的输入清洗逻辑被某次改动破坏了。这种问题单看最终输出永远发现不了。4.3 自己定义追踪边界traceable 与自定义 RunLangChain 的 Agent 是自动追踪的但真实项目里还有很多“看不见”的环节自定义的意图识别函数、后处理脚本、缓存层、业务校验逻辑。这些函数跑得再慢、再出错默认都不会出现在 trace 里。解决办法是给它们也挂上追踪。官方提供了traceable装饰器用法很简单from langsmith import traceable traceable(run_typechain, name意图识别) def detect_intent(text: str) - str: if 天气 in text: return weather if 计算 in text or 算 in text: return calculator return chat traceable(run_typetool, name格式化输出) def format_answer(raw: str) - str: return f【结果】{raw}装饰器会自动把函数调用记录成一个 Run参数作为输入返回值作为输出执行的耗时和异常也会一并记录。run_type建议按函数性质填业务逻辑用chain具体操作用tool纯 LLM 调用用llm这样控制台里的图标和过滤条件更准确。如果你的项目完全不用 LangChain也不只是纯 OpenAI 调用而是有自己的框架可以用wrap_openai加traceable组合或者用 SDK 的 RunTree 手动创建子节点。这里我建议优先用traceable因为它既能装饰同步函数也支持异步函数代码侵入最小。5. 从“能追踪”到“会分析”指标、评估与反馈5.1 读监控面板里的关键指标Trace 一条一条是点状数据看多了就得看面。LangSmith 的 Project 页面会聚合出几个关键指标我每天看项目的习惯是固定盯四件事成功率/错误率哪个项目、哪个时段报错明显变多先看错误趋势再点进具体 trace 定位。平均耗时与 p95 耗时平均正常但 p95 飙高意味着存在长尾慢请求通常是某个工具接口抖动或上下文过长。Token 消耗与估算成本改 prompt 之后 token 涨了多少心里要有数。很多团队上线 Agent 后成本翻倍都是因为工具返回内容太长每轮都带一大段没用的话进上下文。工具调用分布哪些工具被高频调用、哪些工具频繁报错。一个 Agent 里如果某个工具 30% 的调用都失败问题大概率不在模型而在工具本身的鲁棒性。看面板不用多高级关键是建立基线。项目稳定运行一周后把日均耗时、错误率、token 均值记下来之后每次改模型、改 prompt、改工具都拿新数据和基线对比比靠感觉靠谱得多。5.2 用 Dataset Evaluator 做回归测试这是 LangSmith 最有价值、也最容易被忽略的能力把典型问题沉淀成 Dataset修改代码后批量回归。假设你沉淀了一批测试用例叫agent-basic每个 example 包含text用户输入和answer期望输出。然后写一个评估器跑一遍所有用例并打分from langsmith import Client, evaluate client Client() def target(inputs: dict) - dict: return executor.invoke({input: inputs[text]}) def exact_match(run, example) - dict: return { key: exact_match, score: 1.0 if run.outputs[output] example.outputs[answer] else 0.0, } results evaluate( target, dataagent-basic, evaluators[exact_match], ) for r in results: print(r)不同 LangSmith 版本对 evaluator 的函数签名要求略有差异实际使用时以你安装版本的官方示例为准上面的写法是 v0.1 之后的主流传参方式。跑完这次评估控制台里会生成一个 Experiment展示每个用例的通过/失败情况。你会发现很多“看起来没问题”的修改在回归测试里原形毕露比如把 gpt-4o 换成某个便宜模型后正常对话还行但工具调用参数提取能力明显下降。没有这套回归机制这种退化要等用户投诉才能发现。更进一步你还可以在评估器里让一个更强的 LLM 当裁判对比两个 prompt 版本在同一批数据上的表现。LangSmith 支持对比多个 Experiment 的结果这在调 prompt 时非常有用——每次改动都有数据支撑而不是“我觉得这样更好”。6. 常见问题与排查技巧实录6.1 环境配置类问题速查我接 LangSmith 的初期踩过不少配置的坑整理成速查表你按表排查就行现象可能原因解决办法代码跑完控制台没有 traceLANGSMITH_TRACING没设为true或拼写错误检查环境变量确认无空格trace 出现在默认项目里LANGSMITH_PROJECT未设置或项目名拼错确认项目名复制控制台里的准确名称同步/异步函数均无 traceAPI Key 未生效或格式不对确认 key 是ls__开头重新生成并替换工具调用细节看不到自定义函数没有加traceable给非 LangChain 步骤补装饰器上报报 401 错误API Key 过期或权限不足到 Settings 重新生成并更新环境变量异步任务 trace 不完整事件循环结束时 Run 未正常关闭确保使用traceable的异步版本并在任务完成后退出上下文6.2 数据脱敏、成本控制与追踪开销配置通了以后还有几个进阶问题是生产环境绕不开的。敏感信息脱敏。LLM 链路里经常会出现用户手机号、身份证、内部业务数据。这些内容会原样记录在 trace 里如果直接上报到云端的 LangSmith存在数据安全风险。我的做法是进入追踪函数之前先脱敏把敏感字段替换成占位符再传给模型或者在traceable装饰的函数里对 inputs 做一层预处理确保落库的数据已经是过滤后的。如果公司对数据出域有硬性要求就直接评估自托管方案。成本控制。Trace 上报本身会占用额度高频业务如果全量追踪消耗不小。我的经验是开发环境全量追踪生产环境按需开启——比如只追踪错误样本、或按一定比例采样上报。通过环境变量或配置中心动态控制LANGSMITH_TRACING比在代码里写死更灵活。追踪性能开销。很多人担心加了追踪会影响业务延迟。实测下来LangSmith 的上报是异步的对主流程的延迟影响很小除非你做了同步阻塞式上报。真正要留意的是内存长链路、大上下文场景下每个 Run 都持有输入输出副本如果 Agent 频繁循环trace 树会很长。我习惯在关键循环步骤里只保留必要字段避免把整个工具返回的大文本都塞进 Run 输出。最后一个经验是用 trace 的唯一性来定位线上问题。LangSmith 的每条 trace 都有独立 ID我这边所有的报障流程都强制要求用户提供 trace 链接或 trace ID就像传统后端报障要附 requestId 一样。有了这个 ID无论问题发生在哪个环境、哪个模型、哪个工具都能在三分钟内还原当时的完整执行链而不是像以前一样让用户复述“它当时好像说了什么”。我自己的习惯是每次 Agent 项目开工第一天就接好 LangSmith而不是等出了线上事故再补。事后补追踪最尴尬的是那段事故期间的调用根本没被记录等于白排查。先把 trace 打上后面无论是调 prompt、换模型、还是优化工具手里都有数据心里才有底。