Skip to content

bug: remote_compaction_v2 被无条件降级为 /responses/compact,导致原生 V2 上游路由到 *-openai-compact #3970

Description

@1070920013wh

问题概述

当前 Sub2API 会识别 Codex remote_compaction_v2 请求体中的:

{"type":"compaction_trigger"}

但识别后会无条件把请求从:

POST /v1/responses

改写为:

POST /v1/responses/compact

这对于只支持旧版 /responses/compact 的上游是一种兼容方案,但对于已经原生支持新版 V2 协议的 OpenAI-compatible API Key 上游,会导致请求错误地进入旧版 compact 路由。

本次实际表现为:客户端发送的模型是 gpt-5.6-sol,经过 Sub2API 后,上游最终尝试调度 gpt-5.6-sol-openai-compact,并返回 model_not_found

环境

  • Sub2API:0.1.150
  • Codex CLI:0.144.0-alpha.4
  • Codex Feature:remote_compaction_v2 = true
  • 账号类型:OpenAI API Key
  • 上游类型:自定义 OpenAI-compatible Responses API
  • 模型:gpt-5.6-sol
  • 上游原生支持:POST /v1/responses + compaction_trigger

API Key 已脱敏,不在 Issue 中提供。

新版 V2 请求

Codex 当前发送的是标准 V2 请求:

POST /v1/responses
Content-Type: application/json
Accept: text/event-stream
{
  "model": "gpt-5.6-sol",
  "instructions": "压缩对话上下文。",
  "input": [
    {
      "role": "user",
      "content": [
        {
          "type": "input_text",
          "text": "用于验证新版远程压缩协议的上下文。"
        }
      ]
    },
    {
      "type": "compaction_trigger"
    }
  ],
  "stream": true
}

新版协议的压缩身份由 compaction_trigger 表示,模型字段仍应保持 gpt-5.6-sol,不需要使用 gpt-5.6-sol-openai-compact

实际结果

普通请求

通过 Sub2API 请求:

{
  "model": "gpt-5.6-sol",
  "input": "只回复 OK",
  "stream": false
}

结果:

HTTP 200
model = gpt-5.6-sol
status = completed

说明普通模型、账号和上游本身可用。

V2 压缩请求,stream=false

/v1/responses 请求体中加入:

{"type":"compaction_trigger"}

结果:

HTTP 503
{
  "error": {
    "code": "model_not_found",
    "message": "No available channel for model gpt-5.6-sol-openai-compact under group ...",
    "type": "new_api_error"
  }
}

相关请求 ID:

Sub2API Request ID:
200ffcd8-df86-485f-b9fb-d7e8494dbdde

Upstream Request ID:
202607100845101554353358268d9d6vIGMFIZL

V2 压缩请求,stream=true

使用 Codex 实际采用的 stream:true 请求时,连接被直接断开:

curl: (52) Empty reply from server
HTTP 000

这可能还会与当前的 compact SSE bridge/keepalive 错误处理相关,但不是本 Issue 的主要根因。

绕过 Sub2API 直连上游

相同的:

POST /v1/responses
model = gpt-5.6-sol
input 包含 compaction_trigger
stream = true

直连上游可以返回:

HTTP 200
response.output_item.done
item.type = compaction_summary
response.completed

因此上游已经支持原生 V2。失败是 Sub2API 将 V2 请求转换成旧 /responses/compact 后产生的。

当前源码行为

当前 normalizeOpenAIResponsesCompactRequest() 会检测普通 /responses 请求中的 compaction_trigger,随后直接改写 URL:

if !isCompactRequest &&
    isBareOpenAIResponsesPath(c) &&
    service.HasCompactionTriggerInInput(body) {
    c.Request.URL.Path =
        strings.TrimRight(c.Request.URL.Path, "/") + "/compact"
}

源码:

https://github.com/Wei-Shaw/sub2api/blob/main/backend/internal/handler/openai_gateway_handler.go#L583-L618

现有测试也明确要求:

/v1/responses
    ↓
/v1/responses/compact

并删除 streamstoreprompt_cache_key

https://github.com/Wei-Shaw/sub2api/blob/main/backend/internal/handler/openai_gateway_compact_body_signal_test.go

之后 Sub2API 再把旧版 unary compact JSON 合成为新版 SSE:

https://github.com/Wei-Shaw/sub2api/blob/main/backend/internal/service/openai_compact_stream_bridge.go

因此当前实现并不是原生透传 remote_compaction_v2,而是:

V2 body signal
    ↓
强制转换为旧 /responses/compact
    ↓
调用旧 compact 上游
    ↓
再把 unary JSON 合成为 V2 SSE

与已有 Issue 的区别

#3777

#3777

#3777 解决的是:

  • V2 compaction_trigger 未被识别
  • compact SSE 输出项丢失
  • Codex 报 expected exactly one compaction output item, got 0

其修复方案是把 body-signal V2 请求“提升”为旧 /responses/compact 请求。

本 Issue 指出该方案存在新的兼容边界:

并非所有上游都需要降级到 /responses/compact。已经支持原生 V2 的上游应保持 /responses + compaction_trigger,否则会错误进入 *-openai-compact 路由。

#3934

#3934

#3934 报告了 GPT-5.6 压缩失败现象,但没有说明 V2 被强制转换成旧 compact 端点的完整链路。

#3961

#3961

#3961 是 compact 失败后的 keepalive writer panic。本次 stream:true 出现 Empty reply from server 可能与其相关,但本 Issue 主要关注请求被错误降级的问题。

期望行为

Sub2API 应区分两类压缩能力。

原生 V2

POST /v1/responses
input 包含 compaction_trigger

应当:

  1. 保持上游路径 /v1/responses
  2. 保持 stream:true
  3. 保留 compaction_trigger
  4. 模型保持 gpt-5.6-sol
  5. 使用普通 model_mapping
  6. 不应用 compact_model_mapping
  7. 不进入旧版 requireCompact 调度
  8. 原样透传 compaction_summary/compaction SSE 输出项

旧版 compact

POST /v1/responses/compact

或者明确配置为只支持旧协议的账号,可以继续使用现有兼容链路:

  1. /responses/compact
  2. unary JSON
  3. compact model mapping
  4. SSE bridge

建议方案

增加账号级压缩传输能力配置,例如:

compaction_transport = auto | native_v2 | legacy_compact

建议语义:

  • native_v2
    • /responses + compaction_trigger 原样透传
    • 不改路径和模型
  • legacy_compact
    • 使用当前 /responses/compact 兼容桥
  • auto
    • API Key、自定义 base_url 优先探测原生 V2
    • OAuth/ChatGPT internal endpoint 可继续使用旧 compact 兼容路径

由于当前 path 改写发生在账号选择之前,可能需要:

  1. 先识别并标记 compaction_trigger
  2. 账号选择时区分 native_v2legacy_compact
  3. 选定账号后再决定是否改写为 /responses/compact

不建议仅通过:

gpt-5.6-sol-openai-compact -> gpt-5.6-sol

解决,因为这仍然走旧版端点,没有真正使用原生 V2,而且会继续依赖 SSE bridge。

验收标准

  • 原生 V2 账号收到的路径仍是 /v1/responses
  • 请求体保留 compaction_triggerstream:true
  • 上游模型仍为 gpt-5.6-sol
  • 不出现 gpt-5.6-sol-openai-compact
  • Codex 能收到一个 compaction_summary/compaction 输出项
  • 最终收到 response.completed
  • /responses/compact 客户端和 OAuth 兼容链路不回归
  • 普通 /responses 请求不受影响
  • V2 上游失败时返回有效错误或 response.failed,不直接断开连接

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions