ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

LangSmith与Prompt Ops:大模型提示词的可观测性与工程化实践

LangSmith与Prompt Ops:大模型提示词的可观测性与工程化实践

刚上手大模型应用的时候,我最大的感受就是:写提示词一时爽,排查问题火葬场。同一个Prompt,一会儿回答结构完整,一会儿就漏掉关键条件,而且压根不知道问题出在模型理解上,还是上下文被截断了,又或者是某一步检索返回了空结果。直到我开始系统地把LangSmith和Prompt Ops的思路引入日常开发流程,这种感觉才慢慢扭转过来——不是靠运气调提示词,而是把提示词的开发、评估、监控和迭代变成一套能重复、可观测、能留存证据的工程流程。这篇文章就是我把这套思路从概念落到实践的经验总结,适合正在做LLM应用落地、被提示词调优和效果回归折磨的开发者,也适合刚接触LangSmith、想搞清楚它到底能干什么的团队。

1. Prompt Ops到底在解决什么问题

1.1 LLM应用工程的复杂性与定位

传统的软件工程里,代码行为是可预测的,逻辑路径清晰,出了bug可以靠断点、日志和单测来定位。但LLM应用不一样,它的核心依赖是带有概率性质的模型输出。同样的输入,几次调用结果可能不一样,甚至同一个上下文里加了一个无关字符,输出结构就可能整体崩塌。这种非确定性带来了两个连锁反应。

第一,对提示词本身的管理不能再靠聊天记录或者本地草稿。我见过很多团队,提示词散落在各个开发者的本地文件里,版本对不对、线上跑的是哪一版、效果退化时该回滚到哪一版,完全说不清。提示词已经是应用逻辑的一部分,甚至是核心逻辑,却连最基本的版本管理都没有。

第二,对模型输出的验证必须自动化。传统单测断言函数返回值,LLM的输出是自然语言,没法直接比“等于什么”。团队只能靠人眼抽查几条结果判断“大概还行”,一旦数据量上来,或者迭代频繁,这种人工验证根本无法支撑质量回归。

Prompt Ops要解决的,就是这两个问题:把提示词当代码来管理,把模型输出当指标来评测,同时把整个调用链路的过程数据完整记录下来,让每一次效果变化都有据可查。它不是什么高深理论,就是一套围绕着Prompt开发、优化、评估、监控、迭代的工程化方法论。

1.2 Prompt Ops:一套工程化的思路

如果你熟悉DevOps,会发现Prompt Ops和它非常像。DevOps把开发和运维合并,强调自动化流水线、可观测性和持续反馈,Prompt Ops则是把提示词的“开发”和LLM应用的“运行”打通,形成一条持续迭代的闭环。

这个闭环通常包含这样几个环节。

开发环节,你需要一个结构化的环境去写、测试、修改提示词,而不是在网页聊天框里凑合。评估环节,你得有标准的数据集和评估方法,能够量化“这版Prompt比上一版好还是坏”。部署环节,提示词变更应该像代码发布一样有记录有回溯。监控环节,线上应用的真实流量、反馈、成本和延迟都要被追踪,一旦质量下滑能及时发现。持续优化环节,用线上积累的真实数据回流到评估集,驱动下一轮Prompt迭代。

这五个环节合在一起,才是完整的Prompt Ops。只看其中一个,比如只做了评估没有监控,等于盲人摸象;只写了提示词没有评估和版本管理,等于裸奔。

1.3 LangSmith在这一生态中的定位

LangSmith是我接触到的最贴合上述闭环的工具,没有之一。它的核心覆盖面刚好对应Prompt Ops的五个环节。

它的追踪能力解决了可观测性问题:每一次LLM调用的输入输出、token数、耗时、延迟全部自动记录,而且能看到完整的调用链——不说一句话,就能还原整个调用过程。它的数据集和评估能力解决了效果度量问题:你可以把历史问题沉淀成测试集,用自动化评估判断Prompt改动是否引入回归。它的Prompt管理能力解决了版本问题:提示词可以像代码一样提交、分支、上线、回滚,并且保存每一次变更的完整历史。

