多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

大模型Function Calling实战:从原理到AI Agent开发全解析

大模型Function Calling实战:从原理到AI Agent开发全解析 1. 从“聊天”到“干活”Function Calling 的本质跃迁如果你在过去一年里深度使用过 ChatGPT、Claude 或者国内的大模型一定有过这样的体验你跟它聊得天花乱坠它也能对答如流但一旦你想让它帮你做点“实事”比如查一下明天的天气、给你的购物车算个总价、或者把一段对话内容整理成表格发到你的邮箱它多半会礼貌地告诉你“作为一个AI模型我无法直接执行这个操作。” 这种感觉就像你雇了一个知识渊博的顾问他什么都懂但就是不会动手所有事情都得你听完他的建议后自己再去操作一遍。Function Calling的出现彻底改变了这个局面。它不是一个具体的函数而是一种标准化的“协议”或“能力”。简单来说它让大模型从一个“健谈的顾问”变成了一个“能听懂指令并指挥工具干活的管家”。模型本身依然不直接操作外部世界比如它不能真的去访问天气网站但它学会了识别你的自然语言指令中隐含的“意图”并将这个意图精准地“翻译”成对某个具体工具我们称之为“函数”或“API”的调用请求。然后由你的程序去执行这个工具并把结果返回给模型模型再组织成自然语言回复你。这一来一回就完成了从“说”到“做”的闭环。为什么这件事如此重要因为它解决了大模型落地应用的核心瓶颈——连接现实世界。没有 Function Calling大模型是信息孤岛它的价值仅限于文本生成。有了 Function Calling大模型就成了一个万能的中枢大脑可以调度无数的外部工具和服务数据库、计算引擎、邮件系统、硬件设备等真正融入业务流程和用户体验中。我们常说的AI Agent智能体其核心能力之一就是通过 Function Calling 来使用工具。所以掌握 Function Calling是构建实用化 AI 应用、迈向 AI Agent 开发的必经之路。2. 核心原理拆解大模型如何学会“发号施令”要理解 Function Calling我们需要暂时跳出代码看看它背后大模型与开发者之间是如何“默契配合”的。这个过程并非魔法而是一套设计精巧的交互协议。2.1 交互流程的三步舞曲一次完整的 Function Calling 交互通常包含三个核心步骤我把它比作一场精心编排的“三步舞曲”第一步开发者“亮出工具箱”定义函数在向大模型发送用户问题之前我们开发者需要先告诉模型“嗨我这里有这些工具你可以用。” 这个“告诉”的方式就是以 JSON Schema 的形式描述一个或多个函数的名称、功能说明、以及所需的参数及其类型。例如我们定义一个get_current_weather的函数描述是“获取指定城市的当前天气”它有一个参数叫location类型是字符串。这一步的关键在于描述要清晰、准确因为模型完全依赖这个描述来理解工具的用途。第二步大模型“理解并开单”解析意图并返回调用请求当用户提出一个问题比如“北京天气怎么样”时我们将用户问题和我们定义好的函数列表一起发送给大模型。大模型会进行如下思考用户的问题是否需要调用我已知的工具来解决意图识别如果需要哪个工具最匹配函数选择调用这个工具需要哪些具体信息参数提取如果模型判断需要调用函数它不会直接执行函数而是会返回一个结构化的 JSON 对象。这个对象里包含了它“想”调用的函数名称以及它从用户问题中提取并填充好的参数。例如{name: get_current_weather, arguments: {location: 北京}}。注意此时函数并没有被真正执行模型只是返回了一个“调用指令单”。第三步程序“执行并反馈”执行函数并返回结果我们的应用程序收到这个 JSON 指令后就会在本地或远程找到对应的get_current_weather函数传入参数“北京”真正执行它。执行后我们会得到一个结构化的结果比如{temperature: 22, unit: celsius, description: 晴朗}。然后我们将这个结果作为新的信息再次发送给大模型并请它根据这个结果来生成面向用户的最终回答。模型此时会说“北京目前天气晴朗气温22摄氏度。”2.2 关键设计结构化输出与“零样本”学习Function Calling 的实现依赖于大模型两项核心能力的结合强大的结构化信息抽取能力大模型特别是 GPT-4 这个级别的模型在理解自然语言并从中精准提取结构化信息如时间、地点、人名、事件方面表现卓越。Function Calling 本质上就是将这种能力标准化、定向化。我们通过函数描述引导模型去关注和提取我们关心的那几类参数。基于描述的“零样本”函数理解我们不需要给模型提供成千上万个调用示例来训练它。我们只需要用自然语言清晰地描述函数是干什么的、需要什么参数模型就能在“零样本”即没有见过该函数具体调用例子的情况下正确理解并使用它。这得益于大模型在预训练阶段获得的、对世界知识的通用理解能力。注意这里有一个非常重要的认知纠正。Function Calling 并不是在模型内部“运行”了你的代码。模型对函数一无所知它只是根据你的描述做了一个“模式匹配”和“信息填充”的工作。真正的执行权始终牢牢掌握在你的应用程序手中。这既是出于安全考虑防止模型执行恶意代码也使得架构更加清晰业务逻辑与模型逻辑分离。3. 主流平台实战OpenAI、Anthropic 与国产模型的实现理论讲清楚了我们来看看具体怎么干。不同的大模型平台对 Function Calling 的实现细节略有不同但核心思想一致。我将以最主流的 OpenAIGPT和快速崛起的 AnthropicClaude为例并简要对比国产主流模型的现状。3.1 OpenAI / Azure OpenAI API 实现详解OpenAI 是 Function Calling 的提出者和标准定义者其 API 最为成熟。在最新的 Chat Completions API 中通过tools参数来定义函数。第一步定义工具函数列表你需要准备一个tools数组其中每个元素描述一个函数。type固定为function核心在function对象内的描述。tools [ { type: function, function: { name: get_current_weather, description: 获取指定城市的当前天气信息, parameters: { type: object, properties: { location: { type: string, description: 城市名称例如北京、San Francisco }, unit: { type: string, enum: [celsius, fahrenheit], description: 温度单位默认为摄氏度celsius } }, required: [location] } } } ]第二步发起对话并处理模型响应将用户消息和tools一起发送给 API。关键是要设置tool_choice参数。如果设为auto模型将自行决定是否调用函数如果设为{type: function, function: {name: xxx}}则可以强制要求模型调用特定函数。import openai from openai import OpenAI client OpenAI(api_keyyour-api-key) response client.chat.completions.create( modelgpt-3.5-turbo, # 或 gpt-4-turbo messages[{role: user, content: 上海今天热吗}], toolstools, tool_choiceauto, # 让模型自主决定 ) message response.choices[0].message第三步判断并执行函数调用检查响应中是否包含tool_calls。如果有则遍历每个调用请求执行对应的本地函数并将结果收集起来。if message.tool_calls: # 准备一个列表来存放所有工具调用的结果 tool_messages [] for tool_call in message.tool_calls: function_name tool_call.function.name function_args json.loads(tool_call.function.arguments) # 根据 function_name 找到并执行对应的本地函数 if function_name get_current_weather: location function_args.get(location) unit function_args.get(unit, celsius) # 调用你的真实天气API或函数 weather_result get_real_weather(location, unit) # 将结果格式化为模型期望的格式 tool_messages.append({ role: tool, content: json.dumps(weather_result), # 结果必须是字符串 tool_call_id: tool_call.id # 必须关联对应的调用ID }) # 第四步将工具执行结果送回给模型让它生成最终回答 second_response client.chat.completions.create( modelgpt-3.5-turbo, messages[ {role: user, content: 上海今天热吗}, message, # 包含原始 tool_calls 的助理消息 *tool_messages # 展开所有工具执行结果 ] ) final_answer second_response.choices[0].message.content print(final_answer) # 输出“上海今天天气多云气温28摄氏度体感较热。” else: # 如果模型没有调用工具直接输出其回复 print(message.content)实操心得描述description是灵魂函数的description和参数的description至关重要。要用清晰、无歧义的自然语言撰写。例如“获取天气”就不如“获取指定城市的实时温度、天气状况和湿度”来得精确。处理多个工具调用模型在一次回复中可能同时调用多个工具parallel function calling。你的代码需要能处理一个message中包含多个tool_call的情况并确保将每个结果通过正确的tool_call_id关联回去。温度temperature参数对于需要稳定输出结构化数据的 Function Calling 场景建议将temperature设置为 0 或接近 0 的值以减少输出的随机性确保参数提取的准确性。3.2 Anthropic Claude Messages API 实现Anthropic 的 Claude 3 系列模型也支持类似功能但术语和 API 格式有所不同。它称之为Tool Use。整体流程相似但细节有差异。第一步定义工具Tools在 Claude API 中工具定义在tools参数下结构略有不同。tools [{ name: get_current_weather, description: 获取指定城市的当前天气信息, input_schema: { type: object, properties: { location: { type: string, description: 城市名称 }, unit: { type: string, enum: [celsius, fahrenheit], default: celsius } }, required: [location] } }]第二步发起对话并处理 Block 响应Claude 的响应消息 (Message) 包含一个content数组其中可能包含TextBlock和ToolUseBlock。import anthropic client anthropic.Anthropic(api_keyyour-api-key) response client.messages.create( modelclaude-3-sonnet-20240229, max_tokens1024, toolstools, messages[{role: user, content: 对比一下北京和杭州的天气。}] ) # 检查响应内容 for block in response.content: if block.type ‘text‘: print(f“Text: {block.text}“) elif block.type ‘tool_use‘: print(f“Tool to use: {block.name}“) print(f“Tool input: {block.input}“) # 这里 block.id 相当于 OpenAI 的 tool_call_id第三步执行工具并提交结果你需要收集所有ToolUseBlock执行对应函数然后将结果以ToolResultBlock的形式作为新的用户消息提交给 Claude 进行后续处理。tool_results [] for block in response.content: if block.type ‘tool_use‘: if block.name “get_current_weather“: weather get_real_weather(block.input[“location“]) tool_results.append({ “type“: “tool_result“, “tool_use_id“: block.id, # 关联 ID “content“: json.dumps(weather) }) # 将工具结果作为后续对话输入 if tool_results: follow_up_response client.messages.create( model“claude-3-sonnet-20240229“, max_tokens1024, messages[ {“role“: “user“, “content“: “对比一下北京和杭州的天气。“}, response, # 包含 ToolUseBlock 的助理消息 {“role“: “user“, “content“: tool_results} # 提交工具结果 ] ) print(follow_up_response.content[0].text)与 OpenAI 的主要差异点术语OpenAI 叫tools/function calling Claude 叫tools/tool use。响应结构OpenAI 的调用信息在message.tool_calls里Claude 的则在content数组中以ToolUseBlock形式出现。结果提交OpenAI 要求将结果以role: tool的消息格式插入历史Claude 要求将结果作为新的user消息中的ToolResultBlock提交。并行调用两者都支持但处理代码的编写方式因上述结构差异而不同。3.3 国产大模型的适配与现状目前国内主流大模型平台如百度文心、阿里通义、智谱GLM、月之暗面Kimi等也都在快速跟进 Function Calling 能力。通常有以下几种实现方式兼容 OpenAI 格式这是最友好的方式。许多国产模型 API 在设计上力求与 OpenAI API 兼容这意味着你为 GPT 编写的tools定义和调用代码只需更换 API Endpoint 和 API Key就能在一定程度上直接运行。例如部分平台提供的“Chat Completions 兼容接口”。自有格式部分平台提供了自己定义的函数调用格式通常也会提供详细的 SDK 和文档。其核心流程定义、调用、执行、回调万变不离其宗学习成本在于熟悉其特定的 JSON 结构字段名。插件/工具平台一些平台通过更高层次的“插件市场”或“工具平台”来提供类似能力开发者以配置化方式上传工具模型通过平台调度。这种方式对开发者更简单但灵活性可能不如直接调用 API。给开发者的建议在开始一个涉及 Function Calling 的项目前先调研目标模型平台的官方文档查看其“工具调用”或“函数调用”相关章节。优先选择提供 OpenAI 兼容接口的平台可以最大程度复用代码和降低迁移成本。同时要关注不同模型在意图理解准确率、复杂参数提取能力上的差异必要时需要调整函数描述或加入少量示例few-shot进行引导。4. 高级模式与架构设计构建健壮的 AI 应用掌握了基础调用我们就可以探讨更复杂的应用模式了。单个函数调用解决单一问题而现实世界的任务往往是多步骤、有条件分支的。这就需要更高级的设计模式。4.1 多轮对话与状态管理Function Calling 天然支持多轮对话。例如用户问“帮我订一张明天从北京飞上海的机票。”模型可能先调用search_flights函数返回一堆航班选项。用户接着说“要下午出发的。”这时你需要将整个对话历史包括之前的函数调用和结果连同新的用户指令一起发送给模型。模型会理解上下文它可能不会再次调用搜索而是从之前的搜索结果中筛选或者调用一个新的filter_flights函数。关键点状态管理。你的应用程序需要维护完整的对话历史记录。一个典型的对话状态ConversationState应该包括用户和助理的文本消息列表。历史上所有发生过的工具调用tool_calls及其结果tool_messages。任何自定义的会话上下文如用户ID、会话偏好等。每次与模型交互时都将这个完整的状态作为messages发送过去。模型会根据整个上下文来决定下一步是直接回复还是调用新工具。4.2 并行与串行调用策略并行调用当用户的一个请求涉及多个独立任务时模型可以一次性返回多个tool_calls。例如“查一下北京天气和上海股市大盘”。你的后端应该并行执行get_weather和get_stock_index这两个函数以降低总延迟。所有结果返回后再一次性提交给模型进行总结。串行调用很多任务具有依赖性必须串行。例如“查一下特斯拉的股价如果超过200美元就发邮件提醒我”。这需要先调用get_stock_price(“TSLA”)得到结果后在下一轮对话中模型根据结果判断条件成立再调用send_email函数。这需要你的程序能驱动多轮交互循环。架构设计模式控制流引擎对于复杂的串行或带条件分支的任务一个常见的架构是引入一个轻量级的“控制流引擎”或“工作流引擎”。这个引擎负责维护与模型的对话循环。解析模型的响应判断是文本回复还是工具调用。执行工具调用管理其输入输出。根据工具执行结果和预定义的业务逻辑或让模型决策决定下一步是继续询问模型还是执行其他操作。这种模式是构建复杂 AI Agent 的雏形。4.3 错误处理与用户反馈工具执行可能失败网络超时、API限流、参数错误等。健全的 Function Calling 应用必须有完善的错误处理机制。工具执行失败当本地函数执行抛出异常时不要崩溃。应该将错误信息例如“天气服务暂时不可用”作为该工具调用的“结果”返回给模型。模型有能力理解错误并可能生成对用户友好的解释如“抱歉天气查询服务出了点问题请您稍后再试。”模型“幻觉”调用有时模型可能会错误地调用一个不存在的函数或为参数生成完全不合逻辑的值如location: 12345。你的代码需要在执行前进行验证检查function_name是否在已注册的工具列表中。使用 JSON Schema 验证器如jsonschema库检查arguments是否符合预定义的模式。对于关键参数进行业务逻辑验证如城市名是否在支持列表中。 如果验证失败可以将验证错误信息作为结果返回给模型让它有机会纠正。用户澄清如果模型提取的参数模糊不清例如location: “南方”你的工具函数可能无法处理。一种策略是让工具返回一个特定的错误码或消息模型收到后会主动向用户提问以澄清“您具体想查询南方哪个城市的天气呢”5. 实战从零构建一个智能旅行助手 Agent让我们综合运用以上知识构建一个简单的“智能旅行助手”AI Agent。它能理解用户关于旅行的复杂请求并通过调用多个外部工具来完成。5.1 定义工具集我们为助手装备三个核心工具search_flights查询航班。search_hotels查询酒店。get_city_info获取城市基本信息如天气、景点这里简化为一个函数。tools [ { “type“: “function“, “function“: { “name“: “search_flights“, “description“: “根据出发地、目的地、日期搜索航班信息。日期格式为 YYYY-MM-DD。“, “parameters“: { “type“: “object“, “properties“: { “departure_city“: {“type“: “string“, “description“: “出发城市“}, “arrival_city“: {“type“: “string“, “description“: “到达城市“}, “date“: {“type“: “string“, “description“: “出发日期YYYY-MM-DD“} }, “required“: [“departure_city“, “arrival_city“, “date“] } } }, { “type“: “function“, “function“: { “name“: “search_hotels“, “description“: “根据城市和入住/离店日期搜索酒店信息。日期格式为 YYYY-MM-DD。“, “parameters“: { “type“: “object“, “properties“: { “city“: {“type“: “string“, “description“: “城市名称“}, “check_in“: {“type“: “string“, “description“: “入住日期“}, “check_out“: {“type“: “string“, “description“: “离店日期“} }, “required“: [“city“, “check_in“, “check_out“] } } }, { “type“: “function“, “function“: { “name“: “get_city_info“, “description“: “获取城市的基本信息如当前天气、主要景点等。“, “parameters“: { “type“: “object“, “properties“: { “city_name“: {“type“: “string“, “description“: “城市名称“} }, “required“: [“city_name“] } } } ]5.2 实现主控循环与工具执行器我们将创建一个TravelAssistant类来管理整个对话状态和流程。import json import openai from typing import List, Dict, Any class TravelAssistant: def __init__(self, api_key: str, model: str “gpt-3.5-turbo“): self.client openai.OpenAI(api_keyapi_key) self.model model self.conversation_history: List[Dict] [] # 存储完整对话历史 def add_user_message(self, content: str): “““添加用户消息到历史。“““ self.conversation_history.append({“role“: “user“, “content“: content}) def _execute_tool(self, tool_name: str, arguments: Dict) - str: “““模拟执行工具。在实际应用中这里会调用真实的API。“““ if tool_name “search_flights“: # 模拟航班搜索 return json.dumps({ “flights“: [ {“airline“: “航司A“, “flight_no“: “CA1234“, “dep_time“: “08:00“, “price“: 1200}, {“airline“: “航司B“, “flight_no“: “MU5678“, “dep_time“: “14:00“, “price“: 900} ] }) elif tool_name “search_hotels“: # 模拟酒店搜索 return json.dumps({ “hotels“: [ {“name“: “酒店X“, “star“: 4, “price_per_night“: 500}, {“name“: “酒店Y“, “star“: 5, “price_per_night“: 1200} ] }) elif tool_name “get_city_info“: # 模拟城市信息 city arguments.get(“city_name“) return json.dumps({ “weather“: “晴朗25℃“, “attractions“: [“景点1“, “景点2“] }) else: return json.dumps({“error“: f“未知工具: {tool_name}“}) def process_query(self, user_query: str) - str: “““处理用户查询的核心循环。“““ # 1. 添加用户消息到历史 self.add_user_message(user_query) # 2. 调用模型传入完整历史和工具定义 response self.client.chat.completions.create( modelself.model, messagesself.conversation_history, toolstools, tool_choice“auto“, temperature0 ) assistant_message response.choices[0].message # 3. 将助理的回复可能包含文本或工具调用加入历史 self.conversation_history.append(assistant_message.to_dict()) final_answer None # 4. 检查是否需要调用工具 if assistant_message.tool_calls: tool_messages [] for tool_call in assistant_message.tool_calls: func_name tool_call.function.name func_args json.loads(tool_call.function.arguments) print(f“[Agent 正在执行] {func_name}({func_args})“) # 执行工具 tool_result self._execute_tool(func_name, func_args) # 构造工具结果消息 tool_messages.append({ “role“: “tool“, “content“: tool_result, “tool_call_id“: tool_call.id }) # 5. 将所有工具结果加入历史 self.conversation_history.extend(tool_messages) # 6. 再次调用模型让它基于工具结果生成最终回复 second_response self.client.chat.completions.create( modelself.model, messagesself.conversation_history, temperature0 ) final_message second_response.choices[0].message self.conversation_history.append(final_message.to_dict()) final_answer final_message.content else: # 没有工具调用直接返回文本回复 final_answer assistant_message.content return final_answer # 使用助手 assistant TravelAssistant(api_key“your-key“) answer assistant.process_query(“我想下周五从北京飞上海住两晚推荐一下航班和酒店顺便说说上海现在天气怎么样。“) print(“助手回复“, answer)5.3 运行示例与解析当你运行上面的代码输入复杂查询时模型可能会进行以下操作首先它识别出三个子任务查航班、查酒店、查天气。它可能一次性并行调用search_flights(北京上海下周五) 和get_city_info(上海)。因为这两个任务没有依赖关系。在得到航班和城市信息后它发现查询酒店需要入住和离店日期。它可以从用户“住两晚”和航班日期中推断出日期然后调用search_hotels。最后它汇总所有工具返回的结构化数据生成一段连贯、友好的自然语言回复给你“为您找到以下航班... 上海的天气晴朗25℃推荐景点有... 根据您的行程推荐以下酒店...”这个简单的 Agent 已经具备了处理多步骤、有条件任务的能力。通过扩展工具集如租车、景点门票、餐厅预订它可以变得更强大。6. 避坑指南与性能优化在实际生产环境中应用 Function Calling会遇到许多在教程中不会提及的“坑”。以下是我从多个项目中总结出的关键经验。6.1 描述Description撰写的艺术函数的description是模型理解工具用途的唯一途径。写得不好模型就会用错。避免歧义“处理数据”是糟糕的描述。“根据用户ID从数据库查询其最近3个月的订单记录”是好的描述。明确输入输出在函数描述中可以简要说明输入是什么输出大概是什么。例如“输入城市名返回该城市当前温度、天气状况和湿度百分比。”参数描述要具体location的描述如果是“地点”模型可能填入“我家门口”。改成“城市或机场的IATA代码如‘北京’或‘PEK’”效果会好得多。利用枚举enum和默认值对于有限的选项如单位{“celsius“, “fahrenheit“}或分类{“economy“, “business“}使用enum可以极大提高准确率。对于可选参数设置合理的default值。6.2 处理模型的不确定性即使描述再完美模型也可能出错。设置最大重试次数当模型返回的参数无法通过验证或调用了错误的函数时不要直接报错给用户。可以设计一个重试循环将验证错误信息作为系统提示重新向模型提问例如“上次调用失败原因是参数XX格式错误。请根据以下正确格式重新生成调用。”通常重试1-2次就能成功。提供少量示例Few-shot对于极其复杂或容易出错的函数可以在messages的系统提示或历史中提供一两个正确调用该函数的示例对话。这能显著提升模型在复杂场景下的表现。后处理与修正对于模型提取的参数在执行前进行后处理。例如将“明天”转换为具体的日期将“北上广”拆分为三个城市等。6.3 成本与延迟优化Function Calling 会增加 API 调用次数一次用户查询可能触发多轮模型调用进而增加成本和延迟。批量处理鼓励用户一次性提出完整需求利用模型的并行调用能力减少交互轮次。缓存工具结果对于相同参数的工具调用如短时间内多次查询同一城市天气在客户端或服务端实现缓存避免重复调用外部 API。精简上下文虽然需要维护完整对话历史但对于非常长的对话可以考虑只保留最近N轮或总结之前的对话内容以减少发送给模型的 token 数量降低成本。模型选型对于工具调用本身即解析意图、生成调用参数gpt-3.5-turbo在大多数场景下已经足够准确且成本更低。对于需要高度推理或总结复杂工具结果的最终回复可以酌情使用gpt-4。6.4 安全与权限考量让模型决定调用哪个函数存在潜在风险。最小权限原则只向模型暴露完成当前任务所必需的最少工具。例如一个订餐助手不需要拥有“删除用户账户”的工具。参数校验与净化在执行任何工具前必须对模型提供的参数进行严格的校验和净化防止 SQL 注入、命令注入等攻击。永远不要相信模型的直接输入。用户确认对于具有重大影响或不可逆的操作如发送邮件、支付、删除数据即使在模型调用后也应在真正执行前增加一层用户确认例如“我将为您发送这封邮件确认吗”。Function Calling 不仅仅是一个 API 特性它代表了一种全新的、让 AI 融入现实工作流的范式。从简单的数据查询到复杂的多步骤自动化它的边界只取决于你的工具库和想象力。现在是时候为你的大模型装上“手和脚”让它真正开始为你“干活”了。
返回列表