2026-06-04 · DECISION TRACE

AnyRouter Codex Adapter 接入 CPA:从假成功到可用工具桥

保留方案、指标、验证、复盘和治理判断,让结论能够追溯。

2026-06-04记录日期
决策记录记录类型
26章节数

AnyRouter Codex Adapter 接入 CPA:从假成功到可用工具桥

  • HTML: https://decision.ht1072.top/2026-06-04-anyrouter-codex-adapter-cpa-tool-bridge.html
  • Local HTML: [已移除本地路径]
  • Generated: 2026-06-04T14:33:59+08:00

AnyRouter Codex Adapter 接入 CPA:从假成功到可用工具桥的排障复盘

页面摘要区

核心结论: AnyRouter 不能直接作为普通 OpenAI-compatible upstream 挂入 CPA;必须通过本地 Codex-shape adapter 模拟 Codex CLI /v1/responses 请求,才能稳定把 gpt-5.5 暴露为 CPA alias ANY-5.5

推荐路径: 保留 CPA 原生 Codex OAuth 作为主路,ANY-5.5 作为实验/备用 provider;adapter 继续本地监听 127.0.0.1:8331,CPA 通过 openai-compatibility 接入。

关键依据: 直连 AnyRouter TLS 会 SSLV3_ALERT_HANDSHAKE_FAILURE,必须走 127.0.0.1:7890;普通 Chat/Responses 调用会出现 HTML 假 200 或 invalid codex request;Codex-shape body + headers 后,CPA 非流式和流式都能返回 OK,并且非流式 usage 可记录 token。

落地方式: 已实现 [已移除本地路径],已创建并启用 anyrouter-codex-adapter.service,已在 CPA config.yaml 加入 alias ANY-5.5,并补齐工具调用桥接,避免 terminalskill_viewweb_search 等工具参数 JSON 泄漏成正文。

风险与边界: 当前是 systemd --user 服务,不是 /etc/systemd/system 系统级 unit;user linger 当前为 no,如果需要开机无人登录自动启动,应额外执行 loginctl enable-linger ht 或迁移为 system unit。AnyRouter adapter 仍依赖上游对 Codex-shape 请求的兼容和本机 7890 代理。

行动清单: 继续把 ANY-5.5 当备用 provider 观察;若要作为主模型,需要压测多工具、多轮、长上下文和流式 usage;如需系统级托管,由涛哥执行 sudo/系统级 unit 安装。

背景

用户提供 AnyRouter:

  • 地址:https://anyrouter.top
  • 模型:gpt-5.5
  • 目标:写入 CPA 后虽然显示通了,但实际不能稳定用,尤其工具调用异常。

最初 CPA 侧出现过 “HTTP 200 成功” 记录,但深入检查后发现它们不是模型成功,而是 HTML/边缘防护页或空 token 记录。后续围绕 AnyRouter、CPA、Codex CLI 请求形态和工具桥接做了完整排障。

关键发现

1. CPA 里的早期 AnyRouter 成功是“假成功”

CPA-Manager usage 中有 provider=any 的记录,表面:

  • HTTP 200
  • failed=false
  • path 包含 /v1/chat/completions/v1/responses

但 raw evidence 显示:

  • Content-Type: text/html; charset=utf-8
  • X-Tengine-Error: denied by http_custom
  • tokens.total = 0

结论:这是边缘/WAF 返回的 HTML,不是模型 JSON/SSE 响应。

2. 真正成功的 23:36 附近调用是 CPA 原生 Codex OAuth

当时 request_id 6a103d71

  • provider: codex
  • executor_type: CodexExecutor
  • model: gpt-5.5-high
  • resolved_model: gpt-5.5
  • total_tokens: 119639

这条与 AnyRouter 无关。

3. AnyRouter 直连不能省代理

A/B 测试:

  • direct no proxy:SSLV3_ALERT_HANDSHAKE_FAILURE
  • via http://127.0.0.1:7890:可进入 API/SSE 层

因此 adapter service 当前必须保留:

