news 2026/10/26 13:29:37

Gemini Python开发实战:从API接入到生产级容错与函数调用

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Gemini Python开发实战:从API接入到生产级容错与函数调用

这个系列写到第四篇,我决定换个聊法。前三篇我们一直在解决怎么把 Google Gemini(也就是以前叫 Bard 的那位)接入 Python:API Key 拿到了,SDK 装好了,也能跑通几次简单的问答。这些属于编程指南的基础盘,但光跑通不算完。这篇我不打算再堆基础概念,而是把重点放在“从一个能跑的 Demo,变成能干活的功能”——顺便把我在真实项目里被坑过的几个地方,摊开来说清楚。

我会默认你已经有一个可用的 Gemini API Key,Python 环境是 3.9 以上。如果这些还没准备好,建议先回去翻一下本系列前三篇,或者直接pip install google-generativeai装好再回来。这篇代码我会尽量给完整,复制到自己的工程里改改就能用。

1. 环境与鉴权:版本、Key 管理和模型名,三个常见翻车点

1.1 旧 SDK 与新 SDK:先别急着二选一

现在搜 Gemini Python 教程,你会看到两种主要写法。一种是老牌的google-generativeai,也就是常见的import google.generativeai as genai;另一种是 Google 后来推的google-genai,包名是from google import genai,API 风格更贴近新模型的 Function Calling 能力。

我个人的建议很直接:如果只是做文本问答、多模态识别、简单对话,google-generativeai的生态最成熟,网上排错资料也多,优先用它。如果你的项目要从第一天就重度依赖函数调用、异步任务、流式输出,那直接上google-genai,省得后面迁来迁去。

安装没什么好说的:

pip install google-generativeai # 或者 pip install google-genai

但注意一件事:SDK 的版本更新频率很高,新版本偶尔会改接口行为。我吃过一次亏,老代码跑得好好的,升级 SDK 后有天突然报AttributeError: 'GenerateContentResponse' object has no attribute 'text',查了半天发现是新版本把某些字段挪到了result.candidates[0].content.parts下面。所以项目里建议锁版本:

pip install "google-generativeai>=0.8,<1.0"

锁版本不是不求上进,而是给生产环境的稳定性留一条活路。

1.2 API Key 管理:别把密钥写进代码再传 GitHub

这是新手最容易踩的坑,也是我强调最多次的:API Key 相当于你账号的钱包,写死在代码里,等于把密码明文放在家里大门上。正确做法是放环境变量。

建议用python-dotenv,在项目根目录放一个.env文件:

GEMINI_API_KEY=你的key

然后代码里这样加载:

import os from dotenv import load_dotenv import google.generativeai as genai load_dotenv() genai.configure(api_key=os.getenv("GEMINI_API_KEY"))

顺手在.gitignore里把.env加进去,不然哪天提交代码时手滑,Key 就跟着出去了。我在 GitHub 上见过不止一个公开仓库里带着真实 Key,几分钟内就被人拿去做识别、跑量,收到账单才知道出事了。

Key 获取渠道就是 Google AI Studio 的 Developers API 页面,创建 API Key 后复制保存,没有别的花样。生成之后建议把它当密码对待,不分享、不截图发群、不提交到仓库。

1.3 模型名:一字之差就是 404

有个细节特别容易翻车:模型名写错。Gemini 的模型名类似gemini-1.5-flash、gemini-1.5-pro、gemini-2.0-flash,这个字符串必须严格匹配。写错之后,请求直接抛 404 或者 400,报错信息里会有一句类似models/gemini-11.5-flash not found。

有次我同事把gemini-1.5-flash写成了gemini-1.5-flash-001,在他机器上能跑,我的环境报错,折腾半天才意识到是模型版本后缀在不同区域不通用。最稳妥的办法是不猜,直接调模型列表接口查:

import google.generativeai as genai for m in genai.list_models(): if "gemini" in m.name.lower(): print(m.name)

把支持的模型名打印出来再选,避免手滑。这个习惯我一直保留到现在。

