fix(audio): 兼容百炼 OpenAI 音频输入

为阿里云百炼 OpenAI-compatible Chat 统一保留标准 input_audio,并将旧版 audio_url 转换为 input_audio,同时根据 MIME 或扩展名补齐音频格式。

补充请求资产水合测试,确认 input_audio.data 会转换为裸 Base64 且保留 format。

验证:在 apps/api 执行 env -u AI_GATEWAY_TEST_DATABASE_URL go test ./... -count=1 全部通过。
This commit is contained in:
2026-07-27 23:50:17 +08:00
parent 95776c84f6
commit 046c16fc69
4 changed files with 297 additions and 0 deletions
+73
View File
@@ -344,6 +344,79 @@ func TestOpenAIClientChatContract(t *testing.T) {
}
}
func TestOpenAIClientAliyunChatSendsStandardInputAudioAndNormalizesLegacyAudioURL(t *testing.T) {
var captured map[string]any
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if err := json.NewDecoder(r.Body).Decode(&captured); err != nil {
t.Fatalf("decode request: %v", err)
}
_ = json.NewEncoder(w).Encode(map[string]any{
"id": "chatcmpl-audio",
"object": "chat.completion",
"model": captured["model"],
"choices": []any{map[string]any{
"message": map[string]any{"role": "assistant", "content": "ok"},
}},
})
}))
defer server.Close()
_, err := (OpenAIClient{HTTPClient: server.Client()}).Run(context.Background(), Request{
Kind: "chat.completions",
Model: "qwen-omni",
Body: map[string]any{
"model": "qwen-omni",
"messages": []any{
map[string]any{
"role": "user",
"content": []any{
map[string]any{
"type": "input_audio",
"input_audio": map[string]any{
"data": "https://cdn.example.com/audio/standard.wav",
"format": "wav",
},
},
map[string]any{
"type": "audio_url",
"audio_url": map[string]any{"url": "https://cdn.example.com/audio/legacy.m4a"},
},
},
},
},
},
Candidate: store.RuntimeModelCandidate{
Provider: "aliyun-bailian-openai",
BaseURL: server.URL,
ProviderModelName: "qwen-omni",
Credentials: map[string]any{"apiKey": "test-key"},
},
})
if err != nil {
t.Fatalf("run aliyun openai-compatible client: %v", err)
}
messages, _ := captured["messages"].([]any)
message, _ := messages[0].(map[string]any)
content, _ := message["content"].([]any)
standard, _ := content[0].(map[string]any)
if standard["type"] != "input_audio" {
t.Fatalf("standard input_audio was not preserved: %+v", standard)
}
standardAudio, _ := standard["input_audio"].(map[string]any)
if standardAudio["data"] != "https://cdn.example.com/audio/standard.wav" || standardAudio["format"] != "wav" {
t.Fatalf("unexpected standard input_audio: %+v", standardAudio)
}
legacy, _ := content[1].(map[string]any)
if legacy["type"] != "input_audio" {
t.Fatalf("legacy audio_url was not normalized: %+v", legacy)
}
legacyAudio, _ := legacy["input_audio"].(map[string]any)
if legacyAudio["data"] != "https://cdn.example.com/audio/legacy.m4a" || legacyAudio["format"] != "m4a" {
t.Fatalf("unexpected normalized legacy audio: %+v", legacyAudio)
}
}
func TestOpenAIClientImageEditUsesMultipartWithMultipleImages(t *testing.T) {
var contentType string
var receivedFields map[string][]string