适用场景:Hermes(或其他 Python OpenAI SDK 客户端)调用 OpenCode Go 订阅的模型(deepseek-v4-flash、grok-4.5、kimi-k3 等)响应极慢(120 秒+),而 DeepSeek 官方 API、Claude CLI 却很快。

一、问题现象

在 Hermes 中配置 OpenCode Go 订阅后,调用模型:

  • 每次 API 调用固定 120~130 秒
  • 同一台服务器上:
    • DeepSeek 官方 API:约 2 秒 ✅
    • Claude CLI 调 OpenCode:很快 ✅
    • curl 直接请求 OpenCode API:TTFB 0.4 秒 ✅
    • Python httpx 请求:20~200 秒 ❌

结论:模型本身不慢,是"客户端身份"的问题。

二、排查证据(为什么是 Cloudflare 的锅)

测试 TTFB
curl(libcurl TLS 指纹) 0.2~0.4s
Python httpx / requests(OpenSSL 指纹) 1.5~200s(随频率累积)
curl_cffi 模拟 Chrome 指纹 1.3~1.7s

响应头确认:

server: cloudflare
cf-ray: a27b8672ce4f75b2-SEA

opencode.ai 在 Cloudflare 后面。Cloudflare 对 Python/OpenSSL 的 TLS 指纹执行了延迟处理(类似 bot 检测),而 curl / 浏览器指纹直接放行。这就是为什么:

  • DeepSeek 官方 API(不走 Cloudflare)快
  • Claude CLI(原生二进制,TLS 栈不同)快
  • Hermes(Python httpx)慢

三、解决方案:本地 curl_cffi 代理

思路:让 Hermes 的请求先到本地代理,代理用 curl_cffi 模拟 Chrome 浏览器 TLS 指纹 转发到 opencode.ai,绕过 Cloudflare 的指纹识别。

Hermes ──> 127.0.0.1:18080 (curl_cffi 代理, Chrome 指纹) ──> opencode.ai

只影响 opencode-go 这一个 provider,其他 provider(DeepSeek、mimo 等)完全直连不受影响。

效果

状态 延迟
修复前(直连) 120~130s
修复后(走代理) 2~7s

四、实施步骤

1. 安装 curl_cffi

假设 Hermes 装在 ~/hermes-agent,使用 venv:

cd ~/hermes-agent
venv/bin/python -m pip install curl_cffi

验证:

venv/bin/python -c "import curl_cffi; print(curl_cffi.__version__)"

2. 创建代理脚本

sudo mkdir -p /opt/opencode-proxy
sudo chown $USER:$USER /opt/opencode-proxy

创建 /opt/opencode-proxy/proxy.py

#!/usr/bin/env python3
"""opencode.ai 本地代理:curl_cffi 模拟 Chrome TLS 指纹转发请求"""
import http.server
import socketserver
import sys
from curl_cffi import requests

# 注意:不要带 /v1,Hermes 请求路径自带 /v1
UPSTREAM = "https://opencode.ai/zen/go"
IMPERSONATE = "chrome"  # 模拟 Chrome 浏览器 TLS 指纹
LISTEN_HOST = "127.0.0.1"
LISTEN_PORT = 18080


class Handler(http.server.BaseHTTPRequestHandler):
    # HTTP/1.0:连接结束以 EOF 表示 SSE 流结束
    protocol_version = "HTTP/1.0"

    def log_message(self, fmt, *args):
        sys.stderr.write("[proxy] %s\n" % (fmt % args))

    def _forward(self):
        length = int(self.headers.get("Content-Length", 0) or 0)
        body = self.rfile.read(length) if length else b""

        headers = {
            "Content-Type": self.headers.get("Content-Type", "application/json"),
            "Authorization": self.headers.get("Authorization", ""),
            "Accept": "application/json",
        }
        url = UPSTREAM + self.path

        try:
            response = requests.post(
                url,
                content=body,
                headers=headers,
                impersonate=IMPERSONATE,
                stream=True,
                timeout=300,
            )
        except Exception as exc:
            self.send_response(502)
            self.send_header("Content-Type", "text/plain; charset=utf-8")
            self.end_headers()
            self.wfile.write(("proxy error: %s\n" % exc).encode())
            return

        self.send_response(response.status_code)
        self.send_header(
            "Content-Type",
            response.headers.get("Content-Type", "text/event-stream"),
        )
        self.send_header("Cache-Control", "no-cache")
        self.end_headers()

        try:
            # 关键:按原始字节流转发,保留 SSE 空行(事件分隔符)
            # 不要用 iter_lines(),会吞掉空行导致 SDK 解析失败
            for chunk in response.iter_content(chunk_size=8192):
                if chunk:
                    self.wfile.write(chunk)
                    self.wfile.flush()
        except (BrokenPipeError, ConnectionResetError):
            pass
        except Exception:
            pass

    do_POST = _forward
    do_GET = _forward