2. 文本与多模态的最短可用路径:generate_content 全解

2.1 一段能跑的最小代码骨架

Gemini 的调用入口不像某些大模型 SDK 那样绕,最核心的函数就是generate_content。给你看一段我能背下来的最小代码:

import google.generativeai as genai genai.configure(api_key=os.getenv("GEMINI_API_KEY")) model = genai.GenerativeModel("gemini-1.5-flash") response = model.generate_content("用一句话解释什么是量子纠缠") print(response.text)

运行之后,response.text就是你想要的模型输出。流程不复杂:配置 Key、创建模型实例、传内容、取.text。但如果哪天response.text取不到,别慌,先看下是不是被安全策略拦截了,后面第 5 章会细说。

这里有个经验:拿到 response 之后,不要只取.text,调试阶段把整个 response 对象打出来看看。它里面有完整的分段信息、候选结果、prompt 反馈,能帮你定位很多奇怪问题。

2.2 三个重要的生成参数:理解清楚再调

generate_content支持一个generation_config参数,新手最容易把里面的配置项当成“调得越大越好”。其实每个参数管的事完全不一样:

参数作用建议初始值
temperature控制随机性,值越大输出越发散0.7
top_p核采样概率阈值,相当于从概率排名靠前的词里采样0.9
max_output_tokens生成内容的最大长度上限按场景设
stop_sequences遇到这些字符串就停止生成看需求

我常用一个类比解释 temperature:它像做菜时的“自由发挥程度”。temperature 低,厨师严格按菜谱来;temperature 高,厨师开始随便放调料。写代码注释、提炼摘要,建议 0.3 到 0.5;头脑风暴、起标题,可以开到 0.8 到 1.0。

示例:

model = genai.GenerativeModel( "gemini-1.5-flash", generation_config={ "temperature": 0.4, "max_output_tokens": 2048, "top_p": 0.9, }, )

注意一点:max_output_tokens限制的是输出 token 数,不是输入。把长文章一次性丢进去,输出太短导致截断,这就是为什么有时候回答“戛然而止”。合理设置上限,给模型留出完整作答的空间。

2.3 图片输入:PIL 路径和 base64 路径

Gemini 天生支持多模态,传图比想象中简单。最常见的做法是用 Pillow 打开图片,然后作为列表的一起传:

from PIL import Image import google.generativeai as genai model = genai.GenerativeModel("gemini-1.5-flash") image = Image.open("receipt.jpg") response = model.generate_content( ["帮我提取这张图片里的所有文字,按行输出", image] ) print(response.text)

这里有个小坑:列表的第一项是文本指令,第二项是图片对象,顺序不要搞反。有些教程写成[image, text],模型一样能理解,但结果不稳定,我习惯遵守“指令在前、材料在后”的规则。

另一种场景是抓包或者爬虫拿到的是二进制图片流,没有落盘。这种情况不要先保存临时文件再读,直接用 base64 传输:

import base64 import google.generativeai as genai with open("receipt.jpg", "rb") as f: image_data = base64.b64encode(f.read()).decode("utf-8") image_part = { "mime_type": "image/jpeg", "data": image_data, } model = genai.GenerativeModel("gemini-1.5-flash") response = model.generate_content(["识别这个图片中的关键信息", image_part]) print(response.text)

mime_type 要根据实际图片格式填,image/png、image/jpeg、image/webp都支持。填错了识别接口会直接报错,这是典型的“接口文档不仔细看就白费时间”的问题。

3. 从单轮问答到真实功能:流式、记忆和函数调用

3.1 stream=True:打字机效果的性价比

如果你做过聊天机器人,应该清楚“等完整答案再返回”有多痛苦。长文本生成可能要 10 秒、20 秒,用户盯着空白页面以为是卡死了。Gemini 的流式输出用起来很简单:

model = genai.GenerativeModel("gemini-1.5-flash") response = model.generate_content( "写一篇800字的科普短文,主题是黑洞", stream=True, ) full_text = "" for chunk in response: piece = chunk.text print(piece, end="", flush=True) full_text += piece

