简介:Function Call(函数调用)是让大模型从“只会说”进化为“能做事”的关键机制。本文系统拆解其核心概念、六步工作流程、结构化交互协议与工程编排实现,配套工具定义与调用循环的示例代码,并总结参数校验、错误处理、安全边界等最佳实践,帮助开发者快速构建具备实时查询、精确计算与外部系统操作能力的智能应用。
大模型的生成能力有目共睹,但纯粹基于概率的文本生成存在三类天然短板:
Function Call(函数调用,也常称Tool Use/工具调用)正是为弥补这些短板而生的机制:它允许模型在对话过程中主动判断是否需要调用预定义的函数或工具,将实时信息获取、精确计算、外部系统操作等任务委托给真正的执行者,再把结果整合进最终回答。一句话概括——它让模型不仅能“说”,还能“做”。
理解Function Call,本质上是理解大模型工作方式的三个转变:
传统模式下,模型只能依赖训练数据“闭卷作答”;引入工具调用后,模型可以根据用户请求动态地选择并调用外部函数(如API、数据库查询、计算器等),相当于获得了“开卷”获取实时数据的能力,弥补静态知识的不足。
开启工具调用能力后,模型在需要调用工具的轮次中,输出的不再是自然语言,而是一份结构化的调用请求(业界普遍采用JSON格式),其中包含函数名与参数值。外部系统解析这份结构化数据并执行函数,再将结果返回给模型,最终由模型整合为人类可读的自然语言。这种“模型产出结构、程序消费结构”的契约,是整个机制可靠运转的基础。
这是最容易被误解、也最关键的一点:LLM本身从不执行任何工具。它只负责三件事——判断要不要调用、决定调用哪个工具、给出调用所需参数;真正的执行者是用户侧的客户端程序(即你的应用后端)。这种分离设计带来两个好处:
下面用一个最小场景串起完整链路。交互时序如下:
用户 大模型 客户端程序 外部工具/服务│ 提问+工具描述 │ │ ││─────────────>│ │ ││ │ 判断需调用天气工具 │ ││ │ 返回结构化调用请求 │ ││ │─────────────────────>│ ││ │ │ 执行 get_weather ││ │ │───────────────────>││ │ │ 返回天气结果 ││ │ │<──────────────────││ │ 回传工具调用结果 │ ││ │<─────────────────────│ ││ │ 整合生成自然语言回答 │ ││<─────────────│ │ │
第1步:请求与工具描述一并下发。 用户通过大模型API提问“北京今天多少度?”。与此同时,客户端会把可用工具的描述信息一并传入,包括工具名称(如get_weather)以及所需参数的名称与类型(如location、date)。工具描述是模型“知道有哪些能力可用、怎么用”的唯一依据。
第2步:模型进行调用决策。 基于用户问题和工具清单,模型判断本次请求需要调用天气查询工具。如果模型判断无需调用工具(比如用户只是打招呼),则跳过后续步骤,直接生成自然语言回复。
第3步:模型返回结构化调用请求。 模型输出类似如下的结构化结果(示意):
{"name": "get_weather","arguments": { "location": "北京", "date": "今天" }}
第4步:客户端解析并真正执行。 客户端程序接收到函数名与参数后,将其映射到本地实现的真实函数(例如请求某个天气数据源),执行后得到结果:{"temperature": 28, "unit": "℃"}。
第5步:工具结果回传模型。 客户端把执行结果作为一条“工具消息”追加到对话上下文中,再次通过API请求模型。
第6步:模型整合作答。 模型结合工具结果与历史对话,生成自然语言回答:“北京今天气温28℃。”由客户端呈现给用户。
目前包括百度智能云在内的主流大模型服务平台均已原生支持工具调用能力,开发者无需自行微调模型即可在标准对话API中使用上述流程。
工具定义通常遵循JSON Schema风格。一份典型的定义(示意)如下:
{"tools": [{"type": "function","function": {"name": "get_weather","description": "查询指定城市在指定日期的天气情况,包括气温、天气现象等","parameters": {"type": "object","properties": {"location": {"type": "string","description": "城市名称,例如:北京"},"date": {"type": "string","description": "查询日期,例如:今天,或格式为YYYY-MM-DD的日期"}},"required": ["location", "date"]}}}]}
其中有三个值得注意的细节:
从工程视角看,工具调用就是一个“请求—检测—执行—回传—再请求”的循环。以下为示意性伪代码:
messages = [{"role": "user", "content": "北京今天多少度?"}]response = llm.chat(messages, tools=tool_definitions)if response.tool_calls:for call in response.tool_calls:# 1. 解析模型返回的结构化调用请求name = call["function"]["name"] # get_weatherargs = json.loads(call["function"]["arguments"])# {"location": "北京", "date": "今天"}# 2. 执行前先做参数校验(防幻觉、防越界)validate(name, args)# 3. 由客户端(而非模型)真正执行函数result = dispatch_and_execute(name, args)# {"temperature": 28, "unit": "℃"}# 4. 将调用请求与结果追加到上下文messages.append(call["message"])messages.append({"role": "tool","tool_call_id": call["id"],"content": json.dumps(result, ensure_ascii=False)})# 5. 携带工具结果再次请求模型,生成自然语言回答final = llm.chat(messages, tools=tool_definitions)print(final["content"]) # 北京今天气温28℃。else:print(response["content"]) # 模型判断无需调用工具,直接输出
这段循环结构可以自然扩展出两类进阶形态:
while中,让模型每轮决定“继续调用还是给出最终答案”即可。结合工程落地经验,以下几点最值得投入:
Function Call的本质,是为大模型建立了一套与外部世界交互的结构化契约:模型负责决策(调用什么、传什么参数),客户端负责执行(真正干活并把结果回传),二者通过JSON协议协作,最终以自然语言闭环交付。对开发者而言,掌握这套机制的关键在于三点——写好工具描述、实现健壮的编排循环、守住执行侧的安全边界。做到这些,大模型就不再只是一个“聊天窗口”,而成为能够连接实时数据、调用系统能力、完成实际任务的智能执行核心。