
刚开始做智能体项目的时候我以为最大的难点是“怎么让模型会调用工具”。真正的项目跑起来才发现模型天生就会工具调用困难的是当你的智能体在第五轮对话时突然绕了一个大弯路或者连续三次调用了同一个工具却拿不回有效结果你根本不知道问题出在模型指令、工具描述还是上下文被污染了。这个代号为“Polly”的项目是我近期完整走完一个LangChain智能体开发全流程的实践。Polly取“Poly”的音意思是多工具聚合核心定位是一个能够处理多轮查询并自动编排后端工具的智能体。整个开发过程中我把LangSmith作为调试、评测和质量门禁的底座它几乎重构了我对智能体工程化的理解。这篇文章不会停留在概念层面而是把从环境配置、智能体架构搭建到LangSmith追踪接入、数据集回归测试、上线监控这一整套链路原原本本地拆开来讲。1. 先理解Polly要解决什么问题以及为什么选LangSmith1.1 Polly的定位与需求拆解“LangChain智能体开发LangSmith Polly”这个标题拆开来看其实有两层意思第一层是“用LangChain框架开发一个智能体”第二层是“这个智能体的开发过程由LangSmith来支撑”。很多文章会把LangSmith一笔带过仿佛它只是一个可选的监控面板。但我在Polly项目里感受到的恰恰相反如果你没有把追踪和评估嵌入到日常开发循环中智能体项目大概率会陷入“改了一版prompt感觉变好了过了两天又觉得哪里不对劲但是说不清哪里不对劲”的泥潭。Polly本身并不复杂。它需要完成的事情包括接收用户一段自然语言请求比如“查一下某商品在仓库的库存再结合最近五天的销量给一个补货建议”智能体自己规划要调哪些工具、按什么顺序调、参数从上下文哪里抽取最后把多个工具返回的结构化数据整理成一段人话答案。这类业务场景的核心痛点不是某一个工具的调用成功与否而是整个链路的“决策质量”。1.2 为什么需要LangSmith这种工具而不是普通日志传统后端开发里你有一套完整的错误日志、链路追踪、监控告警体系任何一次异常都能通过堆栈定位到具体代码行。但智能体是概率系统相同的输入可能在不同的轮次产生不同的工具选择甚至同样调用了同一个工具模型给工具传的参数也可能不同。你在生产环境里看到一次“用户问库存智能体却调用了天气工具”的诡异行为如果只有日志你只能看到model_response那一大段JSON手动去翻模型到底是怎么想的。LangSmith最核心的价值是把你从“看日志猜原因”切换到“看轨迹懂行为”。它会把一次完整的智能体运行拆解成一条Trace树用户输入、模型调用、工具选择、工具参数、工具返回、下一轮模型输入全部串在一起。开发智能体的时候你需要的不是更多日志而是一张能够展开每一个推理步骤的地图LangSmith就是这么一张地图。1.3 项目初期的技术选型思考在定方案的时候我也权衡过要不要自己写一套追踪系统把每次智能体运行时的模型输入、工具输出落库再做可视化。这个方案的缺点在于智能体的中间步骤结构复杂涉及嵌套调用和多轮循环自己维护一套链路存储和查询成本不低而且LangChain已经在内部适配了LangSmith接入成本几乎为零。另一个考虑是数据安全。LangSmith支持云端SaaS和私有化部署两种形态。云端版本一分钟就能跑通适合快速验证私有化部署则需要额外运维资源适合对数据链路管控更严的团队。Polly项目最终采用了云端版本配合关闭无关数据采集的策略来控制敏感信息的暴露面。这个决策让我在后端开发中省下很多精力。2. 环境准备先把LangSmith追踪打通再动手写业务2.1 项目目录结构与依赖安装智能体项目最容易犯的错误是一上来就写Agent结果代码和工具函数全堆在一个文件里。我在Polly项目里采用了一个相对标准的分层结构polly/ ├── agent/ # 智能体编排逻辑 │ ├── executor.py │ └── state.py ├── tools/ # 工具定义 │ ├── inventory.py │ ├── sales.py │ └── weather.py ├── prompts/ # 提示词模板 │ ├── system.py │ └── fewshot.py ├── eval/ # 评估脚本与数据集 │ └── run_eval.py ├── config/ │ └── settings.py └── tests/ └── test_tools.py目录划分的原则是prompt归prompt、工具归工具、编排归编排。这保证了后续在LangSmith里做对比实验时你能够明确知道改动的是哪一个变量。如果工具和prompt混在一起一次实验改了三个变量实验结果就没有对照价值了。依赖安装可以直接使用pippip install langchain langchain-core langchain-openai langchain-community langsmith需要注意不同版本之间的兼容性。LangChain目前处于快速迭代期不同大版本的API多少有些出入如果你安装的版本和我使用的版本跨度太大某些方法名、参数位置可能出现差异。建议把依赖固定在一个经过验证的版本组合上。2.2 接通LangSmith追踪的完整配置LangSmith接入本身很简单核心是环境变量。在项目根目录的.env文件里配置如下内容LANGCHAIN_TRACING_V2true LANGCHAIN_API_KEYls_你的密钥 LANGCHAIN_PROJECTpolly_dev需要注意LANGCHAIN_TRACING_V2这个变量在目前的SDK版本里必须设置为字符串的true而不是布尔值这个细节排查起来很容易忽略。设置完成之后再启动临时环境变量并运行一段最小链路export LANGCHAIN_TRACING_V2true export LANGCHAIN_API_KEYls_你的密钥 export LANGCHAIN_PROJECTpolly_dev python run_demo.py只要程序里创建了LangChain的模型或Agent调用界面上就能看到一条Trace出现。第一次接入时我建议先跑一个最简单的模型调用确认链路通了再往里面加Agent逻辑否则后面一旦出问题你分不清是业务代码问题还是追踪配置问题。2.3 最小链路验证一例用一个非常基础的LangChain智能体脚本来验证追踪是否生效from langchain_openai import ChatOpenAI from langchain.agents import create_tool_calling_agent, AgentExecutor from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder from langchain_core.tools import tool tool def get_weather(city: str) - str: 查询指定城市当前的天气情况用于出行或活动安排。 return f{city} 今日多云气温 24 摄氏度。 llm ChatOpenAI(modelgpt-4o-mini, temperature0) prompt ChatPromptTemplate.from_messages([ (system, 你是一个智能助手只能使用提供的工具回答问题。), (human, {input}), MessagesPlaceholder(agent_scratchpad), ]) agent create_tool_calling_agent(llm, [get_weather], prompt) executor AgentExecutor(agentagent, tools[get_weather], max_iterations5) result executor.invoke({input: 北京今天适合出门跑步吗}) print(result[output])如果这一条跑通并在LangSmith控制台看到了完整的Trace后续所有智能体运行都会被自动捕获。3. Polly的智能体架构搭建过程3.1 模型层选择支持函数调用的底座Polly对模型的要求比较明确原生支持function calling或tool calling。这直接决定了agent编排的可靠程度。早期一些纯文本式工具调用方案需要模型自己输出特定格式的命令再解析解析失败率比较高支持原生工具调用的模型模型API层就保证了参数以结构化JSON返回少了一层脆弱的中间解析。模型实例化时的参数也很关键。temperature我一般设置成0.1既能保持一定多样性又不会让模型在工具参数选择上过于发散。max_tokens要设置合理如果设得太小模型可能在一段回答写到一半时被截断导致最终工具调用没有完成。这个在Trace里看起来就是“模型输出被截断Agent循环又继续执行”定位起来需要看很多层。3.2 工具层描述比实现更重要工具函数本身写起来很简单用tool装饰器包一层即可tool def query_stock(product_id: str) - str: 根据商品ID查询实时库存数量。若缺货返回最近补货时间。 response inventory_api.query(product_id) return json.dumps(response, ensure_asciiFalse)真正容易踩坑的是工具的描述。很多开发者按人类习惯写工具描述比如“查询库存”但模型是通过描述来决定调用哪个工具的它需要的是“这个工具能做什么、什么场景下用、参数是什么意思”。我在Polly项目里把工具描述重新写了一遍每一句话都朝向“帮模型做决策”。例如不够好的描述“查询商品库存数量”调整后的描述“根据商品ID查询指定商品在仓库中的实时库存。适合在用户询问商品是否有货、能否购买、何时补货时调用。若查询失败请直接告知用户稍后再试”参数定义建议使用Pydantic模型。LangChain的tool装饰器支持直接基于函数的签名推断参数schema但对于复杂参数类型显式使用pydantic可以让参数约束更明确模型给出的参数也更规范。3.3 提示词层给智能体设定行为边界Polly的系统提示词不是一篇长作文而是一组行为约束。我的模板大致是角色说明你是一个多工具调度助手。工具使用规则只允许调用提供的工具不允许虚构数据如果一个工具无法回答可以结合其他工具结果综合回答。多步任务处理用户请求包含多个子任务时拆解后按顺序调用工具。结果报告回答时用简洁的条目式结构给出数据和结论不要堆砌过程。这套设计能让模型在多数情况下保持稳定的行为模式。加入LangSmith之后你会发现这版提示词的效果能直接被评估出来而不是靠主观判断。3.4 记忆层多轮会话的处理策略开始时我天真地把所有历史消息一股脑塞给模型很快上下文就爆了。后来我在message处理层做了一个滚动截断保留最近的八轮用户与助手对话更早的内容如果太长就压缩成一句摘要。这个策略不算新奇但它足够稳定。在LangChain的智能化实现中历史消息通过MessagesPlaceholder传入prompt ChatPromptTemplate.from_messages([ (system, 你是波利助手...), MessagesPlaceholder(chat_history), (human, {input}), MessagesPlaceholder(agent_scratchpad), ])接入LangSmith后我可以轻松地在Trace里看到每一轮实际传给模型的完整上下文从而判断截断策略是否把关键信息切掉了。这个观察视角在普通代码日志里是很难获得的。3.5 编排层AgentExecutor的参数调优AgentExecutor是LangChain智能体运行时的编排核心。常规用法如下executor AgentExecutor( agentagent, toolstools, max_iterations6, return_intermediate_stepsTrue, )max_iterations非常值得关注。如果没有这个上限模型在极端情况下可能反复调用工具直到耗尽配额或超时。我见过一个没有设置上限的智能体在调用某个工具失败后连续尝试了十几次同样的调用浪费了大量Token。设置合理的max_iterations之后模型会被迫在有限步骤内收敛如果真收敛不了也至少能返回一个“抱歉我无法完成这个请求”的兜底。return_intermediate_stepsTrue会把每一步的思考过程和工具返回记录下来这不仅是调试的宝库也是LangSmith构造评估集的重要素材。3.6 结构化输出让智能体最终结果可被系统消费Polly不仅是面向终端用户聊天它还要把结果推给下游系统处理。因此最终输出必须是可解析的结构。我采用了模型的函数调用生成结构化结果或者使用LangChain的with_structured_output机制from langchain_core.pydantic_v1 import BaseModel, Field class FinalAnswer(BaseModel): summary: str Field(description给用户的自然语言总结) data: dict Field(description结构化数据结果包含所有查询到的字段) structured_llm llm.with_structured_output(FinalAnswer)这种做法有效避免了“模型输出一段漂亮话但拿不到关键字段”的尴尬。评估时也可以用字段级的准确性来打分数比单纯比较整段文本更精细。4. 用LangSmith驱动智能体的开发迭代4.1 每日开发的起点读懂TraceLangSmith界面上每次智能体运行会形成一棵Trace树。树的根节点是这次Agent的整体运行子节点是一次次模型调用和工具调用。我在每天开发开始前会先做一件事把前一天的异常案例在LangSmith里打开沿着Trace看一遍。比如有一次用户反馈Polly在回答库存问题时给出了错误数据。Trace显示模型先成功调用了库存工具拿到了库存数量但在写最终回答时又调了一次销售工具然后可能被销售数据的某个字段干扰把库存数字写成了销售数字。这个问题不靠Trace几乎不可能定位光看最终结果只能一脸茫然。看到调用顺序之后修复方案就很明确在系统提示词里加一句“回答用户问题时应以库存工具的返回值为准不要与其他工具数据混淆”。4.2 用数据集建立回归测试资产LangSmith最强的地方在于它能把你日常运行中真实发生过的案例沉淀为数据集。我在Polly项目里建了一个核心回归集叫polly_regression里面每一条都是一次真实运行记录的输入、期望输出和可选的参考结果。你可以通过客户端把历史示例提交到数据集from langsmith import Client client Client() dataset client.create_dataset(polly_regression, description核心业务回归集) client.create_examples( inputs[{input: 商品A在哪个仓库有货}], outputs[{answer: 商品A在华东仓库有货库存为120件。}], )这个回归集是Polly项目的核心资产。每次修改prompt、调整工具描述或升级模型我都会在同样数据集上跑一遍看看哪些case变差了、哪些变好了而不是拍脑袋说“这次改动效果不错”。4.3 运行评估实验与对比在LangSmith里可以针对一个数据集发起一次评估实验。评估器选择很灵活我常用的有三类评估器类型适用场景备注精确匹配字段级答案校验用于验证工具返回参数是否正确代码式评估器需自定义业务规则比如检查答案中是否包含所有必填字段大模型评审评估自然语言回答质量从正确性、完整性、格式合规三个维度打分针对Polly我配置了一个大模型评估器提示词要求评审者基于参考答案判断模型输出是否包含了正确的库存数据、补货建议是否合理、格式是否清晰。评估跑完后LangSmith会给出一个对比视图你可以看到每个案例的得分、模型输出以及评估器的评分理由。这个反馈闭环让prompt优化从“感觉”变成了“数据”。4.4 提示词版本管理与发布开发阶段一直改prompt很需要版本管理。LangSmith提供了prompt版本回溯能力我在每次有显著效果的改动后都会把prompt保存为一个新版本并记录改动原因。等到要发布到生产环境时再基于经过回归测试的版本创建一个release版本。这种流程和多智能体项目特别契合因为系统里往往同时存在调度专用prompt、工具调用描述、总结生成prompt等多个模块。如果prompt没有版本管理一旦线上出问题你想回退都不知道该退到哪一版。4.5 上线后的生产监控思路上线之后LangSmith的角色从调试工具切换为监控工具。Polly在生产环境里我关注四个指标请求成功率、平均响应时长、单次调用的模型Token消耗、以及工具调用失败率。运行时的延迟主要来自模型来回调用和工具耗时。如果某类请求耗时明显上升Trace里能直接看到是哪个工具变慢了还是模型多绕了一圈。Token消耗则是成本控制的核心指标我在Trace里按项目维度聚合了每天的Token总消耗并设置了告警阈值。还有一个容易被忽略的点生产环境里用户反馈“回答不对”时如果只是口头投诉很难找回当时的上下文。我在应用里加了一个反馈按钮用户点击“不满意”时就把当前会话的运行标识记录到后端。凭借这个标识我在LangSmith里能直接调出当时的Trace把问题样本补充到测试数据集里。这类“反馈闭环”做法能让线上质量问题越积越少。5. 常见问题与排查技巧实录5.1 LangSmith里看不到Trace怎么办这是新手接入时最常遇到的情况。排查顺序如下确认环境变量是否在当前进程内生效。direnv或source .env没执行成功是常见原因。确认LANGCHAIN_TRACING_V2确实是字符串true而不是布尔True。确认LANGCHAIN_API_KEY有效密钥在控制台复制之后不要带多余空格。确认LangChain和langsmith包版本兼容两个包版本跨度过大会出现追踪上报失败但业务代码看起来一切正常。如果业务代码已经跑完但在界面上看不到Trace可以在代码里临时增加langsmith手动上报来验证不过一般前四步排查就能解决。5.2 智能体陷入工具调用循环模型反复调用同一个工具并且每次参数都一样这是智能体开发里非常典型的问题。Trace里会呈现一连串相同的工具调用节点让人看着血压升高。原因通常是模型拿到的工具返回信息不够支持它得出结论于是反复尝试或者工具描述里没有说明“失败之后应该怎么办”。我在Polly里通过两个手段解决一是在工具描述末尾加一句“如果查询失败请直接告知用户暂时无法查询不要重试”二是设置max_iterations6拦截兜底。另外将工具的异常返回设计为“查询失败 理由”而不是抛出异常也能显著减少循环。5.3 上下文窗口与成本失控智能体在长时间多轮对话里上下文长度会快速增长。如果不控制历史消息每一轮的Token消耗会像滚雪球一样变大。解决办法包括历史消息截断只保留最近N轮完整对话更早的压缩为摘要。工具描述精简描述要精炼但语义要明确避免把工具文档大段拼进prompt。小模型分流简单的“判断是否需要调用工具”这类任务可以先用更小的模型做再交给主力模型执行复杂推理。LangSmith的trace记录里能看到每一轮消耗的Token数量基于这份数据可以算出一条对话的平均成本做成本预估就容易多了。5.4 评估得分和人工判断不一致用大模型评估器时偶尔会出现大模型评估员给了一个看起来不合理的低分或高分。我的做法是评估器不只给分还要输出评分理由。这样每一个低分都有据可查。同时在评估集里加入几个固定的人工示例用来校准大模型评估员的偏差。另外业务字段正确性我坚持用代码式评估器来校验理由是这种指标确定性强输出中是否包含正确库存数字用字符串匹配或字段比对就能确定纯靠大模型打分反而容易误判。6. 项目复盘与我的几点体会Polly项目让我最深刻的体会是LangChain智能体开发的核心难点从来不是写工具而是“让智能体的行为可观察、可复现、可评估”。LangSmith在这个体系里充当的是智能体开发的“行车记录仪”和“训练场记录员”它让每一次运行都有据可查让每一次修改都有对比实验。如果让我给还没接入LangSmith的人一个建议那就是不要等智能体写完整了再考虑评估工具从第一个Hello World级别的Agent开始就把追踪打开。把每次运行中遇到的边界case收集进数据集它们是你最重要的调试资产。这个习惯越早养成项目后期走的弯路就越少。后续我准备在Polly里加入更复杂的多智能体协作编排一个主智能体负责任务拆解多个子智能体分别处理库存、销售、物流等不同领域再通过统一格式汇集结果。这种架构对可观测性的要求更高而我已经习惯依赖LangSmith提供的调试范式来推进开发了。智能体工程没有银弹但“先观测、再抽象、再优化”这条路大概率不会走错。