注意一个容易踩坑的点:流式响应用完之后,response.text不一定能拿到完整内容。我见过不少人在循环结束后尝试response.text,结果只拿到第一段或者直接报错。正确做法是像上面一样,自己在循环里拼full_text。

流式的另一个好处是首字延迟低。我实际测下来,普通非流式请求可能要等 5 秒才看到第一个字,流式模式差不多 1 秒内就有内容出来。对用户感知来说,这种差距体验区别非常大。

3.2 多轮对话:start_chat 与手动 contents

做聊天功能,免不了多轮上下文。最简单的是用start_chat:

model = genai.GenerativeModel("gemini-1.5-flash") chat = model.start_chat(history=[]) resp1 = chat.send_message("你好,我接下来会连续提问,请尽量简短回答") resp2 = chat.send_message("Python 的 GIL 是什么?") print(resp2.text) # 查看完整历史 for msg in chat.history: print(msg.role, msg.parts[0].text)

chat.history会保留所有消息,这是方便也是隐患。对话超过一定轮数,历史全塞进请求里,token 消耗会指数级上涨。我之前做过一个客服机器人,用户聊到 50 轮还不停,每轮的请求量已经大到离谱,成本直接失控。

更可控的方式是手动组织 contents:

contents = [ {"role": "user", "parts": ["你好"]}, {"role": "model", "parts": ["你好,有什么可以帮你?"]}, {"role": "user", "parts": ["今天北京天气怎么样?"]}, ] model = genai.GenerativeModel("gemini-1.5-flash") response = model.generate_content(contents) print(response.text)

手动模式的好处是,你可以在喂给模型之前,对历史做截断、摘要、去重。比如只保留最近 10 轮,或者把前 20 轮压缩成一段话再接上来。这是做真实产品必须掌握的技能。

3.3 Function Calling:让模型决定调用哪个函数

这是 Gemini 最值得花时间研究的特性。简单说,你给模型声明几个函数,模型在理解用户意图后,返回“该调用哪个函数、参数是什么”,而不是直接生成文本。真正执行函数的是你的本地代码。

流程一共三步:声明函数给模型、模型返回 function_call、你执行后把结果回填给模型生成最终回答。

以新 SDKgoogle-genai为例,代码结构更清晰:

from google import genai from google.genai import types import os client = genai.Client(api_key=os.getenv("GEMINI_API_KEY")) def get_weather(city: str) -> str: # 这里替换成真实天气接口 return f"{city}:晴,22℃" weather_tool = types.Tool(function_declarations=[ types.FunctionDeclaration( name="get_weather", description="查询指定城市的当前天气", parameters={ "type": "OBJECT", "properties": { "city": { "type": "STRING", "description": "城市名称,例如 北京", }, }, "required": ["city"], }, ) ]) config = types.GenerateContentConfig(tools=[weather_tool]) response = client.models.generate_content( model="gemini-2.0-flash", contents="北京今天穿什么衣服合适?", config=config, ) if response.function_calls: fc = response.function_calls[0] if fc.name == "get_weather": result = get_weather(city=fc.args["city"]) print("模型想调用 get_weather,参数是:", fc.args) print("本地函数返回:", result)

如果只是打印结果,那还没形成完整闭环。真正要做的,是把函数返回结果回填给模型,让模型结合结果生成自然语言回答。不同 SDK 版本的回填方式略有区别,但核心套路是一样的:把 function_call 那一段原样放回消息历史,再追加一个 role 为 tool 的结果,最后再让模型生成。

Function Calling 最大的价值在于把“大模型的意图理解”和“你系统的确定性逻辑”结合起来。用户输入再随意,最终触发的是你代码里已经写好的函数,而不是让模型直接生成一句不可控的 SQL 或者按钮点击动作。这也是第 4 章两个案例的共同底座。

4. 两个落地案例:爬虫文本清洗与策略参数解析

