大模型Function Calling怎么用,调用外部函数完整流程

文章导读
Function Calling 实际做的事很小:让模型输出一段结构化的「调用意图」——函数名加参数 JSON。真正执行函数的是你自己的服务端代码,模型既不碰你的数据库,也不发请求。判断能不能用,先看两个条件:目标动作能不能拆成参数可枚举、可校验的函数;你的链路能不能承受至少两轮模型调用(第一轮选函数填参,第二轮基于结果作答)。两条都成立,再往下接。
📋 目录
  1. 壹 先确认当前场景和目标
  2. 贰 按这个顺序处理
  3. 叁 改完后怎么验证
  4. 肆 容易忽略的边界
A A

Function Calling 实际做的事很小:让模型输出一段结构化的「调用意图」——函数名加参数 JSON。真正执行函数的是你自己的服务端代码,模型既不碰你的数据库,也不发请求。判断能不能用,先看两个条件:目标动作能不能拆成参数可枚举、可校验的函数;你的链路能不能承受至少两轮模型调用(第一轮选函数填参,第二轮基于结果作答)。两条都成立,再往下接。

Function Calling 适合“模型要基于实时或私有数据作答、且动作能用结构化参数描述”的场景。做法是先用 JSON Schema 描述函数名、参数类型和必填项,让模型只负责选函数和填参,由你的程序做白名单校验、实际执行和结果回传;验证看三件事:函数名是否稳定命中预期、参数是否通过校验、回传后回答是否引用真实返回值。参数无法结构化、调用顺序固定、写操作权限不可控的场景,用提示词约定 JSON 加规则解析更稳。

先确认当前场景和目标

Function Calling 不是“让模型会做事”的开关,而是把模型变成一个参数生成器。它成立的前提是:你要的动作边界清晰,参数可以用类型、枚举、必填项描述出来。订单查询、价格计算、创建工单、查库存这类动作通常合适;跨系统长流程、需要人判断的分支、参数是大段自由文本的操作,往往拆不干净。

动手前先把这几个信息确认清楚,缺一个就先补上:

  • 函数清单:一共几个函数,名字是否唯一且语义无重叠(两个函数都能“查订单”就会互相抢)。
  • 参数契约:每个参数的名称、类型、是否必填、取值范围。枚举值能写死的尽量写死,比让模型自由发挥靠谱。
  • 调用身份:工具以谁的身份执行,是只读查询还是带写操作,写操作是否需要人工确认。
  • 轮次预算:一次问答允许几轮模型调用,超时和失败怎么办。
  • 是否真的需要模型选函数:如果每次都是固定顺序调用,直接代码编排,不必绕这一圈。

如果参数描述不清、或者你只有一轮调用预算,可以先退回低配方案:在提示词里约定输出固定 JSON 结构,程序侧解析并做兜底重试,这比硬上工具声明更容易控住。

按这个顺序处理

下面是一条通用接入骨架,把占位部分替换成你自己的实现即可,不依赖具体厂商 SDK 的写法。

第一步,写函数声明。用 JSON Schema 描述,描述文本要写清“什么时候该用”,因为模型选函数主要靠这段文字。

大模型Function Calling怎么用,调用外部函数完整流程
{
  "name": "get_order_status",
  "description": "根据订单号查询订单当前状态。仅在用户提供了订单号时使用。",
  "parameters": {
    "type": "object",
    "properties": {
      "order_id": {
        "type": "string",
        "description": "订单号,通常为纯数字或字母数字组合"
      },
      "channel": {
        "type": "string",
        "enum": ["web", "app"],
        "description": "订单来源渠道,不确定时不要传"
      }
    },
    "required": ["order_id"]
  }
}

第二步,把工具声明和用户问题一起提交给模型,要求它只输出调用意图,不要编造执行结果。模型返回的东西大致长这样,注意参数是模型生成的,不是事实:

{
  "tool_call": {
    "name": "get_order_status",
    "arguments": { "order_id": "20240612001", "channel": "app" }
  }
}

第三步,程序侧先校验再执行。这一步是整个流程里最不能省的地方:函数名走白名单,参数逐项校验类型和范围,不通过就把错误信息回给模型让它重填,而不是直接抛异常。下面是可以替换的骨架:

