如果你也和曾经的我一样,习惯在本地把大模型返回结果打印出来,用肉眼盯半天找问题,那你迟早会被 LLM 应用的不可控性搞崩溃。我在做客服机器人时第一次经历这种场景:昨天代码还能正常回复,今天同一个 prompt 就开始胡言乱语,翻遍日志什么都没看出来。后来把 LangSmith 接入项目,才真正看到每一次运行背后的完整链路,调试 LLM 应用的方式彻底变了。
LangSmith 是 LangChain 生态里的 LLM 应用观测与评估平台,核心做四件事:追踪、调试、评估、监控。你可以把它理解成飞机上的黑匣子,模型输入、输出、中间检索、工具调用、Token 消耗、耗时,一次请求里的所有细节都会被记录下来。如果你正在做 RAG 知识库问答、Agent 自动化流程、客服机器人这类应用,又经常被“为什么输出是这个样子”折磨,这篇实战文章就是给你准备的。
1. 为什么需要 LangSmith:从“黑盒”到“全链路可见”
1.1 调试 LLM 应用的最大痛点:不是代码跑不通,而是答案不稳定
传统软件开发里,代码报错是一件相对“友好”的事:有异常栈、有错误码,照着排查就好。但 LLM 应用不是这样的。代码往往能正常跑完,状态码是 200,返回的字符串也完整,但内容就是不对。这种“运行成功但结果错误”才是真正的噩梦。
我以前经常遇到这些场景:用户问了一个涉及隐私的问题,模型冷不丁回了一段不该出现的文本;同一个问题在上午和下午回答差异很大;换了 prompt 之后,原本正常的例子反而回答质量下降。一开始我只会反复打印 prompt 和输出,但 prompt 往往经过多轮拼接、检索、历史消息插入,真正送到模型手里的内容是什么,本地日志里根本看不全。更要命的是,这类问题不是每次都能复现,等你想抓现场的时候,它又表现正常了。
这种情况下,需要的不只是一行日志,而是把一次请求的完整执行轨迹记录下来:用户输入、检索到的片段、拼装后的 system prompt、模型实际输出、用的模型和温度参数、Token 消耗、每步耗时。LangSmith 的追踪功能正好覆盖这些。它不只是记录结果,而是把“这一秒发生了什么”从输入到输出,全部串成一条可回放的轨迹。
1.2 LangSmith 在技术栈里的定位:追踪、评估、监控一体
LangSmith 并不是一个独立于 LangChain 的“附加工具”,它更像是 LLM 应用的生命周期管理平台。你先用 LangChain 或直接调用大模型 API 写完应用,然后把应用接到 LangSmith 上,日常开发时看追踪,发版前跑评估,上线后盯监控。
在技术层面的核心模型是 Run 和 Trace。一次完整请求是一个 Trace,Trace 内部由多条 Run 组成,每条 Run 可以嵌套。例如一个典型 RAG 请求:先有一个根 Run,代表整个请求;下面挂着 Retriever 的 Run,记录向量检索用了几毫秒、召回了多少片段;再往下是 LLM 的 Run,记录最终 prompt 和生成的回答。如果过程中调用了工具,还会有 Tool 类型的 Run,完整记录工具的输入输出。
这些数据不只能看,还可以对比。同一个数据集,当你改了检索器或者换了提示词,通过 LangSmith 的评估功能跑一遍批量测试,就能得到量化结果,而不是靠感觉说“好像变好了”。下面我用实际流程带你从环境配置开始,一步步把追踪跑起来。
2. 环境准备与基础配置:五分钟把应用接到 LangSmith
2.1 注册项目并获取 API Key
使用 LangSmith 之前,需要先到官网创建一个账号。登录之后,在控制台里创建一个项目,项目名最好一眼能看出来用途,例如产品问答、客服助手、数据分析助手,不要全都堆在默认项目里。创建完成后,进入个人设置找到 API Key 的创建入口,生成一个属于你自己的 Key。
这里有两个容易踩的坑:第一,API Key 会关联到账号权限,尽量给不同项目或者不同环境的 Key 分开设置,方便后面做权限隔离和审计;第二,Key 创建后只显示一次,一定要先复制保存到自己的密钥管理工具里,别等着页面刷新再后悔。
LangSmith 本身支持很多平台,我一般直接从官方渠道进入,按引导完成注册即可。如果你和团队一起协作,可以把 Key 放到统一的环境变量管理服务里,本地开发时再加载,避免明文写在代码仓库中。
2.2 在 LangChain 应用里开启追踪
最直接的方式是设置三个环境变量:
import os os.environ["LANGCHAIN_TRACING_V2"] = "true" os.environ["LANGCHAIN_API_KEY"] = "ls__你的key" os.environ["LANGCHAIN_PROJECT"] = "ch8-demo"如果是 Python 项目,可以在代码入口处设置,也可以写在.env文件里。更推荐的做法是在启动脚本或者在部署平台的“环境变量”配置里管理。注意LANGCHAIN_TRACING_V2必须设置为字符串"true",不是布尔值 true,这一行我见过太多人写错导致追踪不生效。
设置完成之后,只要你的应用是用 LangChain 的链(Chain)或者可运行组件(Runnable)构建的,比如prompt | model | parser这种结构,LangSmith 会自动捕获每一次调用的上下文,不需要在业务代码里手动埋点。这一点非常省事,它依靠 LangChain 内部回调机制实现,读取环境变量之后自动上报。
如果你没有用 LangChain,用的是原生大模型 SDK,也可以直接引入 LangSmith 的 Python SDK,手动创建 Run 并上传数据。不过从卢卡经验和维护成本来看,LangChain 集成是体验最顺的路子。
2.3 验证追踪是否生效
环境变量配置好之后,先不要急着跑完整业务,建议用一条最简单的大模型调用验证:
from langchain_openai import ChatOpenAI llm = ChatOpenAI(model="gpt-4o-mini") response = llm.invoke("你好,请用一句话介绍你自己") print(response.content)跑完之后,打开 LangSmith 控制台,进入之前创建的项目,正常情况下能看到一条新的 Trace。如果看不到,先按下面的顺序排查:
- 是否设置了
LANGCHAIN_TRACING_V2=true,这个变量名容易打成别的形式。 - API Key 是否有效,控制台里有没有报 401 错误。
- 项目名是否正确,如果不存在会自动创建,但有时候因为网络问题,首条数据上报有延迟,等几秒刷新再看看。
我自己的习惯是每次新建项目后,先用最小调用“探路”,确认 Trace 出现之后再继续写业务逻辑。否则后面业务一复杂,你会分不清是代码问题还是平台没打通。
3. 核心功能实战:追踪、仪表盘与数据洞察
3.1 看懂 Trace 视图里的每一次调用
当你的应用开始有真实流量或者你自己跑了几组测试之后,Trace 列表会越来越长。点开任意一条 Trace,默认会看到一个层级树。对于 RAG 应用,一般是这样:
Root: 客服问答 ├── Retriever: 向量检索 │ └── Embedding: 文本向量化 └── LLM: 生成回答每个节点都有关键信息,常用的几个字段整理如下:
| 字段 | 作用 | 我常用的场景 |
|---|---|---|
| Start / End | 该环节起止时间 | 对比总耗时和单步耗时 |
| Latency | 该环节耗时 | 快速定位慢节点 |
| Input / Output | 该节点输入输出 | 查看检索到的片段、最终 prompt |
| Tokens | 该次请求的 Token 数 | 估算成本和排查异常膨胀 |
| Model | 实际调用的模型 | 确认是不是用错了模型版本 |
刚开始用的时候,我会被密密麻麻的层级吓到,但其实你只需要重点看三个地方:根节点的输出、LLM 节点的输入、Retriever 节点的输出。根节点输出代表最终回答,LLM 节点的输入可以看到完整 prompt,Retriever 节点的输出可以看到召回内容。这三个地方基本覆盖了 90% 的调试需求。
3.2 用时间线和延迟定位性能瓶颈
有一次客服应用响应特别慢,用户点“发送”之后经常转圈好几秒。我最初以为是模型生成慢,结果打开 Trace 一看,发现真正耗时的大头在检索环节。Emebdding 模型和向量库的调用占掉了大部分时间,LLM 本身只用了不到一秒。
这种问题的排查思路很直接:先把 Trace 链上每个子节点的 Latency 从大到小排序。耗时最长的节点几乎就是瓶颈本体。然后结合 Token 数据判断是数据量太大,还是调用方式不对。比如同一个请求里总是反复调用同一段检索逻辑,那大概率是代码里循环拼接造成的重复调用,而不只是模型问题。
另一个常见现象是:LLM 生成耗时突然从 2 秒涨到 8 秒,但别的环节都正常。这时候看Total Tokens,如果输出 Token 没有明显变化,但输入 Token 暴涨,说明是 system prompt 或历史消息一直在累加,每次请求都把之前所有对话完整塞进去。解决方式就是把历史消息截断,或者做摘要后传给模型。没有 Trace 之前,这类问题只能靠日志盲猜,有了 Token 和耗时曲线,基本一眼定位。
3.3 在 Playground 里快速调整 Prompt 与参数
调试 LLM 应用时,最痛苦的事情之一就是改 prompt 时要跑一遍完整流程。以前我习惯在代码里改完然后重新运行测试,数据多的时候一次跑好几分钟。LangSmith 的 Playground 帮我省掉了不少时间。
在一条 Trace 里,右侧可以直接打开 Playground。它会自动把这条 Trace 当时的原始输入、prompt、模型参数都带过来。你可以在页面上直接修改 system prompt,切换模型版本,调整 max tokens 或者 temperature,然后点击运行,立刻看到新的输出。重点是它可以放在原 Trace 的上下文中重放,不需要再手动拼一遍请求参数。
我经常用它做“小步调参”:先改温度,看输出是否稳定;再改 prompt 措辞,对比效果。等改出满意结果之后,再把最终修改同步回代码。这个过程比“改代码->重跑整个 App->人工检查”快得多。不过要注意,Playground 只适合单轮快速验证,真正要评估某个 Prompt 是否整体可用,还是需要走到下一节的批量评估环节。
4. 评估 LLM 应用:从“人眼看输出”到“自动化回归”
4.1 离线评估:用数据集跑批量测试
跑通追踪之后,下一步是评估。我见过很多人把评估做成“看几个例子感觉还行”,这在 demo 阶段没问题,但只要你开始改 prompt、换模型、改检索策略,没有一套可重复的评估机制,很快就会被细节埋没。
LangSmith 的离线评估流程一般是:先准备一个包含测试问题的数据集,然后把同一批问题跑一遍新版本应用,最后用预置或自定义的评分器对结果打分。
创建数据集很简单,在控制台的数据集页面新建一个 Dataset,每个 Example 就是一个测试样本。通常包含两个字段:输入问题和期望答案。我习惯再加一个备注字段,写清楚这个样本想验证什么能力,比如“多轮上下文保持”“拒绝回答无关问题”“引用正确来源”。
然后写一段评估代码:
from langsmith import evaluate experiment_results = evaluate( lambda x: my_chain.invoke({"question": x["question"]}), data="ch8-demo-data", experiment_prefix="prompt-v2", )evaluate会读取数据集里的每一条样本,调用你传入的 lambda 或函数,自动生成实验记录。跑完以后,控制台会给出实验结果总览。你可以在实验列表里看到每个样例的详细运行轨迹,再也不用自己拿 Excel 一条条做记录了。
4.2 在线评估:给线上请求打反馈
离岸评估只能覆盖你准备好的测试集,线上真实用户提出的问题才是更丰富的样本。LangSmith 提供了 feedback 机制,可以直接记录用户对某次输出的反馈。
在线评估的核心是把 Trace ID 当作锚点。当用户对你的应用结果点了赞或者点了踩,前端把反馈信息发给你的后端,后端调用 LangSmith SDK 把反馈关联到对应的 Trace 上:
from langsmith import Client client = Client() client.create_feedback( run_id="具体trace节点的run_id", key="user_feedback", score=1 # 或者0,表示是否认可 )等真实反馈积累多了,你可以按反馈分数筛选出表现差的请求,再把这些请求整理成新的测试样本,加入数据集。这样你手里的评估数据会越来越逼近真实用户的使用习惯,而不是永远停留在几个自己编的问题上。
我自己的做法是:每周集中把线上反馈差的 Trace 过一遍,挑选有代表性的问题补充进数据集。每次改模型或者改 prompt 时,用扩充后的数据集重新跑评估,避免出现“一个例子修好了,另外五个例子又坏了”的回归。
4.3 自定义评估器:让评分更贴合业务
LangSmith 自带一些预置评估器,但实际业务里更多需要自定义评分逻辑。比如知识库问答场景,你可能关心回答是否包含指定关键词、格式是否规范、是否引用了知识库内容。一个简单的关键字判分器可以这样写:
from langsmith.evaluation import evaluate from langsmith.schemas import Example, Run def keyword_match(run: Run, example: Example): reference = example.outputs.get("answer", "") prediction = run.outputs.get("output", "") return { "key": "keyword_match", "score": int(reference in prediction), } results = evaluate( lambda x: my_chain.invoke({"question": x["question"]}), data="ch8-demo-data", evaluators=[keyword_match], )注意run是模型或链路执行过程的节点对象,example是数据集里的样本对象。你从run.outputs里取预测结果,从example.outputs里取期望答案,然后返回一个字典,key是评分项名称,score通常取 0 或 1。
如果你想用大模型当裁判,LangSmith 也支持把 LLM 作为评估器,但我的经验是:不要让同一个模型既生成答案又来给自己打分,容易有系统性偏差。至少也得换成不同系列的模型,或者限定好评分维度,比如“是否忠实于给定材料”“是否回答了用户核心诉求”。自动化评估的价值不是追求 100% 准确,而是让每次改动的优劣变化有痕迹、可对比。
5. 调试疑难杂症:几个真实问题的排查思路
5.1 模型输出被“带偏”:用 Trace 找到污染源头
有一次客服系统的模型突然开始输出与业务无关的内容,回复里甚至混入了“忽略以上指令”之类的字眼。我当时第一反应是模型被注入了。但注入到底发生在哪一步,没有工具前只能猜。后来打开对应 Trace,查看 LLM 节点的完整输入,发现问题不是出在用户问题本身,而是向量检索召回的一段文档里包含了外部文本,那段文本里带着改写系统提示词的恶意指令。
这个案例给我的启发是:LLM 应用的问题不要只盯着模型,你送到模型里的 prompt 才是整个系统的核心。LangSmith 把最终拼装完成的 prompt 原样记录下来,遇到异常输出时,首先要看的就是这串 prompt 里的 content 是不是和预期一致。只要发现混入了不该有的内容,就能顺藤摸瓜找到是检索、历史消息,还是在某个工具返回里带入的污染。
5.2 效果回归:两个版本跑同一数据集对比
改 prompt 导致效果下降,是另一个高频问题。我在初版 prompt 里加了一段“扮演谨慎的助手”的指令,原本是想减少胡编乱造,结果测试时发现很多本来能答的问题开始回答“无法确定”。靠肉眼对比几条例子很难判断整体影响,最稳妥的做法是让两个版本跑同一份数据集,然后看实验对比图。
操作上很简单:先基于当前代码跑一个实验,然后再跑另一个分支版本的实验。在 LangSmith 的控制台实验对比页里,选择两个实验对象,表格会列出同一个样本在两版下的得分差异。这时你就能清楚地看到哪一类问题变好了,哪一类问题变差了。如果整体分数下降,宁愿回滚到旧版本,再针对差的部分单独优化,而不是在下降的基础上继续硬调。
5.3 成本与延迟失控:揪出“吃 Token”的环节
还有一次线上 Agent 在特定场景下 Token 消耗暴涨,账单吓了我一跳。通过 Trace 我看到 agent 在回答过程中连续调用了同一个工具 6 次,每次工具返回的都是大段文本,模型之间又反复“思考”,最终请求总 Token 数是正常的几十倍。这种问题光看应用日志很难发现,因为单次工具调用看起来都正常,只有把整条 Trace 串起来才看到重复循环。
定位之后,我在代码里给工具调用加上了结果缓存,并限制了连续调用次数。再把改动后的应用跑一趟评估,Token 总量断崖式下降。其实优化 Token 消耗最重要的不是背模型参数,而是知道每一次调用到底为什么发生、消耗在哪。LangSmith 的 Token 统计和调用次数列表,基本就是你的成本账单明细。
6. 团队协作与持续改进的落地建议
6.1 项目命名与权限规划
一开始为了方便,我把所有测试请求都发到了同一个默认项目里,结果没过几天就分不清哪条 Trace 对应哪个业务。后来我强制自己按“业务-环境”的规则命名项目,例如产品问答的生产环境用product-faq-prod,测试环境用product-faq-test,不同团队再细分前缀。这样在 Trace 列表里一眼就能定位,也方便后续按权限隔离。
如果团队多人协作,尽量让每个人用自己的 API Key,并且在每个 Trace 上都能看到是谁跑的。这一点对排查问题很重要:当一条 Trace 出现异常,你能确认是不是同事在调试时临时改了环境变量,而不是线上流量出了问题。
6.2 把评估接入 CI/CD
自动化评估如果只在需要时才手动跑,很容易被偷懒跳过。我的建议是把评估写进 CI/CD 流程,像跑单元测试一样跑数据集。在合并代码之前,用固定的数据集评估一遍,分数不达标就阻断合并。下面是一段简化思路:
# ci_eval.py from langsmith import evaluate def evaluate_current(): results = evaluate( lambda x: build_chain().invoke({"question": x["question"]}), data="regression-set", evaluators=[keyword_match, json_valid], ) if results.average_score("keyword_match") < 0.9: raise SystemExit("评估未通过")这样每次改动是否真的有效,就会从一个“我觉得还行”的主观判断,变成一条是否通过 CI 的客观标准。一开始不用把阈值设得太高,先让流程能跑起来,再逐步收紧。
6.3 回归监控与告警配置
上线之后,LangSmith 的监控面板可以帮你盯住应用健康度。我优先关注三个指标:错误率、平均延迟、Token 用量。错误率一旦从 0.1% 跳到 5%,说明不是偶发问题;平均延迟突然上涨,常见原因是模型服务拥塞或者 prompt 膨胀;Token 用量异常,往往是代码里来了不必要的循环调用。
监控不需要一上来全指标都上。我自己是从错误率和 95 分位延迟开始,先跑一两周,确认正常曲线长什么样,再设置告警阈值。告警信息里我会把 Trace 列表链接一起带上,这样收到告警后,点击进去就是现场回放,不需要再跑去日志系统重建上下文。
最后分享一点个人经验
用了 LangSmith 这几个月,最大的变化是团队讨论问题的方式变了。以前同事说“模型今天效果很差”,我只能回“是吗?哪个模型?哪次请求?”,现在大家直接在问题单里贴一条 Trace 链接,点进去就是完整现场,省去了很多软磨硬泡的排查时间。
评估也是一样,我之前总想建一个几百条的数据集,结果维护成本很高,后来改成固定几十条高质量样本,每次动手改 prompt 或换模型之前先跑一遍,比人肉对比输出可靠得多。先把这一套跑顺,再慢慢扩大覆盖范围,比一上来就追求大而全要实用很多。