iT邦幫忙

2026 iThome 鐵人賽

DAY 9
0

把推論端點接上去,表面上只是填一組金鑰跟一個模型名稱。實際會出問題的是端點選哪一個:Google 同時提供原生 API 與 OpenAI 相容層,兩者填進去都連得上,差別要到多輪工具呼叫才會顯現。以下拆開組態內容、轉譯路徑,以及一個把金鑰填成「看起來像隨便填」而失敗的案例。


組態長什麼樣

金鑰放 .env,其餘放 config.yaml:

model:
  provider: gemini
  default: gemini-3.5-flash-lite
  base_url: https://generativelanguage.googleapis.com/v1beta
# .env
GOOGLE_API_KEY=<在 AI Studio 產生,由使用者自行填入>

金鑰由本人到 AI Studio 產生後自行寫入,全程在本機完成。hermes config set 會判定鍵名,屬於密鑰的寫進 .env,其餘寫進 config.yaml。

base_url 結尾的 /v1beta 與 /v1beta/openai/ 各自對應一條程式碼路徑。


選用原生端點的理由

Hermes 對 Gemini 有一支專用的轉譯層 agent/gemini_native_adapter.py,它的檔頭直接寫了理由:

Google 的 OpenAI 相容端點對 Hermes 的多輪 agent 與工具迴圈一直不穩定(認證反覆、tool call 重放的異常、thought signature 的要求)。原生 Gemini API 是正規路徑,可以完全避開相容層

判定方式很直接,base_url 含有 generativelanguage.googleapis.com 且結尾不是 /openai,就視為原生端點。

v0.21.0 對兩種填錯做了防禦:

  • base_url 結尾是 /openai 時,會把這一段切掉
  • 模型 ID 帶 google/ 或 gemini/ 前綴時,會把前綴剝掉

這兩個值目前只剩閱讀上的意義,填對仍然比較好,因為組態檔是人在讀的。

provider: gemini 在 Hermes 內部仍維持 api_mode='chat_completions',主迴圈照舊用 OpenAI 形狀的訊息流,轉譯只發生在傳輸層。

agent 的核心邏輯只看到統一的介面,下面接哪一家由 provider 層決定


OpenAI 形狀到 Gemini 原生的轉譯

OpenAI 形狀 Gemini 原生
messages[] contents[]
system 角色的訊息 systemInstruction
tools[].function tools[].functionDeclarations[]
tool_choice: "auto" functionCallingConfig.mode: "AUTO"
tool_choice: "required" functionCallingConfig.mode: "ANY"
tool_choice 指定單一函式 mode: "ANY" 加 allowedFunctionNames

Day09GeminiProviderComparisonBoard

欄位改個名字是容易的部分。實際的麻煩在三個地方:

一、角色必須嚴格交替。 Gemini 對連續兩則同角色的 contents 回傳 HTTP 400,所以轉譯時得把相鄰的同角色內容合併。串流中斷或配額耗盡導致模型那一輪中斷時,歷史裡會出現使用者訊息緊接在工具結果後面,轉譯層在中間插一則佔位文字「先前的回應在完成前被中斷」,讓請求維持交替合法。

二、工具 schema 走白名單。 Gemini 的 FunctionDeclaration.parameters 只接受 OpenAPI 3.0 的一個子集,Hermes 只保留清單內的鍵:

  • 留下:type、format、enum、properties、required、items、minimum、maximum、pattern、anyOf 等二十餘個
  • 丟掉:$schema、additionalProperties、oneOf、allOf 這些清單外的寫法,在送出前靜默消失

實際生效的是轉譯後送出的那一份 schema

三、thought signature 是必填欄位。 Gemini 3 的思考模型在重放歷史時要求每個 functionCall 帶 thoughtSignature,缺少時回傳 400 INVALID_ARGUMENT。跨 provider 的 fallback 產生的工具呼叫紀錄缺少這個欄位,Hermes 的處理是填入字串 skip_thought_signature_validator 當佔位值。

還有一個預設值的差異會直接影響輸出品質。省略 max_tokens 時:

  • OpenAI 相容端點:給的是完整預算
  • Gemini 原生 API:套用一個很低的內部預設值並截斷輸出

Hermes 因此在呼叫端省略該欄位時主動填入 65535,也就是目前各 Gemini 文字模型共通的輸出上限。


hermes model 對免費層金鑰的處置