更重要的是,LangSmith的深度可扩展性让它不只是“开箱即用”的无脑集成。你既可以在几分钟内完成基础追踪配置,也可以后续根据自己的需求添加自定义元数据、自定义评估指标、自动反馈机制。下面我会从核心功能到落地配置,逐一说明。

2. 核心功能逐个拆解

2.1 可观测性:从Trace到反馈

LangSmith的可观测性建立在Trace和Span两个核心概念上。一次完整的应用调用会生成一条Trace,Trace由若干个Span组成,每个Span代表这次调用中的一个逻辑单元,比如一次LLM调用、一次检索、一次工具调用、一次API请求。

这个设计很接近分布式追踪系统的思路。它的价值在于:当线上用户反馈“回答变差了”时,你不再需要靠猜,而是直接去Trace列表找到对应的那一条,逐层展开Span,看是哪一步偏离了预期。如果是检索环节返回了噪声内容,问题很可能出在Embedding或索引上;如果是LLM调用本身输出异常,你就能看到当时的完整Prompt、模型参数和原始输出。

除了结构化的链路信息,LangSmith还自动记录每次LLM调用的元数据:模型名、温度、top_p、最大token数,以及精确到毫秒的耗时和成本估算。我记得有一次线上服务超时,我一开始以为是模型API卡了,查了Trace才发现是某次工具调用嵌套了一个外部请求,这个外部请求才是耗时的真正来源。没有Trace这层数据,这类问题定位效率会低很多。

LangSmith还支持以Human Feedback或自动化反馈的方式,把用户的“评价”回填到Trace上。在UI界面里,你可以对任意一条Trace打“评分”,或者用API把业务侧的用户评价(点赞、点踩、问题被解决与否、回答是否有用)关联到对应Trace上,为后续筛选样本提供了极大的便利。

2.2 数据集与评估:让效果可衡量

如果说Trace解决的是“发生了什么”,数据集和评估解决的是“做得好不好”。LangSmith的数据集(Dataset)是一个结构化的测试样本集合,每个样本至少包含输入,以及一条可选的参考输出(Reference),你还可以自由扩展额外的字段,比如被骗哪里需要特殊处理的上下文、期望拒绝的类别、情绪标签等。

数据集和评估跑起来的方式是:选择一批样本,跑一个Evaluator(评估器),生成打分结果。LangSmith本身提供了几种开箱即用的评估器,例如判断输出是否和参考输出一致,或判断输出是否避免出现特定词汇,你还可以在本地写自定义评估逻辑把它注册成评估器。

这种机制的意义非常大。它意味着你可以把每一次线上出问题的case沉淀到数据集里,形成回归测试集,然后在每次Prompt改动后跑一遍完整数据集,对比评估结果。效果是升是降,一目了然。我见过很多团队在使用LangSmith之后,“又跑挂了”“这版不太行”的说法,慢慢被“这版在某些类别上落后了3个百分点”所取代,这是本质上的变化。

2.3 Prompt版本管理与Hub

LangSmith将Prompt管理从简单的文本存储提升到了版本化、可复用的层面。在Prompts页面中,你可以创建并保存一个Prompt,然后持续迭代它的内容与配置。每一次变更都会产生一个新版本,你可以把线上正在使用的版本与最新版本进行对比,在需要时可以一键回滚到历史版本。

这不仅仅是代码仓库式的管理,它还引入了“提交”与“上线”的概念。LangSmith的每个Prompt版本都可以被标记为生产环境版本。应用代码可以引用某个特定版本,也可以设置为始终拉取“最新生产版本”,于是你在不改动代码的情况下,切换Prompt版本就像切换配置一样方便。

