你有没有过这种体验上周线上一个 RAG 问答答得挺好这周用同样的问法再问回答却跑题了。你去翻日志只看到一行 HTTP 200 和模型吐出的字符串至于它当时检索了哪些文档、套了哪个版本的 prompt、为什么拐进那条工具调用路径一律查不到。问题就出在这里——LLM 应用是非确定性系统同样的输入在不同时刻、不同温度、不同检索结果下会产生不同输出。传统日志记的是发生了什么而你需要的是为什么发生以及每一步的中间态。这正是可观测性要先于优化的根因你没法优化一个看不见的过程。非确定性同样输入下一秒可能是另一个答案把 LLM 应用当成普通后端服务来排障是很多人踩的第一个坑。一个普通的 CRUD 接口同样的请求两次执行路径完全一致但 LLM 应用的每一步都带着随机性。造成差异的来源至少有这几处采样温度temperature 不为 0 时decode 本来就是按概率分布抽样两次生成天然不同。检索漂移向量库的召回结果会随数据更新、索引重建而变动retriever 这一步本身就不稳定。工具副作用同一个工具在不同时间返回的数据可能不同实时行情、库存、天气下游 prompt 拿到的上下文随之改变。隐式版本你以为 prompt 没动其实上游拼接逻辑、few-shot 样例、系统指令哪天被悄悄改了一行自己都忘了。所以当你说这次回答好像比上次好你其实是在拿两个不可复现的随机样本做主观对比。没有把每次运行的完整链路固定下来你的好没有任何可比基线。一次 trace 里到底装了什么LangSmith 的核心动作叫 tracing把一次运行run从头到尾的每一步都结构化记录下来。一个 run 不是一条日志行而是一棵树树上的每个节点是一个 spanspan 之间还有父子关系。用traceable注解你的函数调用时就会自动生成 spanfromlangsmithimporttraceablefromlangchain_openaiimportChatOpenAItraceable(nameretrieve,run_typeretriever)defretrieve(query:str)-list:# 召回相关文档并保留 score 方便复盘returnvectorstore.similarity_search_with_score(query,k4)traceable(namegenerate,run_typellm)defgenerate(query:str,docs:list)-str:llmChatOpenAI(modelgpt-4o,temperature0.2)returnllm.invoke(build_prompt(query,docs)).content一个完整的 trace 至少该包含这些字段Input / Output这一步真实的入参和返回已渲染好的最终 prompt 全文。Model 与参数具体模型、temperature、top_p杜绝我以为是那个版本。Retriever 结果召回的文档片段和对应的相似度分数。Tool Calls调用了哪个工具、传了什么参数、拿到什么结果。Token 用量prompt_tokens / completion_tokens直接挂钩成本。延迟与成本每一步和整体的 latency、单次花费。这些字段合在一起某次回答变差时你不用猜——直接点开那条 trace看是 retriever 召回错了文档还是 model 抽到了一个低概率 token。三件套不是功能清单是一个飞轮很多人把 LangSmith 当成一个能看 trace 的面板这是把它用小了。Tracing、Dataset、Evaluation 三者的关系不是并列功能而是一个自我强化的飞轮。Tracing 是原料每一次线上/线下的运行都在产生 trace这是最真实的系统行为样本。Dataset 是沉淀你从 trace 里挑出有价值的 case典型问题、边界场景、历史事故固化成可复现的测试用例集。Evaluation 是度量在新 prompt / 新模型 / 新检索策略上把这个 dataset 跑一遍得到可量化的分数作为这次改动好坏的判据。关键点在于闭环eval 的结果告诉你哪里还不行你针对性改完再跑 eval 验证——改动的每一轮都有数据兜底。没有 dataseteval 就是空转没有 eval你根本不知道这次改动是前进还是倒车。开启 tracing 只需要几行环境变量之后任意 LangChain 调用自动上报importos os.environ[LANGCHAIN_TRACING_V2]trueos.environ[LANGCHAIN_API_KEY]lsv2_xxxos.environ[LANGCHAIN_PROJECT]rag-prod-monitor# 此后所有调用自动产生 trace沉淀测试集并在其上评估同样几行就能跑起来fromlangsmithimportClientfromlangsmith.evaluationimportevaluate clientClient()# 从复盘过的 trace 中固化回归集datasetclient.create_dataset(rag-qa-regression,descriptionRAG 回归集覆盖多跳、拒答、幻觉高风险场景,)client.create_examples(inputs[{question:A 和 B 谁先成立}],outputs[{answer:A 先于 B依据在公司年报第 3 页}],dataset_iddataset.id,)defpredict(inputs:dict)-dict:return{output:generate(inputs[question])}evaluate(predict,datarag-qa-regression,evaluators[exact_match,correctness_with_llm],experiment_prefixv0.3-prompt-tune,)反模式靠感觉好点了驱动迭代我见过太多团队prompt 改一版就发到群里说感觉好多了然后过两周发现老问题又冒出来却没人能说清是哪次改动引入的。这就是典型的盲飞式迭代。看一段反模式代码# 反模式没有 trace没有 baseline纯靠肉眼defchat(user_msg):replyllm.invoke(user_msg).contentprint(reply)# 人肉读一遍觉得还行returnreply# 什么都没留下无法回归它的危害不是这次答错了而是没有基线你不知道改动前的得分是多少自然判断不了变好还是变坏。无法回归老问题复发时你手里没有一份能复现它的测试用例。归因失效多个改动叠在一起出事了你分不清是哪个坑。协作崩塌团队里每个人都在感觉结论互相矛盾谁也说服不了谁。对照之下有数据支撑的迭代是另一条路径改 prompt → 在固定 dataset 上跑 eval → 分数从 82% 掉到 79%立刻知道这次改动引入了回归→ 打开对应 trace 定位是 retriever 召回退化还是 model 抽样偏移 → 回滚或针对性修复。两种做法的差距在一次两次改动里看不出十次八次之后就是系统越改越稳和系统越改越乱的天壤之别。LangSmith 坐在你开发闭环的哪一站把视角拉到整个开发生命周期LangSmith 不是某个环节的工具而是横穿开发闭环的一条主线。从你写代码那一刻起traceable就把可观测性织进了函数本地调试时 trace 实时可见你把复盘过的 case 沉淀成 Dataset每次提交用 Evaluation 当门禁分数不达标不让合并上线后生产环境的 trace 继续回流新的失败 case 又被捞进 dataset。这样 Tracing、Dataset、Evaluation 三件事在同一个数据平面上循环转动。所以回到开头那句话——你无法优化你看不见的东西。LangSmith 给你的不是一个看日志的面板而是一套让 LLM 应用从玄学调参走向工程迭代的基础设施。先把可观测性立起来优化才有锚点先有 dataset 和 eval每一次改动才走得踏实。