mirror of
https://gitee.com/wanwujie/sub2api
synced 2026-05-05 05:30:44 +08:00
fix(openai-gateway): route APIKey accounts to /v1/chat/completions when upstream lacks Responses API
OpenAI APIKey accounts with base_url pointing to third-party OpenAI-compatible
upstreams (DeepSeek, Kimi, GLM, Qwen, etc.) were failing because the gateway
unconditionally converted Chat Completions requests to Responses format and
forwarded to {base_url}/v1/responses, which only exists on OpenAI's official
endpoint.
Detection-based routing:
- Probe upstream capability on account create/update via a minimal POST to
/v1/responses; HTTP 404/405 means 'unsupported', any other response means
'supported'.
- Persist result as accounts.extra.openai_responses_supported (bool).
- ForwardAsChatCompletions branches at function entry: APIKey accounts with
explicit support=false go through new forwardAsRawChatCompletions which
passthrough-forwards CC body to /v1/chat/completions without protocol
conversion.
Default behavior for accounts without the marker preserves the legacy
'always Responses' path — existing OpenAI APIKey accounts that were working
before this change continue to work without modification (the 'reality is
evidence' principle: an account that has been running implies upstream
capability).
Probe is fired async after Create / Update / BatchCreate; failures only log,
never block the admin flow. BulkUpdate omitted (low signal of base_url
changes; can be added if needed).
Implementation:
- New pkg internal/pkg/openai_compat: marker key + ShouldUseResponsesAPI
- New service file openai_apikey_responses_probe.go: probe + persist
- New service file openai_gateway_chat_completions_raw.go: CC pass-through
- Account test endpoint short-circuits with explicit message for
probed-unsupported accounts (full CC test path is a TODO)
Zero schema changes, zero migrations, zero frontend changes, zero wire
modifications — all wired through existing AccountTestService injection.
Closes: DeepSeek-OpenAI account (id=128) production failure
This commit is contained in:
75
backend/internal/pkg/openai_compat/upstream_capability.go
Normal file
75
backend/internal/pkg/openai_compat/upstream_capability.go
Normal file
@@ -0,0 +1,75 @@
|
||||
// Package openai_compat 提供 OpenAI 协议族在不同上游间的能力差异判定工具。
|
||||
//
|
||||
// 背景:sub2api 的 OpenAI APIKey 账号通过 base_url 接入多种第三方 OpenAI 兼容上游
|
||||
// (DeepSeek、Kimi、GLM、Qwen 等)。这些上游普遍只支持 /v1/chat/completions,
|
||||
// 不存在 /v1/responses 端点。但网关历史代码无差别走 CC→Responses 转换并打到
|
||||
// /v1/responses,导致兼容上游 404。
|
||||
//
|
||||
// 本包提供基于"账号探测标记"的能力判定,配合
|
||||
// internal/service/openai_apikey_responses_probe.go 在创建/修改账号时一次性
|
||||
// 探测并落标。
|
||||
//
|
||||
// 设计取舍:
|
||||
// - 不维护静态 host 白名单——避免新增厂商时必须改代码(讨论沉淀于
|
||||
// pensieve/short-term/knowledge/upstream-capability-detection-design-tradeoffs)
|
||||
// - 标记缺失时默认 true(即"走 Responses"),保持与重构前老代码完全一致的存量
|
||||
// 账号行为("现状即证据"原则;详见
|
||||
// pensieve/short-term/maxims/preserve-existing-runtime-behavior-when-replacing-logic-in-stateful-systems)
|
||||
package openai_compat
|
||||
|
||||
// AccountResponsesSupport 描述账号上游对 OpenAI Responses API 的支持状态。
|
||||
//
|
||||
// 仅用于 platform=openai + type=apikey 的账号;其他账号类型不应调用本包判定。
|
||||
type AccountResponsesSupport int
|
||||
|
||||
const (
|
||||
// ResponsesSupportUnknown 表示账号尚未完成能力探测(extra 字段缺失)。
|
||||
// 上游路由层应按"现状即证据"原则默认走 Responses,保持与重构前一致。
|
||||
ResponsesSupportUnknown AccountResponsesSupport = iota
|
||||
|
||||
// ResponsesSupportYes 探测确认上游支持 /v1/responses。
|
||||
ResponsesSupportYes
|
||||
|
||||
// ResponsesSupportNo 探测确认上游不支持 /v1/responses,应走
|
||||
// /v1/chat/completions 直转路径。
|
||||
ResponsesSupportNo
|
||||
)
|
||||
|
||||
// ExtraKeyResponsesSupported 是 accounts.extra JSON 中存储探测结果的键名。
|
||||
// 值类型为 bool:true=支持、false=不支持、键缺失=未探测。
|
||||
const ExtraKeyResponsesSupported = "openai_responses_supported"
|
||||
|
||||
// ResolveResponsesSupport 从账号的 extra map 中读取探测标记。
|
||||
//
|
||||
// 标记缺失或类型不匹配时返回 ResponsesSupportUnknown——调用方应按
|
||||
// "未探测=保留旧行为=走 Responses" 处理(参见 ShouldUseResponsesAPI)。
|
||||
func ResolveResponsesSupport(extra map[string]any) AccountResponsesSupport {
|
||||
if extra == nil {
|
||||
return ResponsesSupportUnknown
|
||||
}
|
||||
v, ok := extra[ExtraKeyResponsesSupported]
|
||||
if !ok {
|
||||
return ResponsesSupportUnknown
|
||||
}
|
||||
supported, ok := v.(bool)
|
||||
if !ok {
|
||||
return ResponsesSupportUnknown
|
||||
}
|
||||
if supported {
|
||||
return ResponsesSupportYes
|
||||
}
|
||||
return ResponsesSupportNo
|
||||
}
|
||||
|
||||
// ShouldUseResponsesAPI 判断 OpenAI APIKey 账号的入站 /v1/chat/completions 请求
|
||||
// 是否应走"CC→Responses 转换 + 上游 /v1/responses"路径。
|
||||
//
|
||||
// 返回 true 的两种情况:
|
||||
// 1. 账号已探测确认支持 Responses
|
||||
// 2. 账号未探测(标记缺失)——按"现状即证据"原则保留旧行为
|
||||
//
|
||||
// 仅当账号已探测且确认不支持时返回 false,此时调用方应走 CC 直转路径
|
||||
// (详见 internal/service/openai_gateway_chat_completions_raw.go)。
|
||||
func ShouldUseResponsesAPI(extra map[string]any) bool {
|
||||
return ResolveResponsesSupport(extra) != ResponsesSupportNo
|
||||
}
|
||||
Reference in New Issue
Block a user