加上Prompt Hub,这种共享模式被进一步放大。你可以把调试好的Prompt模板提交到一个共享空间,团队内部可以直接引用别人贡献的Prompt,互相在评估集上进行对比,从而减少重复造轮子的成本。如果你的团队已经有了一套成熟的Prompt资产,把它们全部纳入LangSmith管理,会是一个极其有效的知识沉淀方式。

3. 落地实操:从零集成LangSmith

3.1 前置准备与基础配置

使用LangSmith的前提是有一个LangSmith账号,创建一个API Key,并确定项目(Project)名称。注意这里项目并非传统意义的“代码项目”,而是追踪数据(Trace)的分组维度,同一个Project下可以查看该业务方向全部调用链。

在代码侧你需要安装sdk。如果你使用Python,通常需要安装langsmith包:

pip install langsmith

如果你的应用基于LangChain,会简单很多,因为LangChain自身已经集成了LangSmith的处理逻辑。你只需要设置环境变量,后续的自动埋点就会在你创建链、调用LLM时自动生效。

import os os.environ["LANGSMITH_TRACING"] = "true" os.environ["LANGSMITH_API_KEY"] = "lsv2_...你的key..." os.environ["LANGSMITH_PROJECT"] = "your_project_name"

LANGSMITH_TRACING控制是否启用追踪,LANGSMITH_PROJECT指定数据归入哪个项目。如果你用的是OpenAI等模型,LangSmith会自动捕获模型API的调用信息,包括model名、token数、响应内容、延时。这就是最基础的“自动埋点”。

如果你想进一步控制哪些调用进入追踪,可以手动决定是否开启追踪:

from langsmith import Client client = Client() with client.trace("manual_trace"): result = my_llm_call("你好,请介绍一下你自己")

手动trace的好处是你能够精确控制追踪粒度,毕竟不是每个内部函数都值得被记录。但如果你只是想让项目尽快跑起来,建议先开启全局自动追踪,后续再按需调整。

3.2 用LangChain构建应用时的详细埋点方法

如果你的代码是基于LangChain写的,默认情况下的埋点已经非常全面,但你还应该学会为函数添加额外的元数据,这会在后续排查问题时发挥很大价值。

以一段简单的问答链为例:

from langchain.chat_models import ChatOpenAI from langchain.chains import LLMChain from langchain.prompts import ChatPromptTemplate from langsmith import traceable llm = ChatOpenAI(model="gpt-4o-mini", temperature=0.7) prompt = ChatPromptTemplate.from_messages([ ("system", "你是一名专业的法律咨询助理,请基于给定文档回答,若信息不足请明确说明。"), ("human", "问题:{input}\n文档:{context}") ]) chain = LLMChain(llm=llm, prompt=prompt) @traceable def run_query(user_input: str, document: str, user_id: str) -> str: # user_id等业务信息一并传给LangSmith return chain.run({"input": user_input, "context": document}, metadata={"user_id": user_id}) result = run_query("未签字的合同是否有效?", "合同法摘要...", user_id="U12345")

这段代码里,user_id会被作为metadata记录到Trace上。将来线上突然集中出现同一类用户反馈时,你可以按user_id去过滤,精准定位某个用户或某一批用户遇到了什么样的链路问题。

如果你用的不是LangChain,而是直接调用一个模型SDK,也不影响追踪。LangSmith提供装饰器模式,把任意函数包成一个可追踪的Span:

from langsmith import traceable import openai @traceable(run_type="llm", name="openai_chat_completion") def call_model(prompt: str, model: str = "gpt-4o-mini"): resp = openai.ChatCompletion.create( model=model, messages=[{"role": "user", "content": prompt}] ) # 将用到的token与消息内容持久化到Trace上 return {"tokens": resp.usage, "content": resp.choices[0].message.content} output = call_model("翻译这句话")

