Files
sub2api/backend/internal/pkg/openai_compat/upstream_capability_test.go

56 lines
1.9 KiB
Go
Raw Normal View History

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
2026-04-30 19:25:45 +08:00
package openai_compat
import "testing"
func TestResolveResponsesSupport(t *testing.T) {
tests := []struct {
name string
extra map[string]any
want AccountResponsesSupport
}{
{"nil extra", nil, ResponsesSupportUnknown},
{"empty extra", map[string]any{}, ResponsesSupportUnknown},
{"key missing", map[string]any{"other": "value"}, ResponsesSupportUnknown},
{"value true", map[string]any{ExtraKeyResponsesSupported: true}, ResponsesSupportYes},
{"value false", map[string]any{ExtraKeyResponsesSupported: false}, ResponsesSupportNo},
{"value wrong type string", map[string]any{ExtraKeyResponsesSupported: "true"}, ResponsesSupportUnknown},
{"value wrong type number", map[string]any{ExtraKeyResponsesSupported: 1}, ResponsesSupportUnknown},
{"value nil", map[string]any{ExtraKeyResponsesSupported: nil}, ResponsesSupportUnknown},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
got := ResolveResponsesSupport(tc.extra)
if got != tc.want {
t.Errorf("ResolveResponsesSupport(%v) = %v, want %v", tc.extra, got, tc.want)
}
})
}
}
func TestShouldUseResponsesAPI(t *testing.T) {
tests := []struct {
name string
extra map[string]any
want bool
}{
// 关键不变量:未探测必须返回 true保留旧行为
{"unknown defaults to true (preserve old behavior)", nil, true},
{"unknown empty defaults to true", map[string]any{}, true},
{"unknown wrong type defaults to true", map[string]any{ExtraKeyResponsesSupported: "yes"}, true},
// 已探测:标记决定
{"explicitly supported", map[string]any{ExtraKeyResponsesSupported: true}, true},
{"explicitly unsupported", map[string]any{ExtraKeyResponsesSupported: false}, false},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
got := ShouldUseResponsesAPI(tc.extra)
if got != tc.want {
t.Errorf("ShouldUseResponsesAPI(%v) = %v, want %v", tc.extra, got, tc.want)
}
})
}
}