三亩地 三亩地SAN MU DI · CODE DIARY
ARTICLE DETAIL

日记详情

真实记录编程学习的某一天,欢迎挑你感兴趣的翻一翻。

hermes使用OpenCode Go订阅模型慢?本地curl_cffi代理一招搞定

hermes使用OpenCode Go订阅模型慢?本地curl_cffi代理一招搞定

适用场景: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-mpipinstallcurl_cffi

验证:

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

2. 创建代理脚本

sudomkdir-p/opt/opencode-proxysudochown$USER:$USER/opt/opencode-proxy

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

#!/usr/bin/env python3"""opencode.ai 本地代理:curl_cffi 模拟 Chrome TLS 指纹转发请求"""importhttp.serverimportsocketserverimportsysfromcurl_cffiimportrequests# 注意:不要带 /v1,Hermes 请求路径自带 /v1UPSTREAM="https://opencode.ai/zen/go"IMPERSONATE="chrome"# 模拟 Chrome 浏览器 TLS 指纹LISTEN_HOST="127.0.0.1"LISTEN_PORT=18080classHandler(http.server.BaseHTTPRequestHandler):# HTTP/1.0:连接结束以 EOF 表示 SSE 流结束protocol_version="HTTP/1.0"deflog_message(self,fmt,*args):sys.stderr.write("[proxy] %s\n"%(fmt%args))def_forward(self):length=int(self.headers.get("Content-Length",0)or0)body=self.rfile.read(length)iflengthelseb""headers={"Content-Type":self.headers.get("Content-Type","application/json"),"Authorization":self.headers.get("Authorization",""),"Accept":"application/json",}url=UPSTREAM+self.pathtry:response=requests.post(url,content=body,headers=headers,impersonate=IMPERSONATE,stream=True,timeout=300,)exceptExceptionasexc: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())returnself.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 解析失败forchunkinresponse.iter_content(chunk_size=8192):ifchunk:self.wfile.write(chunk)self.wfile.flush()except(BrokenPipeError,ConnectionResetError):passexceptException:passdo_POST=_forward do_GET=_forwardclassThreadingServer(socketserver.ThreadingMixIn,http.server.HTTPServer):daemon_threads=Trueallow_reuse_address=Trueif__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()exceptKeyboardInterrupt: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-time60-XPOST 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

启动:

sudosystemctl daemon-reloadsudosystemctlenable--nowopencode-proxysudosystemctl 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 configsetcustom_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--userrestart 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|grep18080# 看谁占着pkill-f"opencode-proxy/proxy.py"sudosystemctl restart opencode-proxy

4. 改完还是慢

检查三件事:

sudosystemctl 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 服务条款。
← 返回列表