LangChain 工具调用机制:从工具定义到完整调用闭环 一:定义工具1.tool简单定义fromlangchain_core.toolsimporttooltooldefadd(a:int,b:int)-int:两数相加。 Args: a: 第一个整数 b: 第二个整数 returnab resultadd.invoke({a:1,b:2})print(result)函数名-工具名称 文档字符串-工具描述 类型提示-工具参数信息注意:这个工具描述必须写,不然会报错优点简单 适合参数少的工具 适合快速定义缺点参数描述能力一般 复杂参数不够清晰Pydantic 定义参数 Schemafromlangchain_core.toolsimporttoolfrompydanticimportBaseModel,FieldclassAddInput(BaseModel):a:intField(...,description第一个整数)b:intField(...,description第二个整数)tool(args_schemaAddInput)defadd(a:int,b:int)-int:两数相加。returnabargs_schemaAddInput 意思是这个工具的参数结构由 AddInput 这个类来描述Field(…, description“第一个整数”) 里面的三个点表示这个字段是必填项没有默认值这种方式的好处是参数描述更完整。尤其当你的工具参数很多的时候比如城市 日期 用户 ID 课程 ID 查询类型 分页数量这时候用Pydantic会更清晰。Annotated 给参数加描述fromtyping_extensionsimportAnnotatedfromlangchain_core.toolsimporttooltooldefadd(a:Annotated[int,第一个整数],b:Annotated[int,第二个整数])-int:两数相加。returnab这种写法的特点是不用额外写 Pydantic 类 但又能给参数加上描述适合参数不多但是你又希望参数解释更清楚的情况。参数对比方式写法适合场景普通tool函数 文档字符串 类型提示简单工具tool(args_schema...)Pydantic 类定义参数参数复杂、需要校验Annotated直接给参数添加描述参数少但希望描述清楚2.StructuredTool.from_function()不用 tool 装饰器也可以用 StructuredTool.from_function() 把普通 Python 函数变成 LangChain 工具。不使用 tool而是使用fromlangchain_core.toolsimportStructuredTool然后通过 StructuredTool.from_function() 把普通函数转成工具。代码大概是这样fromlangchain_core.toolsimportStructuredTooldefadd(a:int,b:int)-int:两数相加。returnab add_toolStructuredTool.from_function(funcadd)resultadd_tool.invoke({a:2,b:5})print(result)不用函数文档字符串手动传工具信息假设函数里面不写文档字符串defadd(a:int,b:int)-int:returnab那我们可以在 from_function() 里面手动传add_toolStructuredTool.from_function(funcadd,nameADD,description两数相加)二:绑定工具和调用工具1.先选择工具用户问题 ↓ 绑定工具后的聊天模型 ↓ 模型判断是否需要工具 ↓ 生成 tool_calls ↓ 程序执行对应工具 ↓ 获得工具结果 ↓ 把结果交回模型 ↓ 模型生成最终回答代码fromlangchain_core.toolsimporttoolfromlangchain_ollamaimportChatOllamafromlangchain_deepseekimportChatDeepSeekfromlangchain.chat_modelsimportinit_chat_modelfromlangchain_core.messagesimportSystemMessage,HumanMessagefromlangchain_core.output_parsersimportStrOutputParsertooldefadd(a:int,b:int)-int:两数相加。returnabtooldefmultiply(a:int,b:int)-int:两数相乘returna*b modelinit_chat_model(modeldeepseek-chat)tools[add,multiply]model_with_toolsmodel.bind_tools(tools)responsemodel_with_tools.invoke(请帮我计算 2 3)print(response)print(------------)print(response.content)# 大模型返回给用户看的内容content好的我来计算 2 3。# 额外参数# refusal表示是否拒绝回答# None表示正常回答additional_kwargs{refusal:None}# 模型返回元数据response_metadata{# Token消耗统计token_usage:{completion_tokens:68,# 输出Tokenprompt_tokens:345,# 输入Tokentotal_tokens:413# 总Token},# 模型提供商model_provider:deepseek,# 模型名称model_name:deepseek-v4-flash,# 模型指纹system_fingerprint:fp_8b330d02d0_prod0820_fp8_kvcache_20260402,# 本次请求IDid:36a06579-7f92-4d28-8bdf-5195e0d73c03,# 结束原因# stop - 正常回答结束# tool_calls - 准备调用工具finish_reason:tool_calls,# 概率信息logprobs:None}# LangChain本次运行IDidlc_run--019e9148-06d2-7641-aa41-f3ae29bbfd49-0# 工具调用信息最重要tool_calls[{# 模型选择的工具name:add,# 工具参数args:{a:2,b:3},# 本次工具调用IDid:call_00_UroDWIRVETe9N7jcpvwy0311,# 调用类型type:tool_call}]# 无效工具调用# 为空说明格式正确invalid_tool_calls[]# LangChain统一封装的Token统计usage_metadata{input_tokens:345,output_tokens:68,total_tokens:413,# 命中缓存Tokeninput_token_details:{cache_read:256},output_token_details:{}}从返回的消息可以看到它没有给我们计算而是给出了一个选择工具的信息它选择了add# 工具调用信息tool_calls[{# 模型选择的工具name:add,# 工具参数args:{a:2,b:3},# 本次工具调用IDid:call_00_UroDWIRVETe9N7jcpvwy0311,# 调用类型type:tool_call}]这时候我们就需要再次把这个消息返回给ai2.从 tool_calls 里取出工具名和参数tool_callai_msg.tool_calls[0]tool_resultmultiply.invoke(tool_call)此时 tool_call 大概长这样{name:multiply,args:{a: 2,b: 3},id:...,type:tool_call}然后我们可以执行tool_resultmultiply.invoke(tool_call)注意这里LangChain的工具可以直接接收这种tool_call结构。执行之后会得到一个ToolMessage它里面保存了工具执行结果。比如ToolMessage(content6,tool_call_id...)3.完整消息链最终完整消息链HumanMessage AIMessage ToolMessage这三个传给ai然后让ai整理答案和格式给我发过来最终我们要构造一个消息列表messages[HumanMessage(content2 * 3 等于多少),ai_msg,tool_result]它们分别代表HumanMessage用户原始问题 AIMessage模型选择了哪个工具 ToolMessage工具执行后的结果然后再把这个消息列表交给模型final_msgmodel.invoke(messages)print(final_msg.content)最后模型就能回答2 * 3 的结果是 6。这才是完整的工具调用闭环。4.完整代码fromlangchain_core.toolsimporttoolfromlangchain_ollamaimportChatOllamafromlangchain_deepseekimportChatDeepSeekfromlangchain.chat_modelsimportinit_chat_modelfromlangchain_core.messagesimportSystemMessage,HumanMessagefromlangchain_core.output_parsersimportStrOutputParsertooldefadd(a:int,b:int)-int:两数相加。returnabtooldefmultiply(a:int,b:int)-int:两数相乘returna*b modelinit_chat_model(modeldeepseek-chat)tools[add,multiply]model_with_toolsmodel.bind_tools(tools)messages[HumanMessage(content2 * 3 等于多少),]#让ai选择工具ai_msgmodel_with_tools.invoke(messages)messages.append(ai_msg)tool_callai_msg.tool_calls[0]tool_resultmultiply.invoke(tool_call)messages.append(tool_result)finalmodel.invoke(messages)print(final.content)5.进阶调用上述写法其实吧功能写死了.tool_resultmultiply.invoke(tool_call)所以我们需要一个字映射表tool_map{add:add,multiply:multiply}tool_callai_msg.tool_calls[0]selected_tooltool_map[tool_call[name].lower()]tool_resultselected_tool.invoke(tool_call)完整升级代码:fromlangchain_core.toolsimporttoolfromlangchain_core.messagesimportHumanMessagefromlangchain.chat_modelsimportinit_chat_model# 1. 定义工具tooldefadd(a:int,b:int)-int:两数相加returnabtooldefmultiply(a:int,b:int)-int:两数相乘returna*b# 2. 定义模型modelinit_chat_model(modelgpt-4o-mini)# 3. 工具列表tools[add,multiply]# 4. 绑定工具model_with_toolsmodel.bind_tools(tools)# 5. 工具映射表tool_map{add:add,multiply:multiply}# 6. 构造消息列表messages[HumanMessage(content2 * 3 等于多少6 6 等于多少)]# 7. 第一次调用让模型选择工具ai_msgmodel_with_tools.invoke(messages)# 8. 把 AIMessage 加入消息列表messages.append(ai_msg)# 9. 遍历所有 tool_calls真正执行工具fortool_callinai_msg.tool_calls:selected_tooltool_map[tool_call[name].lower()]tool_resultselected_tool.invoke(tool_call)messages.append(tool_result)# 10. 第二次调用让模型根据工具结果组织最终答案final_msgmodel.invoke(messages)print(final_msg.content)