news 2026/9/6 11:13:41

基于Gemini Function Calling构建最小AI Agent实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
基于Gemini Function Calling构建最小AI Agent实战

谷歌AI 最近的产品节奏,可以读成一次典型的分权与收权动作:搜索、浏览器、办公套件、手机系统都还在,但原本分散在各自产品里的智能入口,正在逐步收拢到 Gemini 这个统一模型层。历史故事里的“杯酒释兵权”是皇帝用一场酒宴拿回各地兵权,谷歌AI 做的事情很像:不颠覆原有产品形态,却把真正的决策权交还给一个统一模型,让页面、按钮、接口都变成执行层。

对开发者来说,这不是一条新闻标题,而是 AI 应用架构的具体变化。以前做 AI 功能,核心工作是“调一个聊天接口”;现在做 AI 功能,核心工作是“设计一个有边界的 Agent”。模型需要被允许调用工具,工具需要被限制在业务规则内,调用过程还要能被日志追踪、被成本控制、被异常处理兜底。这篇文章把这条主线落到代码上,基于 Gemini 的 Function Calling 能力,从环境准备开始,实现一个最小可运行的 AI Agent,并说明参数、排查和上线前加固的关键点。

1. 从“杯酒释兵权”理解谷歌AI的技术主线

1.1 这不是产品改名,是控制权迁移

“杯酒释兵权”里的核心动作,不是把将领免职,而是把分散在地方将领手里的军事权力收回中央,再用一套新的制度重新分配。放在谷歌AI 语境里,模型层就是新的中央,旧产品就是原来的地方势力。

最典型的变化是:用户看到的是搜索框、浏览器工具栏、文档里的“帮我写”,但背后真正完成意图理解的,都是同一个模型入口。以前不同产品各自训练模型、各自维护提示词、各自处理上下文,后续演变成统一模型后,产品层的职责从“理解用户”变成了“承接用户输入并展示结果”。

对应用开发者来说,这种控制权迁移意味着:不要把模型当成一个黑盒接口,而要把它当成一个“会做决策的调度者”。真正决定业务正确性的,是你给这个调度者提供了哪些工具、设置了多少轮循环、在什么条件下允许它执行操作。

1.2 AI Agent 是新的“兵权单元”

传统应用里,用户的每次操作都对应一段确定逻辑:点按钮、查数据库、返回结果。AI Agent 不太一样,它先接收一个目标,再由模型判断该调用哪个工具、观察工具返回结果、决定是否继续下一步。

下面用表格看传统 AI 应用和 Agent 应用的区别:

维度传统 AI 应用Agent 应用
用户输入明确的指令或表单目标型描述,可能含糊
处理方式代码写死流程模型规划 + 工具调用
输出结果固定格式动态文本或真实操作
可控性需要边界和权限约束
排查方式看代码分支看模型决策和工具日志

这就是“兵权”的转移:模型拥有决策权,执行权放在工具函数里。工具函数是兵,模型是将军,开发者才是最终授权的人。你不把不安全的操作声明给模型,模型就永远无法调用它。

1.3 对开发者的三个直接要求

围绕这种变化,开发工作不再只是写提示词。至少有三个点必须重新设计。

第一,工具边界。每个工具函数都必须有明确用途、参数校验和权限校验。模型只会根据函数名和描述决定是否调用,它不会替你做安全判断。

第二,循环控制。Agent 会有多轮“模型决策 - 工具执行 - 结果回填”的循环。没有最大轮数、超时时间和终止条件,模型可能会反复调用工具,造成成本和资源浪费。

第三,可观测性。模型内部怎么思考无法完全控制,但“模型返回了什么 functionCall”“工具执行后返回了什么结果”这些信息是可记录的。把这条链路记录下来,线上出问题才能定位。

2. 准备 Gemini 开发环境:API Key、模型与连通性

2.1 环境要求清单

下面示例使用 Gemini API 的原生 HTTP 方式,先把原理跑通,再考虑 SDK 或 Spring AI 封装。这样能避免不同 SDK 版本差异影响对核心逻辑的理解。

项目要求
网络能访问generativelanguage.googleapis.com的 API 端点
API Key在 Google AI Studio 控制台创建
Python3.10 或更高版本
依赖requests
模型名以官方模型列表为准,例如gemini-2.0-flash

