LangChain - Tools
目录+
大模型本身只会生成文本——它不能查数据库、不能上网搜索、更不能打开你电脑上的计算器。Tool(工具)就是挂给模型的"可调用函数清单":模型负责决定"调哪个、传什么参数",真正执行的永远是你的代码。
不带工具时:
你: 现在几点了?
AI: 抱歉,我无法获取实时时间。 ← 它真的做不到
给模型挂上一个 get_current_time 工具后:
你: 现在几点了?
AI: (不直接回答,而是返回一个请求)
→ 我想调用 get_current_time,参数 {}
[你的代码执行函数,把结果 "14:32" 送回去]
AI: 现在是下午 2 点 32 分。
一个容易误解的点:模型从头到尾没有执行任何东西,它只是输出一段结构化文本:"我想调 X,参数是 Y"。解析请求、运行函数、把结果送回去的,都是 LangChain 和你的代码。
模型负责决策,代码负责执行,消息列表负责在两者之间传递。
也正因为决策权在模型,工具的名称和描述写得好不好,直接决定模型能不能在对的时机选对工具——它们是提示词的一部分,不是注释。
工具的定义要素
从上面的流程能反推出,一个完整的工具要有五个要素:
| 要素 | 作用 | 给谁看 |
|---|---|---|
| 名称 (name) | 工具的唯一标识,模型在 tool_calls 里用它指定调用目标 | 模型 |
| 描述 (description) | 告诉模型这个工具能做什么、什么时候该用 | 模型 |
| 参数 Schema (args_schema) | 输入参数的 JSON 模式:有哪些参数、什么类型、什么含义 | 模型 |
| 执行函数 (func) | 实际执行的 Python 函数 | 机器 |
| 返回结果 | 函数的输出,会送回模型作为下一步推理的依据 | 模型 |
注意"给谁看"这一列:前三个要素是给模型看的,它们会被序列化成 JSON Schema 拼进请求里,属于提示词的一部分;只有函数体是给机器执行的。所以工具的 docstring 和参数命名要像写文档一样认真:写得含糊,模型就会选错工具、传错参数。
另外,面向同一个目标的一组工具可以打包成 Toolkit(工具包),比如 SQLDatabaseToolkit 就打包了"列表名、查 Schema、执行 SQL"等一整套数据库工具。
工具调用的完整流程

