资讯详情

LangChain智能体与工具系统实战:从设计到生产化

📅 2026/10/8 10:55:13 | 华诺云谱 👁 阅读
LangChain智能体与工具系统实战:从设计到生产化
1. 从零理解 LangChain 智能体与工具的核心设计1.1 为什么需要智能体而不是简单的链式调用很多人刚接触 LangChain 的时候都是从 LLMChain 或者简单的 PromptTemplate 加模型调用开始的。你给它一段提示词它返回一段文本流程是线性的、确定的。这种模式在处理“翻译一段话”“总结一篇文章”这类任务时完全够用但一旦遇到需要多步推理、动态决策的场景链式调用就捉襟见肘了。我举个实际遇到的例子。假设你要做一个“帮我查一下某只股票最近三天的收盘价然后算一下波动率”的功能。用链式调用怎么做你得先写一个提示词让模型输出股票代码再写一个工具去查数据再把数据塞回提示词让模型计算。每一步的输入输出都得你手动串联而且模型没法自己决定“我下一步该干什么”。如果查询失败了呢如果模型觉得需要先查一下这家公司的财报呢链式调用没法处理这种动态分支。智能体Agent解决的就是这个问题。它的核心思路是给模型一组工具Tools然后让模型自己决定在什么时候调用哪个工具、传什么参数、拿到结果之后下一步做什么。你不再需要把流程写死而是把决策权交给模型。LangChain 里的 Agent 本质上就是一个“推理-行动”循环模型观察当前状态决定调用某个工具拿到工具返回的结果再观察新状态继续决策直到它认为任务完成。这个循环听起来简单但里面有几个关键设计点决定了智能体好不好用。第一是工具的描述质量模型能不能选对工具很大程度上取决于你给工具写的 description 够不够清晰。第二是提示词的设计你得告诉模型“你可以用哪些工具”“用完工具之后怎么继续思考”。第三是容错机制工具调用失败、参数格式错误、模型输出不符合预期这些情况都得有兜底方案。1.2 LangChain 中 Agent 的几种典型架构LangChain 发展到现在Agent 的架构经历过好几轮迭代。早期有zero-shot-react-description后来有conversational-react-description再后来有了OpenAIFunctionsAgent和OpenAIToolsAgent以及现在主推的基于 LangGraph 的 Agent 实现。每一种架构适合的场景不一样选错了会走很多弯路。zero-shot-react-description是最经典的 ReAct 模式实现。它的工作方式是模型先输出一段思考Thought然后决定一个行动Action也就是调用某个工具接着观察工具返回的结果Observation再继续思考循环往复直到给出最终答案Final Answer。这个模式的好处是通用性强不依赖特定模型的能力只要模型能按照格式输出文本就行。缺点是提示词很长每次调用都要把工具描述和格式说明塞进去token 消耗大而且模型有时候会不按格式输出解析起来很头疼。OpenAIFunctionsAgent和OpenAIToolsAgent是利用了 OpenAI 的 function calling 能力。你不需要在提示词里教模型怎么输出 Action 和 Action Input而是把工具定义成 JSON Schema 传给模型模型会直接返回结构化的工具调用请求。这种方式解析起来稳定得多token 消耗也小因为工具描述不需要塞进提示词里。但它的问题是对模型有要求必须是支持 function calling 的模型才行。如果你用的是本地部署的开源模型可能就不支持这个能力。现在 LangChain 主推的是基于 LangGraph 的 Agent。LangGraph 把智能体的执行过程建模成一个状态图每个节点是一个操作比如调用模型、执行工具边是状态转移条件。这种方式的优势是可控性强你可以精确地定义智能体的执行流程加入人工审核节点、条件分支、循环控制等等。对于生产环境的智能体应用LangGraph 是目前最推荐的方案。选哪种架构我的经验是如果是快速原型验证用OpenAIToolsAgent最省事如果需要兼容多种模型用 ReAct 模式如果要上生产环境需要精细控制流程和容错直接上 LangGraph。1.3 工具系统的设计哲学LangChain 的工具系统设计得挺巧妙的。一个工具本质上就是一个函数加上名称、描述和参数定义。模型通过描述来理解这个工具能干什么通过参数定义来知道该怎么传参。工具的定义方式有几种。最简单的是用tool装饰器直接装饰一个 Python 函数from langchain_core.tools import tool tool def search_weather(city: str) - str: 查询指定城市的天气情况。 Args: city: 城市名称比如北京、上海 # 实际查询逻辑 return f{city}今天晴气温25度这个装饰器会自动把函数名变成工具名把 docstring 变成工具描述把函数签名变成参数 schema。对于简单的工具这种方式最方便。如果工具需要更复杂的参数结构可以用 Pydantic 模型来定义from langchain_core.tools import StructuredTool from pydantic import BaseModel, Field class WeatherInput(BaseModel): city: str Field(description城市名称) date: str Field(description日期格式YYYY-MM-DD) def get_weather(city: str, date: str) - str: return f{city}在{date}的天气是晴 weather_tool StructuredTool.from_function( funcget_weather, nameget_weather, description查询指定城市指定日期的天气, args_schemaWeatherInput )工具描述的质量直接决定了智能体的表现。我踩过的一个坑是工具描述写得太简略模型不知道该什么时候用这个工具。比如你有一个“查询订单状态”的工具描述只写“查询订单”模型可能会在用户问“帮我看看我的快递到哪了”的时候不去调用它因为它不知道这个工具能查快递。后来我把描述改成“根据订单号查询订单的当前状态包括物流信息、配送进度、预计送达时间”命中率立刻上去了。还有一个经验是工具的数量不要太多。我试过给智能体挂二十几个工具结果模型经常选错。后来精简到七八个核心工具准确率明显提升。如果确实需要很多功能可以考虑分层设计用一个“路由智能体”先判断任务类型再分发给专门的子智能体。2. 工具定义与智能体初始化的实操细节2.1 自定义工具的参数设计与返回值处理写工具的时候参数设计有几个原则。第一是参数名要语义化query比q好city_name比c好。模型看到参数名就能大概猜出该传什么。第二是尽量用简单类型字符串、数字、布尔值最稳妥。复杂的嵌套对象虽然技术上支持但模型传参的时候容易出错。第三是给参数加上清晰的描述用Field(description...)或者 docstring 里的 Args 部分。返回值处理也很关键。工具返回的内容会作为 Observation 塞回给模型所以返回值的格式直接影响模型的理解。我的建议是返回结构化的文本而不是一大段乱七八糟的原始数据。比如你查数据库返回了一堆 JSON最好在工具内部就整理成“订单号12345状态已发货预计送达3月15日”这样的格式模型读起来更轻松。还有一个细节是返回值长度。如果工具返回的内容太长会占用大量 token而且模型可能会被无关信息干扰。我一般会在工具内部做截断或者摘要只返回最相关的部分。比如搜索工具返回十条结果我可能只取前三条每条只保留标题和摘要。错误处理也得在工具层面做好。如果工具执行失败不要直接抛异常而是返回一个描述错误的字符串比如“查询失败未找到该城市的天气数据”。这样模型看到之后可以决定是重试、换一个工具、还是告诉用户查不到。如果直接抛异常整个智能体流程就中断了。tool def query_database(sql: str) - str: 执行SQL查询并返回结果。 Args: sql: 要执行的SQL语句仅支持SELECT查询 try: # 安全检查只允许SELECT if not sql.strip().upper().startswith(SELECT): return 错误仅支持SELECT查询 result db.execute(sql) if not result: return 查询结果为空 # 格式化返回限制长度 formatted \n.join([str(row) for row in result[:10]]) if len(result) 10: formatted f\n...共{len(result)}条结果仅显示前10条 return formatted except Exception as e: return f查询出错{str(e)}2.2 智能体初始化时的关键参数初始化一个智能体的时候有几个参数需要特别注意。第一个是agent_type也就是智能体的类型。前面说过不同架构适合不同场景。如果你用的是 OpenAI 的模型优先选openai-tools如果需要兼容其他模型选zero-shot-react-description或者conversational-react-description。第二个是tools工具列表。这里有个坑工具的顺序会影响模型的选择。我实测下来把最常用的工具放在前面模型选中的概率会高一些。虽然理论上模型应该根据描述来判断但实际使用中确实存在位置偏差。第三个是verbose是否输出详细日志。开发阶段一定要打开这样你能看到模型每一步的思考过程、调用了哪个工具、传了什么参数、拿到了什么结果。排查问题的时候这个日志就是救命稻草。上线之后可以关掉减少日志量。第四个是max_iterations最大迭代次数。这个参数控制智能体最多执行多少轮“思考-行动”循环。设得太小复杂任务可能没完成就停了设得太大如果模型陷入死循环会浪费大量 token。我一般设 10 到 15 之间根据任务复杂度调整。第五个是handle_parsing_errors解析错误处理。ReAct 模式下模型有时候会输出不符合格式的内容导致解析失败。这个参数设为 True 的话LangChain 会把解析错误信息返回给模型让它重新输出。这个功能非常实用强烈建议开启。from langchain.agents import initialize_agent, AgentType from langchain_openai import ChatOpenAI llm ChatOpenAI(modelgpt-4, temperature0) agent initialize_agent( tools[search_weather, query_database, send_email], llmllm, agentAgentType.OPENAI_TOOLS, verboseTrue, max_iterations12, handle_parsing_errorsTrue, return_intermediate_stepsTrue )return_intermediate_stepsTrue这个参数也很有用它会让智能体返回每一步的中间过程包括调用了哪些工具、传了什么参数、得到了什么结果。对于调试和审计都很方便。2.3 提示词模板的定制化调整虽然OpenAIToolsAgent不需要你在提示词里教模型怎么输出工具调用格式但系统提示词还是需要精心设计的。系统提示词决定了智能体的“人设”和行为边界。我一般会在系统提示词里写清楚这几件事智能体的角色是什么、它能做什么不能做什么、遇到不确定的情况该怎么处理、输出格式有什么要求。比如做一个客服智能体系统提示词可能是这样的你是一个电商平台的客服助手。你的职责是帮助用户查询订单状态、处理退换货申请、解答常见问题。 你可以使用以下工具 - query_order: 根据订单号查询订单详情 - apply_refund: 提交退款申请 - search_faq: 搜索常见问题解答 注意事项 1. 如果用户没有提供订单号先礼貌地询问 2. 退款申请需要确认用户是否已经收到商品 3. 如果工具返回错误向用户说明情况并建议稍后重试 4. 不要编造订单信息所有信息必须来自工具查询结果这段提示词里“不要编造订单信息”这一条特别重要。我遇到过模型在工具查询失败的时候自己编了一个假的订单状态返回给用户。加上明确的约束之后这种情况就少多了。还有一个技巧是在提示词里给几个示例。比如用户帮我查一下订单12345 助手我来帮您查询订单12345的状态。[调用query_order工具] 用户我要退款 助手请问您要退哪个订单请提供订单号我来帮您处理。Few-shot 示例能显著提升智能体的行为一致性尤其是对于格式要求比较严格的场景。3. 智能体执行流程与工具调用的完整实现3.1 从用户输入到工具调用的完整链路让我们把整个链路串起来看一遍。用户输入一句话比如“帮我查一下北京明天的天气如果下雨就提醒我带伞”。第一步智能体把系统提示词、工具描述、用户输入一起发给模型。模型分析之后决定调用search_weather工具参数是{city: 北京, date: 明天}。第二步LangChain 解析模型的输出提取出工具名和参数然后执行对应的工具函数。工具返回“北京明天中雨气温18-22度”。第三步这个结果被包装成一条 ToolMessage追加到对话历史里再次发给模型。模型看到天气信息后判断需要调用send_reminder工具参数是{message: 明天北京有雨记得带伞}。第四步工具执行成功返回“提醒已设置”。模型看到之后认为任务完成输出最终回复“已经帮您查到了北京明天中雨我已经设置了提醒记得带伞哦。”整个过程中LangChain 负责的是管理对话历史、解析模型输出、执行工具、处理错误、控制循环。你只需要定义好工具和提示词剩下的交给框架。但这里有一个容易被忽略的细节对话历史的管理。每一轮工具调用的结果都会追加到对话历史里如果任务比较复杂对话历史会越来越长最终可能超出模型的上下文窗口。LangChain 提供了一些内存管理机制比如ConversationBufferMemory、ConversationSummaryMemory等可以在历史太长的时候自动摘要或者截断。我一般会用ConversationBufferWindowMemory只保留最近 N 轮对话。对于大多数任务保留最近 5 到 10 轮就够了。如果任务需要长期记忆可以考虑用向量数据库做外部存储。3.2 多工具协作与任务分解单个工具能做的事情有限真正强大的智能体往往需要多个工具协作。比如一个“帮我安排下周去上海的出差”的任务可能需要查航班工具、查酒店工具、查天气工具、发邮件工具、日历工具。模型怎么决定先调用哪个这就涉及到任务分解的能力。好的模型会先把任务拆成子任务然后逐个解决。但模型有时候会跳步比如还没查航班就直接发邮件确认行程。为了避免这种情况可以在提示词里明确要求“按顺序执行先查航班再查酒店最后确认行程”。另一个技巧是用 LangGraph 把流程显式地定义出来。比如定义一个状态图节点分别是“查航班”“查酒店”“查天气”“生成行程”“发送确认”边是固定的顺序。这样虽然灵活性降低了但可控性大大增强适合流程固定的业务场景。from langgraph.graph import StateGraph, END from typing import TypedDict, Annotated import operator class TripState(TypedDict): messages: Annotated[list, operator.add] flight_info: str hotel_info: str weather_info: str def search_flight(state): # 查航班逻辑 return {flight_info: CA1234 周一 08:00} def search_hotel(state): # 查酒店逻辑 return {hotel_info: 上海某某酒店 已预订} def search_weather(state): # 查天气逻辑 return {weather_info: 上海下周多云 15-22度} def generate_itinerary(state): # 整合信息生成行程 itinerary f航班{state[flight_info]}\n酒店{state[hotel_info]}\n天气{state[weather_info]} return {messages: [(assistant, itinerary)]} graph StateGraph(TripState) graph.add_node(search_flight, search_flight) graph.add_node(search_hotel, search_hotel) graph.add_node(search_weather, search_weather) graph.add_node(generate_itinerary, generate_itinerary) graph.set_entry_point(search_flight) graph.add_edge(search_flight, search_hotel) graph.add_edge(search_hotel, search_weather) graph.add_edge(search_weather, generate_itinerary) graph.add_edge(generate_itinerary, END) app graph.compile()这种显式编排的方式适合流程确定的场景。如果流程需要动态决策还是得用 Agent 模式。3.3 工具调用中的参数传递与类型转换模型输出的工具参数是 JSON 格式的但有时候类型会对不上。比如工具定义要求count是整数模型可能输出count: 5字符串类型的 5。LangChain 内部会做一些类型转换但不是所有情况都能处理。我遇到过一个典型问题工具参数是枚举类型模型传了一个不在枚举范围内的值。比如工具定义priority只能是high、medium、low模型传了urgent。这种情况下工具执行会报错。解决办法是在工具函数内部做校验和映射tool def create_task(title: str, priority: str) - str: 创建任务。 Args: title: 任务标题 priority: 优先级可选值high, medium, low priority_map {high: 高, medium: 中, low: 低, urgent: 高, important: 高} normalized priority_map.get(priority.lower(), 中) return f任务已创建{title}优先级{normalized}还有一个常见问题是日期格式。模型可能输出2024-03-15也可能输出2024年3月15日还可能输出next Monday。工具内部需要做兼容处理或者用dateutil这样的库来解析。参数校验的另一个思路是用 Pydantic 的 validatorfrom pydantic import BaseModel, Field, field_validator class TaskInput(BaseModel): title: str Field(description任务标题) priority: str Field(description优先级high/medium/low) field_validator(priority) classmethod def validate_priority(cls, v): allowed {high, medium, low} if v.lower() not in allowed: return medium # 默认值 return v.lower()这样即使模型传了不规范的参数工具也能正常执行。4. 常见问题排查与实战避坑经验4.1 智能体不调用工具或调用错误工具怎么办这是最常见的问题。用户明明问了一个需要查数据库的问题模型却直接编了一个答案根本没调用工具。或者有多个相似工具的时候模型选了一个不合适的。排查思路是这样的先看verbose日志确认模型到底输出了什么。如果模型压根没输出工具调用请求那问题出在提示词或者工具描述上。如果模型输出了工具调用请求但解析失败那问题出在格式上。针对“不调用工具”的问题我总结了几种解决办法。第一在系统提示词里明确要求“所有事实性信息必须通过工具查询获得不要依赖你的训练数据”。第二给工具描述加上使用场景的说明比如“当用户询问订单状态时使用此工具”。第三降低模型的 temperature让它更倾向于遵循指令。第四如果用的是 ReAct 模式检查提示词里的格式说明是否清晰。针对“调用错误工具”的问题首先要检查工具描述是否有重叠。比如你有两个工具一个叫search_web一个叫search_database描述都写得很模糊模型就容易搞混。解决办法是把描述写得更具体明确各自的适用场景。其次如果两个工具功能确实相似可以考虑合并成一个工具用参数来区分。还有一个技巧是在工具名称上下功夫。query_order_status比query更明确send_email_notification比send更清晰。模型看到工具名就能大概知道用途。4.2 工具执行超时与异常处理工具执行时间过长是另一个常见问题。比如你有一个调用外部 API 的工具API 响应慢的时候可能要等十几秒。如果智能体有多个工具调用累积起来可能超过整体的超时限制。解决办法是在工具内部设置超时import requests from requests.exceptions import Timeout tool def call_external_api(endpoint: str) - str: 调用外部API获取数据。 try: response requests.get(endpoint, timeout5) return response.text[:500] except Timeout: return API调用超时请稍后重试 except Exception as e: return fAPI调用失败{str(e)}返回错误信息而不是抛异常这样模型可以决定是重试还是换一种方式。对于可能失败的工具我一般会加一个重试机制。但重试逻辑不要放在工具内部而是让模型来决定是否重试。因为模型看到错误信息后可能会选择换一个工具或者调整参数再试一次。如果你在工具内部自动重试模型就失去了这个决策机会。4.3 对话历史过长导致性能下降智能体执行多轮工具调用之后对话历史会变得很长。每一轮的工具调用请求和结果都会追加到历史里token 消耗快速增长。我实测过一个复杂任务执行了 8 轮工具调用之后对话历史超过了 6000 token响应速度明显变慢而且模型开始“遗忘”早期的指令。解决办法有几种。第一种是用ConversationBufferWindowMemory只保留最近几轮对话。第二种是用ConversationSummaryMemory把早期对话摘要成一段简短的文字。第三种是在工具返回结果的时候做截断只保留最关键的信息。我一般会组合使用工具返回结果限制在 500 字符以内对话历史保留最近 10 轮如果超过就自动摘要。这样既能保持上下文连贯性又不会让 token 爆炸。还有一个细节是工具调用的中间结果其实不需要全部保留在历史里。比如查询数据库返回了 100 行数据模型看完之后得出了结论那 100 行数据就没必要一直留在历史里。可以在生成最终答案之后把中间的工具调用记录清理掉只保留最终的用户问题和助手回复。4.4 常见问题速查表问题现象可能原因排查方法解决方案模型不调用工具提示词未强调工具使用查看 verbose 日志在系统提示词中明确要求使用工具模型选错工具工具描述模糊或重叠检查工具 description细化描述明确使用场景工具参数格式错误模型输出类型不匹配查看工具调用日志在工具内部做类型转换和校验工具执行超时外部依赖响应慢检查工具执行时间设置超时返回错误信息而非抛异常对话历史过长多轮工具调用累积统计 token 数量使用窗口内存或摘要内存模型陷入循环任务无法完成或提示词不清观察迭代次数设置 max_iterations优化提示词解析错误频繁模型输出格式不符查看原始输出开启 handle_parsing_errors最终答案不准确工具返回信息不足检查工具返回值丰富工具返回内容增加关键信息这张表是我在实际项目中踩坑之后总结的基本上覆盖了 80% 以上的常见问题。遇到问题的时候按表排查能省不少时间。4.5 几个容易被忽略的实操心得第一个心得是关于工具返回值的格式。我试过返回 JSON 字符串也试过返回自然语言描述。实测下来自然语言描述的效果更好。模型对自然语言的理解能力比对 JSON 的解析能力更强而且自然语言可以包含更多的上下文信息。比如返回“订单12345当前状态为已发货物流公司顺丰运单号SF123456789预计3月15日送达”就比返回{order_id: 12345, status: shipped, carrier: SF, tracking: SF123456789, eta: 2024-03-15}效果更好。第二个心得是关于工具的粒度。工具不要做得太细也不要太粗。太细的话模型需要调用很多次才能完成一个任务容易出错。太粗的话一个工具做太多事情参数复杂模型传参容易出错。我一般是一个工具做一件事但这件事可以有一定的复杂度。比如“查询订单并格式化返回”是一个工具“发送邮件通知”是另一个工具而不是把查询和发送合并成一个工具。第三个心得是关于测试。智能体的行为有很大的不确定性同样的输入可能产生不同的输出。所以测试的时候不能只测一次要跑多次取统计结果。我一般会准备 20 到 30 个测试用例覆盖正常场景、边界场景和异常场景然后跑 3 到 5 遍看通过率。如果某个用例的通过率低于 80%就需要优化提示词或者工具描述。第四个心得是关于成本控制。智能体比简单的链式调用消耗更多的 token因为每一轮工具调用都要把完整的对话历史发给模型。如果任务复杂token 消耗可能是链式调用的 5 到 10 倍。控制成本的方法包括精简提示词、限制工具返回长度、使用更便宜的模型做简单任务、缓存重复的工具调用结果。第五个心得是关于日志和监控。智能体上线之后一定要记录每一步的输入输出包括用户问题、模型思考过程、工具调用参数、工具返回结果、最终答案。这些日志对于排查问题、优化效果、审计合规都至关重要。我一般会把日志存到数据库里方便后续分析和回溯。5. 从单智能体到多智能体协作的进阶思路5.1 什么时候需要多智能体单智能体在工具数量不多、任务流程不复杂的时候完全够用。但当你遇到以下情况时就该考虑多智能体了工具数量超过 15 个模型选择工具的准确率明显下降任务涉及多个专业领域需要不同的知识和提示词需要并行处理多个子任务需要模拟团队协作的场景。多智能体的核心思路是分工。每个智能体负责一个特定的领域或者角色有自己的工具集和提示词。一个“协调者”智能体负责接收用户请求判断任务类型然后分发给对应的“专家”智能体。比如做一个电商客服系统可以设计三个智能体订单智能体负责查订单、改地址、申请退款商品智能体负责查库存、查价格、推荐商品售后智能体负责处理投诉、退换货、维修申请。协调者根据用户问题的关键词或者意图分类把请求路由到对应的智能体。5.2 多智能体协作的两种模式第一种是“路由模式”。一个协调者智能体负责分类和分发专家智能体各自处理自己的任务处理完之后把结果返回给协调者协调者再汇总给用户。这种模式结构清晰适合任务边界明确的场景。第二种是“协作模式”。多个智能体可以互相调用、互相传递信息共同完成一个复杂任务。比如一个“研究智能体”负责查资料一个“分析智能体”负责数据分析一个“写作智能体”负责生成报告。研究智能体查完资料后把结果传给分析智能体分析智能体分析完把结论传给写作智能体。这种模式更灵活但控制起来也更复杂。LangGraph 对多智能体的支持比较好你可以把每个智能体定义成一个子图然后用一个主图来编排它们之间的交互。LangChain 也提供了一些多智能体的抽象比如AgentExecutor可以嵌套使用。5.3 多智能体协作中的通信与状态管理多智能体系统最大的挑战是状态管理。每个智能体有自己的对话历史、工具调用记录、中间结果这些信息怎么共享、怎么隔离、怎么合并都需要仔细设计。我一般会用一个共享的状态对象来存储全局信息比如用户 ID、会话 ID、任务 ID、全局配置等。每个智能体有自己的私有状态存储自己的对话历史和中间结果。智能体之间传递信息的时候只传递必要的部分而不是把整个状态都传过去。通信协议也很重要。智能体之间传递的消息需要有一个统一的格式包括发送者、接收者、消息类型、消息内容、时间戳等。这样方便追踪和调试。还有一个问题是错误处理。在多智能体系统里一个智能体出错可能会影响整个流程。我一般会在每个智能体外面包一层错误处理如果某个智能体执行失败协调者可以决定是重试、跳过、还是终止整个流程。5.4 多智能体系统的测试与评估多智能体系统的测试比单智能体更复杂因为涉及到多个智能体的交互。我一般会分层次测试先单独测试每个智能体的功能确保它在自己的领域内表现正常然后测试智能体之间的通信确保消息能正确传递最后测试端到端的流程确保整个系统能完成预期任务。评估指标方面除了单智能体的准确率、响应时间、token 消耗之外多智能体系统还需要关注任务完成率、智能体之间的调用次数、通信开销、故障恢复能力。我实测下来多智能体系统在复杂任务上的表现确实比单智能体好但开发和维护成本也高得多。所以我的建议是先用单智能体遇到瓶颈再考虑多智能体。不要为了用多智能体而用多智能体。6. 智能体与工具系统的生产化考量6.1 安全性工具调用的权限控制生产环境的智能体必须考虑安全性。工具能执行的操作是有边界的不能让模型随意调用敏感工具。比如删除数据、发送邮件、修改配置这些操作需要加权限校验。我一般会在工具层面做权限控制。每个工具定义的时候标注它需要的权限等级。智能体在执行工具之前先检查当前用户是否有对应的权限。如果没有返回“权限不足”的错误信息。tool def delete_record(record_id: str, user_role: str) - str: 删除指定记录。需要管理员权限。 Args: record_id: 记录ID user_role: 当前用户角色 if user_role ! admin: return 权限不足只有管理员可以删除记录 # 执行删除逻辑 return f记录{record_id}已删除另一个安全考虑是输入校验。模型传过来的参数可能包含恶意内容比如 SQL 注入、命令注入等。工具内部必须做严格的校验和转义。不要信任模型输出的任何内容。还有一个是操作审计。所有工具调用都要记录日志包括谁在什么时候调用了什么工具、传了什么参数、得到了什么结果。这对于事后追溯和安全审计非常重要。6.2 性能优化缓存与并行智能体的性能瓶颈通常在模型调用和工具执行上。模型调用受限于 API 的响应速度工具执行受限于外部系统的性能。缓存是提升性能的有效手段。对于查询类的工具如果同样的参数在短时间内被多次调用可以缓存结果。LangChain 提供了CacheBackedEmbeddings和SQLiteCache等缓存机制也可以自己实现简单的内存缓存。并行执行是另一个优化点。如果多个工具调用之间没有依赖关系可以并行执行。LangGraph 支持并行节点可以同时执行多个工具调用然后合并结果。但要注意并行执行会增加系统的复杂度和资源消耗需要根据实际情况权衡。还有一个优化是模型选择。不是所有任务都需要用最贵的模型。简单的意图分类、参数提取可以用小模型复杂的推理和决策再用大模型。LangChain 支持在同一个智能体里使用不同的模型可以根据任务类型动态切换。6.3 可观测性日志、指标与追踪生产环境的智能体需要完善的可观测性。日志记录每一步的输入输出指标监控系统的健康状态追踪记录请求的完整链路。我一般会用 LangSmith 来做追踪和调试。它可以把智能体的每一步执行都可视化出来包括模型调用、工具执行、状态变化等。对于排查问题和优化性能非常有帮助。指标方面我会关注这几个请求量、响应时间、工具调用成功率、模型调用成功率、token 消耗、错误率。这些指标可以帮助我及时发现系统的异常和瓶颈。日志方面我会记录用户输入、模型输出、工具调用参数和结果、最终答案、执行时间、token 消耗。日志要结构化存储方便后续查询和分析。6.4 版本管理与灰度发布智能体的提示词、工具定义、模型配置都是会变化的。每次变更都可能影响智能体的行为。所以需要做好版本管理每次变更都记录版本号、变更内容、变更原因、测试结果。灰度发布也很重要。新的提示词或者工具上线之前先在小流量上测试观察效果和稳定性。如果发现问题可以快速回滚。我一般会保留最近三个版本的配置方便随时切换。A/B 测试是优化智能体的有效手段。同时运行两个版本的智能体对比它们的准确率、响应时间、用户满意度等指标选择更好的那个。但要注意A/B 测试需要足够的样本量才能得出可靠的结论。7. 智能体开发的个人经验与建议7.1 从简单场景开始逐步增加复杂度我见过很多人一上来就想做一个“全能智能体”挂几十个工具处理各种任务。结果往往是模型选错工具、参数传错、流程混乱最后不了了之。我的建议是从最简单的场景开始。先做一个只挂一个工具的智能体确保它能正确调用工具、正确处理返回值、正确生成最终答案。然后再加第二个工具观察模型的选择准确率。逐步增加工具数量和任务复杂度每一步都做好测试和优化。这种渐进式的开发方式虽然看起来慢但实际上更快。因为你能及时发现和解决问题而不是等到系统复杂到无法调试的时候才后悔。7.2 重视提示词工程但不要过度依赖提示词对智能体的表现影响很大但也不是万能的。我试过花几个小时优化提示词效果提升只有几个百分点。而换一个更好的模型或者优化工具描述效果提升可能更明显。我的经验是提示词要写清楚但不要写得太长。核心指令放在前面细节说明放在后面。用列表和分段来组织避免一大段文字。关键约束要加粗或者用特殊标记让模型更容易注意到。另外不同模型对提示词的敏感度不一样。同样的提示词GPT-4 和 Claude 的表现可能差别很大。所以提示词需要针对具体的模型来调优不能一套提示词走天下。7.3 建立测试集持续评估智能体的行为有很大的不确定性没有测试集的话你根本不知道它到底行不行。我一般会从实际业务场景中收集 50 到 100 个典型问题标注好预期答案作为测试集。每次修改提示词、工具、模型配置之后都跑一遍测试集看通过率的变化。如果通过率下降就要分析原因是提示词改坏了还是工具描述有问题还是模型换了之后行为变了。测试集也要不断更新。随着业务的发展新的场景会出现旧的场景可能不再重要。定期 review 测试集补充新的用例删除过时的用例。7.4 关注成本但不要因噎废食智能体的 token 消耗确实比简单的链式调用高但这是为了获得更好的效果和更强的灵活性。如果为了省钱而牺牲效果那就本末倒置了。我的做法是先确保效果达标然后再优化成本。优化的手段包括精简提示词、限制工具返回长度、使用缓存、选择合适的模型。但前提是这些优化不会显著影响效果。另外要算总账。智能体虽然单次调用成本高但如果它能减少人工客服的工作量或者提升用户满意度那这个投入就是值得的。不要只看 token 成本要看整体的 ROI。7.5 保持学习跟上社区进展LangChain 和智能体领域发展很快新的工具、新的模式、新的最佳实践层出不穷。我一般会关注 LangChain 的官方博客、GitHub 仓库的 release notes、以及一些活跃的社区论坛。但也不要盲目追新。新的东西不一定适合你的场景。我一般会先在小项目上试验证有效之后再迁移到生产环境。保持学习的心态但保持谨慎的行动。智能体和工具系统的开发说到底是一个不断迭代和优化的过程。没有一劳永逸的方案只有持续改进的实践。希望我这些经验能帮你少走一些弯路更快地做出好用的智能体应用。
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