HTTPS_PROXY=http://127.0.0.1:7890
HTTP_PROXY=http://127.0.0.1:7890

4. 普通 Chat→Responses adapter 不够,必须 Codex-shape

普通 adapter 请求会被 AnyRouter 判定:

  • must be stream request
  • invalid codex request

抓取 Codex CLI 0.130.0 真实请求体后确认关键形态:

  • /v1/responses
  • stream: true
  • store: false
  • include: ["reasoning.encrypted_content"]
  • prompt_cache_key
  • text: {"verbosity":"low"}
  • client_metadata.x-codex-installation-id
  • tool_choice: auto
  • parallel_tool_calls: true
  • Codex-style tools schema
  • headers: originator, session_id, x-codex-*, x-client-request-id, Codex User-Agent

补齐后,AnyRouter 返回真实 SSE,并可转换成 OpenAI Chat Completions。

已落地内容

Adapter 文件

[已移除本地路径]

能力:

  • GET /health
  • GET /v1/models
  • POST /v1/chat/completions
  • POST /v1/responses
  • Chat messages → Codex-shaped Responses input
  • OpenAI tools → Responses tools
  • Responses SSE → Chat Completions JSON / SSE
  • 工具调用 JSON 兜底 → Chat tool_calls

systemd --user service

当前已创建:

[已移除本地路径]

状态:

enabled
active
listen: 127.0.0.1:8331

unit 内容:

[Unit]
Description=AnyRouter Codex-shape Chat adapter
After=network-online.target

[Service]
Type=simple
EnvironmentFile=[已移除本地路径]
WorkingDirectory=[已移除本地路径]
ExecStart=/usr/bin/python3 [已移除本地路径]
Restart=on-failure
RestartSec=3
NoNewPrivileges=true
PrivateTmp=true

[Install]
WantedBy=default.target

注意:这是用户级 systemd。当前:

loginctl show-user ht -p Linger
Linger=no

如果要无人登录自动启动,应执行:

sudo loginctl enable-linger ht

或者迁移为系统级 unit。

Env 文件

[已移除本地路径]

权限:

600

包含:

  • ANYROUTER_KEY=<redacted>
  • ANYROUTER_ADAPTER_HOST=127.0.0.1
  • ANYROUTER_ADAPTER_PORT=8331
  • ANYROUTER_BASE=https://anyrouter.top/v1
  • ANYROUTER_DEFAULT_MODEL=gpt-5.5
  • ANYROUTER_MODELS=gpt-5.5
  • ANYROUTER_TIMEOUT=240
  • ANYROUTER_REASONING_EFFORT=medium
  • HTTPS_PROXY=http://127.0.0.1:7890
  • HTTP_PROXY=http://127.0.0.1:7890

CPA 配置

已备份:

[已移除本地路径]

新增 provider:

- name: AnyRouter-Adapter
  base-url: http://127.0.0.1:8331/v1
  api-key-entries:
    - api-key: dummy
  models:
    - name: gpt-5.5
      alias: ANY-5.5

CPA 已热加载,无需成功重启即可在 /v1/models 看到:

ANY-5.5

验证记录

本地 adapter health

GET http://127.0.0.1:8331/health

返回:

{"ok":true,"upstream":"https://anyrouter.top/v1","models":["gpt-5.5"]}

CPA 非流式 smoke

请求:

POST http://127.0.0.1:8317/v1/chat/completions
model: ANY-5.5
stream: false

返回:

{
  "message": {"role":"assistant", "content":"OK"},
  "usage": {"prompt_tokens":182,"completion_tokens":5,"total_tokens":187}
}

CPA 流式 smoke

请求:

model: ANY-5.5
stream: true

返回:

delta.role = assistant
delta.content = OK
finish_reason = stop
[DONE]

多轮历史修复

失败原因:adapter 把历史 assistant 消息转成了 input_text

AnyRouter 报:

{
  "param":"input[1].content[0]",
  "code":"invalid_value"
}