ALLOWED = {"get_order_status", "create_ticket"}

def handle_call(call):
    name = call.get("name")
    args = call.get("arguments") or {}

    if name not in ALLOWED:
        return {"ok": False, "error": "unknown_function"}

    if name == "get_order_status":
        oid = args.get("order_id")
        if not isinstance(oid, str) or not oid.strip():
            return {"ok": False, "error": "order_id_required"}
        data = query_order(oid.strip())      # 换成你的真实查询
        if data is None:
            return {"ok": True, "result": {"found": False}}
        return {"ok": True, "result": {
            "found": True,
            "status": data["status"],
            "updated_at": data["updated_at"]
        }}

    return {"ok": False, "error": "not_implemented"}

第四步,把执行结果裁剪后回传给模型。只回传模型回答问题需要的字段,别把整条数据库记录塞回去;大对象既挤占上下文,也容易让回答跑偏。然后要求模型基于返回结果作答,并明确告诉它:结果里没有的信息就说不知道。

第五步,处理两种非正常路径。一是模型没有发起调用(直接回答或反问),这时不要强行执行;二是工具返回 ok=false,把错误码回给模型,让它用自然语言解释,或者提示用户补充信息。写操作类函数建议再加一道确认,别让模型的单次输出直接改数据。

改完后怎么验证

验证不看你聊了几句顺不顺畅,看结构化的中间结果。准备一批固定输入跑一遍,至少覆盖下面几类:该调某个函数的正常问题、参数缺失只能反问的问题、两个函数都可能命中容易混淆的问题、完全不需要调函数的闲聊、参数里夹带指令的输入(比如订单号位置写一段话)。

大模型Function Calling怎么用,调用外部函数完整流程

每次调用把这几项记下来,形成一张对照表:模型返回的函数名、参数字典、校验是否通过、工具实际返回、最终回答是否引用了工具返回值。

预期信号可以这样判断:

  • 函数名在同类输入下稳定命中同一个,不是这轮 A 下轮 B;不稳定说明描述文本有歧义,收窄描述或加枚举。
  • 参数通过你的校验,缺失必填项时模型选择追问而不是瞎填。
  • 不需要调用的输入确实没有产生调用,而不是每次都硬调一个。
  • 工具返回 found:false 或错误码时,回答如实说明,没有编造订单状态。
  • 最终回答里的关键信息能在工具返回值里找到对应,不是模型自己补的。

同一批输入连跑几次,结果不稳定就先别扩大范围,回到函数描述和参数约束上改,改完再跑同一批,保持用例不变才看得出差异。

容易忽略的边界

第一,声明和实现会漂移。函数加了必填参数但 description 没更新,模型就会持续生成缺字段的调用。建议把 schema 和实现放在同一处维护,改参数时一起改。

大模型Function Calling怎么用,调用外部函数完整流程

第二,模型给的参数一律当外部输入处理。类型校验、长度限制、白名单、越权检查一个都不能少,别因为“是模型生成的”就放松。参数里可能会出现指令性文本,查询类函数要按值处理,不要拼接进 SQL 或命令。

第三,权限与副作用。工具以什么身份执行,能否访问用户不该看的数据,写操作是否可回滚,这些要在接入前定好。创建、删除、支付这类动作建议加人工确认或双人复核,模型输出不作为最终授权依据。

第四,结果体积和多轮成本。大结果集回传会撑爆上下文,建议在工具侧就做字段裁剪或分页,让模型请求更多而不是一次给全。

第五,不同模型和接口对并行调用、多轮工具调用的支持程度不一样,是否支持多个调用意图一起返回、是否支持边调边答,需要结合你实际使用的接口文档确认,不要按一个模型的习惯套到另一个上。

不适用或需要回退的情况也很明确:参数无法结构化成 schema、调用顺序固定、接口不支持工具声明、或者调用结果必须绝对准确不能靠模型判断时,改回“提示词约定 JSON + 程序解析 + 规则路由”更可控;写操作失败时不要自动重放,转人工处理比让模型反复尝试安全。