使用工具分三步:定义 → 组装 → 接入 agent,需要写的代码只有这些:
1. 定义工具 @tool 把函数变成工具(名称/描述/参数 Schema 自动生成)
2. 组装成工具集 把要用的工具放进一个列表 tools = [...]
3. 接入 agent create_agent(model, tools=tools),之后 invoke 即可
# 1. 定义工具
@tool
def get_current_time() -> str:
"""获取当前时间"""
from datetime import datetime
return datetime.now().strftime("%Y-%m-%d %H:%M:%S")
# 2. 组装成工具集
tools = [get_current_time]
# 3. 接入 agent
agent = create_agent(model=model, tools=tools)
result = agent.invoke({"messages": [{"role": "user", "content": "现在几点了?"}]})
print(result["messages"][-1].content) # "现在是 14 点 32 分。"
invoke 内部会自动跑一个"决策 → 执行 → 回传 → 回答"的运行时循环:
模型返回 tool_calls(调谁、传什么)→ agent 执行对应函数 → 结果包成 ToolMessage 送回模型 → 模型给出答案,或继续要求调工具。
循环的每一步都记录在 result["messages"] 里,打印出来可以逐条对照:
HumanMessage("现在几点了?") ← 你的输入
AIMessage(tool_calls=[{'name': 'get_current_time',
'args': {}, 'id': 'call_xxx'}]) ← 决策:调谁、传什么
ToolMessage(content="2026-07-02 14:32:00",
tool_call_id="call_xxx") ← 执行 + 回传
AIMessage("现在是 14 点 32 分。") ← 最终回答
几个关键细节:
tool_calls是一个列表——模型一轮可能同时要求调多个工具;ToolMessage带着对应的tool_call_id,模型靠它把"请求"和"结果"对上号;- 拿到工具结果后模型也可能再次返回
tool_calls(比如先查了表名、接着要查 Schema),agent 会一直循环,直到模型不再要求调工具才停。
定义工具的几种方式
方式一:Tool 类直接实例化(旧式,简单直接)
最原始的方式:先写普通函数,再用 Tool 构造函数手动包装,名称和描述都显式传入:
from datetime import datetime
from langchain_core.tools import Tool
def get_current_time(input: str = "") -> str:
return f"当前时间{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}"
def recom_drink(input: str = "") -> str:
return "距离您500米内有:蜜雪冰城、茶颜悦色"
tools = [
Tool(name="get_current_time", func=get_current_time,
description="当你想知道现在的时间时调用"),
Tool(name="recom_drink", func=recom_drink,
description="用户口渴,推荐附近的饮料店"),
]
两个特点:
- 三要素全手动:模型看到的名称、描述以构造参数为准,函数名和 docstring 不参与;
- 只支持单字符串输入:
Tool是旧式工具,不解析类型注解,参数 Schema 固定是"一个字符串"——所以即使函数不需要参数,也得留一个input: str = ""的占位参数;多参数、带类型的工具它表达不了。
适合无参或单字符串参数的简单工具;参数复杂时用下面的方式。
方式二:@tool 装饰器(最常用)
一行装饰器把普通 Python 函数变成工具,函数名→名称,docstring→描述,类型注解→参数 Schema,三个"给模型看的要素"全部自动生成:
from langchain.tools import tool
@tool
def multiply(a: int, b: int) -> int:
"""计算两个整数的乘积"""
return a * b
print(multiply.name) # multiply
print(multiply.description) # 计算两个整数的乘积
print(multiply.args) # {'a': {...'type': 'integer'}, 'b': {...'type': 'integer'}}
参数含义比较复杂、想给模型更多提示时,用 Pydantic 模型精细描述每个参数:
from pydantic import BaseModel, Field
from langchain.tools import tool
class SearchInput(BaseModel):
query: str = Field(description="搜索关键词,应提取用户问题中的核心实体")
limit: int = Field(default=5, description="返回结果条数,1~20")
@tool(args_schema=SearchInput)
def search_docs(query: str, limit: int = 5) -> str:
"""在内部知识库中搜索文档,当用户询问公司制度、产品细节时使用"""
...
方式三:StructuredTool.from_function
不方便加装饰器(比如函数来自第三方库、或要动态批量注册)时,用类方法包装,还能同时挂同步/异步两个实现:
from langchain_core.tools import StructuredTool
def query_order(order_id: str) -> str:
return f"订单 {order_id}:已发货"
async def aquery_order(order_id: str) -> str:
...
order_tool = StructuredTool.from_function(
func=query_order,
coroutine=aquery_order, # 异步版本,agent 异步执行时自动选用
name="query_order",
description="根据订单号查询订单状态",
)
方式四:继承 BaseTool(完全控制)
需要在工具里维护状态、自定义错误处理等复杂场景时,直接继承基类实现 _run。日常用得少,了解即可:
from langchain_core.tools import BaseTool
class WeatherTool(BaseTool):
name: str = "get_weather"
description: str = "查询指定城市的实时天气"
def _run(self, city: str) -> str:
return f"{city}:晴,28℃"
方式五:预定义工具 / Toolkit(拿来即用)
社区包里有大量现成工具,实例化后直接使用:
# 内置搜索工具:Tavily
from langchain_community.tools import TavilySearchResults
search = TavilySearchResults(max_results=2)
client_with_tools = client.bind_tools([search])
# Toolkit:一次拿到一组数据库工具
from langchain_community.agent_toolkits import SQLDatabaseToolkit
toolkit = SQLDatabaseToolkit(db=db, llm=client)
tools = toolkit.get_tools() # 列表名/查Schema/执行SQL 等一整套
五种方式对比:
| 方式 | 适用场景 | 定义成本 |
|---|---|---|
Tool 类实例化 | 无参/单字符串参数的简单工具(旧式) | 低 |
@tool 装饰器 | 自己写函数,绝大多数场景 | 最低 |
StructuredTool.from_function | 包装已有函数、需要同步+异步双实现 | 低 |
继承 BaseTool | 工具内部要维护状态、深度定制 | 高 |
| 预定义工具 / Toolkit | 搜索、数据库等通用能力 | 零(开箱即用) |
工具调用的几种方式
定义好的工具,有三种调用方式,自动化程度从低到高。
方式一:直接 invoke(测试工具本身)
工具本身就是可执行对象,可以不经过模型直接调用。开发时用来单测工具逻辑:
print(get_current_time.invoke({})) # "2026-07-02 14:32:00"
print(multiply.invoke({"a": 3, "b": 4})) # 12
这一步没有模型参与,纯粹验证函数本身能不能跑对。
方式二:bind_tools 手动循环
把工具绑到模型上,模型只负责返回 tool_calls,解析、执行、回传都自己写:
from langchain.messages import HumanMessage, ToolMessage
# 绑定:把工具的说明书挂到模型上
model_with_tools = model.bind_tools([get_current_time])
# 决策:模型不直接回答,而是返回 tool_calls
messages = [HumanMessage(content="现在几点了?")]
ai_msg = model_with_tools.invoke(messages)
# 执行 + 回传:跑函数,结果包成 ToolMessage 追加进消息列表
messages.append(ai_msg)
for tc in ai_msg.tool_calls:
result = get_current_time.invoke(tc["args"])
messages.append(ToolMessage(content=result, tool_call_id=tc["id"]))
# 最终回答:模型看到工具结果,给出自然语言答案
final = model_with_tools.invoke(messages)
print(final.content)
模型可能连续多轮要求调工具,实际要写成 while 循环。这种方式能精确控制每一步,但代码繁琐,主要价值是理解原理。
方式三:create_agent 自动循环(推荐)
就是前面"使用工具三步"的写法:工具接入 agent,"决策 → 执行 → 回传 → 回答"的循环由 invoke 内部完成:
agent = create_agent(model=model, tools=[get_current_time])
result = agent.invoke({"messages": [{"role": "user", "content": "现在几点了?"}]})
还可以配 checkpointer(多轮记忆)、system_prompt、middleware 等能力,实际项目用这种。
三种方式对比:
| 方式 | 模型参与 | 循环谁来写 | 用途 |
|---|---|---|---|
直接 invoke | 无 | 无循环 | 单测工具逻辑 |
bind_tools | 决策 | 自己写 | 理解原理、精确控制 |
create_agent | 决策 | 框架写 | 实际项目 |
小结
- Tool 的本质:给模型一份函数说明书,模型输出"调谁、传什么"的决策,执行在你的代码里;
- 五要素里名称、描述、参数 Schema 是给模型看的提示词,写工具先把这三样当文档写好;
- 使用工具三步:定义工具 → 组装工具集 → 接入 agent;"决策 → 执行 → 回传 → 回答"的运行时循环由
invoke内部自动完成,tool_call_id负责把请求和结果配对; - 定义方式首选
@tool,参数复杂配 Pydanticargs_schema;Tool类实例化只适合单字符串输入的简单工具;通用能力优先找现成的预定义工具和 Toolkit; - 调用方式:直接
invoke单测工具、bind_tools手动循环理解原理、create_agent自动循环用于实际项目。