一:定义工具
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+bargs_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)