这里最需要注意的是,run_type参数建议标注清楚(llm、chain、tool等),因为LangSmith在UI里会根据run_type对节点进行不同渲染,错误归类会导致排查链路时视觉混乱。

3.3 构建数据集与跑通评估的基本姿势

评估能力,我个人认为是最值得优先建设的一块。先用最纯粹的方式说明:你有一个数据集,你要把模型输出的结果交给一个打分器,打分器返回一个分数。

假设你要做一个客服摘要应用,期望摘要必须包含“客户姓名、问题类别、解决方案”三要素。你的评估器可以这么写:

from langsmith import Client from langsmith.schemas import Run, Example client = Client() def check_summary(run: Run, example: Example) -> dict: required = ["客户姓名", "问题类别", "解决方案"] output = run.outputs["summary"] if run.outputs is not None else "" missing = [k for k in required if k not in output] score = 1 if not missing else 0 return {"key": "summary_completeness", "score": score, "comment": f"缺少:{missing}"}

然后创建一个数据集并提交样本:

dataset_name = "客服摘要回归集" # 首次创建 dataset = client.create_dataset(dataset_name=dataset_name) client.create_example( dataset_id=dataset.id, inputs={"dialogue": "客户张伟来电反应无法登录,客服重置密码后解决。"}, outputs={"summary": "客户姓名:张伟;问题类别:账户登录;解决方案:重置密码。"} ) # 再把你的链在数据集上跑一遍,得到Run记录 # 每个Run会与Example关联,然后调用评估器 results = client.evaluate( run_query, data=dataset_name, evaluators=[check_summary] )

运行结束后,在UI里你能看到每条样本的得分,以及所有的Trace细节。如果你有多个版本的Prompt,可以各跑一遍,直接对比两列分数,效果差距一目了然。

检查项这个思路还可以扩展到很多维度,比如“是否避免出现X类词”“输出是否包含引言”“格式是否合法”等。把这些指标设计好,你的Prompt迭代就有了客观依据,而不是总在靠感觉微调。

3.4 用反馈与元数据进行线上实时监控

线上评估是许多人的盲区。很多人测试集上跑得很好,一上线立刻崩。原因就是测试集太小太干净,和真实用户输入之间有巨大差异。LangSmith可以通过反馈(Feedback)机制把线上用户互动数据拉回质量评估体系。

例如某次回答被用户点踩,你可以通过API把该结果标记为负面反馈:

from langsmith import Client client = Client() run_id = "你从Trace里取到的Run ID" client.create_feedback( run_id=run_id, key="thumb_down", score=0, comment="用户投诉该回答不符合诉求" )

也可以接入业务数据进行自动反馈,比如如果用户在通话结束后的问卷调查中明确打低分,就把这个分数同步到LangSmith对应的Trace上。一旦这些反馈量积累起来,LangSmith就能自动聚合成各类质量指标,当异常波动发生时触发人工介入。

线上追踪精度还可以通过采样率来控制。在高并发业务场景下,全局所有请求全部追踪会产生较大开销。LangSmith支持设置追踪采样率,比如20%:

os.environ["LANGSMITH_TRACING_SAMPLING"] = "0.2"

我的建议是初期全量追踪,等你能看到大部分常见case之后,再按需降低采样率。追踪的本质是为了发现未知问题,覆盖率太短信噪比反而低。

4. 我在实操中踩过的坑与排查技巧

4.1 常见错误及原因

我刚开始接入时,最常遇到的是三类问题。

第一类是环境变量忘了设置,尤其在不同环境切换时。比如你在本地开了自动追踪,但由于没有配置LANGSMITH_API_KEY或LANGSMITH_TRACING,导致LangSmith根本没有追踪数据。这个问题其实很容易排查:如果UI上完全看不到新的Trace数据,基本就是环境变量没有正确传到当前进程。常见原因是使用了.env但没有加载,或者为了调试把LANGSMITH_TRACING手动置成了false。

