news 2026/6/5 13:41:18

LangChain 工具调用机制:从工具定义到完整调用闭环

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
LangChain 工具调用机制:从工具定义到完整调用闭环

一:定义工具

1.@tool

简单定义

fromlangchain_core.toolsimporttool@tooldefadd(a:int,b:int)->int:"""两数相加。 Args: a: 第一个整数 b: 第二个整数 """returna+b result=add.invoke({"a":1,"b":2})print(result)函数名->工具名称 文档字符串->工具描述 类型提示->工具参数信息

注意:这个工具描述必须写,不然会报错

优点:

简单 适合参数少的工具 适合快速定义

缺点:

参数描述能力一般 复杂参数不够清晰

Pydantic 定义参数 Schema

fromlangchain_core.toolsimporttoolfrompydanticimportBaseModel,FieldclassAddInput(BaseModel):a:int=Field(...,description="第一个整数")b:int=Field(...,description="第二个整数")@tool(args_schema=AddInput)defadd(a:int,b:int)->int:"""两数相加。"""returna+b

args_schema=AddInput 意思是:
这个工具的参数结构,由 AddInput 这个类来描述

Field(…, description=“第一个整数”) 里面的三个点表示:

这个字段是必填项,没有默认值

这种方式的好处是参数描述更完整。

尤其当你的工具参数很多的时候,比如:

城市 日期 用户 ID 课程 ID 查询类型 分页数量

这时候用Pydantic会更清晰。

Annotated 给参数加描述

fromtyping_extensionsimportAnnotatedfromlangchain_core.toolsimporttool@tooldefadd(a:Annotated[int,"第一个整数"],b:Annotated[int,"第二个整数"])->int:"""两数相加。"""returna+b

这种写法的特点是:

不用额外写 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:"""两数相加。"""returna+b add_tool=StructuredTool.from_function(func=add)result=add_tool.invoke({"a":2,"b":5})print(result)

不用函数文档字符串,手动传工具信息

假设函数里面不写文档字符串:

defadd(a:int,b:int)->int:returna+b

那我们可以在 from_function() 里面手动传:

add_tool=StructuredTool.from_function(func=add,name="ADD",description="两数相加")

二:绑定工具和调用工具

1.先选择工具

用户问题 ↓ 绑定工具后的聊天模型 ↓ 模型判断是否需要工具 ↓ 生成 tool_calls ↓ 程序执行对应工具 ↓ 获得工具结果 ↓ 把结果交回模型 ↓ 模型生成最终回答

代码:

fromlangchain_core.toolsimporttoolfromlangchain_ollamaimportChatOllamafromlangchain_deepseekimportChatDeepSeekfromlangchain.chat_modelsimportinit_chat_modelfromlangchain_core.messagesimportSystemMessage,HumanMessagefromlangchain_core.output_parsersimportStrOutputParser@tooldefadd(a:int,b:int)->int:"""两数相加。"""returna+b@tooldefmultiply(a:int,b:int)->int:"""两数相乘"""returna*b model=init_chat_model(model="deepseek-chat")tools=[add,multiply]model_with_tools=model.bind_tools(tools)response=model_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,# 输出Token'prompt_tokens':345,# 输入Token'total_tokens':413# 总Token},# 模型提供商'model_provider':'deepseek',# 模型名称'model_name':'deepseek-v4-flash',# 模型指纹'system_fingerprint':'fp_8b330d02d0_prod0820_fp8_kvcache_20260402',# 本次请求ID'id':'36a06579-7f92-4d28-8bdf-5195e0d73c03',# 结束原因# stop -> 正常回答结束# tool_calls -> 准备调用工具'finish_reason':'tool_calls',# 概率信息'logprobs':None}# LangChain本次运行IDid='lc_run--019e9148-06d2-7641-aa41-f3ae29bbfd49-0'# 工具调用信息(最重要)tool_calls=[{# 模型选择的工具'name':'add',# 工具参数'args':{'a':2,'b':3},# 本次工具调用ID'id':'call_00_UroDWIRVETe9N7jcpvwy0311',# 调用类型'type':'tool_call'}]# 无效工具调用# 为空说明格式正确invalid_tool_calls=[]# LangChain统一封装的Token统计usage_metadata={'input_tokens':345,'output_tokens':68,'total_tokens':413,# 命中缓存Token'input_token_details':{'cache_read':256},'output_token_details':{}}

从返回的消息可以看到,它没有给我们计算,而是给出了一个选择工具的信息,它选择了add

# 工具调用信息tool_calls=[{# 模型选择的工具'name':'add',# 工具参数'args':{'a':2,'b':3},# 本次工具调用ID'id':'call_00_UroDWIRVETe9N7jcpvwy0311',# 调用类型'type':'tool_call'}]

这时候我们就需要再次把这个消息返回给ai

2.从 tool_calls 里取出工具名和参数

tool_call=ai_msg.tool_calls[0]tool_result=multiply.invoke(tool_call)

此时 tool_call 大概长这样:

{"name":"multiply","args":{"a": 2,"b": 3},"id":"...","type":"tool_call"}

然后我们可以执行:

tool_result=multiply.invoke(tool_call)

注意,这里LangChain的工具可以直接接收这种tool_call结构。