安装依赖:

pip install requests

如果是公司网络或本地网络对 API 端点有访问限制,要先解决网络可达性,再用下面的 curl 命令验证。网络不通时,后续所有请求都会卡在连接阶段,和代码无关。

2.2 用 curl 验证模型连通性

先设置环境变量,避免把密钥写死在命令记录里:

export GEMINI_API_KEY="你的 API Key"

然后发送一个最简单的对话请求:

curl -s -X POST \ "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent" \ -H "Content-Type: application/json" \ -H "x-goog-api-key: ${GEMINI_API_KEY}" \ -d '{ "contents": [ { "role": "user", "parts": [ {"text": "用一个短句解释 AI Agent"} ] } ] }'

正常响应会包含candidates数组,里面是模型生成的内容:

{ "candidates": [ { "content": { "role": "model", "parts": [ { "text": "AI Agent 是一个能自主规划、调用工具并根据结果继续执行的智能程序。" } ] }, "finishReason": "STOP" } ] }

这里的关键点是x-goog-api-key请求头。很多新手把它放在 URL 上也能工作,但统一用请求头传递,更利于后续接入网关和密钥管理。

2.3 先在 AI Studio 里验证功能和提示词

写代码之前,建议先在 AI Studio 的界面里试一遍提示词和工具声明。AI Studio 能直接看到模型在什么输入下会调用工具、什么输入下不会调用,这对于调整工具描述非常有帮助。

学习阶段的重点是快速跑通,不需要考虑高并发和高可用。但要注意,AI Studio 创建的 API Key 是开发阶段的密钥,进入生产环境前需要纳入正式的密钥管理流程,并定期轮换。

3. 用 Function Calling 实现一个最小 AI Agent

3.1 Agent 需要哪些组件

一个最小 Agent 不需要很复杂,但必须包含四部分:

  • 模型入口:负责接收对话上下文,返回文本或函数调用指令。
  • 工具声明:用 JSON Schema 描述函数名、参数和用途。
  • 工具执行器:在本地真实调用函数,并返回给模型。
  • 循环控制:在最大轮数内,重复“模型调用 - 工具执行 - 结果回填”。

下面用一个“服务状态查询和重启”的例子,演示完整流程。

3.2 定义工具声明

工具声明是一份给模型看的说明书。模型不会读取你的 Python 代码,它只根据这段 JSON 结构决定要不要调用函数。

tools = [ { "functionDeclarations": [ { "name": "get_service_status", "description": "查询指定服务的当前状态,返回 running、degraded 或 stopped。", "parameters": { "type": "object", "properties": { "service_name": { "type": "string", "description": "服务名,例如 user-service、payment-service" } }, "required": ["service_name"] } }, { "name": "restart_service", "description": "重启指定服务。仅当服务状态为 degraded 或 stopped 时使用。", "parameters": { "type": "object", "properties": { "service_name": { "type": "string", "description": "要重启的服务名" } }, "required": ["service_name"] } } ] } ]

这里要注意两点。

第一,description要写清楚“什么条件下该调用”。第二个工具的描述里明确写了仅在 degraded 或 stopped 时使用,这能减少模型乱调用。

第二,参数尽量用required约束。缺参会让模型在后续生成里强行猜测,增加错误结果概率。

3.3 实现 Agent 循环

完整脚本如下:

