纲要
- Tool Call 的核心定义与价值
- 标准工具调用工作流程
- 支持 Tool Call 的主流模型生态
- LangChain 框架下的工具定义方法
- 基于
@tool装饰器的函数式定义 - 基于
BaseTool类的面向对象定义
- 基于
- 工具与模型的绑定机制
- 工具调用的执行与结果回传
- 完整可运行代码示例与解析
Tool Call 机制概述
Tool Call(工具调用)是指大语言模型在推理过程中,不直接生成最终文本答案,而是输出一个高度结构化的指令对象。该指令明确指定了需要调用的外部工具(函数、API、服务等)及其参数。这一机制使模型能够突破其训练数据的时间与领域限制,获取实时信息、执行精确计算、操控外部系统,从而真正承担起 AI Agent 决策核心的职能。
在实践层面,Tool Call 广泛支撑着以下场景:实时天气查询、复杂数学运算、数据库检索与更新、本地文件系统操作、IoT 设备控制等。可以认为,缺乏 Tool Call 能力的模型仅能进行信息复述与简单推理,而无法作为独立 Agent 完成实际任务。
标准工具调用流程
一次完整的工具调用交互遵循请求-响应-执行的循环模式。其核心在于:模型生成调用指令,开发者环境执行指令,执行结果回传模型进行下一步推理。这一流程通过以下时序图清晰展示:
需要特别强调的是,模型本身并不具备执行工具的能力。它仅负责生成调用指令。开发者必须在应用层解析这些指令,调度并执行对应的本地函数,最后将执行结果封装为标准化消息,重新传递给模型。模型基于这些结果进行下一轮推理,直至生成最终答案。
支持 Tool Call 的模型生态
工具调用能力并非所有大模型的标配功能。以下主流模型及系列已提供官方支持(具体版本可能随时间更新,请以厂商最新文档为准):
| 厂商 | 模型系列/名称 | 备注 |
|---|---|---|
| OpenAI | GPT-4、GPT-3.5-turbo | 通过 Chat Completion API 提供 tools 参数 |
| Anthropic | Claude 3 系列(Opus, Sonnet, Haiku) | 支持 tools 定义与调用 |
| Gemini 系列(Pro, Ultra) | 通过 function_calling 配置启用 | |
| 开源社区 | 部分通过 Ollama 或 vLLM 部署的微调模型 | 需确认具体模型变体是否支持 |
在接入前,务必查阅对应厂商的 API 参考文档,验证所选模型版本是否开放了 Tool Call 接口,并明确其参数定义规范。
在 LangChain 中定义工具
LangChain 框架提供了两种核心工具定义方式,开发者可根据项目复杂度与定制需求灵活选择。两种方式定义的工具在元数据结构上完全兼容,绑定到模型时的行为等价。
基于 @tool 装饰器的函数式定义
对于大多数简单到中等复杂度的场景,推荐使用 @tool 装饰器。该方式通过解析函数的类型注解与文档字符串(docstring)自动生成工具的名称、描述和参数 JSON Schema。
适用版本:langchain-core >= 0.1.0
from langchain_core.tools import tool
@tool
def multiply(a: int, b: int) -> int:
"""将两个整数相乘并返回结果。"""
return a * b
@tool
def add(a: int, b: int) -> int:
"""将两个整数相加并返回结果。"""
return a + b
关键点:
- 函数名称自动成为工具名称。
- 函数的 docstring 自动成为工具描述,模型将依据此描述决定调用时机。
- 参数的类型注解(
int、str等)用于生成参数校验 Schema。
基于 BaseTool 类的面向对象定义
当需要更精细地控制参数校验规则、异步执行逻辑或复杂初始化过程时,推荐通过继承 BaseTool 类实现。
适用版本:langchain-core >= 0.1.0
from langchain_core.tools import BaseTool
from pydantic import BaseModel, Field
class MultiplyInput(BaseModel):
a: int = Field(description="第一个乘数")
b: int = Field(description="第二个乘数")
class MultiplyTool(BaseTool):
name: str = "multiply"
description: str = "将两个整数相乘"
args_schema: type[BaseModel] = MultiplyInput
def _run(self, a: int, b: int) -> int:
"""同步执行逻辑。"""
return a * b
async def _arun(self, a: int, b: int) -> int:
"""异步执行逻辑(可选实现)。"""
return self._run(a, b)
关键点:
name、description和args_schema为类属性,定义工具元数据。_run方法实现同步调用逻辑,_arun实现异步逻辑。可根据需要只实现同步版本。- 通过 Pydantic
BaseModel定义参数结构,可添加详细的字段描述和校验器。
工具与模型的绑定
定义工具后,需要通过 bind_tools 方法将工具列表注入到 Chat Model 实例中。绑定后,模型在每次调用时会自动分析用户输入,并根据指令决定是否需要生成工具调用请求。
适用版本:langchain-openai >= 0.0.5(OpenAI 后端)
from langchain_openai import ChatOpenAI
llm = ChatOpenAI(
model="gpt-3.5-turbo",
temperature=0,
)
llm_with_tools = llm.bind_tools([multiply, add])
bind_tools 返回一个新的模型实例,该实例在每次 invoke 时会自动将工具定义附加到 API 请求中。模型若判断需要调用工具,则返回的 AIMessage 对象中将填充 tool_calls 属性。
执行工具调用与结果回传
当模型返回的 AIMessage 包含 tool_calls 属性时,开发者需要完成以下循环操作:
- 遍历
tool_calls列表,解析每个调用的name、args和id。 - 根据
name调度并执行对应的本地函数。 - 将执行结果封装为
ToolMessage,并确保tool_call_id与原始请求 ID 一一对应。 - 将
ToolMessage与历史对话消息一同发回模型,用于生成最终回复。
此循环可能执行多轮,直到模型不再生成 tool_calls 为止。
完整可运行代码示例
以下示例演示了从用户提问到模型最终回复的完整工具调用流程。代码包含工具定义、模型绑定、调用解析、工具执行和结果回传五个阶段。
环境准备
安装依赖包(建议使用 Python 3.9+ 环境):
pip install langchain-openai langchain-core
设置环境变量 OPENAI_API_KEY,或在代码中显式传入 api_key 参数。
完整代码
适用版本:langchain-openai == 0.0.5,langchain-core == 0.1.0
import os
from langchain_openai import ChatOpenAI
from langchain_core.tools import tool
from langchain_core.messages import HumanMessage, AIMessage, ToolMessage
# ---------- 1. 定义工具 ----------
@tool
def multiply(a: int, b: int) -> int:
"""计算两个整数的乘积。"""
return a * b
@tool
def add(a: int, b: int) -> int:
"""计算两个整数的和。"""
return a + b
# ---------- 2. 初始化模型并绑定工具 ----------
llm = ChatOpenAI(
model="gpt-3.5-turbo",
temperature=0,
api_key=os.getenv("OPENAI_API_KEY") # 建议从环境变量读取
)
llm_with_tools = llm.bind_tools([multiply, add])
# ---------- 3. 构造用户请求 ----------
user_question = "3乘以12再加上5的结果是多少?"
print(f"用户: {user_question}")
# ---------- 4. 第一次调用:模型生成工具调用指令 ----------
response = llm_with_tools.invoke([HumanMessage(content=user_question)])
# ---------- 5. 检查并处理工具调用 ----------
if response.tool_calls:
print("模型请求的工具调用:")
for tc in response.tool_calls:
print(f" {tc['name']}({tc['args']})")
# 执行工具调用,并收集结果消息
tool_messages = []
for tc in response.tool_calls:
# 根据工具名称调度执行
if tc["name"] == "multiply":
result = multiply.invoke(tc["args"])
elif tc["name"] == "add":
result = add.invoke(tc["args"])
else:
result = "未知工具"
print(f"工具返回: {result}")
# 封装为 ToolMessage,务必携带正确的 tool_call_id
tool_messages.append(
ToolMessage(
content=str(result),
tool_call_id=tc["id"]
)
)
# ---------- 6. 第二次调用:传入工具结果,生成最终答案 ----------
final_answer = llm_with_tools.invoke(
[HumanMessage(content=user_question)] + [response] + tool_messages
)
print(f"助手: {final_answer.content}")
else:
# 模型未请求工具,直接输出回答
print(f"助手(直接回答): {response.content}")
运行输出示例
用户: 3乘以12再加上5的结果是多少?
模型请求的工具调用:
multiply({'a': 3, 'b': 12})
add({'a': 36, 'b': 5})
工具返回: 36
工具返回: 41
助手: 3乘以12等于36,再加上5等于41。
关键点解读
- 自动分解任务:模型将复合算术问题自动拆解为乘法与加法两步独立调用。
- 参数传递准确:第二次调用(加法)的参数依赖于第一次调用(乘法)的结果,模型在生成调用序列时已进行内部推理。
- 消息关联:
ToolMessage中的tool_call_id必须与AIMessage.tool_calls中的id严格匹配,否则模型将无法正确关联工具结果与请求。 - 结果综合:最终答案由模型基于工具返回的结构化数据进行组织,以自然语言形式呈现给用户。
API 速览
以下为本博客涉及的核心 API 及其用法概览,适用于 langchain-core >= 0.1.0。
tool 装饰器
| 属性 | 说明 |
|---|---|
| 所属库 | langchain_core.tools |
| 用法 | @tool 装饰一个函数,自动转换为 StructuredTool 实例 |
| 关键参数 | 函数名称(自动)、docstring(自动)、类型注解(自动) |
| 返回值 | StructuredTool 对象,具备 invoke 和 ainvoke 方法 |
BaseTool 基类
| 属性 | 说明 |
|---|---|
| 所属库 | langchain_core.tools |
| 关键属性 | name、description、args_schema |
| 关键方法 | _run(同步执行)、_arun(异步执行) |
| 返回值 | 子类实例,具备 invoke 和 ainvoke 方法 |
ChatOpenAI.bind_tools
| 属性 | 说明 |
|---|---|
| 所属库 | langchain_openai |
| 方法签名 | bind_tools(tools: List[BaseTool]) -> Runnable |
| 参数 | 工具对象列表(StructuredTool 或 BaseTool 实例) |
| 返回值 | 绑定了工具定义的可运行对象(Runnable),支持 invoke、stream 等 |
ToolMessage
| 属性 | 说明 |
|---|---|
| 所属库 | langchain_core.messages |
| 构造方法 | ToolMessage(content: str, tool_call_id: str) |
| 关键字段 | content:工具执行结果(字符串形式);tool_call_id:对应请求的 ID |
Demo 示例
本博客的完整代码示例已集成于上一章节。此处提供一个可直接运行的 HTML + JavaScript 前端演示(使用浏览器原生 fetch 调用 OpenAI API),以便直观展示工具调用在前端应用中的形态。
运行说明
- 将以下 HTML 代码保存为
tool_call_demo.html。 - 在浏览器中打开该文件。
- 在输入框中填入 OpenAI API Key 和自然语言问题(例如“3乘以12再加5等于多少?”)。
- 点击“发送”按钮,查看控制台日志及最终结果。
代码说明
- 前端直接调用 OpenAI Chat Completion API,传递
tools参数定义算术工具。 - 当模型返回
tool_calls时,前端 JavaScript 解析并执行本地函数,再将结果作为tool角色消息回传。 - 整个过程通过异步
fetch实现,展示了 Tool Call 机制与前端交互的无缝集成。
完整 Demo 代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>AI Agent Tool Call 前端演示</title>
<style>
body { font-family: system-ui, sans-serif; max-width: 800px; margin: 40px auto; padding: 0 20px; }
textarea { width: 100%; padding: 8px; margin: 8px 0; }
button { padding: 10px 24px; background: #007bff; color: white; border: none; border-radius: 6px; cursor: pointer; }
pre { background: #f5f5f5; padding: 12px; border-radius: 6px; overflow-x: auto; }
</style>
</head>
<body>
<h2>AI Agent Tool Call 演示</h2>
<label>OpenAI API Key:</label><br>
<input type="password" id="apiKey" style="width:100%; padding:8px; margin-bottom:12px;" placeholder="sk-...">
<label>用户问题:</label><br>
<textarea id="userQuestion" rows="2" placeholder="例如:3乘以12再加上5的结果是多少?">3乘以12再加上5的结果是多少?</textarea><br>
<button id="sendBtn">发送</button>
<h3>输出日志</h3>
<pre id="logOutput">等待操作...</pre>
<script>
document.getElementById('sendBtn').addEventListener('click', async () => {
const apiKey = document.getElementById('apiKey').value.trim();
const question = document.getElementById('userQuestion').value.trim();
const log = document.getElementById('logOutput');
if (!apiKey) { log.textContent = '请填写有效的 OpenAI API Key。'; return; }
if (!question) { log.textContent = '请输入用户问题。'; return; }
log.textContent = '正在处理...';
const tools = [
{
type: "function",
function: {
name: "multiply",
description: "将两个整数相乘",
parameters: {
type: "object",
properties: {
a: { type: "integer", description: "第一个乘数" },
b: { type: "integer", description: "第二个乘数" }
},
required: ["a", "b"]
}
}
},
{
type: "function",
function: {
name: "add",
description: "将两个整数相加",
parameters: {
type: "object",
properties: {
a: { type: "integer", description: "第一个加数" },
b: { type: "integer", description: "第二个加数" }
},
required: ["a", "b"]
}
}
}
];
const messages = [{ role: "user", content: question }];
try {
// 第一次调用
let response = await callOpenAI(apiKey, messages, tools);
let assistantMessage = response.choices[0].message;
if (assistantMessage.tool_calls) {
log.textContent = '模型请求工具调用:\n' + JSON.stringify(assistantMessage.tool_calls, null, 2);
// 执行工具
messages.push(assistantMessage);
for (const tc of assistantMessage.tool_calls) {
const funcName = tc.function.name;
const args = JSON.parse(tc.function.arguments);
let result;
if (funcName === 'multiply') {
result = args.a * args.b;
} else if (funcName === 'add') {
result = args.a + args.b;
} else {
result = '未知工具';
}
log.textContent += `\n工具 ${funcName} 返回: ${result}`;
messages.push({
role: "tool",
tool_call_id: tc.id,
content: String(result)
});
}
// 第二次调用
response = await callOpenAI(apiKey, messages, tools);
const finalAnswer = response.choices[0].message.content;
log.textContent += `\n助手最终回答: ${finalAnswer}`;
} else {
log.textContent = '模型直接回答:\n' + assistantMessage.content;
}
} catch (err) {
log.textContent = '错误:' + err.message;
}
});
async function callOpenAI(apiKey, messages, tools) {
const resp = await fetch('https://api.openai.com/v1/chat/completions', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${apiKey}`
},
body: JSON.stringify({
model: 'gpt-3.5-turbo',
messages: messages,
tools: tools,
temperature: 0
})
});
if (!resp.ok) {
const errData = await resp.json();
throw new Error(errData.error?.message || `HTTP ${resp.status}`);
}
return await resp.json();
}
</script>
</body>
</html>
参考文档
官方文档
- LangChain Core Tools 官方文档
- OpenAI Function Calling 官方指南
- Anthropic Tool Use 文档
- Google Gemini Function Calling 文档
参考链接
总结
本文系统梳理了大模型工具调用(Tool Call)的核心机制,从概念定义、工作流程、模型生态到 LangChain 框架下的具体实现,提供了完整的理论讲解与可运行的代码示例。关键要点如下:
- Tool Call 使模型具备与外部世界交互的能力,是构建 AI Agent 的基石。
- 标准流程为“模型生成调用指令 → 开发者执行工具 → 结果回传模型 → 综合生成最终答案”。
- LangChain 提供
@tool装饰器和BaseTool继承两种灵活的定义方式,推荐优先使用装饰器。 bind_tools方法将工具列表绑定到模型,模型通过AIMessage.tool_calls返回调用请求。- 开发者必须正确解析
tool_calls,执行本地函数,并使用ToolMessage回传结果,确保tool_call_id匹配。 - 实际项目应注重工具描述的准确性,以提升模型的调用决策质量。
通过本文的实战示例,开发者可以快速将 Tool Call 能力集成到自己的 Agent 系统中,实现从“能说”到“能做”的跨越。
1185



被折叠的 条评论
为什么被折叠?