修复:assistant 历史用 output_text

验证:user → assistant → user 多轮请求成功:

total_tokens: 215
failed: 0

工具调用桥接修复

用户观察到正文出现:

{"background":false,"command":"date",...}

以及:

{"file_path":"","name":"hermes-agent"}

根因:AnyRouter/Codex 有时把工具参数 JSON 作为普通文本输出,adapter 之前没有转回 OpenAI Chat tool_calls

已补通用规范化层,覆盖:

  1. 裸参数 JSON:{"command":"date"}{"name":"hermes-agent","file_path":""}{"query":"Hermes Agent"}
  2. Markdown fenced JSON
  3. OpenAI wrapper:{"name":"terminal","arguments":{...}}
  4. tool_calls 数组
  5. 多工具调用数组
  6. tool_choice 强制指定
  7. Responses 原生 function_call
  8. 流式 delta.tool_calls

验证通过:

  • terminal nonstream → message.tool_calls[0].function.name=terminal
  • skill_view nonstream → skill_view
  • web_search nonstream → web_search
  • skill_view stream → delta.tool_calls,无正文 JSON 泄漏

离线回归测试

新增:

[已移除本地路径]

测试全部通过:

ok test_fenced_json_is_unwrapped
ok test_forced_tool_choice_disambiguates_overlap
ok test_openai_tool_calls_array_is_flattened
ok test_plain_json_without_matching_schema_is_not_tool_call
ok test_response_native_function_call_done_with_arguments
ok test_response_native_function_call_maps_to_chat_tool_call
ok test_skill_view_bare_json_infers_by_schema
ok test_terminal_bare_json_infers_by_required_command
ok test_top_level_array_multi_tool_calls
ok test_wrapper_name_arguments_is_respected

系统级 unit 建议

当前用户无权限直接写 /etc/systemd/system。如果要做成系统级 systemd,可执行:

sudo tee /etc/systemd/system/anyrouter-codex-adapter.service >/dev/null <<'EOF'
[Unit]
Description=AnyRouter Codex-shape Chat adapter
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
User=ht
EnvironmentFile=[已移除本地路径]
WorkingDirectory=[已移除本地路径]
ExecStart=/usr/bin/python3 [已移除本地路径]
Restart=on-failure
RestartSec=3
NoNewPrivileges=true
PrivateTmp=true

[Install]
WantedBy=multi-user.target
EOF

sudo systemctl daemon-reload
sudo systemctl enable --now anyrouter-codex-adapter.service
sudo systemctl status anyrouter-codex-adapter.service

如果保留 user service,只需要补:

sudo loginctl enable-linger ht

风险与边界

风险 1:依赖 AnyRouter 对 Codex-shape 请求的兼容

AnyRouter 若改变对 Codex CLI headers/body 的校验,adapter 可能需要同步调整。

风险 2:必须走 7890 代理

当前直连 TLS 握手失败。若 7890 不在,service 虽然启动,调用会上游失败。

风险 3:流式 usage 仍不完整

非流式 usage 可记录 token;流式 CPA-Manager usage 仍可能显示 total_tokens=0,因为 Chat SSE chunk 未完整回传 usage。

风险 4:工具 JSON 兜底不能无限激进

当前策略是高置信才转换:必须能匹配 tools schema / wrapper / tool_choice / 常见字段。低置信 JSON 会保留正文,避免误把正常 JSON 回答变成工具调用。

后续建议

  1. ANY-5.5 先作为实验/备用 provider,不替换 CPA 原生 Codex OAuth 主路。
  2. 如果要长期跑,建议补 loginctl enable-linger ht 或迁移系统级 unit。
  3. 后续继续观察工具调用:如出现新的 JSON 泄漏形态,优先扩展 test_anyrouter_codex_adapter_tools.py,再 patch adapter。
  4. 如果要用于 Hermes 主模型,建议再压测:多轮工具调用、tool result 回灌、并行工具、长上下文、流式工具调用与真实 WebUI/Feishu 两端表现。