sub2api/backend/internal/pkg/openai_compat/upstream_capability.go

116 lines
4.8 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 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
)
// ResponsesSupportMode 描述账号级 Responses API 路由覆盖模式。
type ResponsesSupportMode string
const (
// ResponsesSupportModeAuto 表示跟随自动探测结果。
ResponsesSupportModeAuto ResponsesSupportMode = "auto"
// ResponsesSupportModeForceResponses 强制使用 /v1/responses。
ResponsesSupportModeForceResponses ResponsesSupportMode = "force_responses"
// ResponsesSupportModeForceChatCompletions 强制使用 /v1/chat/completions。
ResponsesSupportModeForceChatCompletions ResponsesSupportMode = "force_chat_completions"
)
// ExtraKeyResponsesMode 是 accounts.extra JSON 中存储手动覆盖模式的键名。
// 值类型为 stringauto=跟随探测force_responses=强制 Responses
// force_chat_completions=强制 Chat Completions。
const ExtraKeyResponsesMode = "openai_responses_mode"
// ExtraKeyResponsesSupported 是 accounts.extra JSON 中存储自动探测结果的键名。
// 值类型为 booltrue=支持、false=不支持、键缺失=未探测。
const ExtraKeyResponsesSupported = "openai_responses_supported"
// NormalizeResponsesSupportMode 归一化账号级 Responses API 路由覆盖模式。
// 缺失或非法值按 auto 处理,以保持存量行为。
func NormalizeResponsesSupportMode(mode string) ResponsesSupportMode {
switch ResponsesSupportMode(mode) {
case ResponsesSupportModeForceResponses:
return ResponsesSupportModeForceResponses
case ResponsesSupportModeForceChatCompletions:
return ResponsesSupportModeForceChatCompletions
default:
return ResponsesSupportModeAuto
}
}
// ResolveResponsesSupport 从账号的 extra map 中读取手动覆盖模式与探测标记。
//
// 标记缺失或类型不匹配时返回 ResponsesSupportUnknown——调用方应按
// "未探测=保留旧行为=走 Responses" 处理(参见 ShouldUseResponsesAPI
func ResolveResponsesSupport(extra map[string]any) AccountResponsesSupport {
if extra == nil {
return ResponsesSupportUnknown
}
if mode, ok := extra[ExtraKeyResponsesMode].(string); ok {
switch NormalizeResponsesSupportMode(mode) {
case ResponsesSupportModeForceResponses:
return ResponsesSupportYes
case ResponsesSupportModeForceChatCompletions:
return ResponsesSupportNo
}
}
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
}