4.1 案例一:爬虫商品描述清洗为结构化 JSON

很多做电商数据采集的朋友都遇到过这个问题:requests 抓回页面,BeautifulSoup 去掉标签之后,剩下的文本里全是广告词、导航文字、乱码和无意义的重复信息。传统正则写到怀疑人生,换了个商品页面就失效。

我的做法是拿 Gemini 做语义清洗。把抓到的文本按 3000 字符左右切成片段,每个片段要求模型提取结构化信息并输出 JSON:

import json import google.generativeai as genai model = genai.GenerativeModel("gemini-1.5-flash") raw_text = "这里放爬虫抓取并清洗后的原始文本,可能包含大量无用内容" prompt = f""" 从下面的商品描述文本中提取结构化信息,只输出 JSON,不要输出其他内容。 格式要求: {{"标题": "...", "品牌": "...", "型号": "...", "价格": "...", "保修期": "...", "有效描述": "..."}} 某个字段如果不存在,用 null 代替。 原始文本: {raw_text[:3000]} """ resp = model.generate_content(prompt) print(resp.text)

这里的经验主要有三个。

第一,文本要切片。超长文本直接丢进去,不仅 token 开销大,模型中途还容易“走神”,输出结构开始飘。控制在 3000 到 4000 字符是一个比较稳的区间。

第二,JSON 解析要容错。模型输出的 JSON 偶尔会带 Markdown 代码块标记,比如开头是```json,结尾是```。直接json.loads必炸,所以我一般先做一层清理:

def safe_parse_json(text): text = text.strip() if text.startswith("```"): text = text.strip("`") if text.startswith("json"): text = text[4:] try: return json.loads(text) except json.JSONDecodeError: # 去掉尾逗号后重试一次,这是模型输出的常见问题 import re fixed = re.sub(r",\s*([}\]])", r"\1", text) return json.loads(fixed)

第三,模型返回的字段,永远要在下游代码里做白名单校验。比如价格字段,不能假设它一定是一个数字字符串,有可能模型填了个“暂无报价”。我的做法是先检查字段是否在允许集合里,再做类型转换,不符合就置 null 并记录日志。

4.2 案例二:自然语言策略指令映射成量化参数

第二个案例来自我做过的一个量化回测工具。用户不想对着配置文件改参数,而是直接说一句:“当5日均线上穿20日均线且成交量放大50%时买入。”传统 NLP 要做实体识别、意图分类,太折腾。有了 Function Calling,这个需求变成了“让模型把这句话翻译成结构化的函数调用参数”。

我的设计是这样的:本地写好一个策略配置函数,声明给模型:

def set_strategy(ma_short: int, ma_long: int, volume_ratio: float, action: str) -> dict: # 这里更新策略参数,返回最终配置 return { "ma_short": ma_short, "ma_long": ma_long, "volume_ratio": volume_ratio, "action": action, }

函数声明里明确描述每个参数含义:

strategy_tool = types.Tool(function_declarations=[ types.FunctionDeclaration( name="set_strategy", description="根据自然语言指令设置量化策略参数", parameters={ "type": "OBJECT", "properties": { "ma_short": {"type": "INTEGER", "description": "短期均线周期,默认5"}, "ma_long": {"type": "INTEGER", "description": "长期均线周期,默认20"}, "volume_ratio": {"type": "NUMBER", "description": "成交量放大的倍数阈值,例如1.5表示放量50%"}, "action": {"type": "STRING", "enum": ["buy", "sell", "hold", "close"], "description": "交易动作"}, }, "required": ["ma_short", "ma_long", "volume_ratio", "action"], }, ) ])

模型解析完自然语言后,会返回类似这样的 function_call:

fc.args # {"ma_short": 5, "ma_long": 20, "volume_ratio": 1.5, "action": "buy"}