第二类是数据集和评估脚本的字段名对不上,尤其是Example的inputs和outputs字段,与你的应用抽取逻辑不完全一致。比如你在评估器里写了run.outputs["summary"],但你的链返回的字段叫result,这时候就会取不到数据,得到全部分数异常的反馈。遇到这种问题,先去UI里检查一条评估失败的Run的输出结构,再回头核对字段名。

第三类是模型SDK和LangSmith兼容性产生冲突,特别是当你对某个模型使用了异步调用,又使用了多线程任务,可能导致Trace的父子关系混乱,甚至串Trace。从实测来看,简单同步调用一般很稳定,涉及并发时务必检查是否有竞态问题。我的做法是避免在多线程场景里直接依赖自动追踪,必要时手动给每个线程单独创建独立Project或加上隔离的metadata,以便定位数据归属。

4.2 针对真实问题的定位排查法

当你面对“线上大量反馈质量下降”这种模糊问题时,最好的做法就是用LangSmith的Trace列表逆推。先按Project和关键词筛选,找到一段典型用户的真实Trace,重点看三点:

第一,看LLM调用时的实际输入Prompt是否和你在Prompts页维护的一致。如果发现线上Trace里很久之前就已经跑在了旧版本Prompt上,说明你虽然更新了Prompts,但代码拉取的还是旧版本,没有正确绑定。

第二,看检索或外部工具的Span,是不是把无关于扰信息带了进去。很多“回答质量变差”的根因并不在Prompt本身,而在于上游上下文污染,Trace能帮你彻底定位。

第三,看返回结果是否触发了某个评估器规则。如果某一类问题频繁发生,你可以把这个case加入回归数据集,以后每次改动都先跑一遍全套。这个沉淀过程越早做越好,我见过比较好的做法是每周把本周围绕线上问题的问题样本补充进数据集,让回归集始终贴近现状。

4.3 避坑建议清单

问题/情形建议做法原理说明
忘记设置环境变量,没有追踪数据把LANGSMITH_TRACING、LANGSMITH_API_KEY、LANGSMITH_PROJECT集中放入启动配置追踪功能依赖环境变量驱动
数据集字段名频繁变动定义明确的输入输出Schema并固定字段评估器和字段强耦合,改名会导致评估结果失效
高并发导致的追踪数据错乱调低采样率,或给不同业务使用不同Project降低数据量,同时保证链路完整性
分不清线上Prompt版本用PromptVersion等元数据字段标记到Trace上排查问题时能直接定位实际执行的Prompt版本
测试集效果不错,线上实时质量下滑尽快收集线上负面反馈,构成负面样本集测试集偏正面衰减,线上反馈才是准确信号

4.4 实践中的性能与成本控制经验

追踪是很好的能力,但它会带来额外开销。在超大规模并发场景下,全量写Trace可能导致存储成本上升和服务延迟增加。这方面的经验是,把项目拆分为多个Project,把高频低价值的链路(比如基础健康检查、内部自动测试)单独立项且关闭追踪,仅让用户可感知的关键业务链路进入追踪。

同时要注意成本数据的准确性。LangSmith自动记录的token数依赖于模型API返回的usage信息,如果你在应用中使用了缓存、或者手动改了返回结构,token统计可能偏差。遇到这种情况,我会在代码里显式传入token_usage字段,确保成本报表在可接受误差范围内。

还有一点是关于隐私合规。追踪链路里可能包含用户信息或敏感文本。我在实践中尽可能只把业务标识符写入metadata,避免把完整业务数据原样强制写入Prompt的输入字段。假如你不可避免地要记录复杂上下文,我建议评估一下数据保存周期和脱敏策略,并尽量只在指定Project中保留测试数据。

5. 扩展玩法与前后端协作的联动思路

5.1 把LangSmith接入现有标注平台的路径