import requests GEMINI_API_KEY = "YOUR_API_KEY" MODEL_NAME = "gemini-2.0-flash" GEMINI_URL = f"https://generativelanguage.googleapis.com/v1beta/models/{MODEL_NAME}:generateContent" MAX_AGENT_ROUNDS = 3 tools = [ { "functionDeclarations": [ { "name": "get_service_status", "description": "查询指定服务的当前状态,返回 running、degraded 或 stopped。", "parameters": { "type": "object", "properties": { "service_name": { "type": "string", "description": "服务名,例如 user-service、payment-service" } }, "required": ["service_name"] } }, { "name": "restart_service", "description": "重启指定服务。仅当服务状态为 degraded 或 stopped 时使用。", "parameters": { "type": "object", "properties": { "service_name": { "type": "string", "description": "要重启的服务名" } }, "required": ["service_name"] } } ] } ] def call_model(contents): response = requests.post( GEMINI_URL, headers={"Content-Type": "application/json", "x-goog-api-key": GEMINI_API_KEY}, json={"contents": contents, "tools": tools}, timeout=60 ) response.raise_for_status() return response.json() def get_service_status(service_name): fake_status = { "user-service": "degraded", "payment-service": "running", "ai-service": "stopped" } return {"result": fake_status.get(service_name, "unknown")} def restart_service(service_name): return {"result": f"{service_name} restart request accepted"} def execute_tool(name, args): if name == "get_service_status": return get_service_status(args["service_name"]) if name == "restart_service": return restart_service(args["service_name"]) return {"error": f"unknown tool: {name}"} def run_agent(user_text): contents = [{"role": "user", "parts": [{"text": user_text}]}] for _ in range(MAX_AGENT_ROUNDS): data = call_model(contents) candidate = data["candidates"][0] model_content = candidate["content"] parts = model_content.get("parts", []) function_calls = [part for part in parts if "functionCall" in part] if not function_calls: return "".join(part.get("text", "") for part in parts) # 把模型的 functionCall 放回会话上下文 contents.append(model_content) # 执行模型请求的每个函数 for fc in function_calls: fname = fc["functionCall"]["name"] fargs = fc["functionCall"].get("args", {}) result = execute_tool(fname, fargs) contents.append({ "role": "function", "parts": [ {"functionResponse": {"name": fname, "response": result}} ] }) raise RuntimeError(f"Agent 在 {MAX_AGENT_ROUNDS} 轮内没有结束") if __name__ == "__main__": query = "user-service 现在是什么状态?如果它 degraded,就重启它。" print(run_agent(query))

这个脚本的核心是run_agent里的循环。模型第一次返回的通常是一个functionCall,脚本拿到函数名和参数后执行本地函数,再把functionResponse追加回contents。下一次请求模型时,模型能看到工具执行结果,从而决定是继续调用工具,还是输出最终文本。

生产环境要注意:工具函数里的模拟数据必须替换成真实服务,并且在执行操作前加上权限校验。示例里的restart_service是危险操作,真实项目绝不能无条件执行。

3.4 运行与预期输出

保存为gemini_agent.py后执行:

python gemini_agent.py

正常输出类似:

user-service 当前状态为 degraded,已发送重启请求。

如果模型返回的不是一句话,而是 JSON 里的functionCall,也不要紧张。这是正常流程。模型在一次请求中说“我需要调用某个函数”,应用层执行完后,再把这个结果交还给模型,模型才能给出最终回复。

验证时建议打开调试:在call_model返回后加一行print(data)。这样能清楚看到模型是先返回函数调用,还是直接返回文本。很多 Agent 问题都出在这一步:你以为模型没调用工具,其实工具结果没有正确回填。

4. 关键参数与工具配置详解

4.1 generationConfig 中的关键参数

Gemini API 请求体里可以携带generationConfig,用来控制模型输出的确定性、长度等表现。

参数作用建议
temperature控制随机性,值越高输出越发散工具调用场景建议设置为 0 到 0.3
topP核采样,控制候选词累计概率与 temperature 不要同时大力调整
maxOutputTokens限制单次输出最大 token 数Agent 多步推理可用 1024,复杂场景调大
stopSequences遇到指定字符串停止生成用于解析结构化输出时很有用

工具调用场景中,随机性太强会导致模型频繁选择错误工具。只要能稳定完成任务,temperature越低越好。

一个带参数的请求体示例:

{ "contents": [], "tools": [], "generationConfig": { "temperature": 0.2, "topP": 0.8, "maxOutputTokens": 1024 }, "toolConfig": { "functionCallingConfig": { "mode": "AUTO" } } }

4.2 toolConfig 控制模型调用工具的力度

toolConfig.functionCallingConfig.mode控制模型是否必须调用工具,常用三种模式:

模式行为适用场景
AUTO模型自主决定调用哪个工具或不调用默认场景,推荐
ANY模型必须调用其中一个工具跳过思考,直接执行工具时使用
NONE禁止调用任何工具只做普通文本生成时使用

在排查“模型不调用工具”的问题时,可以把AUTO临时改成ANY,确认工具链路本身没有问题。但最终生产环境还是推荐AUTO,给模型判断余地。

4.3 工具声明越细,Agent 越稳定