拿到这个参数,本地再做一层“边界校验”再落配置。我自己踩过坑:模型偶尔会把volume_ratio填成 0.5,它以为“放大 50%”就是 0.5,但业务上这个字段代表 1.5。所以我在校验层强制要求volume_ratio >= 1.0,不满足就直接拒绝并提示用户修改指令。

这类场景里,模型的作用是理解意图、填参数,而不是做最终决策。凡是涉及资金、权限、物理设备等真实操作,一律走人工确认或日志审计。我见过有人让模型直接输出买卖信号,这非常危险——模型输出带概率性,不能当确定逻辑用。

4.3 两个案例通用的容错套路

不管做文本清洗还是参数解析,所有接 Gemini 的代码都应该有一套统一的容错流程。我总结下来就四步:重试、解析兜底、字段校验、日志留底。

重试用十次性网络请求很容易理解,Gemini 也会有短暂的限流或服务波动。我习惯用tenacity做指数退避:

import time from tenacity import retry, stop_after_attempt, wait_exponential @retry(stop=stop_after_attempt(3), wait=wait_exponential(multiplier=1, max=10)) def call_gemini(model, prompt): return model.generate_content(prompt).text

字段校验和日志留底,前面两个案例里已经提了,这里不重复。核心思想只有一句话:凡是模型输出的内容,进生产链路之前,默认它是不可信的。

5. 接入生产前要知道的边界:限流、超时、成本和内容安全

5.1 429、5xx 和超时的分级处理

真实项目跑起来,你首先遇到的一定不是“模型回答得不好”,而是请求报错。最常见的有三种:

报错类型典型状态码含义处理建议
资源耗尽429触发配额限制指数退避重试,降低请求频率
无效请求400contents 格式、参数不对检查代码,不重试
服务端错误500、503Google 侧临时故障退避重试,通常秒级恢复

我最开始做的错误处理特别粗暴:所有异常一律重试三次。结果有些格式错误反复重试,白烧 token。后来改成每次异常都先打日志,再根据异常类型决定是否重试。Google SDK 的异常体系里常见的是google.api_core.exceptions.ResourceExhausted、ServiceUnavailable这种,代码里可以精确捕获。

超时也是一个隐性问题。默认超时时间在某些大 Prompt 下容易不够,长文本生成经常跑满十几秒。建议在模型实例化或请求时设置合理超时,比如 60 到 120 秒,避免 UI 层一直傻等。

5.2 成本控制:选型、token 压缩与缓存

Gemini 不是按请求次数计费,而是按 token 计费。每调用一次,输入 token 和输出 token 都算钱。我见过很多新手把几千字的论文全文塞进 prompt,一次就烧掉大量 token,关键是很多内容根本用不上。

控制成本的核心就三条:选对模型、压缩输入、缓存结果。

模型层面,flash系列适合高频批处理,pro系列适合复杂推理。不是所有任务都值得用 pro,简单清洗分类用 flash 就够了。我自己的经验是,先把 prompt 写稳定,再决定要不要换大模型。

token 压缩方面,上面提到的手动构造对话历史就是很好的做法,只传最近 N 轮,或者先让一个轻量模型把长对话压缩成摘要再传给重量级模型。

缓存更直接:对于同一份商品文本清洗,清洗结果短期内不会变,可以把结果存数据库,下次直接查缓存,不用重复调 API。我在爬虫采集场景里做过一次实测,加缓存后 API 费用降了七成。

5.3 safety_settings 与输出边界:默认值尽量别动

Gemini 默认有一套内容安全策略,有些用户发现某些 prompt 被拦截,第一个想法是把安全阈值调到最低。我的态度是:非必要不动。

safety_settings确实可以通过代码设置,把某些类别的阈值从BLOCK_MEDIUM_AND_ABOVE改成BLOCK_ONLY_HIGH甚至BLOCK_NONE,但这样做会让输出稳定性变差。同一个正常 prompt,可能在放宽后拿到带脏话的结果,也可能被上游策略风控,反而更容易触发报错。

