用户问:“A1042 签收多久了?退货运费谁承担?”模型可以解释一般的售后概念,却无法仅凭训练时学到的知识知道这张订单的签收时间。这个事实在订单系统中,需要应用查询。
这时你会同时遇到 Function Calling、Tool Calling 和 MCP。三个词经常一起出现,但并不表示三个互相竞争的方案。先看一条完整链路:模型根据任务提出查询意图,应用执行查询,将结果送回模型;如果能力来自外部服务,应用还需要一种连接方式。
本系列使用虚构的订单 A1042:已签收十天,用户报告质量问题但尚未核实。教学政策规定三十天内、经核实的质量问题退货由商家承担运费。这些是解释机制的材料,不是现实售后承诺。
普通函数本来怎样被调用
下面是一个没有模型的 Python 函数。调用者知道函数名和参数,直接执行即可。
def get_order(order_id: str) -> dict:
"""读取教学订单;这个最小例子尚未加入身份授权。"""
# 用固定记录模拟订单库,不访问真实业务数据。
orders = {"A1042": {"status": "delivered", "days_since_delivery": 10}}
if order_id not in orders:
# 没找到订单就返回明确结果,不能根据编号编造一个订单。
return {"ok": False, "error": "order_not_found"}
return {"ok": True, "order_id": order_id, **orders[order_id]}
# 普通程序由开发者决定调用哪个函数,以及参数取什么值。
print(get_order("A1042"))
如果用户把问题换成自然语言,应用需要判断是否查询订单、查询哪一张、是否还缺订单号。Function Calling 将其中的调用选择和参数组织交给模型,同时让程序保留执行权。
模型通常不需要看到这个 Python 函数的实现。应用给它的是工具契约:名称、用途以及参数 Schema。运行时另有一张注册表,把名称映射到真正的函数。模型知道的是可用能力的描述,程序持有的是可执行实现。
模型输出调用,不代表函数已经执行
模型接收问题和工具定义后,可能产生下面的结构。这里是统一教学表示,不是任意供应商 API 都接受的格式。
{
"call_id": "call_01",
"name": "get_order",
"arguments": {"order_id": "A1042"}
}
它表示“请求运行 get_order,并传入这个订单号”。只有应用验证名称、参数、身份和权限,调用实际实现以后,订单查询才发生。没有执行器,模型生成再漂亮的调用 JSON,也只是一段数据。
完整过程包含两次模型调用:第一次根据问题产生工具请求;应用执行函数得到结果;第二次把结果连同必要上下文交回模型,让它解释查询到的事实。一次工具查询也可能不足以回答,第二轮模型还会请求适用政策,然后才给最终答复。
模型第一次生成了 get_order,不能因此向用户报告“查询成功”。即使查询结果成功返回,回答仍可能误读十天和三十天的条件。参数生成、函数执行和回答正确性是三个需要分别观察的环节。
这仍然是生成过程,为什么能输出函数名
在语言模型层面,工具名称、描述、参数定义和对话会被编码成模型能够处理的输入。不同服务使用自己的模板、特殊标记或结构化表示,不能假设所有模型都是简单拼接一段 JSON。
支持工具调用的模型通常经过包含调用轨迹的训练或适配,学习在何种问题下产生调用,以及怎样从用户问题和前序结果组织参数。开发者提供工具定义是推理时输入,不等于这一次请求重新训练了模型,也不是动态向模型参数中安装 Python 函数。
“生成哪个工具”与“参数填什么”同样可能出错。用户没有提供订单号时,模型可能要求补充,也可能错误猜测。工具描述中的调用条件、边界和例子能帮助选择,但不能替代运行时校验。
某些服务支持约束生成,使输出符合允许的结构。它可以减少缺字段、类型错误或无效 JSON,却不能证明 A1042 确实存在,更不能证明当前用户有权访问。关于结构化生成与校验,可以衔接 Prompt 的结构化输出。
工具选择设置能控制什么
许多模型接口允许应用选择自动判断、禁用工具、要求调用工具,或指定某一个工具。具体字段名和支持组合由接口决定,不能把某一家的 tool_choice 值复制到所有模型。
例如自动模式下,“什么是退货运费”可以直接解释;要求调用模式下,即使问题不需要查询,模型也可能被迫请求一个工具。指定 get_order 能限制名称,却不能保证它填出的订单号正确。因此强制工具适合已经明确需要某个步骤的流程,不是提高所有答案质量的通用开关。
是否允许一轮产生多个调用也是独立设置。禁用并行调用可以简化执行顺序,但不能保证整个任务只调用一次;模型下一轮仍可能提出新请求。运行时的总调用上限与后端授权始终需要单独执行。
Function Calling 和 Tool Calling 怎样区分
在很多产品中,这两个词有重叠:Function Calling 强调函数名称和参数形式,Tool Calling 常用于描述更广泛的工具使用机制。不能仅凭名字推断某个产品是否支持浏览器、搜索或代码执行,应该看实际工具类型与执行方式。
| 形式 | 例子 | 谁提供实际执行能力 |
|---|---|---|
| 应用函数工具 | 查询订单、创建草稿 | 你的程序或业务服务 |
| 平台工具 | 平台提供的搜索、代码执行 | 平台托管执行环境 |
| 外部协议工具 | MCP Server 暴露订单查询 | Server 及其下游服务 |
以 Claude 为例,官方区分客户端工具和服务端工具,客户端工具结果需由应用回填;具体消息类型见 Tool use 文档。这是一个产品中的具体实现,不是所有模型 API 的统一字段标准。
“工具”也不必产生副作用。查询订单是只读工具,修改地址是写工具。是否只读与使用 Function Calling 还是 MCP 没有直接对应关系。
MCP 在哪一层出现
如果函数就在当前进程,应用直接分发即可。如果订单能力由另一个服务提供,并希望多个 AI 应用复用,就需要发现能力、传递请求和接收结果。
MCP 为 Host 与外部能力提供统一协议。Host 是模型应用,其中的 MCP Client 与 Server 通信;Server 将工具调用转给函数或业务接口。模型通常看到 Host 整理后的工具定义,而不是自己操作网络连接。
例如 Host 启动后取得 Server 的工具清单,选出允许暴露的 get_order,转换成当前模型的工具格式。模型请求调用后,Host 把它映射成 MCP tools/call,Server 执行同一订单函数,结果再映射回模型协议。
因此 Function Calling 可以与 MCP 同时使用:前者帮助模型表达意图,后者帮助应用连接能力。MCP 接通并不会替你决定订单归属,也不会保证重试退款安全。
四个 ID 为什么不能混用
模型产生的 call ID 用来对应模型工具结果;MCP 请求 ID 用来匹配协议请求与响应;业务操作 ID 标识一次退款等实际意图;Trace ID 串起整条诊断链。
比如模型工具调用 call_01 对应 MCP 请求 42,执行的是业务操作 refund-op-9,都属于 trace 88。网络重试可以产生新的协议请求 ID,但同一业务操作必须保留适当的稳定身份。直接用瞬时模型调用 ID 做退款去重,会让重试创建一个新操作。
这些 ID 应在记录中关联,不需要在每条用户回答里全部展示。
怎样亲手观察这个过程
配套 tool_loop.py下载 先使用预置的模型调用消息,展示分发与回填,避免一开始被模型随机输出干扰。它不是自主规划 Agent。
claude_tool_loop.py下载 提供实际 Messages API 接入,模型由环境变量选择,不固定一个可能过时的型号。它会在读者配置后发送网络请求,本次写作没有运行。MCP 内存与 stdio 接入在后面逐步完成。
先观察“模型要做什么”和“程序真的做了什么”之间的交接,再进入工具契约,就能理解为什么一个清晰的函数名和一个严格的执行边界同样重要。
继续阅读
下一篇:怎样设计一个好用的工具:名称、参数、结果与业务契约。
下载文件、依赖与运行边界见配套指南下载。