尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

Gemini函数调用实战:从原理到复杂工作流构建指南

Gemini函数调用实战:从原理到复杂工作流构建指南 1. 从“对话”到“执行”为什么函数调用是AI应用的分水岭如果你最近在折腾大模型应用开发尤其是围绕Google的Gemini系列那么“函数调用”这个词一定高频出现在你的视野里。它不再是那个藏在API文档深处、只有资深开发者才会去碰的高级特性而是变成了连接大模型“思考能力”与外部世界“执行能力”的核心桥梁。简单来说以前的大模型就像一个知识渊博但“手无缚鸡之力”的顾问它能告诉你“理论上”应该怎么做比如“要查询天气你需要调用某个天气API传入城市参数”。而现在有了函数调用这个顾问不仅能告诉你步骤还能亲手通过代码帮你把这件事给办了。Gemini 1.5 Pro的发布让长上下文和强大的推理能力成为焦点而Gemini 3.5系列包括Pro和Flash则在函数调用的支持上变得更加成熟和稳定。我经历过从早期摸索OpenAI的Function Calling到在Gemini上实践整个工作流的全过程发现很多教程只讲了“怎么用”但没讲清楚“为什么这么用”以及“什么时候会翻车”。这篇指南就想解决这个问题我们不只聊如何写一个简单的函数调用更要深入到如何设计复杂的、嵌套的、有状态的工作流这些都是把Demo变成真正可用产品的关键。你会发现掌握函数调用意味着你的应用可以从简单的聊天机器人进化成能自动处理订票、分析数据、管理工单的智能助手。这其中的转变就在于模型能否理解你的意图并精准地调度正确的工具函数去完成任务。接下来我们就从最基础的原理开始一步步拆解Gemini 3.5的函数调用直到构建出稳健的复杂工作流。2. 函数调用的核心机制模型如何“决定”与“返回”在开始写代码之前我们必须先理解背后的运行机制。这能帮你从根本上调试那些“模型为什么不调用我的函数”或者“为什么参数老是传错”的问题。Gemini的函数调用流程可以粗略分为两个阶段模型决策阶段和开发者执行阶段。2.1 模型决策阶段从自然语言到结构化意图当你把用户的问题比如“上海明天天气怎么样”和一组你定义好的函数描述比如get_weather(location: string, unit: c | f)一起交给Gemini时模型内部在进行一场复杂的“匹配”与“推理”。首先模型并不是在执行你的函数代码。它是在阅读理解。它会分析用户的输入并和你提供的函数描述进行比对。函数描述通常包括函数名、功能描述和参数列表包括参数名、类型、描述。模型的工作是判断“用户当前的这个请求是否需要调用函数来完成如果需要调用哪一个函数最合适这个函数需要的各个参数分别应该从用户的话里提取出什么值”这个过程的关键输出是一个或多个函数调用请求。在Gemini API的返回中这体现在functionCall对象里。这个对象会包含name它决定调用的函数名和args一个键值对包含它认为应该传入的参数值。例如对于“上海明天天气”它可能返回{“name”: “get_weather”, “args”: {“location”: “上海”, “date”: “2024-05-20”}}。这里有一个至关重要的细节模型返回的只是“调用建议”而不是调用结果。它说“我认为你应该调用get_weather函数并传入这些参数”但它自己并不会、也不能去执行这个函数。执行权完全在你手里。2.2 开发者执行阶段安全与逻辑的守门员拿到模型的functionCall建议后你的应用程序代码需要接管。这一步是保障安全性和业务逻辑正确性的关键。验证与映射你需要检查模型返回的函数名name是否确实存在于你提供的函数列表中。这是一个基本的安全校验防止模型“幻觉”出一个不存在的函数。然后根据name找到你本地实际定义的函数比如一个Python函数def get_weather(location: str): ...。参数处理与补全模型返回的args可能不完整。比如用户说“明天天气”模型可能只返回了{“date”: “2024-05-20”}缺失了location。这时你的代码需要决定是直接报错还是通过一个追问流程让模型生成追问用户的问题来补全或者如果你能从对话上下文中推断出位置例如用户之前说过自己在上海也可以手动补全这个参数。执行函数用处理好的参数调用你本地的函数。这个函数可以做任何事查询数据库、调用第三方API、执行计算、操作文件系统等等。生成执行结果函数执行后会返回一个结果比如{“temperature”: 22, “condition”: “晴”}。你需要将这个结果以特定的格式同样是结构化的交还给Gemini模型。模型整合与回复你把函数执行的结果作为新一轮对话的上下文的一部分再次发送给Gemini。模型会看到它之前“建议”调用的函数已经执行并得到了结果{“temperature”: 22, “condition”: “晴”}。然后它会基于这个结果生成一段面向用户的、自然的回复比如“上海明天天气晴朗气温大约22摄氏度。”这个“模型建议 - 开发者执行 - 结果反馈给模型 - 模型生成最终回复”的闭环就是一次完整的函数调用交互。理解这个闭环是设计任何复杂工作流的基础。3. 从零开始你的第一个Gemini函数调用理论说得再多不如动手跑通一遍。我们用一个最简单的例子查询城市信息。假设我们有一个本地的“城市数据库”或者一个模拟的函数。首先确保你已安装Google的AI Python SDKpip install google-generativeai并设置好API密钥。import google.generativeai as genai # 配置你的API密钥 genai.configure(api_keyYOUR_API_KEY) # 1. 定义工具函数 tools [ { “function_declarations”: [{ “name”: “get_city_info”, “description”: “根据城市名称获取该城市的基本信息如国家、人口、特色等。”, “parameters”: { “type”: “object”, “properties”: { “city_name”: { “type”: “string”, “description”: “城市的名称必须是中文或英文全称。” } }, “required”: [“city_name”] } }] } ] # 2. 模拟的本地函数实现 def get_city_info(city_name: str) - str: # 这里应该是查询数据库或调用API我们模拟数据 city_data { “北京”: “中国的首都政治文化中心人口超2000万。”, “巴黎”: “法国的首都被誉为‘光之城’以艺术和时尚闻名。”, “东京”: “日本的首都全球最大的都市圈之一人口密集科技发达。” } return city_data.get(city_name, f“未找到城市 {city_name} 的信息。”) # 3. 初始化模型并指定使用工具 model genai.GenerativeModel(gemini-1.5-pro-latest, toolstools) # 4. 启动对话 chat model.start_chat() # 5. 用户输入 user_input “告诉我巴黎的基本情况。” response chat.send_message(user_input) # 6. 检查响应中是否有函数调用请求 if response.candidates[0].content.parts[0].function_call: fc response.candidates[0].content.parts[0].function_call print(f“模型建议调用函数: {fc.name}”) print(f“模型建议的参数: {fc.args}”) # 7. 执行函数 if fc.name “get_city_info”: city_name fc.args.get(“city_name”) if city_name: function_response get_city_info(city_name) print(f“函数执行结果: {function_response}”) # 8. 将结果以工具函数响应的形式发送回模型 tool_response_part genai.protos.Part( function_responsegenai.protos.FunctionResponse( namefc.name, response{“content”: function_response} # 注意结构 ) ) # 9. 将工具响应发送给模型获取最终的自然语言回复 final_response chat.send_message(tool_response_part) print(f“AI的最终回复: {final_response.text}”) else: print(“错误未从参数中获取到 city_name”) else: print(f“错误收到未知函数调用请求 {fc.name}”) else: # 如果没有函数调用直接输出回复 print(f“AI回复: {response.text}”)这段代码虽然简单但包含了所有核心环节。有几个关键点需要注意function_declarations的结构这是Gemini SDK要求的格式。description非常重要模型主要靠它来理解函数用途。参数的description也要清晰它指导模型如何从用户话语中提取值。响应检查response.candidates[0].content.parts[0]这个路径是获取内容部分的通用方法需要检查其中是否存在function_call。结果格式将函数结果返回给模型时需要包装成genai.protos.Part对象其中包含一个FunctionResponse。response字段是一个字典我们这里简单用了{“content”: ...}你也可以返回更结构化的数据。错误处理实际应用中必须对模型可能返回的异常参数、函数执行失败等情况做处理。跑通这个例子你就完成了函数调用的“Hello World”。但现实中的应用远比这复杂我们接下来就要面对这些复杂性。4. 进阶实战处理多函数、参数缺失与并行调用单一函数调用太理想化了。真实场景中用户的需求可能涉及多个步骤或者模型需要在多个函数间做出选择。4.1 多函数选择与冲突解决假设我们提供了两个函数search_flights查询航班和search_hotels查询酒店。用户说“我想去三亚旅行帮我看看。” 模型如何选择这时函数描述的清晰度就至关重要。如果两个函数的描述都很泛泛如“查询旅行信息”模型可能会困惑甚至随机选择一个。最佳实践是精确描述search_flights的描述强调“查询两点间的航班时刻与价格”search_hotels的描述强调“查询某地酒店的房源与价格”。利用系统指令你可以在初始化模型时通过system_instruction参数给出更高层的指导例如“你是一个旅行助手。当用户表达旅行意向时优先询问其具体需求如时间、预算以明确是查航班还是酒店除非用户明确指出来。”如果模型仍然调用了“错误”的函数比如用户要酒店却调了航班查询你的代码在收到function_call后可以不立即执行而是设计一个逻辑判断该调用是否符合当前对话上下文如果不符合可以手动构造一个提示让模型重新思考。例如将“用户要酒店但你建议查航班请重新考虑。”作为用户消息再次发送。4.2 参数缺失与主动追问模型并不总能提取出所有参数。对于可选参数这没问题。但对于必需参数required缺失会导致函数无法执行。你有两种策略自动补全如果某些参数能从会话历史中推断例如用户之前说过“我住在上海”那么查询天气时location可以默认为“上海”你的代码可以在执行函数前自动补全。让模型生成追问这是更通用和交互式的做法。当检测到必需参数缺失时不要直接执行函数而是将当前的function_call包含缺失参数的args和对话历史再次发送给模型但这次不提供工具并附加一条指令如“请根据已提取的信息和缺失的参数向用户提出一个清晰的问题来补全信息。”实际上Gemini模型在参数缺失时有时会主动在response.text中生成追问文本而不发出function_call。所以更健壮的逻辑是优先检查是否有function_call如果有但参数缺失则走上述的追问流程如果没有function_call但回复看起来像是在追问比如包含“请问您想查询哪个城市”则直接将此回复呈现给用户。4.3 并行函数调用的可能性在某些场景下用户的一个请求可能隐含多个独立任务。例如“比较一下北京和上海下周的天气。” 理想情况下模型可以同时建议调用两次get_weather函数分别传入location北京和location上海。Gemini 3.5的API响应中一个response的parts里是可能包含多个function_call的。你的代码需要能遍历response.candidates[0].content.parts收集所有function_call对象。然后你可以使用多线程或异步编程如asyncio来并发执行这些函数调用显著提升响应速度。执行完毕后将所有结果收集起来再一次性反馈给模型让它生成综合性的比较回答。# 伪代码展示并行处理思路 import asyncio async def execute_function_call(fc): # 执行单个函数调用 ... async def handle_parallel_calls(response): function_calls [] for part in response.candidates[0].content.parts: if hasattr(part, ‘function_call’) and part.function_call: function_calls.append(part.function_call) # 并发执行所有函数调用 tasks [execute_function_call(fc) for fc in function_calls] results await asyncio.gather(*tasks) # 将所有结果组装成工具响应列表发送回模型 tool_response_parts [] for fc, result in zip(function_calls, results): tool_response_parts.append(genai.protos.Part( function_responsegenai.protos.FunctionResponse( namefc.name, response{“content”: result} ) )) # 发送 tool_response_parts 给模型处理好奇数调用、参数补全和并行调用你的函数调用应用就具备了处理大多数简单交互场景的能力。但这还不够因为真实世界的工作流往往是顺序的、有依赖的、甚至需要循环的。5. 构建复杂嵌套工作流状态机与循环调用当任务步骤超过一步且后一步依赖前一步的结果时就构成了一个工作流。例如“帮我订一张明天北京飞上海的最便宜机票并用我的公司邮箱发送行程单到我的邮箱。” 这至少涉及1) 查询航班2) 选择最便宜航班3) 模拟下单或调用下单API4) 生成行程单5) 发送邮件。5.1 工作流设计模式状态机思维你不能指望只调用一次Gemini把所有函数描述丢给它它就能自动串起这五步。模型单次调用的“规划”能力是有限的。你需要引入一个外部状态机来管理工作流进度。定义工作流状态为你的应用定义几个状态例如IDLE空闲、SEARCHING_FLIGHTS查询航班、CONFIRMING_SELECTION确认选择、BOOKING下单、SENDING_ITINERARY发送行程单。初始化从IDLE状态开始用户输入触发。单轮决策将当前状态、对话历史、以及仅与当前状态相关的函数描述发送给Gemini。例如在SEARCHING_FLIGHTS状态你只提供search_flights函数。模型根据当前对话用户说“订票”和状态很可能调用这个函数。执行与状态转移执行函数根据结果和业务逻辑决定下一个状态。例如search_flights返回了航班列表你的代码可以将这些列表存入上下文然后将状态转移到CONFIRMING_SELECTION。下一轮决策在新的状态下再次调用Gemini。此时你可以提供confirm_selection让用户选择航班和book_flight预订指定航班两个函数。模型结合历史已有航班列表和当前状态可能会先调用confirm_selection来让用户选择或者如果业务规则允许直接调用book_flight预订最便宜的那个。循环重复步骤3-5直到工作流完成到达终态如BOOKING_COMPLETE或失败。这种模式下Gemini模型扮演的是每一步的决策者而你的代码是工作流引擎和状态管理者。这大大降低了模型的认知负担也让整个流程更可控、更易调试。5.2 实现循环调用与上下文保持要实现上述状态机关键是如何在多次send_message调用中保持连贯的上下文。genai.ChatSession对象通过model.start_chat()创建会自动维护一个会话历史。你每次调用chat.send_message()无论是用户消息、函数调用响应还是系统指令都会被追加到这个历史中。因此在工作流循环中你只需要不断向同一个chat会话发送新的消息即可。消息内容可以是用户的新输入。你代码生成的状态提示例如以系统身份说“当前已找到航班列表等待用户选择。”。函数执行的响应结果。模型会看到完整的历史从而做出符合上下文的决策。这里的一个技巧是你可以通过system_instruction或在用户消息中巧妙插入提示来隐式地告诉模型当前处于什么阶段引导其调用正确的函数。5.3 错误处理与工作流回退复杂工作流中错误是常态。函数可能执行失败API超时、库存不足模型也可能做出不符合预期的调用决策。函数执行错误当你的本地函数抛出异常时不要直接崩溃。应该捕获异常并将错误信息例如“航班查询服务暂时不可用”作为function_response返回给模型。模型有能力理解错误并可能生成向用户道歉或建议重试的回复。同时你的状态机可以决定是停留在当前状态重试还是回退到上一个状态。模型决策错误如果模型在某个状态调用了“错误”的函数例如在预订状态却调用了搜索函数你的代码可以拦截这个调用。一种策略是不执行该函数而是构造一个系统消息如“当前阶段应处理预订而非重新搜索。”发送给模型要求其重新考虑。这相当于一次轻量的“回滚”和“重试”。通过将状态机、循环调用和健壮的错误处理结合起来你就能构建出能够处理真实场景复杂需求的AI应用。这不再是玩具而是具备了初步生产力的工具。6. 避坑指南与性能优化来自实战的经验在多个项目中实践Gemini函数调用后我积累了一些文档里不会写的教训和技巧。6.1 函数描述是“咒语”需要精心设计模型的决策质量极度依赖函数描述。模糊的描述导致模糊的调用。坏例子description: “处理数据”。parameters:{“data”: {“type”: “string”}}好例子description: “对给定的JSON字符串进行格式化美化增加缩进和换行使其易于阅读。输入应为有效的JSON字符串。”parameters:{“json_string”: {“type”: “string”, “description”: “需要被格式化的、紧凑的JSON字符串。”}}为参数添加详细的description能极大提高模型提取参数的准确率。如果参数是枚举值使用enum字段明确列出所有选项。6.2 控制“幻觉”调用与过度调用有时模型会“过度积极”在不需要时也调用函数或者调用一个不存在的函数幻觉。设置tool_config在初始化模型时可以通过tool_config参数进行控制。例如genai.GenerationConfig(tool_configgenai.ToolConfig(function_calling_config“AUTO”))。“AUTO”是默认值模型自主决定是否调用。你可以设置为“ANY”来强制模型必须使用工具或“NONE”来禁止使用工具。在复杂工作流中根据状态动态切换这个配置很有用。提供“无操作”函数对于某些边界情况你可以定义一个“do_nothing”或“clarify_question”的函数当模型认为不需要执行具体操作但又被配置为必须调用函数时它可以调用这个安全函数你的代码收到后可以简单地继续对话。6.3 上下文长度与成本管理函数声明尤其是详细的描述和函数响应的内容都会消耗模型的上下文窗口。Gemini 1.5/3.5 Pro虽然有超长的上下文但无节制地增加工具数量也会影响性能和成本。按需加载工具这正是前面状态机模式的优势。不要在每次请求中都加载所有函数定义。根据应用状态和用户当前最可能的需求动态地向模型提供相关的2-3个函数而不是几十个。精简函数响应函数返回给模型的结果应简洁、结构化只包含必要信息。避免返回冗长的HTML或无关的日志信息。如果需要详细数据给用户看可以让模型在生成最终回复时基于简洁的结构化数据去“展开”描述。6.4 测试与评估函数调用的测试不能只靠人工聊天。单元测试函数本身确保你的本地函数在各种边界输入下都能正确工作。集成测试调用链路编写测试用例模拟用户输入验证模型是否能正确触发预期的函数并且参数提取准确。需要处理模型输出的非确定性可能需要对同一输入运行多次统计成功率。评估函数调用准确性这是关键指标。记录下模型“建议调用函数A但实际应调用函数B”或“参数提取错误”的情况反过来优化你的函数描述和系统指令。函数调用是让大模型从“聊天”走向“行动”的关键。从理解其基础机制开始逐步掌握多函数调度、参数处理和状态管理最终你就能设计出稳健的复杂AI工作流。这个过程需要不断的迭代和测试但一旦跑通你将解锁大模型应用的巨大潜力。记住模型是“大脑”你的代码是“四肢”和“调度中心”两者紧密配合才能创造出真正智能的体验。
返回列表