LangChain 工具调用实战:从 @tool 定义到 bind_tools 与 tool_choice
大模型本身只能生成文字,不能直接访问天气接口、数据库或业务系统。工具(Tool)就是把一个外部函数描述给模型,让模型在需要时提出调用请求,再由程序真正执行函数并把结果交回模型。
完整链路可以概括为:
定义 Python 函数
↓
转换为模型可理解的工具 Schema
↓
model.bind_tools(tools)
↓
模型返回 tool_calls
↓
程序执行工具
↓
ToolMessage 回传模型
↓
模型生成最终回答
一、工具有两种调用方式
1. 直接调用工具
from langchain_core.tools import tool
@tool
def get_weather(city: str) -> str:
"""获取指定城市的天气信息"""
return f"{city} 晴天,温度 15°C"
result = get_weather.invoke({"city": "北京"})
print(result)
这里的 get_weather 已经不是普通函数,而是一个 LangChain StructuredTool。调用 .invoke() 时传入字典,字典的键名要和工具参数名一致。
2. 基于模型调用工具
model_with_tools = model.bind_tools([get_weather])
response = model_with_tools.invoke("北京天气如何?")
if response.tool_calls:
print("AI 想调用工具:", response.tool_calls)
else:
print("AI 直接回答:", response.content)
bind_tools() 的作用不是执行工具,而是把工具的名称、描述和参数 Schema 发送给模型。模型根据用户问题决定:直接回答,还是生成 tool_calls。
二、不使用 @tool 时,工具描述是怎样生成的?
LangChain 最终需要把 Python 函数转换为模型能理解的 JSON Schema。可以直接查看转换结果:
from langchain_core.utils.function_calling import convert_to_openai_tool
def get_weather(city: str = "北京"):
"""
查询城市的天气
Args:
city: 具体的城市
"""
return f"{city}天气晴朗"
print(convert_to_openai_tool(get_weather))
转换后的描述大致包含:
{
"type": "function",
"function": {
"name": "get_weather",
"description": "查询城市的天气",
"parameters": {
"type": "object",
"properties": {
"city": {"type": "string", "description": "具体的城市"}
}
}
}
}
模型并不会读取 Python 函数体来理解工具,它主要依赖这份结构化描述。因此工具名称、函数注释、参数类型和默认值都很重要。
三、工具描述的四个关键部分
1. 工具名称
默认使用 Python 函数名:
def get_weather(city: str):
...
模型看到的工具名通常就是 get_weather。名称应简洁、明确,使用动词更容易理解,例如 search_news、query_order。
也可以自定义名称:
@tool(parse_docstring=True, name_or_callable="getWeather")
def get_weather(city: str):
"""获取城市天气"""
return f"{city}天气晴朗"
实际开发通常建议直接使用清晰的函数名,避免函数名和工具名不一致导致调试困难。
2. description
工具必须有清楚的描述。使用 @tool 时,如果没有显式提供 description,函数一般需要写 docstring:
@tool
def get_weather(city: str):
"""查询指定城市当日天气"""
return f"{city}天气晴朗"
描述应该说明“工具做什么”,不要只写“工具函数”。模型会依据描述判断什么时候该调用它。
3. 参数说明
推荐使用 Google 风格 docstring,并开启 parse_docstring=True:
@tool(parse_docstring=True)
def get_weather(city: str) -> str:
"""
查询指定城市当日天气
Args:
city: 城市名称,例如北京、上海
"""
return f"{city}天气晴朗"
参数注释会进入 Schema,模型就能知道 city 应该传什么。
4. 类型和默认值
def get_weather(city: str, days: int = 1):
...
str、int、bool 等类型会被转换为 JSON Schema 类型。设置默认值后,该参数通常不会出现在 required 中,模型可以省略它。
def get_weather(city: str = "北京"):
...
没有默认值的参数通常是必填参数:
def get_weather(date: str, city: str = "北京"):
...
此时 date 必须提供,city 可以使用默认值。
四、使用 @tool 装饰器
@tool 会把普通函数包装成 LangChain 工具:
from langchain.tools import tool
@tool(parse_docstring=True)
def get_news(domain: str) -> str:
"""
查询指定领域的热点新闻
Args:
domain: 新闻领域
"""
return f"{domain}领域暂无新闻"
装饰前:
get_news 是普通 Python 函数
装饰后:
get_news 具有 name、description、args_schema 和 invoke() 等工具能力
五、使用 Pydantic 定义 args_schema
工具参数复杂时,可以使用 Pydantic 模型集中定义校验规则:
from pydantic import BaseModel, Field
from typing import Literal
from langchain.tools import tool
class WeatherInput(BaseModel):
city: str = Field(default="北京", description="具体城市")
unit: Literal["celsius", "fahrenheit"] = "celsius"
include_forecast: bool = Field(
default=False,
description="是否包含未来五天预报",
)
@tool(args_schema=WeatherInput)
def get_weather(
city: str,
unit: str = "celsius",
include_forecast: bool = False,
) -> str:
"""获取城市天气"""
return f"{city}天气晴朗"
这里:
Field(description=...)给模型提供参数含义;Literal[...]限制参数只能取指定值;- 默认值让参数变成可选;
args_schema把这套规则绑定到工具上。
六、从 Message 流转看完整工具调用
模型不会直接运行 Python 函数。程序必须负责中间的执行步骤:
from langchain.messages import HumanMessage
@tool
def get_weather(city: str) -> str:
"""获取天气"""
return f"{city}天气晴朗"
model_with_tools = model.bind_tools([get_weather])
messages = [HumanMessage("今天北京天气如何?")]
# 第一次调用:模型只提出工具请求
response = model_with_tools.invoke(messages)
messages.append(response)
for tool_call in response.tool_calls:
if tool_call["name"] == "get_weather":
tool_response = get_weather.invoke(tool_call)
messages.append(tool_response)
# 第二次调用:模型读取 ToolMessage,组织最终回答
final_response = model_with_tools.invoke(messages)
print(final_response.content)
运行顺序是:
1. HumanMessage 提出问题
2. model_with_tools 返回带 tool_calls 的 AIMessage
3. 程序读取 tool_calls
4. get_weather.invoke(tool_call) 执行真实函数
5. 生成 ToolMessage 并追加到 messages
6. 再次调用模型
7. 模型根据工具结果返回自然语言答案
这也是大模型和 Agent 的一个重要区别:单独的大模型通常只会提出工具调用请求,Agent 或程序代码负责真正执行工具并继续循环。
七、多工具调用
可以一次绑定多个工具:
@tool(parse_docstring=True)
def get_weather(city: str) -> str:
"""获取当日天气"""
return f"{city}当天晴朗"
@tool(parse_docstring=True)
def get_news() -> str:
"""获取当日新闻"""
return "今天有新的科技新闻"
model_with_tools = model.bind_tools([get_weather, get_news])
response = model_with_tools.invoke(
"今天杭州天气如何?今天有什么新闻?"
)
模型可能返回一个或多个 tool_calls。程序应遍历全部调用,并根据 tool_call["name"] 分发给对应工具:
for tool_call in response.tool_calls:
if tool_call["name"] == "get_weather":
messages.append(get_weather.invoke(tool_call))
elif tool_call["name"] == "get_news":
messages.append(get_news.invoke(tool_call))
工具名称必须和绑定时的名称一致。代码中如果把 get_weather 错写成 get_weacher_and_forecast,就不会进入正确的分支。
八、tool_choice:控制模型是否调用工具
1. tool_choice="none"
禁止调用工具:
model_with_tools = model.bind_tools(
[get_weather],
tool_choice="none",
)
即使用户问天气,模型也只能直接生成文本答案。
2. tool_choice="auto"
由模型自行判断:
model_with_tools = model.bind_tools(
[get_weather],
tool_choice="auto",
)
用户问天气时可能调用工具;用户问“2 + 3 = ?”时可能直接回答。它是最常见、最自然的模式。
3. tool_choice="required"
要求模型必须调用工具:
model_with_tools = model.bind_tools(
[get_weather],
tool_choice="required",
)
即使用户问一个简单数学问题,模型也会被要求从工具集合中选一个调用。工具本身是否适合这个问题,需要开发者谨慎设计。
4. 强制调用指定工具
绑定多个工具时,可以强制指定一个:
model_with_tools = model.bind_tools(
[get_weather1, get_weather2],
tool_choice="get_weather2",
)
此时模型必须调用名为 get_weather2 的工具。
九、工具设计的实践建议
1. 工具描述要面向模型
不要只写:
"""工具函数"""
应该写清楚:
"""
查询指定城市当天的天气。
Args:
city: 城市名称,例如北京或上海。
"""
2. 参数尽量少而明确
工具参数越多,模型越容易填错。可以把复杂参数放进 Pydantic Schema,并给枚举字段和默认值。
3. 工具返回结果要便于模型阅读
推荐返回结构清晰的字符串或字典,而不是大段无关日志。
4. 工具执行必须有异常处理
真实 API 可能超时、鉴权失败或返回空数据。生产环境应把异常转换为模型可理解的工具错误消息,并决定是否重试。
5. 不要把 API Key 写进工具代码
使用 .env 和 load_dotenv(override=True) 加载密钥,并通过环境变量读取。
十、总结
@tool:把普通函数包装成 LangChain 工具
docstring:提供工具和参数说明
args_schema:用 Pydantic 约束复杂参数
bind_tools:把工具描述交给模型,不会自动执行工具
tool_calls:模型提出的工具调用请求
ToolMessage:程序执行工具后返回给模型的结果
tool_choice:控制不调用、自动调用、必须调用或指定调用
真正完整的工具调用不是一次 API 请求,而是一个消息循环:模型提出请求,程序执行工具,工具结果回到模型,模型再生成最终答案。理解这条链路后,就能进一步使用 LangGraph 构建带工具的 Agent。
转载自 CSDN-专业IT技术社区
原文链接:https://blog.csdn.net/agood_day_/article/details/163763096




