函数调用是什么
函数调用(Tool Calling)让模型在需要外部信息或执行动作时,输出一段结构化的调用请求:函数名加参数 JSON。你的程序执行这个函数,把结果回填给模型,模型再据此生成最终回答。模型本身不执行任何操作,它只负责决定调用什么与传什么参数,所以权限边界与校验责任都在你的代码里。这是把模型接入真实系统的标准方式。
基本信息
| 角色分工 | 模型决定调用意图与参数,代码负责执行与校验 |
|---|---|
| 请求侧 | 在请求里声明工具清单,含名称、说明与参数模式 |
| 模型返回 | 结构化调用请求,可能一次给出多个调用 |
| 回填方式 | 把执行结果作为工具结果消息提交,再请求下一轮 |
| 结果形态 | 工具结果通常是文本,结构可自行约定 |
| 安全边界 | 工具权限最小化,高风险操作保留人工确认 |
四步流程
| 步骤 | 发生什么 | 程序要做什么 |
|---|---|---|
| 声明 | 请求里带上工具清单与参数模式 | 把可用工具与字段说明写清楚 |
| 决策 | 模型返回函数名与参数 | 解析参数并做合法性校验 |
| 执行 | 你的代码真正调用外部系统 | 按最小权限执行,失败要有明确返回 |
| 回填 | 把结果作为工具消息提交 | 带上对应的调用标识,保持一一对应 |
| 收尾 | 模型基于结果给出最终回答 | 检查是否还需要继续调用直到满足停止条件 |
典型使用场景
- 查实时数据:订单状态、库存、天气等模型知识之外的信息
- 走业务系统:创建工单、发起审批、写入表单
- 检索增强:把知识库检索包装成一个工具,由模型决定何时查
- 代码执行:把计算、数据处理交给沙箱环境运行
- 多系统编排:连续调用多个接口完成一次完整任务
工具定义与调用回填
工具定义与调用回填
// 请求里声明工具
"tools": [ { "type": "function", "function": {
"name": "query_order", "description": "按订单号查询订单状态",
"parameters": { "type": "object", "properties": { "order_id": { "type": "string" } },
"required": ["order_id"] } } } ]
// 模型返回调用请求后,回填结果时必须带同一个调用标识
{ "role": "tool", "tool_call_id": "call_1", "content": "{\"status\":\"已发货\"}" }调用循环的基本结构
调用循环的基本结构
msgs = [{"role": "user", "content": user_input}]
while True:
r = client.chat.completions.create(model="gpt-4o", messages=msgs, tools=tools)
msg = r.choices[0].message
msgs.append(msg) # 助手消息(含调用请求)要原样带回
if not msg.tool_calls:
break # 没有调用请求即得到最终答案
for c in msg.tool_calls: # 一次可能返回多个调用
out = dispatch(c.function.name, c.function.arguments) # 执行并校验
msgs.append({"role": "tool", "tool_call_id": c.id, "content": out})
# 循环要设次数上限,避免模型反复调用陷入死循环工具设计要点
| 要点 | 做法 | 原因 |
|---|---|---|
| 名称与说明 | 写清用途、边界与返回含义 | 模型据此决定是否调用 |
| 参数模式 | 字段限定类型与必填项 | 减少参数缺失与类型错误 |
| 返回精简 | 只返回模型需要的信息 | 结果越长越贵,也越容易干扰判断 |
| 错误表达 | 失败时返回明确的错误说明 | 让模型能换策略或向用户说明 |
| 幂等与确认 | 写操作支持重复执行与人工确认 | 避免重试引发重复下单之类后果 |
工具数量增加会明显拉长提示词并降低选择准确率:把同类操作合并成带参数的工具,比堆一长串细粒度工具更有效;工具说明里写清适用与不适用条件,选择错误会少很多。
模型给出的参数一律视为不可信输入:必须做类型、范围与权限校验,尤其当工具会写数据库或调用外部系统时。