hermes model 是互動式的 provider 設定流程。選 Gemini 並填入金鑰後,它會先發一個 maxOutputTokens: 1 的最小請求探測金鑰等級,再決定是否把 Gemini 存成預設 provider。

探測結果 判定 設定流程
回應標頭 x-ratelimit-limit-requests-per-day 小於等於 1000 free 中止,組態維持原狀
該標頭大於 1000 paid 通過
無該標頭,HTTP 429 且內容含 free_tier free 中止,組態維持原狀
無該標頭,HTTP 2xx paid 通過
連線失敗或其餘狀態碼 unknown 通過

判定為 free 時印出的理由是 Hermes 每個使用者回合通常發出 3 到 10 次 API 呼叫(工具迭代加上輔助任務),免費層的每日上限在幾則訊息內就會用完,一個 agent session 的用量高於這個上限。

以 gemini-3.5-flash-lite 送出最小請求,Google 回 HTTP 200 而且完全不帶 x-ratelimit-* 標頭,落在「無標頭且 2xx」這一條,判定為 paid。探測函式的預設模型是 gemini-3.7-flash,該模型當下回 503 表示負載過高,落到 unknown,同樣放行。

標頭缺席時,一次成功的請求就會被判定為付費層,這道閘門實際攔下的是已經回 429 的金鑰

配額還有剩的金鑰會在設定這一關通過。 實際會遇到的是執行期的 429,錯誤處理會在訊息後面附上同一段免費層說明。

金鑰格式另有一條時程,Google 官方論壇的公告分兩階段:

日期 範圍
2026-06-19 拒絕未設限的 Standard 金鑰,已加上限制的仍可使用
2026 年 9 月 拒絕所有 Standard 金鑰,改用綁定 Google Cloud 服務帳戶的 auth key

AI Studio 目前簽發的就是新的 auth key,字首從 AIza 換成 AQ.,實測可正常呼叫。這一側遇到的錯誤是 401 並要求改用 OAuth 2 存取權杖,訊息本身會把人導向 OAuth 方向。


除錯紀錄:金鑰填了值,仍然回報找不到憑證

段落 內容
現象 接一個地端的 OpenAI 相容端點,api_key 已填值,執行時回報 No usable credentials found for custom
根本原因 該端點接受任何字串當金鑰,於是填的是 dummy。Hermes 這一側 hermes_cli/auth.py 的 has_usable_secret() 有兩道檢查,長度至少 4 個字元,以及不在佔位字串清單內。清單含 dummy、none、null、placeholder、changeme、example 等,dummy 被判定為未填
修正方式 改填一串隨機字元
迴歸測試 換過金鑰後執行 hermes doctor,看該 provider 的憑證狀態是否由缺少憑證轉為可用

同一個區域還有一個更早的缺陷:Desktop 的設定流程在 api_key 留空時,會在憑證池留下 api_key: "none" 的殘留項並卡在最高優先序,之後從介面填入的正確值排在它後面(NousResearch/hermes-agent #38799)。當時的處理是改用 hermes model 從 CLI 重設。

v0.21.0 已對這一類情況加上本機回送端點的豁免,127.0.0.1 之類的本機服務即使金鑰過短也會放行(迴歸測試 #86864)。長度下限與佔位字串清單仍然生效。


心得

接受任何字串的端點,配上會擋佔位字串的客戶端,湊在一起會產生一個錯誤訊息指向別處的失敗。

地端跑起來的 OpenAI 相容端點多半接受任意 api_key,於是「隨便填一個」是常見做法。Hermes 這一側則假設能填進去的都是真憑證,會把明顯是佔位符的值視為留空。兩邊各自都合理。

結果是「隨便填一個值」跟「填一個看起來像隨便填的值」行為不同。填 dummy 得到的錯誤訊息是找不到可用憑證,排查方向因此歪到金鑰是否寫進檔案、路徑是否正確這些地方,而檔案一直是對的。

錯誤訊息指向的層級決定排查方向,指錯層級時會在對的檔案上繞很久


明天

更換推論端點,確認其餘職責維持原狀,以及一個把 agent 當成模型端點呼叫的反例。


上一篇
【Day 8】Profile 的隔離邊界:狀態各自獨立,檔案系統共用
下一篇
【Day 10】可替換性實測:一次端點替換與一次兩層 agent 的衝突
系列文
打造具備記憶與執行能力的常駐 AI Agent:Hermes Agent × Gemini × MCP 的 Harness 設計 共 13 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言