LangChain 1.0+ 第五篇:Tools 工具------让大模型从认识世界到改变世界
摘要:大模型只会生成文字,Tools 让它可以查询天气、调用 API、操作数据。本文讲透工具调用的完整流程、两种工具定义方式(手动 schema 与 @tool 装饰器),以及 tool_choice 强制使用工具的四种策略。
引子:只会聊天的大模型,如何"干实事"?
纯 LLM 的局限很明显:训练数据有截止时间、没有实时数据、没有外部系统访问权限 。问它"今天北京天气"它只能编;让它"帮我下单"它做不到。Tools(工具)就是破解之道:把能力以函数形式暴露给模型,模型决定何时调用、传什么参数,程序执行后把结果回传给模型,从而让模型"认识世界"变成"改变世界"。
承接 \[LangChain 1.0+ 入门:从只会调API到搭出AI应用] 与 \[LangChain 1.0+ 第四篇:Message 消息与提示词模板------让模型记住对话]。
一、工具调用的整体流程
结论:工具调用的本质是"模型不执行代码,只发调用请求,程序执行并把结果喂回模型"。
1. 两种调用方式
| 方式 | 说明 |
|---|---|
| 底层 API | bind_tools() 绑定工具,返回 ToolCall 请求,程序手动执行 |
| 高级封装 | create_agent() 创建智能体,自动完成"模型→工具→结果→模型"循环 |
2. 工具调用的四步流程(bind_tools 方式)
arduino
① 工具绑定:model.bind_tools(tools) → 模型"认识"这些工具
② 模型生成调用请求:AIMessage 返回 tool_calls(含工具名、参数、ID)
③ 程序执行:按 tool_calls 逐个调用真实函数,得到结果
④ 结果回传:把 ToolMessage 加回消息列表,再次 invoke 模型,生成最终回答
python
# ① 绑定工具
model_with_tools = model.bind_tools([multiply])
response = model_with_tools.invoke("3*4等于多少")
# ② 模型返回 tool_calls
# response.tool_calls → [{'name': 'multiply', 'args': {'a': 3, 'b': 4}, 'id': 'call_xxx'}]
# ③④ 执行并回传(完整循环见后文"应用案例")
3. 工具调用的关键消息链
模型返回工具调用后,必须按顺序把这些消息放回列表,再重新请求模型:
scss
HumanMessage(用户提问)
→ AIMessage(tool_calls=[调用请求]) ← 模型决定调用
→ ToolMessage(content=执行结果, tool_call_id=对应ID) ← 结果回传
→ AIMessage(最终回答) ← 模型综合后作答
易错点 :每个 ToolMessage 的 tool_call_id 必须与对应 AIMessage.tool_calls[i]["id"] 一一对应,否则模型无法关联结果。
二、定义工具方式一:不使用 @tool(手动 schema)
结论:直接把 Python 函数 + 手动构造的 JSON Schema 传给 convert_to_openai_tool() 完成转换。
1. 定义普通函数
python
def multiply(a: int, b: int) -> int:
"""将两个整数相乘。"""
return a * b
2. 手动构造参数 Schema
python
from langchain_core.tools import convert_to_openai_tool
multiply_schema = {
"type": "function",
"function": {
"name": "multiply",
"description": "将两个整数相乘",
"parameters": {
"type": "object",
"properties": {
"a": {"type": "integer", "description": "第一个整数"},
"b": {"type": "integer", "description": "第二个整数"}
},
"required": ["a", "b"]
}
}
}
tool = convert_to_openai_tool(multiply_schema)
手动写 schema 的三大弊端:
- 函数改了,schema 容易忘记同步改(职责分散);
- 手动维护,容易写错(类型、必填项);
- 参数一多,编写体验差。
所以生产环境强烈推荐下一种方式。
三、定义工具方式二:@tool 装饰器(推荐)
结论:@tool 装饰器从函数签名与 docstring 自动生成 schema,描述越规范,模型调用越准确。
1. 基本用法
python
from langchain_core.tools import tool
@tool
def multiply(a: int, b: int) -> int:
"""将两个整数相乘。"""
return a * b
@tool 自动完成三件事:
- 根据函数签名生成参数 schema(含类型、必填项);
- 根据函数名生成工具名;
- 根据 docstring 生成工具描述。
2. 描述如何影响模型选择
工具描述(description)决定模型"什么时候用、怎么用"这个工具:
- 描述中要写清楚:用途、使用时机、参数含义、边界条件;
- docstring 写得好,模型才能正确填参;
- 描述模糊 → 模型乱用或不用。
3. @tool 高级参数
| 参数 | 作用 |
|---|---|
name_or_callable |
自定义工具名,如 @tool("天气查询") |
description |
显式指定描述(覆盖 docstring) |
parse_docstring=True |
从 docstring 提取参数说明(Google 风格 Args:) |
args_schema |
用 Pydantic 类或 JSON Schema 精细控制参数 |
return_direct |
工具结果直接返回用户(不经过模型总结),适合简单工具 |
response_format |
工具响应格式 |
handle_tool_error |
工具报错时返回预设提示(布尔或函数) |
Google 风格 docstring 示例(配合 parse_docstring):
python
@tool(parse_docstring=True)
def get_weather(city: str, date: str = "今天") -> str:
"""
查询指定城市指定日期的天气情况。
Args:
city: 城市名称,如"北京"
date: 日期,如"2026-08-20",默认为今天
"""
return f"{city} {date} 天气晴朗"
4. args_schema:精细控制参数
方式一:Pydantic 类(推荐,校验+默认值)
python
from pydantic import BaseModel, Field
from langchain_core.tools import tool
class AddExclamationInput(BaseModel):
a: int = Field(description="加数1")
b: int = Field(description="加数2")
style: str = Field(description="感叹号风格", default="普通")
@tool(args_schema=AddExclamationInput)
def add_exclamation(a: int, b: int, style: str = "普通") -> str:
"""两个数相加,并按 style 风格加上感叹号。"""
if style == "普通":
return f"{a + b}!"
return f"{a + b}!!!"
方式二:JSON Schema
python
args_schema = {
"type": "object",
"properties": {
"a": {"type": "integer", "description": "加数1"},
"b": {"type": "integer", "description": "加数2"}
},
"required": ["a", "b"]
}
@tool(args_schema=args_schema)
def add(a: int, b: int) -> str:
"""两个整数相加。"""
return str(a + b)
四、完整应用案例:从"问天气"到"算乘法"
python
from langchain_core.tools import tool
from langchain_core.messages import AIMessage, ToolMessage
# 1. 定义两个工具
@tool
def multiply(a: int, b: int) -> int:
"""将两个整数相乘。"""
return a * b
@tool
def get_weather(city: str, date: str = "今天") -> str:
"""查询指定城市指定日期的天气情况。"""
return f"{city} {date} 天气晴朗"
# 2. 绑定工具
model_with_tools = model.bind_tools([multiply, get_weather])
messages = [HumanMessage(content="北京今天天气怎么样?3*4等于多少?")]
# 3. 模型生成调用请求
ai_msg = model_with_tools.invoke(messages)
messages.append(ai_msg)
# 4. 执行工具并回传结果
for tool_call in ai_msg.tool_calls:
if tool_call["name"] == "multiply":
result = multiply.invoke(tool_call["args"])
elif tool_call["name"] == "get_weather":
result = get_weather.invoke(tool_call["args"])
# 关键:tool_call_id 一一对应
messages.append(ToolMessage(content=str(result), tool_call_id=tool_call["id"]))
# 5. 再次调用模型,生成最终回答
final = model_with_tools.invoke(messages)
print(final.content)
一次提问可触发多个工具调用 (如同时查天气和算乘法),模型会并行返回多个 tool_calls,循环逐个执行回传即可。
五、强制使用工具:tool_choice
结论:tool_choice 控制模型是否必须调用工具,四种策略覆盖"不调用"到"指定工具"。
| 值 | 行为 |
|---|---|
"auto" |
模型自主决定是否调用(默认) |
"none" |
强制不调用任何工具,只输出文本 |
"required"(或 "any") |
强制调用工具(不指定具体哪个) |
工具名(如 "multiply") |
强制调用指定工具 |
python
model.bind_tools([multiply, get_weather], tool_choice="required")
model.bind_tools([multiply], tool_choice="multiply") # 指定工具
model.bind_tools([multiply], tool_choice="none")
六、实践要点总结
- 描述清晰:docstring 写清"用途、时机、参数、边界",模型才能正确选工具。
- 功能单一:一个工具只做一件事,职责单一最容易被模型正确调用。
- 失败处理 :三层防护------
handle_tool_error返回友好提示、模型侧提示词告知工具可能失败、必要时人工介入。 - 返回字符串:工具尽量返回字符串(模型好消费),复杂数据先序列化再返回。
- 同步/异步 :同步工具用
@tool,异步工具用@tool装饰async def,Agent 自动适配。 - 生产优先 @tool + Pydantic args_schema:schema 自动生成且带校验,避免手动维护漂移。
下一篇解决大模型输出的"最后一公里":结构化输出,让模型返回程序可用的 JSON,而不是自由文本。