實(shí)戰(zhàn):從API接入到生產(chǎn)級(jí)容錯(cuò)與函數(shù)調(diào)用)
這個(gè)系列寫到第四篇我決定換個(gè)聊法。前三篇我們一直在解決怎么把 Google Gemini也就是以前叫 Bard 的那位接入 PythonAPI Key 拿到了SDK 裝好了也能跑通幾次簡(jiǎn)單的問答。這些屬于編程指南的基礎(chǔ)盤但光跑通不算完。這篇我不打算再堆基礎(chǔ)概念而是把重點(diǎn)放在“從一個(gè)能跑的 Demo變成能干活的功能”——順便把我在真實(shí)項(xiàng)目里被坑過的幾個(gè)地方攤開來(lái)說清楚。我會(huì)默認(rèn)你已經(jīng)有一個(gè)可用的 Gemini API KeyPython 環(huán)境是 3.9 以上。如果這些還沒準(zhǔn)備好建議先回去翻一下本系列前三篇或者直接pip install google-generativeai裝好再回來(lái)。這篇代碼我會(huì)盡量給完整復(fù)制到自己的工程里改改就能用。1. 環(huán)境與鑒權(quán)版本、Key 管理和模型名三個(gè)常見翻車點(diǎn)1.1 舊 SDK 與新 SDK先別急著二選一現(xiàn)在搜 Gemini Python 教程你會(huì)看到兩種主要寫法。一種是老牌的google-generativeai也就是常見的import google.generativeai as genai另一種是 Google 后來(lái)推的google-genai包名是from google import genaiAPI 風(fēng)格更貼近新模型的 Function Calling 能力。我個(gè)人的建議很直接如果只是做文本問答、多模態(tài)識(shí)別、簡(jiǎn)單對(duì)話google-generativeai的生態(tài)最成熟網(wǎng)上排錯(cuò)資料也多優(yōu)先用它。如果你的項(xiàng)目要從第一天就重度依賴函數(shù)調(diào)用、異步任務(wù)、流式輸出那直接上google-genai省得后面遷來(lái)遷去。安裝沒什么好說的pip install google-generativeai # 或者 pip install google-genai但注意一件事SDK 的版本更新頻率很高新版本偶爾會(huì)改接口行為。我吃過一次虧老代碼跑得好好的升級(jí) SDK 后有天突然報(bào)AttributeError: GenerateContentResponse object has no attribute text查了半天發(fā)現(xiàn)是新版本把某些字段挪到了result.candidates[0].content.parts下面。所以項(xiàng)目里建議鎖版本pip install google-generativeai0.8,1.0鎖版本不是不求上進(jìn)而是給生產(chǎn)環(huán)境的穩(wěn)定性留一條活路。1.2 API Key 管理別把密鑰寫進(jìn)代碼再傳 GitHub這是新手最容易踩的坑也是我強(qiáng)調(diào)最多次的API Key 相當(dāng)于你賬號(hào)的錢包寫死在代碼里等于把密碼明文放在家里大門上。正確做法是放環(huán)境變量。建議用python-dotenv在項(xiàng)目根目錄放一個(gè).env文件GEMINI_API_KEY你的key然后代碼里這樣加載import os from dotenv import load_dotenv import google.generativeai as genai load_dotenv() genai.configure(api_keyos.getenv(GEMINI_API_KEY))順手在.gitignore里把.env加進(jìn)去不然哪天提交代碼時(shí)手滑Key 就跟著出去了。我在 GitHub 上見過不止一個(gè)公開倉(cāng)庫(kù)里帶著真實(shí) Key幾分鐘內(nèi)就被人拿去做識(shí)別、跑量收到賬單才知道出事了。Key 獲取渠道就是 Google AI Studio 的 Developers API 頁(yè)面創(chuàng)建 API Key 后復(fù)制保存沒有別的花樣。生成之后建議把它當(dāng)密碼對(duì)待不分享、不截圖發(fā)群、不提交到倉(cāng)庫(kù)。1.3 模型名一字之差就是 404有個(gè)細(xì)節(jié)特別容易翻車模型名寫錯(cuò)。Gemini 的模型名類似gemini-1.5-flash、gemini-1.5-pro、gemini-2.0-flash這個(gè)字符串必須嚴(yán)格匹配。寫錯(cuò)之后請(qǐng)求直接拋 404 或者 400報(bào)錯(cuò)信息里會(huì)有一句類似models/gemini-11.5-flash not found。有次我同事把gemini-1.5-flash寫成了gemini-1.5-flash-001在他機(jī)器上能跑我的環(huán)境報(bào)錯(cuò)折騰半天才意識(shí)到是模型版本后綴在不同區(qū)域不通用。最穩(wěn)妥的辦法是不猜直接調(diào)模型列表接口查import google.generativeai as genai for m in genai.list_models(): if gemini in m.name.lower(): print(m.name)把支持的模型名打印出來(lái)再選避免手滑。這個(gè)習(xí)慣我一直保留到現(xiàn)在。2. 文本與多模態(tài)的最短可用路徑generate_content 全解2.1 一段能跑的最小代碼骨架Gemini 的調(diào)用入口不像某些大模型 SDK 那樣繞最核心的函數(shù)就是generate_content。給你看一段我能背下來(lái)的最小代碼import google.generativeai as genai genai.configure(api_keyos.getenv(GEMINI_API_KEY)) model genai.GenerativeModel(gemini-1.5-flash) response model.generate_content(用一句話解釋什么是量子糾纏) print(response.text)運(yùn)行之后response.text就是你想要的模型輸出。流程不復(fù)雜配置 Key、創(chuàng)建模型實(shí)例、傳內(nèi)容、取.text。但如果哪天response.text取不到別慌先看下是不是被安全策略攔截了后面第 5 章會(huì)細(xì)說。這里有個(gè)經(jīng)驗(yàn)?zāi)玫?response 之后不要只取.text調(diào)試階段把整個(gè) response 對(duì)象打出來(lái)看看。它里面有完整的分段信息、候選結(jié)果、prompt 反饋能幫你定位很多奇怪問題。2.2 三個(gè)重要的生成參數(shù)理解清楚再調(diào)generate_content支持一個(gè)generation_config參數(shù)新手最容易把里面的配置項(xiàng)當(dāng)成“調(diào)得越大越好”。其實(shí)每個(gè)參數(shù)管的事完全不一樣參數(shù)作用建議初始值temperature控制隨機(jī)性值越大輸出越發(fā)散0.7top_p核采樣概率閾值相當(dāng)于從概率排名靠前的詞里采樣0.9max_output_tokens生成內(nèi)容的最大長(zhǎng)度上限按場(chǎng)景設(shè)stop_sequences遇到這些字符串就停止生成看需求我常用一個(gè)類比解釋 temperature它像做菜時(shí)的“自由發(fā)揮程度”。temperature 低廚師嚴(yán)格按菜譜來(lái)temperature 高廚師開始隨便放調(diào)料。寫代碼注釋、提煉摘要建議 0.3 到 0.5頭腦風(fēng)暴、起標(biāo)題可以開到 0.8 到 1.0。示例model genai.GenerativeModel( gemini-1.5-flash, generation_config{ temperature: 0.4, max_output_tokens: 2048, top_p: 0.9, }, )注意一點(diǎn)max_output_tokens限制的是輸出 token 數(shù)不是輸入。把長(zhǎng)文章一次性丟進(jìn)去輸出太短導(dǎo)致截?cái)噙@就是為什么有時(shí)候回答“戛然而止”。合理設(shè)置上限給模型留出完整作答的空間。2.3 圖片輸入PIL 路徑和 base64 路徑Gemini 天生支持多模態(tài)傳圖比想象中簡(jiǎn)單。最常見的做法是用 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)這里有個(gè)小坑列表的第一項(xiàng)是文本指令第二項(xiàng)是圖片對(duì)象順序不要搞反。有些教程寫成[image, text]模型一樣能理解但結(jié)果不穩(wěn)定我習(xí)慣遵守“指令在前、材料在后”的規(guī)則。另一種場(chǎng)景是抓包或者爬蟲拿到的是二進(jìn)制圖片流沒有落盤。這種情況不要先保存臨時(shí)文件再讀直接用 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([識(shí)別這個(gè)圖片中的關(guān)鍵信息, image_part]) print(response.text)mime_type 要根據(jù)實(shí)際圖片格式填image/png、image/jpeg、image/webp都支持。填錯(cuò)了識(shí)別接口會(huì)直接報(bào)錯(cuò)這是典型的“接口文檔不仔細(xì)看就白費(fèi)時(shí)間”的問題。3. 從單輪問答到真實(shí)功能流式、記憶和函數(shù)調(diào)用3.1 streamTrue打字機(jī)效果的性價(jià)比如果你做過聊天機(jī)器人應(yīng)該清楚“等完整答案再返回”有多痛苦。長(zhǎng)文本生成可能要 10 秒、20 秒用戶盯著空白頁(yè)面以為是卡死了。Gemini 的流式輸出用起來(lái)很簡(jiǎn)單model genai.GenerativeModel(gemini-1.5-flash) response model.generate_content( 寫一篇800字的科普短文主題是黑洞, streamTrue, ) full_text for chunk in response: piece chunk.text print(piece, end, flushTrue) full_text piece注意一個(gè)容易踩坑的點(diǎn)流式響應(yīng)用完之后response.text不一定能拿到完整內(nèi)容。我見過不少人在循環(huán)結(jié)束后嘗試response.text結(jié)果只拿到第一段或者直接報(bào)錯(cuò)。正確做法是像上面一樣自己在循環(huán)里拼full_text。流式的另一個(gè)好處是首字延遲低。我實(shí)際測(cè)下來(lái)普通非流式請(qǐng)求可能要等 5 秒才看到第一個(gè)字流式模式差不多 1 秒內(nèi)就有內(nèi)容出來(lái)。對(duì)用戶感知來(lái)說這種差距體驗(yàn)區(qū)別非常大。3.2 多輪對(duì)話start_chat 與手動(dòng) contents做聊天功能免不了多輪上下文。最簡(jiǎn)單的是用start_chatmodel genai.GenerativeModel(gemini-1.5-flash) chat model.start_chat(history[]) resp1 chat.send_message(你好我接下來(lái)會(huì)連續(xù)提問請(qǐng)盡量簡(jiǎn)短回答) resp2 chat.send_message(Python 的 GIL 是什么) print(resp2.text) # 查看完整歷史 for msg in chat.history: print(msg.role, msg.parts[0].text)chat.history會(huì)保留所有消息這是方便也是隱患。對(duì)話超過一定輪數(shù)歷史全塞進(jìn)請(qǐng)求里token 消耗會(huì)指數(shù)級(jí)上漲。我之前做過一個(gè)客服機(jī)器人用戶聊到 50 輪還不停每輪的請(qǐng)求量已經(jīng)大到離譜成本直接失控。更可控的方式是手動(dòng)組織 contentscontents [ {role: user, parts: [你好]}, {role: model, parts: [你好有什么可以幫你]}, {role: user, parts: [今天北京天氣怎么樣]}, ] model genai.GenerativeModel(gemini-1.5-flash) response model.generate_content(contents) print(response.text)手動(dòng)模式的好處是你可以在喂給模型之前對(duì)歷史做截?cái)?、摘要、去重。比如只保留最?10 輪或者把前 20 輪壓縮成一段話再接上來(lái)。這是做真實(shí)產(chǎn)品必須掌握的技能。3.3 Function Calling讓模型決定調(diào)用哪個(gè)函數(shù)這是 Gemini 最值得花時(shí)間研究的特性。簡(jiǎn)單說你給模型聲明幾個(gè)函數(shù)模型在理解用戶意圖后返回“該調(diào)用哪個(gè)函數(shù)、參數(shù)是什么”而不是直接生成文本。真正執(zhí)行函數(shù)的是你的本地代碼。流程一共三步聲明函數(shù)給模型、模型返回 function_call、你執(zhí)行后把結(jié)果回填給模型生成最終回答。以新 SDKgoogle-genai為例代碼結(jié)構(gòu)更清晰from google import genai from google.genai import types import os client genai.Client(api_keyos.getenv(GEMINI_API_KEY)) def get_weather(city: str) - str: # 這里替換成真實(shí)天氣接口 return f{city}晴22℃ weather_tool types.Tool(function_declarations[ types.FunctionDeclaration( nameget_weather, description查詢指定城市的當(dāng)前天氣, parameters{ type: OBJECT, properties: { city: { type: STRING, description: 城市名稱例如 北京, }, }, required: [city], }, ) ]) config types.GenerateContentConfig(tools[weather_tool]) response client.models.generate_content( modelgemini-2.0-flash, contents北京今天穿什么衣服合適, configconfig, ) if response.function_calls: fc response.function_calls[0] if fc.name get_weather: result get_weather(cityfc.args[city]) print(模型想調(diào)用 get_weather參數(shù)是, fc.args) print(本地函數(shù)返回, result)如果只是打印結(jié)果那還沒形成完整閉環(huán)。真正要做的是把函數(shù)返回結(jié)果回填給模型讓模型結(jié)合結(jié)果生成自然語(yǔ)言回答。不同 SDK 版本的回填方式略有區(qū)別但核心套路是一樣的把 function_call 那一段原樣放回消息歷史再追加一個(gè) role 為 tool 的結(jié)果最后再讓模型生成。Function Calling 最大的價(jià)值在于把“大模型的意圖理解”和“你系統(tǒng)的確定性邏輯”結(jié)合起來(lái)。用戶輸入再隨意最終觸發(fā)的是你代碼里已經(jīng)寫好的函數(shù)而不是讓模型直接生成一句不可控的 SQL 或者按鈕點(diǎn)擊動(dòng)作。這也是第 4 章兩個(gè)案例的共同底座。4. 兩個(gè)落地案例爬蟲文本清洗與策略參數(shù)解析4.1 案例一爬蟲商品描述清洗為結(jié)構(gòu)化 JSON很多做電商數(shù)據(jù)采集的朋友都遇到過這個(gè)問題requests 抓回頁(yè)面BeautifulSoup 去掉標(biāo)簽之后剩下的文本里全是廣告詞、導(dǎo)航文字、亂碼和無(wú)意義的重復(fù)信息。傳統(tǒng)正則寫到懷疑人生換了個(gè)商品頁(yè)面就失效。我的做法是拿 Gemini 做語(yǔ)義清洗。把抓到的文本按 3000 字符左右切成片段每個(gè)片段要求模型提取結(jié)構(gòu)化信息并輸出 JSONimport json import google.generativeai as genai model genai.GenerativeModel(gemini-1.5-flash) raw_text 這里放爬蟲抓取并清洗后的原始文本可能包含大量無(wú)用內(nèi)容 prompt f 從下面的商品描述文本中提取結(jié)構(gòu)化信息只輸出 JSON不要輸出其他內(nèi)容。 格式要求 {{標(biāo)題: ..., 品牌: ..., 型號(hào): ..., 價(jià)格: ..., 保修期: ..., 有效描述: ...}} 某個(gè)字段如果不存在用 null 代替。 原始文本 {raw_text[:3000]} resp model.generate_content(prompt) print(resp.text)這里的經(jīng)驗(yàn)主要有三個(gè)。第一文本要切片。超長(zhǎng)文本直接丟進(jìn)去不僅 token 開銷大模型中途還容易“走神”輸出結(jié)構(gòu)開始飄??刂圃?3000 到 4000 字符是一個(gè)比較穩(wěn)的區(qū)間。第二JSON 解析要容錯(cuò)。模型輸出的 JSON 偶爾會(huì)帶 Markdown 代碼塊標(biāo)記比如開頭是json結(jié)尾是。直接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: # 去掉尾逗號(hào)后重試一次這是模型輸出的常見問題 import re fixed re.sub(r,\s*([}\]]), r\1, text) return json.loads(fixed)第三模型返回的字段永遠(yuǎn)要在下游代碼里做白名單校驗(yàn)。比如價(jià)格字段不能假設(shè)它一定是一個(gè)數(shù)字字符串有可能模型填了個(gè)“暫無(wú)報(bào)價(jià)”。我的做法是先檢查字段是否在允許集合里再做類型轉(zhuǎn)換不符合就置 null 并記錄日志。4.2 案例二自然語(yǔ)言策略指令映射成量化參數(shù)第二個(gè)案例來(lái)自我做過的一個(gè)量化回測(cè)工具。用戶不想對(duì)著配置文件改參數(shù)而是直接說一句“當(dāng)5日均線上穿20日均線且成交量放大50%時(shí)買入?!眰鹘y(tǒng) NLP 要做實(shí)體識(shí)別、意圖分類太折騰。有了 Function Calling這個(gè)需求變成了“讓模型把這句話翻譯成結(jié)構(gòu)化的函數(shù)調(diào)用參數(shù)”。我的設(shè)計(jì)是這樣的本地寫好一個(gè)策略配置函數(shù)聲明給模型def set_strategy(ma_short: int, ma_long: int, volume_ratio: float, action: str) - dict: # 這里更新策略參數(shù)返回最終配置 return { ma_short: ma_short, ma_long: ma_long, volume_ratio: volume_ratio, action: action, }函數(shù)聲明里明確描述每個(gè)參數(shù)含義strategy_tool types.Tool(function_declarations[ types.FunctionDeclaration( nameset_strategy, description根據(jù)自然語(yǔ)言指令設(shè)置量化策略參數(shù), parameters{ type: OBJECT, properties: { ma_short: {type: INTEGER, description: 短期均線周期默認(rèn)5}, ma_long: {type: INTEGER, description: 長(zhǎng)期均線周期默認(rèn)20}, volume_ratio: {type: NUMBER, description: 成交量放大的倍數(shù)閾值例如1.5表示放量50%}, action: {type: STRING, enum: [buy, sell, hold, close], description: 交易動(dòng)作}, }, required: [ma_short, ma_long, volume_ratio, action], }, ) ])模型解析完自然語(yǔ)言后會(huì)返回類似這樣的 function_callfc.args # {ma_short: 5, ma_long: 20, volume_ratio: 1.5, action: buy}拿到這個(gè)參數(shù)本地再做一層“邊界校驗(yàn)”再落配置。我自己踩過坑模型偶爾會(huì)把volume_ratio填成 0.5它以為“放大 50%”就是 0.5但業(yè)務(wù)上這個(gè)字段代表 1.5。所以我在校驗(yàn)層強(qiáng)制要求volume_ratio 1.0不滿足就直接拒絕并提示用戶修改指令。這類場(chǎng)景里模型的作用是理解意圖、填參數(shù)而不是做最終決策。凡是涉及資金、權(quán)限、物理設(shè)備等真實(shí)操作一律走人工確認(rèn)或日志審計(jì)。我見過有人讓模型直接輸出買賣信號(hào)這非常危險(xiǎn)——模型輸出帶概率性不能當(dāng)確定邏輯用。4.3 兩個(gè)案例通用的容錯(cuò)套路不管做文本清洗還是參數(shù)解析所有接 Gemini 的代碼都應(yīng)該有一套統(tǒng)一的容錯(cuò)流程。我總結(jié)下來(lái)就四步重試、解析兜底、字段校驗(yàn)、日志留底。重試用十次性網(wǎng)絡(luò)請(qǐng)求很容易理解Gemini 也會(huì)有短暫的限流或服務(wù)波動(dòng)。我習(xí)慣用tenacity做指數(shù)退避import time from tenacity import retry, stop_after_attempt, wait_exponential retry(stopstop_after_attempt(3), waitwait_exponential(multiplier1, max10)) def call_gemini(model, prompt): return model.generate_content(prompt).text字段校驗(yàn)和日志留底前面兩個(gè)案例里已經(jīng)提了這里不重復(fù)。核心思想只有一句話凡是模型輸出的內(nèi)容進(jìn)生產(chǎn)鏈路之前默認(rèn)它是不可信的。5. 接入生產(chǎn)前要知道的邊界限流、超時(shí)、成本和內(nèi)容安全5.1 429、5xx 和超時(shí)的分級(jí)處理真實(shí)項(xiàng)目跑起來(lái)你首先遇到的一定不是“模型回答得不好”而是請(qǐng)求報(bào)錯(cuò)。最常見的有三種報(bào)錯(cuò)類型典型狀態(tài)碼含義處理建議資源耗盡429觸發(fā)配額限制指數(shù)退避重試降低請(qǐng)求頻率無(wú)效請(qǐng)求400contents 格式、參數(shù)不對(duì)檢查代碼不重試服務(wù)端錯(cuò)誤500、503Google 側(cè)臨時(shí)故障退避重試通常秒級(jí)恢復(fù)我最開始做的錯(cuò)誤處理特別粗暴所有異常一律重試三次。結(jié)果有些格式錯(cuò)誤反復(fù)重試白燒 token。后來(lái)改成每次異常都先打日志再根據(jù)異常類型決定是否重試。Google SDK 的異常體系里常見的是google.api_core.exceptions.ResourceExhausted、ServiceUnavailable這種代碼里可以精確捕獲。超時(shí)也是一個(gè)隱性問題。默認(rèn)超時(shí)時(shí)間在某些大 Prompt 下容易不夠長(zhǎng)文本生成經(jīng)常跑滿十幾秒。建議在模型實(shí)例化或請(qǐng)求時(shí)設(shè)置合理超時(shí)比如 60 到 120 秒避免 UI 層一直傻等。5.2 成本控制選型、token 壓縮與緩存Gemini 不是按請(qǐng)求次數(shù)計(jì)費(fèi)而是按 token 計(jì)費(fèi)。每調(diào)用一次輸入 token 和輸出 token 都算錢。我見過很多新手把幾千字的論文全文塞進(jìn) prompt一次就燒掉大量 token關(guān)鍵是很多內(nèi)容根本用不上??刂瞥杀镜暮诵木腿龡l選對(duì)模型、壓縮輸入、緩存結(jié)果。模型層面flash系列適合高頻批處理pro系列適合復(fù)雜推理。不是所有任務(wù)都值得用 pro簡(jiǎn)單清洗分類用 flash 就夠了。我自己的經(jīng)驗(yàn)是先把 prompt 寫穩(wěn)定再?zèng)Q定要不要換大模型。token 壓縮方面上面提到的手動(dòng)構(gòu)造對(duì)話歷史就是很好的做法只傳最近 N 輪或者先讓一個(gè)輕量模型把長(zhǎng)對(duì)話壓縮成摘要再傳給重量級(jí)模型。緩存更直接對(duì)于同一份商品文本清洗清洗結(jié)果短期內(nèi)不會(huì)變可以把結(jié)果存數(shù)據(jù)庫(kù)下次直接查緩存不用重復(fù)調(diào) API。我在爬蟲采集場(chǎng)景里做過一次實(shí)測(cè)加緩存后 API 費(fèi)用降了七成。5.3 safety_settings 與輸出邊界默認(rèn)值盡量別動(dòng)Gemini 默認(rèn)有一套內(nèi)容安全策略有些用戶發(fā)現(xiàn)某些 prompt 被攔截第一個(gè)想法是把安全閾值調(diào)到最低。我的態(tài)度是非必要不動(dòng)。safety_settings確實(shí)可以通過代碼設(shè)置把某些類別的閾值從BLOCK_MEDIUM_AND_ABOVE改成BLOCK_ONLY_HIGH甚至BLOCK_NONE但這樣做會(huì)讓輸出穩(wěn)定性變差。同一個(gè)正常 prompt可能在放寬后拿到帶臟話的結(jié)果也可能被上游策略風(fēng)控反而更容易觸發(fā)報(bào)錯(cuò)。我的建議是先搞清楚為什么被攔。如果頻繁被攔截優(yōu)先優(yōu)化 prompt 表述而不是調(diào)安全參數(shù)。只有你明確知道自己要處理的內(nèi)容是正常技術(shù)內(nèi)容并且有信心承擔(dān)相應(yīng)責(zé)任時(shí)再考慮調(diào)整閾值。業(yè)務(wù)合規(guī)永遠(yuǎn)比單次功能跑通更重要。另外response.prompt_feedback字段會(huì)告訴你請(qǐng)求是否被 block 以及原因。遇到輸出為空先看這個(gè)字段別上來(lái)就懷疑模型壞了。寫到這里我想分享一個(gè)這些年反復(fù)驗(yàn)證過的體會(huì)接入 Gemini 這類模型不要把它當(dāng)成一個(gè)普通 Python 函數(shù)來(lái)看。普通函數(shù)輸入輸出可控模型響應(yīng)本質(zhì)是概率性的。凡是它返回的東西進(jìn)生產(chǎn)鏈路之前都要過一道校驗(yàn)凡是它要執(zhí)行的操作涉及真實(shí)世界的影響都要有人工確認(rèn)的環(huán)節(jié)。我自己項(xiàng)目里所有接模型的地方旁邊一定掛一個(gè)日志開關(guān)出問題先把完整 response 存下來(lái)再排查而不是直接吞掉異常。帶一個(gè)會(huì)犯錯(cuò)的“實(shí)習(xí)生”干活管控好它的邊界收益才會(huì)真的大于風(fēng)險(xiǎn)。