LLMNotes

第 14 章:Function Calling

zjc 于 2026-01-14 发布

这是《LLM 应用开发 零基础实战指南》的独立章节版。本章从概念、实操和生产排查三个视角展开,代码块保留了原书可直接运行的版本。 Function Calling 让模型输出结构化工具调用请求,由应用执行工具并把结果返回给模型。模型不直接执行代码,真正执行者永远是应用服务。

14.1 基本流程

User
  -> LLM
     -> tool_call: get_order(order_id)
        -> Application validates permission
           -> Execute function
              -> return tool result
                 -> LLM final answer

关键点:

  1. 模型决定“是否调用”和“参数是什么”;
  2. 应用决定“是否允许”和“如何执行”;
  3. 工具 schema 是接口契约;
  4. 工具结果必须可验证;
  5. 高危工具要审批;
  6. 调用过程要记录审计。

14.2 工具定义

{
  "type": "function",
  "function": {
    "name": "get_order",
    "description": "查询当前用户有权限查看的订单状态",
    "parameters": {
      "type": "object",
      "properties": {
        "order_id": {
          "type": "string",
          "pattern": "^[A-Z]{2}-[0-9]{6,12}$"
        }
      },
      "required": ["order_id"],
      "additionalProperties": false
    }
  }
}

description 要写清楚:

  1. 工具用途;
  2. 什么时候不该用;
  3. 参数格式;
  4. 返回内容;
  5. 副作用;
  6. 权限要求。

14.3 调用示例

import json
from openai import OpenAI

client = OpenAI()

tools = [{
    "type": "function",
    "function": {
        "name": "get_order",
        "description": "查询当前用户订单",
        "parameters": {
            "type": "object",
            "properties": {"order_id": {"type": "string"}},
            "required": ["order_id"],
        },
    },
}]

response = client.chat.completions.create(
    model="gpt-4.1-mini",
    messages=[{"role": "user", "content": "帮我查订单 A-10001"}],
    tools=tools,
    tool_choice="auto",
)

message = response.choices[0].message
if message.tool_calls:
    call = message.tool_calls[0]
    args = json.loads(call.function.arguments)

14.4 返回工具结果

messages = [
    {"role": "user", "content": "帮我查订单 A-10001"},
    message,
    {
        "role": "tool",
        "tool_call_id": call.id,
        "content": json.dumps({"status": "shipped", "eta": "2026-08-27"}, ensure_ascii=False),
    },
]

final = client.chat.completions.create(
    model="gpt-4.1-mini",
    messages=messages,
)

工具返回建议:

  1. 结构化 JSON;
  2. 只返回必要字段;
  3. 敏感字段脱敏;
  4. 明确错误类型;
  5. 控制大小;
  6. 带上数据版本或更新时间。

14.5 参数校验

from pydantic import BaseModel, field_validator

class GetOrderArgs(BaseModel):
    order_id: str

    @field_validator("order_id")
    @classmethod
    def validate_order_id(cls, value: str):
        if not value.startswith("A-"):
            raise ValueError("invalid order id")
        return value

校验层次:

示例
Schema 类型、必填、枚举
格式 正则、长度、时间
业务 订单是否存在
权限 是否属于当前用户
风控 金额、频率、状态

14.6 权限控制

def execute_tool(name, args, user):
    policy = TOOL_POLICY[name]
    if name not in user.allowed_tools:
        raise PermissionError("tool not allowed")
    if policy["scope"] == "own" and args.get("user_id") not in (None, user.id):
        raise PermissionError("cross user access")
    return policy["handler"](args, user)

权限设计:

  1. 用户只能查看自己的数据;
  2. 客服按工单归属授权;
  3. 管理员工具与普通工具分离;
  4. 写操作独立审批;
  5. 工具凭证使用最小权限;
  6. 服务间调用也认证。

14.7 错误处理

错误 返回给模型 用户看到
参数格式错 invalid_argument 换种说法提示
未登录 unauthorized 登录引导
无权限 forbidden 不暴露细节
数据不存在 not_found 查不到提示
依赖超时 upstream_timeout 稍后重试
频率超限 rate_limited 降级提示

不要把堆栈、SQL、内部主机名返回给模型。

14.8 工具选择策略

避免一次提供过多工具:

意图路由
  -> order tools
  -> logistics tools
  -> account tools

优化方法:

  1. 工具按业务域分组;
  2. 每组数量控制在合理范围;
  3. 先路由再暴露工具;
  4. 相似工具描述要区分;
  5. 删除废弃工具;
  6. 用评测集测工具选择准确率;
  7. 对高风险工具强制确认。

14.9 审计日志

{
  "trace_id": "t-1001",
  "user_id": "u-1",
  "tool": "get_order",
  "arguments": {"order_id": "A-10001"},
  "allowed": true,
  "status": "success",
  "latency_ms": 45,
  "result_summary": "status=shipped"
}

记录参数前要脱敏。工具结果正文可只存摘要或采样。

14.10 常见问题

现象 处理
模型不调用工具 描述不清、工具太多
参数编造 schema 和示例加强
循环调用 最大轮数和重复检测
调错相似工具 区分 description
权限绕过 服务端强制校验
结果太大 截断、分页、摘要
副作用重复 幂等键

本章小结

Function Calling 的工程重点不是让模型“会调函数”,而是定义清晰工具契约、严格校验参数、在服务端执行权限控制、处理错误并记录审计。模型提出请求,系统拥有最终执行权。

思考题

  1. 模型输出的工具参数为什么要二次校验?
  2. 如何避免模型调用越权工具?
  3. 工具返回大结果时应如何处理?
  4. 相似工具太多会导致什么问题?
  5. 哪些工具必须设计成幂等?