我的建议是:先搞清楚为什么被拦。如果频繁被拦截,优先优化 prompt 表述,而不是调安全参数。只有你明确知道自己要处理的内容是正常技术内容,并且有信心承担相应责任时,再考虑调整阈值。业务合规永远比单次功能跑通更重要。

另外,response.prompt_feedback字段会告诉你请求是否被 block 以及原因。遇到输出为空,先看这个字段,别上来就怀疑模型坏了。

写到这里,我想分享一个这些年反复验证过的体会:接入 Gemini 这类模型,不要把它当成一个普通 Python 函数来看。普通函数输入输出可控,模型响应本质是概率性的。凡是它返回的东西,进生产链路之前都要过一道校验;凡是它要执行的操作,涉及真实世界的影响都要有人工确认的环节。我自己项目里所有接模型的地方,旁边一定挂一个日志开关,出问题先把完整 response 存下来再排查,而不是直接吞掉异常。带一个会犯错的“实习生”干活,管控好它的边界,收益才会真的大于风险。

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

基于YOLOv5的工业轴承缺陷智能检测系统实践

1. 项目概述&#xff1a;工业质检的智能化升级 轴承作为机械设备的核心部件&#xff0c;其质量直接影响整机寿命与运行安全。传统人工检测存在效率低&#xff08;每分钟仅能检测3-5个&#xff09;、漏检率高&#xff08;约15%&#xff09;等问题。我们开发的系统采用YOLOv5s模型…

作者头像 李华
网站建设 2026/10/26 13:28:52

ChatGPT原理与AI幻觉:从鹦鹉学舌到大模型训练与人工智能训练师

“当你知道对方是一只鹦鹉”——这个梗最近在社交平台上传疯了&#xff0c;配图通常是ChatGPT一本正经却又明显跑偏的回答。嘲讽的意思很直接&#xff1a;这东西看着能聊天、能写诗、能写代码&#xff0c;可骨子里是不是就是一个高级复读机&#xff0c;靠海量语料硬“背”出来的…

作者头像 李华
网站建设 2026/10/26 13:28:45

AI芯片算力爆发:技术驱动力与行业影响分析

1. 芯片算力爆发式增长背后的技术驱动力过去五年间&#xff0c;AI芯片的算力以每年300%的复合增长率持续攀升&#xff0c;这个数字背后是半导体行业多项关键技术突破的集中体现。制程工艺从7nm演进到3nm&#xff0c;晶体管密度提升带来最直接的性能增益。以台积电3nm工艺为例&a…

作者头像 李华
网站建设 2026/10/26 13:28:07

平衡小车从能编译到能站住:MPU6050姿态解算与三环PID调参实战

简介&#xff1a;平衡小车car--葛凤佩.rar 是一份面向自动控制、嵌入式与机器人方向学习者的工程实践包&#xff0c;围绕平衡小车的姿态检测、电机驱动与闭环控制组织内容&#xff0c;可用于对照PID调节、卡尔曼滤波、状态观测等知识点理解算法如何落地。压缩包共1420个文件、约…

作者头像 李华
网站建设 2026/10/26 13:27:19

AI应用自研模型是伪命题:技术虚荣如何杀死产品

1. 一个反直觉的结论&#xff1a;模型越自研&#xff0c;产品死得越快先抛一个可能让不少技术团队不舒服的判断&#xff1a;绝大多数 AI 应用公司自研底层模型&#xff0c;是纯粹的负收益行为。这话不是拍脑袋&#xff0c;是我在看过、参与过、也踩过一批 AI 产品从立项到关停的…

作者头像 李华
网站建设 2026/10/26 13:25:31

Selenium 驱动 Firefox:geckodriver 安装配置与排错

简介&#xff1a;面向Python初学者及需要开展Firefox自动化测试的开发者&#xff0c;压缩包提供可运行的Python环境文件与Geckodriver驱动&#xff0c;可用于Selenium脚本调试、网页自动化与爬虫验证的环境搭建。包内共880个文件&#xff0c;约11.82MB&#xff0c;以py源码和py…

作者头像 李华