系列文章目录【 LangChain v1.2 入门系列教程】【一】开篇入门 | 从零开始跑通你的第一个 AI Agent【 LangChain v1.2 入门系列教程】【二】消息类型与提示词工程【 LangChain v1.2 入门系列教程】【三】工具Tools开发让 Agent 连接外部世界【 LangChain v1.2 入门系列教程】【四】结构化输出让 Agent 返回可预测的结构【 LangChain v1.2 入门系列教程】【五】记忆管理让 Agent 记住对话【 LangChain v1.2 入门系列教程】【六】流式输出 让 Agent 告别“想好了再说”文章目录系列文章目录前言一、 什么是Tools工具二 、Agent 什么时候调用工具三、 tool 装饰器1. 参数类型注解1 基础类型注解2 复杂参数用 Pydantic 模型做强校验2. 工具重命名3. 覆盖描述四、ToolRuntime 运行环境注入1. 访问会话(State)2. 获取用户上下文Context五 、 多工具协同让 Agent 完成复杂任务六 、 工具调用调试七、总结前言在前两篇中我们已经学会了搭建基础的 Agent而 Agent 之所以能从 “只会聊天的大模型” 变成 “能解决实际问题的智能体”核心就在于Tools工具。本篇文章我们将循序渐进用最简洁的代码和概念带你掌握LangChain v1.2中工具开发的核心技巧。一、 什么是Tools工具工具就是 Agent 可以使用的“外部能力”。比如查询数据库、发送邮件、搜索网页、计算数学表达式……任何一个具体的、可重复执行的动作都可以封装成一个工具其本质是一个函数。二 、Agent 什么时候调用工具当用户的问题无法仅靠模型自身的知识回答时例如实时天气、库存数量、用户等级。当模型判断需要执行某个动作才能完成用户指令时例如发送邮件、保存文件、查询 API。调用时机完全由 Agent 内部的模型自主决定。你只需要把工具注册给 Agent它会像人类一样思考“这个问题我能- 不能直接回答不能的话该用哪个工具”三、 tool 装饰器在LangChain v1.2中使用 tool 装饰器创建一个工具。它可以把一个普通的Python函数瞬间变成Agent可用的工具。示例fromlangchain.toolsimporttool........#定义工具从数据库查询商品库存tooldefcheck_stock(product_id:str)-str:查询商品库存数量# 模拟库存数据stock_data{A1001:15,B2002:0,C3003:3,}stockstock_data.get(product_id,0)ifstock0:returnf商品{product_id}库存充足剩余{stock}件else:returnf商品{product_id}已售罄暂时无货#创建agentagentcreate_agent(modelllm,tools[check_stock]#注册工具)#调用模型responseagent.invoke({messages:[HumanMessage(A1001商品还有货吗)]})print(response[messages][-1].content)#商品 A1001 当前库存充足还有 15 件在售说明参数类型注解是必需的 —— 它们定义了工具的输入参数模式Schema大模型据此知道该传什么参数函数名即工具名 —— 建议用动词名词具有语义化命名帮助模型理解函数作用如 get_weather、send_emailDocstring 示例中的““查询商品库存数量””被自动提取为工具描述 —— 这是模型决定是否调用该工具的重要依据描述越清晰Agent调用工具的时机和方式就越准确。1. 参数类型注解LangChain v1.2 中参数的类型注解是强制要求的—— 没有类型注解的参数无法生成正确的工具 Schema大模型也就不知道该传什么类型的参数会直接导致工具调用失败。1 基础类型注解最常用的基础类型包括str、int、float、bool直接给参数加上即可可选参数必须设置默认值tooldefsearch_customer_info(customer_name:str,max_result:int6)-str:查询客户信息当用户需要查找指定客户的资料时调用此工具。 Args: customer_name: 要查询的客户姓名必填项 max_result: 返回的最大结果条数默认为6条 returnf找到{max_result}条名为【{customer_name}】的客户信息2 复杂参数用 Pydantic 模型做强校验frompydanticimportBaseModel,FieldfromtypingimportOptional# 定义参数类型classEmailInput(BaseModel):邮件发送参数to:strField(description收件人邮箱地址)subject:strField(description邮箱主题)body:Optional[str]Field(default,description邮件正文内容可选)# 定义工具发邮件tooldefsend_email(param:EmailInput)-str: 发送邮件到指定收件人。 当用户需要发送邮件、通知或报告时使用此工具。 #模拟发送成功响应returnf邮件已发送至{param.to}主题{param.subject},正文内容{param.body}用 Pydantic 模型的核心优势自动做参数校验用Field给每个参数加精细化说明比写在 docstring 里更清晰支持可选值Optional支持枚举值Literal2. 工具重命名默认情况下工具名称 函数名。但你可以通过传入字符串参数来显式指定# 默认工具名 searchtooldefsearch(query:str)-str:搜索网页内容returnf搜索结果{query}# 自定义工具名 web_searchtool(web_search)defsearch(query:str)-str:搜索网页内容returnf搜索结果{query}3. 覆盖描述默认情况下工具描述 函数的 docstring。但你可以通过 description 参数强制覆盖# 方式1依赖 docstring默认tooldefcalculator(expression:str)-str:执行数学计算# 模型看到的就是这行returnstr(eval(expression))# 方式2显式覆盖优先级最高tool(description专门用于处理加减乘除和复杂数学表达式。当用户询问任何数学问题时必须调用此工具。)defcalculator(expression:str)-str:执行数学计算# 这行会被忽略returnstr(eval(expression))四、ToolRuntime 运行环境注入LangChain v1.2 中ToolRuntime 是一项核心升级。它会在工具执行时自动将当前 Agent 的运行环境注入到工具中,而且这个参数不会暴露给 LLM完全不影响工具的输入 Schema既保留了功能又保障了安全性。通过 ToolRuntime你的工具可以访问常用三类核心信息State状态短期记忆包括当前对话的消息列表、自定义字段等。Context上下文不可变的配置信息如用户ID、会话ID等在调用时传入。Store存储长期记忆跨会话持久化保存的数据。1. 访问会话(State)通过 runtime.state[“messages”]工具可以读取当前会话的 State短期记忆比如对话历史、自定义的会话状态等从而做出更智能的决策。tooldefget_user_question_count(runtime:ToolRuntime)-str:统计当前会话中用户提出的问题总数当用户询问自己问了多少个问题时调用。# 从State中获取所有对话消息all_messagesruntime.state[messages]# 统计用户发送的消息数量user_message_countlen([msgformsginall_messagesifisinstance(msg,HumanMessage)])returnf当前会话中您一共提出了{user_message_count}个问题agentcreate_agent(modelllm,tools[get_user_question_count],system_prompt你是一个智能客服助手回答用户的提问和帮助用户解决问题,)# 调用时注入具体上下文responseagent.invoke({messages:[HumanMessage(我想买iPhone 16),AIMessage(好的iPhone 16 是我们的热门商品。),HumanMessage(现在有货吗),AIMessage(库存充足需要我帮你下单吗),HumanMessage(我问了多少个问题了),]},)print(response[messages][-1].content)#输出你一共提出了3个问题2. 获取用户上下文Context通过 runtime.context可以让你获取调用时传入的不可变配置信息例如用户ID、权限等级等。这对于实现个性化服务至关重要。fromlangchain.toolsimportToolRuntime,toolfromdataclassesimportdataclass# 定义上下文的数据结构dataclassclassUserContext:user_id:struser_level:int2# 用户等级1vip用户2普通用户默认普通用户# 工具获取当前用户信息tooldefget_user_info(runtime:ToolRuntime[UserContext])-str: 获取当前用户信息并根据等级返回不同的权限说明。 ctxruntime.context# 获取当前上下文user_idctx.user_id# 获取用户IDlevelctx.user_level# 获取用户等级iflevel1:return(f 用户{user_id}VIP会员特权无限制访问可查询历史订单、专属折扣。)else:returnf 用户{user_id}普通用户限制仅可查询本月订单每日限查询 3 次。# 创建 Agentagentcreate_agent(modelllm,tools[get_user_info],context_schemaUserContext,# 指定 context_schemasystem_prompt你是客服助手了解用户身份后再提供相应服务。,)# 调用时注入具体上下文responseagent.invoke({messages:[{role:user,content:请问我的账户有什么权限}]},contextUserContext(user_iduser_1,user_level1),# 注入用户配置VIP 用户)print(response[messages][-1].content)# 输出用户 user_1VIP会员特权无限制访问可查询历史订单、专属折扣。五 、 多工具协同让 Agent 完成复杂任务实际业务场景中一个复杂的用户需求往往需要多个工具配合才能完成。LangChain 的 Agent 天生支持多工具协同你只需要把所有工具注册到 Agent 中它会自动规划调用顺序完成多步操作无需你手动写流程控制代码。来看下面一个经典示例# 定义上下文的数据结构 dataclassclassUserContext:user_id:str # 获取用户所在城市 tool defget_user_location(runtime:ToolRuntime[UserContext])-str:获取当前用户所在的城市名称user_idruntime.context.user_id # 从上下文获取用户ID# 模拟用户位置数据库数据 user_locations{user_1:北京,user_2:上海,user_3:广州,}returnuser_locations.get(user_id,未知城市)# 查询指定城市天气 tool defget_city_weather(city:str)-str:查询指定城市的天气# 模拟查询天气数据,实际从api获取 weather_data{北京:北京小雨气温10-18℃,上海:上海暴雨气温15-22,广州:广州晴转多云气温20-28℃,}returnf{weather_data.get(city, 未知天气)}agentcreate_agent(modelllm,tools[get_user_location,get_city_weather],)# 调用时注入具体上下文 responseagent.invoke({messages:[HumanMessage(今天的天气如何)]},contextUserContext(user_iduser_3),)print(response[messages][-1].content)# 输出今天广州的天气是晴转多云气温在20到28摄氏度之间。运行这段代码你会看到 Agent 的自动执行流程1.收到用户问题发现没有指定城市先调用get_user_location工具拿到用户所在城市 “广州”2.再调用get_city_weather工具传入 “广州”拿到天气信息3.最后整理成自然语言回答用户全程 Agent 自动规划、自动调用无需我们写任何流程控制代码这就是 LangChain 中 Agent 提供的强大能力。六 、 工具调用调试Agent 的决策过程对开发者来说并非完全透明——我们知道它有哪些工具可用但如果不主动观察就难以直观了解它何时调用了工具、传入了哪些参数、哪一步出现了问题。想看清Agent的思考过程最直接的方式是查看response[“messages”]观察工具调用痕迹仍以上面的多工具协同示例为例print(response[messages])输出结果中可以看到如下关键信息[HumanMessage(content今天的天气如何,....),AIMessage(content,tool_calls[{name:get_user_location,args:{},id:...,type:tool_call}],invalid_tool_calls[]),ToolMessage(content广州,nameget_user_location,id...,tool_call_id...),AIMessage(tool_calls[{name:get_city_weather,args:{city:广州},id:...,type:tool_call}],invalid_tool_calls[]),ToolMessage(content广州晴转多云气温20-28℃,nameget_city_weather,...),AIMessage(content今天广州的天气是晴转多云气温在20到28摄氏度之间。)]通过分析这些消息可以清晰看到 Agent 的每一步决策与执行第一个 AIMessagetool_calls 非空表示模型决定调用 get_user_location 工具参数为空且未进入 invalid_tool_calls说明参数格式合法可正常执行。第一个 ToolMessage表示工具执行完成返回内容为“广州”。第二个 AIMessage再次发起工具调用这次是 get_city_weather参数为 {“city”: “广州”}。第二个 ToolMessage返回天气信息。最后一个 AIMessage模型根据工具返回结果生成最终回答。说明若 invalid_tool_calls 数组非空则表示模型生成的工具调用参数格式错误框架会跳过执行这也是排查工具调用失败的重要线索。可以使用如下代码过滤保留工具调用关键信息formsginresponse[messages]:ifhasattr(msg,tool_calls)and msg.tool_calls:# AIMessage 中的工具调用意图print(f【模型想调用】: {msg.tool_calls})elifisinstance(msg,ToolMessage):# 工具执行后的返回结果print(f【工具返回】: {msg.content[:100]}...)运行输出【模型想调用】:[{name:get_user_location,args:{},id:019d3de71518050cae285450f221b027,type:tool_call}]【工具返回】:广州...【模型想调用】:[{name:get_city_weather,args:{city:广州},id:019d3de7188dc957ba14814eaba5b41c,type:tool_call}]【工具返回】:广州晴转多云气温20-28℃...七、总结工具是 Agent 连接外部世界的桥梁掌握了工具开发你就能给 Agent 赋予无限的能力比如让它帮你查实时股票、操作数据库、发送邮件、控制智能家居、调用任意第三方 API 等等。在下一篇教程中我们将继续学习Agent“结构化输出“。