如果你所在团队已经有一套人工标注平台,不必把标注流程整体迁移到LangSmith。一个很自然的做法是:在标注平台中创建一个“是否满意”的字段,之后将人工标注结论通过API写回LangSmith的对应Run。反过来,当LangSmith的评估器在某个样本上发现异常时,也可以触发消息通知,提醒标注人员在平台内复核。

这样的双向联动核心在于,每个业务样本都必须能与LangSmith的Run ID一一对应。常用方案是,在你生成对话结果时,把模型返回结果页面里携带的Run ID和业务ID一起写进业务数据库,后续通过该ID做关联与同步。这个方案我今天仍然认为是成本最低、维护最简单的方式。

5.2 评估结果如何反过来改进上游流程

Prompt Ops的最终目的是给用户一个好的结果,而真正高质量的LLM应用永远不只是靠优化一个Prompt完成的。我遇到过醒目案例:某团队客服机器人的Prompt怎么调都容易漏信息,后来在LangSmith Trace中看到了检索数据源本身存在大量陈旧信息,于是进行了数据清洗与字段增强,效果立刻大幅提升。

把LangSmith当做一个“放大镜”去反推上游:数据质量是否达标、上下文切分是否合理、模型参数选择是否恰当、响应链路是否有冗余调用。评估结果与线上反馈,其实是一整套可以指导全链路优化的线索,而不只是服务于Prompt的局部调参。

5.3 团队内共享Prompt与评估资产的几点建议

当团队扩张到多业务线时,如果没有统一管理,每一个算法工程师都会维护自己的一套Prompt与测试集,最终会出现“你优化你的,我优化我的”,整体收益非常有限。LangSmith的多用户和权限协作机制帮助缓解了这种割裂。

建议的做法是:每个业务方向维护统一的Project和数据集,新增Prompt一律先评估,后上线。任何版本发布之前,至少在回归数据集上跑一遍。同时,将制定的评估标准或重要评估器分享到公共空间,形成流水线式协作。这样一来,新的同学能快速上手,也减少了大量无意义的重复劳动。

5.3.1 团队协作中最容易被忽视的细节

团队协作还需要建立某种共识:评估器本身也需要测试。我见过一个团队写了复杂的评估正则,却因为正则规则过于严格,导致所有输出整体得分为零,反而刺激大家去“优化Prompt使其符合正则格式”。这种“为分数服务”的情况会扭曲业务目标。所以评估器本身应该尽量保持简单,并且每隔一段时间用真实样本来验证它仍然符合预期。简单、可解释、贴近业务评判标准,才是评估器耐用的根本。

5.3.2 对频繁变动的提示词做差异对比

Prompt的修改如果幅度过大,可能根本不像是同一种风格的调整。有时候只是加了一段话,就可能让模型在多个维度上表现迥异。LangSmith的Prompts页面有版本对比功能,你可以直观看到两个版本之间的文案差异、参数差异和评估结果差异。这也是我每次上线前必须走的一个动作:检查差异,确认改动意图,然后再评估上线。

5.4 将LLM应用变成有据可查的产品

我在这个领域最深的感触是:LLM应用很快会被“产品化”,而产品化的重要前提之一就是“状态下可记录、问题可复现、效果可比较”。LangSmith在技术上帮了大忙,但更重要的是要形成一整套以质量指标为核心的开发习惯。模型能力再强,如果团队靠“感觉”来迭代,最终交付的风险会很高。

做一个比喻:没有监控的分布式系统与有监控的分布式系统,在故障面前的状态完全不同,前者靠猜,后者靠证据链。Prompt Ops正是大模型应用生态中那套至关重要的“证据链基础设施”。

在我自己的实践中,我会首先固定项目与数据集,将各种标准评估器建立完善,再把线上反馈持续回流。这些动作看上去不起眼,却是整个AI应用可持续迭代的真正底气。经历过从“混沌式调Prompts”到“工程化地管理Prompt与效果”,再回头看最初的无序,你会确实感受到这条路是值得走的。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进