











最近学习 Agent、Tool Calling、Function Calling 和 MCP 时,我一直有一个疑问:
用户只是问了一句话,大模型到底怎么知道系统里有哪些 Tool?
又是怎么知道哪个 Tool 是查订单、哪个 Tool 是查库存的?
真正搞懂以后,核心其实只有一句话:
不是把所有接口说明手工写进 Prompt,而是在每次调用模型 API 时,把当前可用的 Tool 定义通过专门的
tools参数一起传给模型。
下面用一个“业务系统问答助手查询订单”的例子,从头讲清楚。
业务系统原来有一个订单接口:
GET /api/orders/123
用户在问答助手里说:
帮我查一下订单 123 现在什么状态
我们希望最终变成:
用户问题
↓
大模型判断应该调用 query_order
↓
业务系统真正执行 query_order("123")
↓
query_order 调订单接口
↓
返回“已发货”
↓
结果重新交给大模型
↓
大模型回答:订单 123 已发货
这里最关键的就是:
大模型为什么知道有一个叫
query_order的 Tool?
对于业务系统来说,Tool 可以先理解成:
一个普通后端函数 + 一份给大模型看的函数说明。
例如我们先写一个普通 Python 函数:
def query_order(order_no: str):
# 实际项目中,这里可以调用订单 Service 或 HTTP API
return {
"order_no": order_no,
"status": "已发货"
}
这只是一个普通函数。
此时大模型还完全不知道:
query_order 是什么
它有什么参数
什么时候应该调用它
所以还要再给模型一份 Tool 定义。
例如:
tools = [
{
"type": "function",
"name": "query_order",
"description": "根据订单号查询订单当前状态。",
"parameters": {
"type": "object",
"properties": {
"order_no": {
"type": "string",
"description": "订单号,例如 123"
}
},
"required": ["order_no"]
}
}
]
可以把它翻译成人话:
name
=
这个 Tool 叫 query_order
description
=
这个 Tool 是用来查询订单状态的
parameters
=
调用这个 Tool 时,需要传哪些参数
order_no
=
订单号,字符串类型
所以真正让模型理解 Tool 的,不是 Python 函数代码本身,而是:
name
description
parameters / Schema
这些结构化信息。
不是你每次手工写:
你现在有三个工具:
query_order 是查订单的……
query_inventory 是查库存的……
而是你的程序调用模型 API 时,同时传:
用户问题
+
tools
例如使用 OpenAI Responses API,可以写成:
from openai import OpenAI
client = OpenAI()
tools = [
{
"type": "function",
"name": "query_order",
"description": "根据订单号查询订单当前状态。",
"parameters": {
"type": "object",
"properties": {
"order_no": {
"type": "string",
"description": "订单号,例如 123"
}
},
"required": ["order_no"]
}
}
]
response = client.responses.create(
model="gpt-6-astra",
input="帮我查一下订单 123 现在什么状态",
tools=tools,
tool_choice="auto"
)
注意这一段:
tools=tools
这就是关键。
程序实际向模型服务发送的内容,可以简单理解成:
input:
帮我查一下订单 123 现在什么状态
tools:
- name: query_order
description: 根据订单号查询订单当前状态
parameters:
order_no: string
所以:
模型确实拿到了 Tool 的说明,只是它不是普通 Prompt 文字,而是模型 API 请求里的结构化
tools字段。
从概念上说:
模型这一轮要使用哪些 Tool,这一轮调用时就必须让模型知道这些 Tool。
但不是开发人员每次手工复制粘贴。
实际程序通常这样写:
TOOLS = [
# 所有 Tool 定义
]
程序启动时就已经准备好了。
以后每次用户提问:
response = client.responses.create(
model="gpt-6-astra",
input=user_question,
tools=TOOLS
)
也就是说:
开发一次 Tool 定义
↓
程序保存 Tool 定义
↓
用户每次提问
↓
程序自动把当前允许的 Tool 一起传给模型
用户完全感知不到这一过程。
假设模型看到:
用户:
帮我查一下订单 123 现在什么状态
可用 Tool:
query_order
作用:根据订单号查询订单状态
参数:order_no
模型会判断:
这个问题需要真实订单数据
↓
我不能自己编
↓
有 query_order 可以用
↓
应该调用 query_order
↓
order_no = 123
于是模型可能返回一个 Function Call:
{
"type": "function_call",
"name": "query_order",
"arguments": "{\"order_no\":\"123\"}"
}
注意:
这时候大模型并没有真的执行
query_order()。
它只是告诉你的后端:
“我决定调用
query_order,参数是123。”
你的程序拿到模型返回的 Function Call 后:
import json
for item in response.output:
if item.type == "function_call":
arguments = json.loads(item.arguments)
if item.name == "query_order":
result = query_order(
order_no=arguments["order_no"]
)
真正执行的是这一句:
result = query_order(...)
也就是说:
大模型
=
决定“调用谁、传什么参数”
业务系统后端
=
真正执行函数
大模型本身不能直接进入你的数据库,也不会自动执行你的 Python 函数。
实际项目里,query_order() 可能是:
import httpx
def query_order(order_no: str):
response = httpx.get(
f"http://business-system/api/orders/{order_no}",
timeout=10
)
response.raise_for_status()
return response.json()
完整关系就是:
用户
↓
问答助手
↓
大模型
↓
Function Call:query_order("123")
↓
问答助手后端
↓
query_order()
↓
GET /api/orders/123
↓
业务系统
假设 Tool 执行结果是:
{
"order_no": "123",
"status": "已发货"
}
还需要把这个结果再次交给模型。
例如:
tool_output = json.dumps(
result,
ensure_ascii=False
)
final_response = client.responses.create(
model="gpt-6-astra",
previous_response_id=response.id,
tools=tools,
input=[
{
"type": "function_call_output",
"call_id": item.call_id,
"output": tool_output
}
]
)
print(final_response.output_text)
模型这次看到的意思相当于:
你刚才调用的:
query_order("123")
执行结果:
{
"order_no": "123",
"status": "已发货"
}
于是模型最后回答:
订单 123 当前已经发货。
整个 Function Calling / Tool Calling 流程其实就是:
第一步
用户:
“帮我查订单 123”
↓
第二步
问答助手后端调用模型:
用户问题
+
Tool 定义
↓
第三步
模型判断:
应该调用 query_order
参数 order_no=123
↓
第四步
模型返回 Function Call:
query_order("123")
↓
第五步
业务系统后端真正执行:
query_order("123")
↓
第六步
Tool 调业务接口:
GET /api/orders/123
↓
第七步
接口返回:
已发货
↓
第八步
把 Tool Result 再交给模型
↓
第九步
模型生成最终回答:
“订单 123 当前已经发货。”
这就是最基本的 Agent Tool Calling。
假设系统里定义了:
query_order
= 查询订单
query_inventory
= 查询库存
create_ticket
= 创建工单
调用模型时:
tools = [
query_order 的定义,
query_inventory 的定义,
create_ticket 的定义
]
用户问:
ABC001 还有多少库存?
模型会根据:
Tool 名称
Tool description
参数说明
用户当前问题
进行匹配。
所以它选择:
query_inventory
而不是:
query_order
因此 Tool 的 description 写得是否准确,非常重要。
小系统只有十几个 Tool 时,可以直接全部传。
但是如果以后有:
500 个 Tool
1000 个 Tool
就不适合每次全部给模型。
通常会增加一层 Tool Router,例如:
用户:
“查一下订单”
↓
先判断属于:
订单领域
↓
只取:
query_order
query_order_logistics
cancel_order
↓
把这几个 Tool 给模型
这样可以减少:
Context 占用
Tool 选择干扰
错误调用概率
小白阶段可以先理解为:
Function Calling 是 Tool Calling 最常见的一种实现方式。
例如:
Tool:
query_order
实际上就是:
让模型输出:
函数名
+
函数参数
所以经常会看到:
Function Calling
Tool Calling
两个词一起出现。
现在更常用 Tool Calling 这个更大的概念,因为 Tool 不一定只有普通 Function,还可能包括:
Web Search
File Search
MCP
Shell
Computer
……
普通 Tool Calling:
你的问答助手
↓
你自己在代码中维护 tools
↓
模型
例如:
tools = [
{
"name": "query_order",
...
}
]
Tool 定义是你的问答助手自己写、自己维护的。
MCP 则是:
问答助手 / Codex
↓
MCP Client
↓
MCP Server
MCP Client 连接 MCP Server 后,会从 Server 获取:
有哪些 Tool
Tool 名称
Tool description
inputSchema
例如 MCP Server 返回:
query_order
= 查询订单
query_inventory
= 查询库存
于是 Harness / MCP Client 再把这些 Tool 提供给模型。
所以最终模型看到的东西其实还是:
Tool 名称
Tool 说明
Tool 参数
区别只是:
普通 Tool Calling
=
Tool 定义由你的应用自己维护
MCP
=
Tool 定义由 MCP Server 按统一标准提供
普通方式可能写:
tools = [
query_order_schema,
query_inventory_schema,
create_ticket_schema
]
如果用了 MCP:
MCP Client
↓
连接 MCP Server
↓
获取 Tool 列表
↓
得到 name / description / inputSchema
↓
自动转成模型可以使用的 Tool 定义
↓
交给模型
所以:
MCP 帮你解决了 Tool 的标准化发现和接入问题。
不是说用了 MCP 后模型不需要 Tool 描述。
而是:
Tool 描述由 MCP Server 自动提供,而不是业务问答助手自己硬编码维护。
如果你的场景是:
一个业务系统
+
系统内部自己的问答助手
+
只有这个问答助手会调用这些接口
例如:
业务系统
├── 订单模块
├── 库存模块
└── 问答助手
最简单的方案就是:
问答助手
↓
普通 Tool Calling
↓
Tool 函数
↓
内部 Service / HTTP API
完全没有必要为了 MCP 而 MCP。
这样:
开发简单
链路短
调试方便
如果以后希望同一套能力给:
业务问答助手
Codex
OpenClaw
DSH
其他 Agent
一起使用,就比较适合 MCP。
例如:
ERP MCP Server
/ | \
/ | \
↓ ↓ ↓
Codex OpenClaw 内部助手
ERP MCP Server 统一提供:
query_order
query_inventory
create_purchase_request
所有支持 MCP 的 Agent 都按统一方式接入。
所以 MCP 更适合:
多 Agent 复用
跨系统接入
企业统一能力平台
Tool 数量持续增长
希望降低重复适配开发
这一点特别重要。
不是:
用了 MCP
就不用 Tool
而是:
MCP Server
里面提供的核心能力
本身仍然是 Tool
可以理解成:
Tool
=
具体能力
MCP
=
把这些 Tool 标准化提供出去的协议
例如:
query_order
始终是 Tool。
只给自己系统使用:
内部问答助手
↓
直接 Tool Calling
↓
query_order
提供给多个 Agent:
Codex
↓
MCP
↓
ERP MCP Server
↓
query_order
HTTP API
=
业务系统原来的接口
Tool 函数
=
真正执行某项业务能力的后端函数
Tool Schema
=
告诉模型 Tool 名称、用途和参数
Function Calling / Tool Calling
=
模型根据 Tool Schema 决定调用哪个 Tool
MCP
=
标准化发现和调用这些 Tool 的协议
MCP Server
=
按 MCP 标准提供 Tool 的服务
用户
↓
业务系统问答助手
↓
把用户问题 + tools 参数交给模型
↓
模型选择 query_order
↓
问答助手后端执行 query_order
↓
业务 API
↓
返回结果
↓
再次交给模型
↓
最终回答
MCP Server
↓
提供 Tool Schema
↓
MCP Client 自动获取 Tool
↓
Harness 把 Tool 提供给模型
↓
模型选择 Tool
↓
MCP Client 调 MCP Server
↓
执行业务能力
↓
返回结果
↓
模型最终回答
最需要搞懂的其实就是这一点:
大模型并不会天然知道你的业务系统有哪些接口。
普通 Tool Calling 的做法是:
开发人员定义 Tool Schema
↓
程序调用模型时通过 tools 参数一起传进去
↓
模型根据 name / description / parameters 选择 Tool
MCP 的做法是:
MCP Server 提供 Tool Schema
↓
MCP Client 自动发现
↓
再提供给模型
所以:
只给一个业务系统自己的问答助手使用时,直接 Tool Calling 往往最简单;当同一批 Tool 需要被 Codex、OpenClaw、DSH 等多个 Agent 统一复用时,再使用 MCP。
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。