1
0

Hermes 接入第三方 Gemini 原生代理解决方案

2026-06-27
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: Truepatched: False

之后再重启 gateway 即可:

hermes gateway restart --profile <PROFILE_NAME> # 注意替换!

五、注意事项

  1. hermes update 会覆盖本改动: 托管安装更新时会拉新代码覆盖。更新后需重跑第四节脚本。建议存成 ~/repatch-proxy.sh,更新后执行一次。
  2. 代理换域名时, 同步修改脚本里的域名。
  3. 本改动仅供自用,未提交上游;如需通用化,可参考社区 PR #21747 / #39644。

评论