工具声明本质上是给模型看的接口文档。描述含糊,模型就会猜。

对比一下:

错误描述:查询服务状态。 正确描述:查询指定服务的当前状态,返回 running、degraded 或 stopped。查询前不要修改任何数据。

正确描述里既包含返回值格式,也包含行为边界。参数也尽量用具体枚举或正则提示,例如“只允许字母、数字、中划线”。这样能显著减少模型生成非法参数的概率。

尽量不要把多个操作塞进一个函数。比如“查询并重启服务”看起来省事,但会让模型丧失区分场景的能力。每个工具只做一件事,描述和参数都会更清晰。

5. 常见问题排查:从现象倒推根因

5.1 API Key 与网络问题

如果请求返回 401 或连接超时,先不要调试模型逻辑,按顺序检查三个点:网络能否到达 API 端点、API Key 是否正确、请求头是否携带了x-goog-api-key

问题现象可能原因检查方式处理建议
401 UNAUTHENTICATEDAPI Key 无效或未传递打印请求头,确认 key 值在 AI Studio 重新创建 Key
连接超时网络无法访问 API 端点curl -v观察连接阶段先解决网络可达性
429 RESOURCE_EXHAUSTED免费额度用完或触发限流查看用量和错误体 timeout等待后重试,启用计费,增加退避

5.2 Function Calling 返回异常

如果模型返回了functionCall,但后续请求报400 INVALID_ARGUMENT,绝大多数情况是contents顺序或 role 不对。

正确的结构必须是:

user -> model(带 functionCall) -> function(带 functionResponse) -> model(最终文本)

model的 functionCall 和function的 functionResponse 必须成对出现。打印完整contents是最快的定位方式:

import json print(json.dumps(contents, ensure_
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/2 9:19:29

STM32 USB主机读取U盘文件:从原理到工程实践

2018年5月那次内部培训,我定的主题就是:让STM32当USB主机,插上U盘,直接把文件系统里的文件读出来。当时团队要做固件升级,客户不想每次都用串口线连电脑,理想状态是把升级文件丢进U盘,设备上电自…

作者头像 李华
网站建设 2026/9/1 7:19:32

单片机毕设选题推荐:基于 STM32 单片机的光照辅助照明与安全预警设备开发 基于 STM32 的人体跌倒、障碍物、积水一体化监测装置设计(013505)

博主介绍:✌️码农一枚 ,专注于大学生项目实战开发、讲解和毕业🚢文撰写修改等。全栈领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于嵌入式单片机,Java、小程序技术领域和毕业项目实战 ✌️…

作者头像 李华
网站建设 2026/8/31 19:42:35

牛客三模编程题全解析:从字符串处理到动态规划的笔试冲刺指南

1. 内容整体设计与思路拆解1.1 模考和平时刷题到底差在哪先聊一个很多人容易忽略的问题:刷题和模考,训练的是完全不同的两套能力。平时在牛客上按标签刷题,你可以随时停下来翻题解,可以挑选自己熟悉的知识点先做,甚至可…

作者头像 李华
网站建设 2026/8/31 19:01:23

Python二手车价格预测实战:数据清洗到模型部署全流程

简介:二手车价格预测是典型回归建模任务,其核心在于将非结构化业务数据(如混杂单位的里程、模糊日期、文本型价格)转化为高质量特征。原理上需兼顾数据鲁棒性(处理面议约12万等噪声)、特征可解释性&#xf…

作者头像 李华
网站建设 2026/8/31 15:29:07

OpenAI最大预训练模型Doug曝光,背后工程变化与开发者实践

Doug曝光背后:OpenAI预训练模型的工程路径正在发生什么变化如果你最近在关注大模型圈子,大概率会看到一个消息:OpenAI 的“最大预训练模型”Doug 被曝光了。先给一个明确判断:Doug 这个消息真正值得在意的,不是“OpenA…

作者头像 李华
网站建设 2026/8/31 22:51:25

2026拼多多投产比多少算正常?类目阈值+计算公式

从事拼多多运营多年,相信大家都有同一个困惑:店铺推广ROI到底做到多少才算正常?多少能保本?多少才算盈利? 很多新手商家盲目开直通车、全站推广,只看流量不看投产,看似日单几百,月底…

作者头像 李华