2026/10/22 8:11:56

LangSmith实战:用可观测性拆解Agent黑箱

LangSmith实战:用可观测性拆解Agent黑箱 先说个我真实踩过的坑。某开发者把一个带工具调用的Agent丢到测试环境第一次问“某个客户的账为什么对不上”Agent调用财务接口查到一笔异常紧接着又自作主张调了订单接口、还调了库存接口最后基于三个接口的数据“推断”出一份错误结论。整个过程代码没有任何报错普通日志里十几行输出你根本分不清它到底在哪一步开始跑偏。这种问题一套通用日志根本抓不住非得上追踪系统不可。这就是Agent开发的典型处境模型给你生成出一条执行轨迹但轨迹本身不可见。你越想把Agent做复杂越要先解决“看得见”的问题。所谓AI Agent Harness Engineering直白讲就是给Agent装上完整的外围工程设备——工具注册、权限边界、循环控制、超时回退、记忆管理、可观测性这一整套统称harness。而LangSmith正是这套体系里负责可观测性和评估的那一块全链路追踪、耗时与token分析、可视化、以及基于真实trace做回归评测。这篇文章不聊怎么写prompt而是站在Harness Engineering的角度把LangSmith怎么拆掉Agent黑箱这件事讲透。我会从概念、配置、排障、评估四个层面展开附上可直接抄的实操步骤和踩坑心得适合正在用LangChain搭Agent、或者已经受够“print大法”的工程同学。1. Agent Harness 到底在解决什么问题1.1 Agent不是“一个模型调用”而是一条动态执行链先统一一下语境。我见过的Agent应用无论包装得多花哨本质上都是在一个循环里反复执行“思考→调用工具→观察结果→再思考”直到模型认为任务完成。这个循环里任何一次“再思考”都可能改变后续方向和工具选择所以Agent应用根本不是单个LLM调用而是一条运行期动态生成的执行链。这条链有两个工程特点不可预枚举链条的长度、分支、工具组合是模型现场决定的开发阶段没法把所有路径测一遍。不可强约束你没法写死“只能调这两个接口”模型会根据上下文自己判断。这就引出Harness Engineering的必要性。你没法预判模型会做什么但你可以给它的行为装上轨道和仪表盘。轨道就是工具注册表、权限校验、参数校验、超时控制仪表盘就是结构化日志、全链路追踪、指标监控、trace回放。轨道解决“它不能乱来”仪表盘解决“它做了什么你看得见”。LangSmith属于后者它带给你的不是让Agent变聪明而是让你能审问它。1.2 裸模型天然“黑箱”的三个根源第一个根源是采样生成。同样一个问题你连问两遍模型可能选择不同的工具、给出不同的推理路径。这是它的工作机制决定的不是bug但对你来说就是不确定性。如果你只记录最终答案整个决策过程就是一笔糊涂账。第二个根源是中间推理不显式暴露。模型内部那些“我觉得应该先查一下A再对比B”的过程只有一部分会出现在文本里而且有时候它还“想”了没说。你以为它跳过了某一步其实它只是没把思考过程完整输出。第三个根源是工具调用链路的复杂叠加。一次Agent运行可能要调外部API、查数据库、处理文件、做向量检索每一环都可能出错而且出错方式千奇百怪超时、空结果、JSON格式不对、数据本身脏。模型拿到异常结果后还会基于错误信息继续推理错误就像滚雪球一样被放大。这三个根源叠加就构成一个真正的黑箱你不知道输入之后发生了什么也不知道那句错误的结论到底从哪一步开始蔓延。1.3 为什么print日志顶不上我知道很多团队现在的做法是到处打日志print(step)、print(tool_result)、print(prompt)。坦白讲我早期也这么干过。试过几次后你会发现它有三个硬伤没有层次结构。一个Agent主循环里嵌套工具调用工具内又发LLM请求print打出来全是平铺的线性文本。你看到10行日志但哪个是父调用、哪个是子调用、哪个是重试、哪个是并行分支完全靠脑补。内容被截断。工具返回一大段JSON你一print就是几千行或者直接在日志里被截断。真正出错的那一半内容往往被切掉了。没有成本和时间维度。每个步骤消耗多少token、花了多久print打不出来而这两个指标对定位性能问题和成本爆炸至关重要。所以需要的是一个专门按“树形执行拓扑”来记录的系统父节点是Agent循环子节点是工具调用孙节点是底层LLM请求每个节点都记录输入、输出、耗时、token、状态。LangSmith的追踪模型就是按这个思路设计的。2. LangSmith追踪模型拆解从一次调用到一棵Run Tree2.1 Trace、Span、Run到底指什么LangSmith里最基本的对象叫Run每个Run代表一次独立执行单元。一个LLM请求是一个Run一次工具调用是一个Run一个Agent循环也是一个RunRun之间可以嵌套。嵌套关系形成一棵Run Tree。整棵树的根节点是Agent的入口调用叶子节点是最底层的LLM请求和工具执行。每次你在LangSmith打开一个Trace看到的就是这样一棵树。用快递物流来类比整个大包裹从下单到签收是一条完整流水线Trace流水线里每个节点的处理动作是一条操作记录Run而它们之间的包裹关系就是嵌套结构。物流公司能告诉你包裹卡在哪个中转站LangSmith能告诉你Agent卡在哪个环节。2.2 自动追踪原理LangChain回调机制LangSmith之所以好用很大程度是因为它和LangChain深度绑定你基本不需要改业务代码。LangChain内部有一套回调机制CallbackHandler。你在链上创建Agent时LangChain会在每个关键节点触发事件LLM开始调用、工具开始执行、链开始运行。LangSmith通过注册一个回调处理器把这些事件自动采集下来组装成Run Tree再上报到服务端。这意味着你只要设置好环境变量现有的LangChain代码跑一遍追踪就自动出现了。我自己的项目从零接入到看到第一条完整Trace只花了不到十五分钟其中一半时间还是在找API Key。如果你不用LangChain直接用其他框架或者裸调模型接口LangSmith也有独立SDK可以用后面我会讲手动埋点的方式。2.3 手动埋点覆盖追踪盲区自动追踪覆盖的是LangChain标准组件。但真实系统里总有一些代码不在LangChain体系内你写了一个自定义函数从公司内部系统拉数据你调了一个外部HTTP服务你做了复杂的后处理逻辑。这些盲区会导致一棵树缺枝少叶一旦问题出在这里Trace上就是一片空白。这时候就需要手动埋点。LangSmith提供traceable装饰器你把它加到自定义函数上LangSmith就能自动记录函数名称、入参、出参、耗时from langsmith import traceable traceable(run_typetool, namefetch_invoice_data) def fetch_invoice_data(bill_id: str): # 拉取财务账单数据 return client.query(bill_id)加上装饰器之后这个函数在Trace里就会变成一个独立节点有输入输出、有耗时。对于不在LangChain体系内的自定义逻辑这是最优雅的补盲方案。还有一个建议善用metadata字段。每次Agent执行时把业务相关的信息塞进metadata比如session_id、user_id、order_id、environmentfrom langchain_core.runnables import RunnableConfig config RunnableConfig( metadata{ session_id: abc-123, env: staging, user_id: user_42, } ) agent.invoke(user_input, configconfig)这些元数据不会出现在模型可见的内容里但会在Trace中保留方便你按业务维度筛选。比如排查“哪个商家的Agent调用尤其慢”你不需要翻原始日志直接在过滤条件里输入user_id就行。3. 从零接入LangSmith完整配置与第一次全链路可视化3.1 三步完成接入第一步注册并拿到API Key。进入LangSmith平台在设置里创建API Key记下以ls__开头的那串字符。第二步配置环境变量。在启动Agent服务的机器上设置以下三个关键变量export LANGCHAIN_TRACING_V2true export LANGCHAIN_API_KEYls__your_key_here export LANGCHAIN_PROJECTagent-harness-demo如果你在本地测试也可以写在代码里但建议用环境变量避免把Key提交到Git仓库。第三步重新运行你的Agent。只要代码基于LangChain构建Trace就会自动上报。打开LangSmith控制台选择对应的Project你就能看见刚才那次执行的完整轨迹。3.2 不要省掉Project规划这一步LANGCHAIN_PROJECT这个变量是很多人刚开始会忽略的。如果不设置所有Trace会堆在默认的Project里开发、测试、生产不分家后面想按环境筛选会很痛苦。我建议至少按环境分三个Projectagent-dev、agent-staging、agent-prod。如果项目足够大还可以按业务线分比如agent-payment、agent-invoice、agent-risk。成本核算、性能分析、错误定位全都依赖这个基础分区。同时给每次运行的根节点起个有意义的Run名称别用默认的AgentExecutor。名称里带上业务语义比如invoice_reconcile_agent在列表页扫一眼就能知道哪条Trace对应哪个业务场景。3.3 在UI和API中读一次Trace打开一条Trace你会看到从根节点到叶子节点的完整树形结构。每个节点都能展开展示该节点的输入输出、耗时、token使用量、错误信息。我习惯按三个层次看耗时视角找出耗时最长的节点通常是性能瓶颈所在。如果一个工具调用占了总耗时80%问题大概率不在模型而在下游接口延迟。token视角看每个节点的输入输出token数重点观察消息列表有没有异常膨胀这是上下文超限和成本飙升的前兆。错误视角红色节点代表异常点开能看完整报错堆栈不用再翻服务器日志。除了网页LangSmith还提供Python SDK可以编程式读取Run数据。这对批量分析特别有用from langsmith import Client client Client() runs client.list_runs(project_nameagent-prod, execution_order1) for run in runs: print(run.name, run.start_time, run.error)你甚至可以把过滤条件下的Trace数据拉下来做聚合统计比如算平均工具调用次数、错误率、token成本分布。这些数据喂给监控系统就形成了Agent自己的“业务指标”。4. 可视化之后三个“黑箱”被照亮的真实排查案例4.1 案例一Agent陷入工具调用死循环现象是Agent在测试环境中不退出一直调用同一个查询工具直到被外层超时强杀。代码里明明有max_iterations限制但线上似乎没生效。Trace里看得很清楚根节点下面的工具调用节点长得一模一样连续出现七八个相同的子节点每个节点的输入只是微微变了几个字。这是经典的“模型认为还需要更多信息但工具返回的内容不满足它”的循环。根因是模型在几次工具调用后没有判断出“已经拿到足够信息可以收尾”于是一次次发起新调用。可视化让这个模式变得极其显眼平时靠日志根本察觉不到。解法分两层在harness层把最大循环次数从10改为5并加上熔断逻辑在工具层增加“查询结果是否已足够”的提示让它尽早收敛。改完后再次回放Trace节点数大幅下降。4.2 案例二工具返回解析失败后模型开始“脑补”有个Agent经常给出一本正经的错误结论。代码没有报错但答案明显是编的。这种问题最恼人因为它不像调用失败那样有明确的异常节点。Trace里点开出错前的那次工具调用发现工具返回了一段结构不完整的JSON解析器抛了警告。但警告只被当作日志记录没有被当作错误中断模型拿到的仍然是那段半截文本。随后模型带着残缺数据继续推理在下一步的LLM调用里输出竟然自动“补全”了缺失字段。这就是黑箱的典型表现工具层的错误被模型吞掉然后模型生成了一段看似合理的解释。如果没有Trace你根本找不到那半截JSON才是万恶之源。修复方式是给工具调用加上严格校验返回结构不对就抛异常让Agent进入错误处理分支而不是把坏数据传给模型。同时在渲染工具结果时对解析失败标一个PARSE_ERROR前缀让模型明确知道这次数据不可信。4.3 案例三上下文被日志和工具输出撑爆某Agent运行到某个节点后直接报上下文超限。模型侧的提示词根本改不出效果因为问题不在模型而在喂进去的内容太多。Trace里查看消息列表的token变化发现工具返回的历史记录被完整塞进了下一轮输入每一次循环都翻倍膨胀。日志内容、工具输出、历史对话全被无差别拼接进上下文导致第6轮时输入直接逼近模型上限。根因是消息历史裁剪策略失效。定位之后我在harness层增加了压缩逻辑超过阈值后把早期消息摘要成一小段同时限制单次工具结果最大长度多余部分截断。改完后token曲线趋于平稳成本也随之下降。这三个案例有个共同点问题都不在模型本身而在工程链路而它们全都在Trace里被一眼定位。这就是可观测性对Harness Engineering的价值。5. 从追踪到评估把Agent行为固化成可以回归的质量红线5.1 从Trace一键沉淀数据集追踪不仅能看现场还能把现场变成测试资产。LangSmith提供“将Trace添加到数据集”的功能你在UI里勾选一条Trace点击加入数据集它就变成一个可复用的测试样本。这对Agent测试的意义很大。过去造测试集只能靠人工编编出来的Case往往脱离真实分布。现在你直接从生产环境积累Trace每一条都带着真实的用户输入、工具返回和模型输出测试的置信度高出不少。建议定期把生产环境的典型成功案例和典型失败案例都收录进两个不同数据集分别用于回归验证和纠错训练。5.2 用评估器自动打分数据集建好之后LangSmith可以帮你跑评估。评估分两类一类是传统指标如精确匹配、编辑距离另一类更适合Agent场景用LLM当裁判。比如你想评估“模型是否选择了正确工具”可以定义针对性的评估器让它看模型的工具调用记录做判断。LangSmith支持自定义评估器也内置了常用的LLM-as-judge模板from langsmith import evaluate from langsmith.evaluation import evaluate results evaluate( my_agent, dataagent_success_dataset, evaluators[ tool_selection_correctness_evaluator, answer_relevancy_evaluator, ], )跑完自动生成评估报告每个样本都有打分、理由和对比情况。你可以在Agent的prompt或工具配置调整之后重新跑一遍看分数有没有退化。这就把Agent的“好坏”从主观感觉变成可量化的数字。5.3 把关键指标接进CI与在线监控数据集和评估器就绪后接下来是建立质量红线。在发布流程里加一道Agent回归闸门每次改动Agent代码自动拉取固定评估集跑一遍分数低于阈值就阻止合并。这条线在初期不用设太高先把明显退化拦住慢慢收紧。在线监控则关注三类指标错误率、平均工具调用次数、单次会话token成本。错误率反映工具链路稳定性平均工具调用次数反映Agent收敛能力次数突然升高通常是循环或无效调用增加token成本反映prompt和上下文管理是否合理。这三种指标你可以定期用SDK拉取LangSmith数据计算也可以对接到现有监控体系跑出异常时告警。评估阶段的价值在于没有这一步Trace只是事后诸葛有了这一步Trace能变成事前防线。6. 半年实战记录接入LangSmith常见问题与避坑技巧6.1 数据安全与敏感信息处理追踪系统会把input和output全部上传到平台如果你的Agent处理的是敏感的客户数据这是必须正视的问题。LangSmith支持在客户端配置对上传内容脱敏或截断。我的临时方案很简单手动埋点时在装饰器里只返回处理后的摘要如果必须传原文就用环境变量只对开发环境开启完整Trace生产环境只采样5%且开启脱敏字段。不要图省事把生产环境全量原始数据一股脑传上去出过一次泄露事件就够你后悔半年。6.2 成本与采样率追踪本身会带来额外成本包括请求量和平台费用。默认情况下它是全量上报流量大的时候账单会比较可观。LangSmith提供采样率配置export LANGCHAIN_TRACING_SAMPLING_RATIO0.1设成0.1代表只上报10%的Trace。我的经验是开发环境全量、生产环境按需采样比如默认5%出问题时临时调高到50%定位定位完再调回来。不要长期维持100%多数数据你根本不会回看。6.3 常见问题速查表现象可能原因处理方式控制台看不到Trace环境变量没生效重启服务检查LANGCHAIN_TRACING_V2和API Key只有根节点没有子节点自动回调没触发确认组件基于LangChain构建自定义逻辑用手动埋点错误节点显示乱码工具返回值非UTF-8在渲染工具结果前做编码转换Trace延迟很大采样率过高或上报线程阻塞降低采样率开启异步上报消息列表token翻倍历史消息未裁剪在harness层加摘要压缩和截断逻辑自定义函数不在Trace中未加追踪装饰器用traceable包一层并设置正确的run_type6.4 我的一点收尾实战体会最后再分享一个很细的操作心得。调试Agent时我习惯把max_iterations调大一些刻意让Trace里多长几层树然后完整走一遍“成对验证”流程同一组问题在旧版本和新版本上各跑一次把两条Trace导出并排放在一起。节点的增减、工具选择的差异、token曲线的走势一眼就能看清改动到底带来了什么影响。这个习惯救过我很多次。Agent系统的大部分线上问题都不是单个环节炸掉而是状态在多步之间悄悄漂移。有了Trace和可视化你就在每一条漂移路径旁边放了一台行车记录仪——当时车里到底发生了什么随时可以回放。如果你正准备给Agent搭工程体系我的建议是把可观测性当第一优先级而不是等出了问题再补。毕竟Harness Engineering的前提是你得先看见。