Hermes 接入第三方 Gemini 原生代理解决方案
适用项目:Hermes Agent(NousResearch/hermes-agent)
适用场景:使用只支持 Gemini 原生generateContent的第三方代理(下文以占位域名api.your-proxy.com表示,请替换成你的实际代理域名)。
记录时间:2026-06-27
0. 本人所使用的第三方代理实测结论
| 测试项 | 结果 |
|---|---|
非流式 :generateContent |
✅ HTTP 200,真实返回 |
流式 :streamGenerateContent?alt=sse |
✅ HTTP 200,正常 SSE 分块 |
Authorization: Bearer 鉴权 |
✅ 接受 |
x-goog-api-key 鉴权 |
✅ 也接受 |
| 不带 key | ❌ HTTP 400「缺少授权参数」 |
重要结论:该代理同时接受 x-goog-api-key,而 Hermes 原生客户端本来就发这个头。
因此只需改一处(放宽域名判定)即可,无需改鉴权头。 流式端点工作正常,不必关闭流式。
注:不同第三方代理的鉴权方式可能不同。若你的代理只认
Authorization: Bearer,见第二节末尾的"可选改动"。
一、问题描述
现象
配置了第三方 Gemini 代理后,请求被当成 OpenAI chat completions 发出,报 404:
provider=gemini base_url=https://api.your-proxy.com/v1beta model=gemini-3.1-pro-preview
OpenAI client created
chat_completion_stream_request
Streaming failed before delivery: Error code: 404
代理实际只接受 Gemini 原生格式:
POST https://api.your-proxy.com/v1beta/models/gemini-3.1-pro-preview:generateContent
Authorization: Bearer <API_KEY> # 或 x-goog-api-key: <API_KEY>
Content-Type: application/json
{ "contents": [ { "role": "user", "parts": [ { "text": "hello" } ] } ] }
它不支持 OpenAI 风格的 /chat/completions。
根因
Hermes 内置了原生 Gemini 客户端(GeminiNativeClient),但是否启用原生路径完全由主机名决定, 写死成 Google 官方域名:
agent/gemini_native_adapter.py:
def is_native_gemini_base_url(base_url: str) -> bool:
normalized = str(base_url or "").strip().rstrip("/").lower()
if not normalized:
return False
if "generativelanguage.googleapis.com" not in normalized: # ← 只认 Google 官方主机
return False
return not normalized.endswith("/openai")
唯一的客户端选择点 agent/agent_runtime_helpers.py(约 1381 行):
if agent.provider == "gemini":
...
if is_native_gemini_base_url(base_url): # ← 第三方主机在此返回 False
client = GeminiNativeClient(**safe_kwargs)
return client
# 落空 → 继续往下创建 OpenAI 客户端 → 发出 /chat/completions → 404
client = _ra().OpenAI(**client_kwargs)
只要 base_url 主机名不是 generativelanguage.googleapis.com,就走不到原生客户端,被静默降级为 OpenAI 格式 → 404。
注:原生客户端默认发
x-goog-api-key头(_headers,约 879 行)。实测该代理接受此头,故鉴权无需改动。
对比:Anthropic 格式没有这个问题
Anthropic 原生协议由 api_mode == "anthropic_messages" 决定,不依赖主机名,且有显式配置开关 + /anthropic URL 后缀自动识别,第三方兼容代理开箱即用。Gemini 侧缺失这套机制,是本问题的本质。
社区现状(为何不提 PR)
经检索,该改动已有多个开放但未合并的 PR,再提会判重:
| PR | 说明 | 状态 |
|---|---|---|
| #21747 | feat: support custom base_url for Gemini native adapter —— 几乎相同思路 |
OPEN(陈旧,P3) |
| #39644 | 把 Palantir Foundry /google 代理路由进原生客户端 |
OPEN |
| #36792 | 把 OpenCode Zen gemini 路由进原生客户端 | OPEN |
因此采用本地自用补丁, 不走 PR。
二、解决方案(本地补丁,自用)
改动文件:
agent/gemini_native_adapter.py,只需一处。
把下文api.your-proxy.com替换成你的实际代理域名。
放宽域名判定(约第 54 行)
def is_native_gemini_base_url(base_url: str) -> bool:
"""Return True when the endpoint speaks Gemini's native REST API."""
normalized = str(base_url or "").strip().rstrip("/").lower()
if not normalized:
return False
_native_hosts = ("generativelanguage.googleapis.com", "api.your-proxy.com") # ← 加你的代理域名
if not any(h in normalized for h in _native_hosts): # ← 改这一行
return False
return not normalized.endswith("/openai")
鉴权头无需改动:该代理接受 Hermes 默认发送的
x-goog-api-key。
可选改动 —— 若你的代理只认 Authorization: Bearer
修改 GeminiNativeClient._headers(约第 879 行),按 base_url 分支:
def _headers(self) -> Dict[str, str]:
headers = {
"Content-Type": "application/json",
"Accept": "application/json",
"User-Agent": "hermes-agent (gemini-native)",
}
if "api.your-proxy.com" in (self.base_url or "").lower():
headers["Authorization"] = f"Bearer {self.api_key}" # 代理走 Bearer
else:
headers["x-goog-api-key"] = self.api_key # Google 官方保持不变
headers.update(self._default_headers)
return headers
三、配置
~/.hermes/config.yaml:
model:
default: gemini-3.1-pro-preview
provider: gemini
base_url: https://api.your-proxy.com/v1beta
~/.hermes/.env:
GEMINI_API_KEY=<你的代理 key>
四、一键补丁脚本
在安装根目录( ~/.hermes/hermes-agent 或实际 venv 安装位置)运行。幂等、可重复执行。
先把脚本里的 api.your-proxy.com 改成你的实际域名。
python - <<'PY'
import pathlib
PROXY_HOST = "api.your-proxy.com" # ← 改成你的代理域名
f = pathlib.Path("agent/gemini_native_adapter.py")
s = f.read_text(encoding="utf-8")
old = ''' if "generativelanguage.googleapis.com" not in normalized:
return False'''
new = f''' _native_hosts = ("generativelanguage.googleapis.com", "{PROXY_HOST}")
if not any(h in normalized for h in _native_hosts):
return False'''
if old in s:
s = s.replace(old, new, 1)
f.write_text(s, encoding="utf-8")
print("patched:", PROXY_HOST in s)
PY
输出 patched: True 即成功。改完重启 hermes 即可。
其他方式:
在当前目录( ~/.hermes/hermes-agent)下,创建文件:
vim patch_gemini.py
把下面的内容完整地复制粘贴进去:
import pathlib
PROXY_HOST = "api.your-proxy.com" # 你的代理域名
f = pathlib.Path("agent/gemini_native_adapter.py")
s = f.read_text(encoding="utf-8")
# 只匹配那一行(你里实际是单行,没有紧随的 return False)
old = ''' if "generativelanguage.googleapis.com" not in normalized:'''
# 替换成两行新代码(保持4空格缩进)
new = f''' _native_hosts = ("generativelanguage.googleapis.com", "{PROXY_HOST}")
if not any(h in normalized for h in _native_hosts):'''
if old in s:
s = s.replace(old, new, 1)
f.write_text(s, encoding="utf-8")
print("patched:", PROXY_HOST in s)
else:
print("未找到目标行,请检查文件内容或手动修改")
执行这个文件:
python3 patch_gemini.py
这样它就会立刻执行,并输出 patched: True 或 patched: False。
之后再重启 gateway 即可:
hermes gateway restart --profile <PROFILE_NAME> # 注意替换!
五、注意事项
hermes update会覆盖本改动: 托管安装更新时会拉新代码覆盖。更新后需重跑第四节脚本。建议存成~/repatch-proxy.sh,更新后执行一次。- 代理换域名时, 同步修改脚本里的域名。
- 本改动仅供自用,未提交上游;如需通用化,可参考社区 PR #21747 / #39644。