class ThreadingServer(socketserver.ThreadingMixIn, http.server.HTTPServer):
    daemon_threads = True
    allow_reuse_address = True


if __name__ == "__main__":
    server = ThreadingServer((LISTEN_HOST, LISTEN_PORT), Handler)
    print(f"[proxy] listening on {LISTEN_HOST}:{LISTEN_PORT} -> {UPSTREAM}", flush=True)
    try:
        server.serve_forever()
    except KeyboardInterrupt:
        pass

3. 先手动启动测试

cd ~/hermes-agent
venv/bin/python /opt/opencode-proxy/proxy.py

另开终端测试(把 KEY 换成你的 key):

curl -sN -o /dev/null -w "HTTP: %{http_code} | TTFB: %{time_starttransfer}s | 总: %{time_total}s\n" \
  --max-time 60 -X POST http://127.0.0.1:18080/v1/chat/completions \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer 你的key" \
  -d '{"model":"deepseek-v4-flash","messages":[{"role":"user","content":"hi"}],"max_tokens":5}'

预期:HTTP: 200,总耗时几秒。

4. 配置 systemd 开机自启

创建 /etc/systemd/system/opencode-proxy.service

[Unit]
Description=OpenCode curl_cffi TLS proxy
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
User=你的用户名
ExecStart=/home/你的用户名/hermes-agent/venv/bin/python /opt/opencode-proxy/proxy.py
Restart=always
RestartSec=3
Environment=PYTHONUNBUFFERED=1

[Install]
WantedBy=multi-user.target

启动:

sudo systemctl daemon-reload
sudo systemctl enable --now opencode-proxy
sudo systemctl status opencode-proxy   # 确认 active (running)

5. 修改 Hermes 配置

把 opencode-go 的 base_url 指向本地代理:

# 先确认 opencode-go 在 custom_providers 里的索引(从 0 开始)
hermes config get custom_providers | grep -n "name: opencode-go"

# 假设索引是 2,把 base_url 改成本地代理
hermes config set custom_providers.2.base_url "http://127.0.0.1:18080/v1"

验证:

hermes config get custom_providers.2.base_url
# 应输出 http://127.0.0.1:18080/v1

6. 重启 Hermes Gateway

systemctl --user restart hermes-gateway

五、验证是否生效

grep "API call" ~/.hermes/logs/agent.log | tail -5

修复前:latency=126.8s
修复后:latency=2.4s

也可以直接聊天测试。CLI 首次启动要加载技能/记忆,几十秒是正常的,以日志里的 latency= 为准

六、常见问题

1. 返回 404

路径重复了。代理脚本里 UPSTREAM = "https://opencode.ai/zen/go" 不能带 /v1(请求路径自带 /v1),否则变成 /v1/v1/chat/completions

2. 报 EmptyStreamError / chunks=0

代理用了 iter_lines() 或过滤了空行。SSE 事件用空行分隔,必须用 iter_content() 按原始字节转发。

3. 端口被占用

ss -tlnp | grep 18080   # 看谁占着
pkill -f "opencode-proxy/proxy.py"
sudo systemctl restart opencode-proxy

4. 改完还是慢

检查三件事:

sudo systemctl is-active opencode-proxy        # 代理在跑吗
hermes config get custom_providers.2.base_url  # 指向 127.0.0.1:18080 吗
grep "API call" ~/.hermes/logs/agent.log | tail -1  # 日志里 latency 多少

七、注意事项

  1. API Key 安全:本方案中 key 只存于 Hermes 配置,代理透传不改写。发布教程时务必用 *** 打码。
  2. 只影响 opencode-go:其他 provider(DeepSeek 官方、mimo 等)的 base_url 没改,完全直连。
  3. 代理挂了会怎样:Hermes 会快速报连接错误(比 120 秒卡死好发现得多),systemd 会自动拉起。
  4. 合规提醒:这只是改变 HTTP 客户端 TLS 特征,不涉及任何破解/绕过鉴权,请遵守 OpenCode 服务条款。
Logo

欢迎加入DeepSeek 技术社区。在这里,你可以找到志同道合的朋友,共同探索AI技术的奥秘。

更多推荐