在前面的文章中,我们已经介绍了大模型应用开发所需的基础知识,以及 Prompt Engineering 和 Context Engineering 如何帮助模型理解任务、组织信息并形成判断。然而,理解问题并不等于完成任务。
模型可以识别用户想查询天气、分析销售数据或创建审批任务,但如果不能连接天气服务、业务数据库和审批系统,它最终仍然只能生成文字。Function Calling 解决的正是从“语言理解”到“程序执行”之间的连接问题:模型负责判断需要调用什么能力,应用程序负责校验请求、执行工具并返回真实结果。
这也是普通大模型应用向工具型 Agent 演进的关键一步。
一、Function Calling 到底是什么
Function Calling 也经常被称为 Tool Calling。它允许开发者将应用系统能够提供的函数、接口或工具描述给模型,模型再根据用户目标决定是否调用工具,并生成符合约定的结构化参数。
例如,用户提出:
📌 查询上海今天的天气。
模型不会直接执行天气接口,而是返回一个调用请求:
{
"name": "get_weather",
"arguments": {
"city": "上海",
"date": "2026-08-06"
}
}
📋 模型返回的结构化调用请求
应用程序收到请求后,需要完成参数校验、权限判断和接口调用,再将天气服务返回的结果交给模型:
{
"success": true,
"data": {
"city": "上海",
"weather": "多云",
"temperature": 32
}
}
模型根据工具结果生成最终回答:
🌤️ 上海今天多云,气温约为 32℃。
因此,Function Calling 并不是让模型直接运行 Java、Python 或 JavaScript 函数,而是建立一种模型与应用程序之间的结构化调用协议。模型表达“希望执行什么操作”,应用程序决定该操作能否执行以及如何执行。
二、一次完整的工具调用如何完成
一个完整的 Function Calling 过程通常可以分为五个阶段。
首先,应用程序将用户请求、当前上下文和可用工具定义发送给模型。模型分析任务后,如果能够直接回答,就生成自然语言结果;如果需要实时数据、业务信息或外部操作,则返回一个或多个工具调用请求。
随后,应用程序根据工具名称找到对应执行器,并对参数格式、用户身份、租户范围和资源权限进行检查。校验通过后,应用调用本地函数、数据库、内部微服务或第三方 API,最后将执行结果作为工具消息重新加入模型上下文。
模型读取工具结果后,可以直接生成最终回答,也可以继续调用其他工具。
其核心过程可以概括为:
用户目标 ↓模型理解任务 ↓选择工具并生成参数 ↓应用校验并执行工具 ↓模型观察执行结果 ↓继续调用或生成回答
这已经构成了一个最小的 Agent 执行循环。Function Calling 负责表达模型希望采取的行动,Agent Runtime 则负责工具注册、权限控制、调用执行、结果回传和循环终止。
三、工具定义决定调用质量
模型能否正确选择工具,很大程度上取决于工具定义是否清晰。一个完整的工具定义通常包括工具名称、功能描述、参数结构和约束规则。
以天气查询工具为例:
{
"type": "function",
"name": "get_weather",
"description": "查询指定城市在指定日期的天气,仅用于天气查询,不用于空气质量或历史气候分析。",
"parameters": {
"type": "object",
"properties": {
"city": { "type": "string", "description": "城市名称,例如上海、北京或杭州" },
"date": { "type": "string", "format": "date", "description": "查询日期,格式为 YYYY-MM-DD" },
"unit": { "type": "string", "enum": ["celsius", "fahrenheit"], "description": "温度单位" }
},
"required": ["city", "date"],
"additionalProperties": false
},
"strict": true
}
1. 工具名称应准确表达动作
工具名称最好采用明确的动词短语,例如:
get_weather
query_order
create_review_task
calculate_growth_rate
类似 process、handle、execute 等名称无法准确表达工具用途,会增加模型误选工具的概率。
模型选择工具时并不会搜索代码实现,而是根据工具名称、功能描述和参数定义进行语义判断。工具越多、名称越相似,描述质量越重要。
2. 工具描述应说明使用边界
传统函数注释主要供开发者阅读,而 Function Calling 中的工具描述会直接影响模型决策。除了说明工具能够做什么,还应明确什么时候使用、什么时候不能使用、是否修改数据以及是否需要用户确认。
例如,订单查询工具可以这样描述:
根据订单编号查询订单当前状态。本工具只读取数据,不修改订单。
用户未提供订单编号时不要调用,应先要求用户补充。
相比简单的“查询订单”,这种描述更容易帮助模型形成正确判断。
3. JSON Schema 是参数契约
JSON Schema 用于约束模型生成的调用参数,包括字段类型、必填属性、枚举值、日期格式、数字范围和嵌套结构。生产系统应尽量明确 required、限制枚举范围,并禁止模型生成未定义字段。
但 Schema 只能解决结构合法性,不能替代业务校验。例如:
{
"orderId": "ORDER-001",
"refundAmount": 999999
}
这段数据可能完全符合 JSON Schema,但退款金额是否超过订单实付金额,仍然需要业务系统判断。
因此,即使模型支持严格模式,应用程序也必须再次执行参数校验和业务规则检查。
四、模型如何选择和组合工具
当应用向模型提供多个工具时,模型通常会先识别用户目标,再判断是否需要外部能力,随后比较各工具的名称、描述和参数要求,最终选择一个或多个工具并生成调用参数。
假设系统提供以下三个工具:
| 工具 | 作用 |
| get_weather | 查询指定城市天气 |
| get_sales_summary | 查询指定周期的销售数据 |
| calculate_growth_rate | 计算两个数值之间的增长率 |
当用户提出:
“查询上海今天的天气,并告诉我本月销售额比上月增长了多少。”
模型可以同时发起以下三个查询:
get_weather(上海,今天)
get_sales_summary(本月)
get_sales_summary(上月)
天气、本月销售额和上月销售额彼此没有依赖关系,可以并行执行。当两个月的销售数据返回后,模型再调用:
calculate_growth_rate(本月销售额,上月销售额)
因此,复杂任务中的工具调用通常不是简单地一次完成,而是形成“并行查询—结果汇总—依赖计算—最终生成”的执行过程。
随着工具数量增加,将所有工具定义一次性放入上下文会增加 Token 消耗和选择难度。更合理的做法是先根据用户身份、业务模块和任务类型筛选可用工具,再交给模型选择;在工具规模更大的系统中,还可以引入动态工具发现和分层工具注册机制。
五、Function Calling、ReAct、Tool、Skill 和 MCP 的关系
Function Calling 经常与 ReAct、Tool、Skill 和 MCP 同时出现,但它们并不是同一个层次的概念。
| 概念 | 解决的问题 | 核心定位 |
| Tool | 系统具体能够做什么 | 可执行程序能力 |
| Function Calling | 模型如何请求调用工具 | 结构化调用协议 |
| ReAct | 模型如何根据结果持续行动 | 推理、行动和观察循环 |
| Skill | 一类任务应该按照什么方法完成 | 可复用的流程与领域经验 |
| MCP | Agent 如何统一连接外部工具和数据 | 标准化连接协议 |
1. Function Calling 与 ReAct
ReAct 强调模型在推理、行动和观察之间持续循环:
Reason:判断当前需要解决什么 ↓Act:选择并执行一个动作 ↓Observe:读取动作执行结果 ↓Reason:根据结果决定下一步
Function Calling 则负责将其中的“行动”表达为结构化工具请求。
因此,ReAct 是运行循环,Function Calling 是循环中的行动接口。在一个 ReAct Agent 中,每当模型决定查询天气、读取订单或创建任务时,都可以通过 Function Calling 发起调用。
生产系统通常不需要保存模型完整的内部推理过程,但应记录可审计的执行轨迹,包括模型选择了哪个工具、生成了哪些参数、工具返回了什么结果以及调用在哪一步失败。
2. Tool 与 Skill
Tool 是一项具体的可执行能力,例如读取合同、查询客户、创建审批单或转换文档。
Skill 则描述完成某类任务的方法,通常包含领域说明、处理步骤、检查规则、工具使用方式、示例和模板。
例如,“合同风险审查 Skill”可以规定:
- 提取合同主体和关键日期
- 定位付款、违约和解除条款
- 调用企业风险规则库
- 输出风险结论和证据页码
- 高风险结果进入人工复核
其中,读取合同和查询规则库属于 Tool;按照什么顺序使用工具、如何判断结果以及何时转人工,则属于 Skill。
💡 Tool 提供能力,Skill 提供方法。
3. Function Calling 与 MCP
Function Calling 通常发生在模型与 Agent Runtime 之间,用于表达模型想调用哪个工具以及使用哪些参数。
MCP 则主要解决 Agent Runtime 如何以统一方式连接外部工具、数据源和业务系统。MCP Server 可以对外提供工具、资源和提示模板,Agent Host 通过 MCP Client 发现并调用这些能力。
其关系可以概括为:
大模型
↓
Function Calling
↓
Agent Host / Runtime
↓
MCP Client
↓
MCP Server
↓
数据库、业务 API、文件系统
执行结果再沿相反方向返回模型,模型根据新信息继续推理。
因此,Function Calling 负责表达“模型要调用什么”,MCP 负责解决“Agent 如何标准化连接并执行外部能力”。
六、前端与后端应该如何实现工具调用
Function Calling 可以连接前端界面操作,也可以连接后端业务服务,但二者的安全边界不同。
1. 前端工具调用
前端工具适合处理低风险、可撤销的界面动作,例如打开订单详情、定位到文档指定页面、切换图表或填充表单草稿。
Agent 可以向前端返回如下事件:
{
"type": "ui_tool_call",
"name": "open_order_detail",
"arguments": {
"orderId": "ORDER-20260806-001"
}
}
前端只执行预先注册的白名单函数:
type UiToolCall = {
name: "open_order_detail" | "show_sales_chart";
arguments: Record<string, unknown>;
};
const uiTools = {
open_order_detail: (args) => {
const orderId = String(args.orderId ?? "");
if (!/^ORDER-[A-Za-z0-9-]+$/.test(orderId)) {
throw new Error("订单编号不合法");
}
window.location.assign(`/orders/${encodeURIComponent(orderId)}`);
},
show_sales_chart: (args) => {
window.dispatchEvent(new CustomEvent("agent:show-sales-chart", { detail: args }));
}
};
function executeUiTool(call: UiToolCall): void {
const executor = uiTools[call.name];
if (!executor) throw new Error(`不允许执行前端工具:${call.name}`);
executor(call.arguments);
}
前端不能使用 eval 或动态脚本执行模型生成的代码,模型 API 密钥也不能放在浏览器中。涉及删除、支付、权限修改和数据发布等高风险操作时,应由后端进行鉴权并引入人工确认。
2. 后端业务工具
假设 Agent 需要查询企业销售数据,可以定义以下工具:
{
"type": "function",
"name": "get_sales_summary",
"description": "查询当前用户所属企业在指定日期范围内的销售汇总,只读操作。",
"parameters": {
"type": "object",
"properties": {
"startDate": { "type": "string", "format": "date" },
"endDate": { "type": "string", "format": "date" }
},
"required": ["startDate", "endDate"],
"additionalProperties": false
}
}
模型只负责生成查询时间范围,用户身份和租户信息必须由服务端登录态提供:
public SalesSummary executeSalesTool(
SalesQuery query,
UserContext userContext) {
permissionService.check(userContext.userId(), userContext.tenantId(), "sales:read");
if (query.endDate().isBefore(query.startDate())) {
throw new IllegalArgumentException("结束日期不能早于开始日期");
}
if (ChronoUnit.DAYS.between(query.startDate(), query.endDate()) > 366) {
throw new IllegalArgumentException("查询范围不能超过一年");
}
return salesService.getSummary(userContext.tenantId(), query.startDate(), query.endDate());
}
tenantId、userId、角色、访问令牌和权限代码等可信字段不能交给模型自由生成。模型可以表达用户想查询什么,但不能决定用户是谁、属于哪个租户以及拥有哪些权限。
七、失败处理与权限控制
工具调用失败并不一定意味着系统异常。参数错误、信息缺失、权限不足和业务冲突都需要采用不同的处理方式。
| 失败类型 | 示例 | 推荐处理方式 |
| 参数错误 | 日期格式不正确 | 修正参数后限次重试 |
| 信息缺失 | 用户未提供订单号 | 请求用户补充信息 |
| 权限不足 | 无权读取财务数据 | 停止执行并明确提示 |
| 业务冲突 | 已退款订单再次退款 | 返回业务原因,不自动重试 |
| 系统异常 | 网络超时或服务不可用 | 重试、熔断或降级 |
工具执行结果最好采用统一结构:
{
"success": false,
"error": {
"code": "WEATHER_TIMEOUT",
"message": "天气服务暂时不可用",
"retryable": true
},
"traceId": "7d0e52f2"
}
模型可以根据错误类型调整下一步,但最大重试次数、超时时间、指数退避、熔断策略和最大 Agent 步数应由运行时控制,不能让模型在失败后无限循环。
权限控制同样不能交给模型。模型只能申请调用工具,真正的授权判断必须由应用系统完成。生产环境至少应建立以下控制机制:
- 根据用户、租户和业务场景动态提供工具,避免模型看到无权使用的能力。
- 在每次工具执行前重新校验用户身份、数据范围和资源权限。
- 对删除、支付、退款、授权和对外发布等高风险操作增加人工确认。
- 为写操作设置幂等键,避免模型重试或任务恢复导致重复执行。
- 记录用户、租户、工具名称、参数摘要、权限判断、执行结果和人工确认信息,形成完整审计链路。
安全的工具调用不是“模型生成参数后立即执行”,而是“模型提出行动请求,系统根据确定性规则决定是否执行”。
八、并不是所有任务都需要 Function Calling
Function Calling 适合连接模型无法独立完成的实时数据和外部能力,例如天气、库存、订单、CRM、ERP、审批、文件处理、搜索、代码执行和前端界面操作。
如果任务只是文本改写、摘要、分类或内容生成,并不依赖外部数据,也不会产生真实业务动作,则可以直接由模型完成。金额计算、状态判断、参数校验和权限检查等确定性逻辑,也应优先由程序实现,而不是重新交给模型推理。
合理的职责边界是:
模型负责理解自然语言和处理不确定性,程序负责执行确定性逻辑并控制真实业务。
Function Calling 的价值并不是把所有代码逻辑交给模型,而是让模型在合适的位置选择并组合已有程序能力。
九、小结
Function Calling 让大模型从“生成内容”进一步走向“调用真实程序能力”,但模型并不会直接执行函数。完整的工具调用仍然需要应用程序完成工具注册、参数校验、权限判断、调用执行、结果回传、异常处理和审计记录。
在整个 Agent 技术体系中,Tool 提供具体能力,Function Calling 表达调用请求,ReAct 负责推理与行动循环,Skill 沉淀可复用的方法和领域经验,MCP 则为外部工具与数据提供标准化连接方式。
Function Calling 解决了模型如何发起行动的问题,但业务系统还需要可靠地处理模型生成的数据。例如,模型可以调用工具创建任务单,但任务名称、负责人、优先级、截止日期和验收标准如何稳定映射为 Java DTO、TypeScript Interface 或数据库字段,仍然需要更严格的输出约束。
下一篇将进一步介绍:
Structured Output:让模型输出可被程序可靠处理的数据
我们将重点讨论 JSON Schema、字段校验、枚举、日期、金额、嵌套对象、输出修复和重试机制,以及如何将自然语言需求稳定转换为结构化业务数据。
— 持续更新 · 欢迎关注 —