执行之后会得到一个:

ToolMessage

它里面保存了工具执行结果。

比如:

ToolMessage(content="6",tool_call_id="...")

3.完整消息链(最终)

完整消息链:HumanMessage + AIMessage + ToolMessage

这三个传给ai,然后让ai整理答案和格式,给我发过来

最终我们要构造一个消息列表:

messages=[HumanMessage(content="2 * 3 等于多少?"),ai_msg,tool_result]

它们分别代表:

HumanMessage:用户原始问题 AIMessage:模型选择了哪个工具 ToolMessage:工具执行后的结果

然后再把这个消息列表交给模型:

final_msg=model.invoke(messages)print(final_msg.content)

最后模型就能回答:2 * 3 的结果是 6。

这才是完整的工具调用闭环。

4.完整代码

fromlangchain_core.toolsimporttoolfromlangchain_ollamaimportChatOllamafromlangchain_deepseekimportChatDeepSeekfromlangchain.chat_modelsimportinit_chat_modelfromlangchain_core.messagesimportSystemMessage,HumanMessagefromlangchain_core.output_parsersimportStrOutputParser@tooldefadd(a:int,b:int)->int:"""两数相加。"""returna+b@tooldefmultiply(a:int,b:int)->int:"""两数相乘"""returna*b model=init_chat_model(model="deepseek-chat")tools=[add,multiply]model_with_tools=model.bind_tools(tools)messages=[HumanMessage(content="2 * 3 等于多少?"),]#让ai选择工具ai_msg=model_with_tools.invoke(messages)messages.append(ai_msg)tool_call=ai_msg.tool_calls[0]tool_result=multiply.invoke(tool_call)messages.append(tool_result)final=model.invoke(messages)print(final.content)

5.进阶调用

上述写法其实吧功能写死了.

tool_result=multiply.invoke(tool_call)

所以我们需要一个字映射表

tool_map={"add":add,"multiply":multiply}tool_call=ai_msg.tool_calls[0]selected_tool=tool_map[tool_call["name"].lower()]tool_result=selected_tool.invoke(tool_call)

完整升级代码:

fromlangchain_core.toolsimporttoolfromlangchain_core.messagesimportHumanMessagefromlangchain.chat_modelsimportinit_chat_model# 1. 定义工具@tooldefadd(a:int,b:int)->int:"""两数相加"""returna+b@tooldefmultiply(a:int,b:int)->int:"""两数相乘"""returna*b# 2. 定义模型model=init_chat_model(model="gpt-4o-mini")# 3. 工具列表tools=[add,multiply]# 4. 绑定工具model_with_tools=model.bind_tools(tools)# 5. 工具映射表tool_map={"add":add,"multiply":multiply}# 6. 构造消息列表messages=[HumanMessage(content="2 * 3 等于多少?6 + 6 等于多少?")]# 7. 第一次调用:让模型选择工具ai_msg=model_with_tools.invoke(messages)# 8. 把 AIMessage 加入消息列表messages.append(ai_msg)# 9. 遍历所有 tool_calls,真正执行工具fortool_callinai_msg.tool_calls:selected_tool=tool_map[tool_call["name"].lower()]tool_result=selected_tool.invoke(tool_call)messages.append(tool_result)# 10. 第二次调用:让模型根据工具结果组织最终答案final_msg=model.invoke(messages)print(final_msg.content)
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/5 13:37:04

Mac音乐格式解密指南:3分钟解锁QQ音乐加密文件播放限制

Mac音乐格式解密指南:3分钟解锁QQ音乐加密文件播放限制 【免费下载链接】QMCDecode QQ音乐QMC格式转换为普通格式(qmcflac转flac,qmc0,qmc3转mp3, mflac,mflac0等转flac),仅支持macOS,可自动识别到QQ音乐下载目录,默认…

作者头像 李华
网站建设 2026/6/5 13:34:57

ModelSim仿真信号消失?-voptargs=+acc解决generate块内部信号可见性问题

1. 问题背景与核心痛点在FPGA或ASIC设计验证中,ModelSim/QuestaSim这类仿真器是我们工程师的“老伙计”。它速度快,功能全,但有时候也像一位固执的老师傅,总想帮你“优化”掉一些它认为不必要的东西,结果反而给我们调试…

作者头像 李华
网站建设 2026/6/5 13:33:41

告别喜马拉雅VIP音频无法下载的烦恼:XMly-Downloader-Qt5使用全攻略

告别喜马拉雅VIP音频无法下载的烦恼:XMly-Downloader-Qt5使用全攻略 【免费下载链接】xmly-downloader-qt5 喜马拉雅FM专辑下载器. 支持VIP与付费专辑. 使用GoQt5编写(Not Qt Binding). 项目地址: https://gitcode.com/gh_mirrors/xm/xmly-downloader-qt5 你…

作者头像 李华
网站建设 2026/6/5 13:32:55

多周期期货策略何时算新 K 线:各周期 datetime 分开触发

前言 多周期策略在国内期货里很常见:例如用 30 分钟 K 线判断趋势方向(只做多或只做空),用 5 分钟 K 线找入场点。两个周期对应两张不同的 K 线表,各有自己的 datetime 列(由行情服务按各自周期写入&#x…

作者头像 李华