大模型Function Calling实战:函数工具调用与Agent多轮编排

大模型Function Calling是当前Agent应用的主流交互方式。相比让模型自由输出文本再靠正则解析,Function Calling让模型直接输出结构化的工具调用请求,应用侧解析后执行真实函数并把结果回传给模型。这套机制解决了两类问题:一是让大模型能读取实时数据、操作外部系统,二是把对话流程收敛为”模型决策、应用执行”的清晰边界。本文用一个可运行的示例说明Function Calling的完整链路,并处理多轮工具编排中容易踩的状态问题。

Function Calling与大模型Agent工具调用的关系

Function Calling本质是给模型的推理接口增加一个输出通道。模型在生成回答前先判断是否需要调用某个已声明的函数,需要则返回函数名和参数JSON,不需要则直接返回自然语言回答。应用侧拿到函数名后去执行真实代码,把返回值拼接成一条”工具结果消息”再交给模型续写。整条链路相当于把函数的输入输出暴露给模型,让模型作为调度者。

与早期”让模型输出JSON再解析”的做法相比,Function Calling有两个优势。一是格式稳定,函数参数由接口层用JSON Schema校验,不依赖模型对提示词的记忆。二是可以限制调用范围,只声明允许模型使用的函数,模型不会调用未声明的函数。对安全要求高的场景,配合”只读函数”声明和返回值脱敏,可以控制大模型对生产系统的操作面。

工具声明格式:JSON Schema定义函数签名

工具声明是数组结构,每个元素包含函数的名称、描述和参数Schema。描述写得越具体,模型选对函数和填对参数的概率越高。下面的示例声明了一个天气查询函数和一个订单查询函数:

tools = [
    {
        "type": "function",
        "function": {
            "name": "query_weather",
            "description": "查询指定城市的实时天气,返回温度和天气状况",
            "parameters": {
                "type": "object",
                "properties": {
                    "city": {
                        "type": "string",
                        "description": "城市名,例如北京、上海"
                    }
                },
                "required": ["city"]
            }
        }
    },
    {
        "type": "function",
        "function": {
            "name": "query_order",
            "description": "按订单号查询订单状态",
            "parameters": {
                "type": "object",
                "properties": {
                    "order_id": {
                        "type": "string",
                        "description": "订单号"
                    }
                },
                "required": ["order_id"]
            }
        }
    }
]

参数里required数组必填,缺了这个字段模型可能漏传参数。描述字段在线上环境实测中比类型声明更影响模型准确率,描述里写明参数取值范围和格式会明显减少模型传错参数的情况。

大模型调用主流程:请求-工具-回传循环

一次完整的工具调用包含三次请求:第一次带用户问题和tools声明,模型返回tool_calls;应用执行函数;第二次把执行结果以tool角色消息带回,模型生成最终回答。以下是OpenAI兼容接口下的完整循环:

import json
from openai import OpenAI

client = OpenAI(base_url="https://api.example.com/v1", api_key="your-key")

def execute_tool(name, args):
    if name == "query_weather":
        return {"city": args["city"], "temp": 32, "condition": "晴"}
    if name == "query_order":
        return {"order_id": args["order_id"], "status": "已发货"}
    return {"error": "unknown tool"}

messages = [{"role": "user", "content": "帮我查一下上海今天多少度"}]

for _ in range(5):  # 限制最大工具调用轮数
    resp = client.chat.completions.create(
        model="gpt-4o",
        messages=messages,
        tools=tools,
        tool_choice="auto",
    )
    msg = resp.choices[0].message
    if not msg.tool_calls:
        print(msg.content)
        break
    # 追加助手消息(含tool_calls)
    messages.append(msg)
    for tc in msg.tool_calls:
        result = call_tool(tc.function.name,
                           json.loads(tc.function.arguments))
        messages.append({
            "role": "tool",
            "tool_call_id": tc.id,
            "content": json.dumps(result, ensure_ascii=False)
        })

循环里必须把助手返回的完整消息(含tool_calls)原样追加进messages,再追加对应的tool消息,顺序错乱或漏带tool_call_id会导致接口报错或模型上下文错位。上限设5轮,防止模型陷入反复调用工具的循环。

多轮工具编排:状态怎么管

一次对话里模型可能连续调用多个工具,也可能先查订单再根据结果查物流。这类多步编排的状态不在模型侧,而是在应用侧的会话管理里。每轮对话要把完整的messages数组存下来(数据库或Redis),下一次请求从会话里取messages,再追加新用户输入。需要注意:messages累积会持续消耗输入token,对话超过几十轮后需要做上下文压缩或截断,把历史消息做摘要后替换前面的轮次。

另一个实践是给工具返回值打上函数名和调用时间,模型在长会话里可以知道数据是否过期。比如天气查询结果2小时后失效,让模型看到”queried_at”字段后自行决定是否重新查询,比应用写死缓存策略更灵活。

工具调用失败与重试策略

函数执行失败不能直接把异常文本丢给模型,模型可能会把这当作有效答案。正确做法是返回结构化的错误消息,例如{“error”: “order not found”, “code”: 404},并提示模型改用其他工具或向用户询问。重试策略分两层:应用层对网络超时、限流等瞬时错误重试2次;模型层对参数错误(如城市名不存在)不再重试,直接让模型向用户澄清。

if resp.status_code == 429 or resp.status_code == 500:
    retry_count += 1
    time.sleep(2 ** retry_count)  # 指数退避
    continue
# 工具返回业务错误时,不再重试,把错误结构化返回给模型
return {"error": "no data", "code": 404}

生产环境Function Calling的注意事项

第一,工具列表不能太长。一次请求里塞几十个函数声明会显著浪费输入token,还稀释模型的注意力,建议按场景分批注册,例如按”用户意图”先粗分场景再注入对应工具集。第二,函数参数要做白名单校验,模型可能传入超出声明的值,应用侧必须对危险参数(路径、命令、金额)做二次校验。第三,并发控制与限流,模型频繁调用成本型函数(如发短信、下单)时,应用侧要设置调用频控,防刷接口。第四,日志审计,工具调用参数和返回值建议全部落库,Agent出错时可回溯是哪一步产生了错误。

Function Calling把大模型的推理能力和系统的执行能力解耦,是Agent工程化的基本单元。把工具声明、调用循环、会话状态和错误处理四个环节做扎实,多轮编排的稳定性会大幅提升。

原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/da-mo-xing-functioncalling-shi-zhan-han-shu-gong-ju-diao/

(0)
小编小编
上一篇 20小时前
下一篇 19小时前

相关推荐