大模型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/