mirror of
https://gitee.com/wanwujie/sub2api
synced 2026-04-23 08:04:45 +08:00
feat: 添加 OpenAI Chat Completions 兼容端点
基于 @yulate 在 PR #648 (commit 0bb6a392) 的工作,解决了与最新 main 分支的合并冲突。 原始功能(@yulate): - 添加 /v1/chat/completions 和 /chat/completions 兼容端点 - 将 Chat Completions 请求转换为 Responses API 格式并转换回来 - 添加 API Key 直连转发支持 - 包含单元测试 Co-authored-by: yulate <yulate@users.noreply.github.com>
This commit is contained in:
132
backend/internal/service/openai_chat_completions_test.go
Normal file
132
backend/internal/service/openai_chat_completions_test.go
Normal file
@@ -0,0 +1,132 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestConvertChatCompletionsToResponses(t *testing.T) {
|
||||
req := map[string]any{
|
||||
"model": "gpt-4o",
|
||||
"messages": []any{
|
||||
map[string]any{
|
||||
"role": "user",
|
||||
"content": "hello",
|
||||
},
|
||||
map[string]any{
|
||||
"role": "assistant",
|
||||
"tool_calls": []any{
|
||||
map[string]any{
|
||||
"id": "call_1",
|
||||
"type": "function",
|
||||
"function": map[string]any{
|
||||
"name": "ping",
|
||||
"arguments": "{}",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
map[string]any{
|
||||
"role": "tool",
|
||||
"tool_call_id": "call_1",
|
||||
"content": "ok",
|
||||
"response": "ignored",
|
||||
"response_time": 1,
|
||||
},
|
||||
},
|
||||
"functions": []any{
|
||||
map[string]any{
|
||||
"name": "ping",
|
||||
"description": "ping tool",
|
||||
"parameters": map[string]any{"type": "object"},
|
||||
},
|
||||
},
|
||||
"function_call": map[string]any{"name": "ping"},
|
||||
}
|
||||
|
||||
converted, err := ConvertChatCompletionsToResponses(req)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "gpt-4o", converted["model"])
|
||||
|
||||
input, ok := converted["input"].([]any)
|
||||
require.True(t, ok)
|
||||
require.Len(t, input, 3)
|
||||
|
||||
toolCall := findInputItemByType(input, "tool_call")
|
||||
require.NotNil(t, toolCall)
|
||||
require.Equal(t, "call_1", toolCall["call_id"])
|
||||
|
||||
toolOutput := findInputItemByType(input, "function_call_output")
|
||||
require.NotNil(t, toolOutput)
|
||||
require.Equal(t, "call_1", toolOutput["call_id"])
|
||||
|
||||
tools, ok := converted["tools"].([]any)
|
||||
require.True(t, ok)
|
||||
require.Len(t, tools, 1)
|
||||
|
||||
require.Equal(t, map[string]any{"name": "ping"}, converted["tool_choice"])
|
||||
}
|
||||
|
||||
func TestConvertResponsesToChatCompletion(t *testing.T) {
|
||||
resp := map[string]any{
|
||||
"id": "resp_123",
|
||||
"model": "gpt-4o",
|
||||
"created_at": 1700000000,
|
||||
"output": []any{
|
||||
map[string]any{
|
||||
"type": "message",
|
||||
"role": "assistant",
|
||||
"content": []any{
|
||||
map[string]any{
|
||||
"type": "output_text",
|
||||
"text": "hi",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"usage": map[string]any{
|
||||
"input_tokens": 2,
|
||||
"output_tokens": 3,
|
||||
},
|
||||
}
|
||||
body, err := json.Marshal(resp)
|
||||
require.NoError(t, err)
|
||||
|
||||
converted, err := ConvertResponsesToChatCompletion(body)
|
||||
require.NoError(t, err)
|
||||
|
||||
var chat map[string]any
|
||||
require.NoError(t, json.Unmarshal(converted, &chat))
|
||||
require.Equal(t, "chat.completion", chat["object"])
|
||||
|
||||
choices, ok := chat["choices"].([]any)
|
||||
require.True(t, ok)
|
||||
require.Len(t, choices, 1)
|
||||
|
||||
choice, ok := choices[0].(map[string]any)
|
||||
require.True(t, ok)
|
||||
message, ok := choice["message"].(map[string]any)
|
||||
require.True(t, ok)
|
||||
require.Equal(t, "hi", message["content"])
|
||||
|
||||
usage, ok := chat["usage"].(map[string]any)
|
||||
require.True(t, ok)
|
||||
require.Equal(t, float64(2), usage["prompt_tokens"])
|
||||
require.Equal(t, float64(3), usage["completion_tokens"])
|
||||
require.Equal(t, float64(5), usage["total_tokens"])
|
||||
}
|
||||
|
||||
func findInputItemByType(items []any, itemType string) map[string]any {
|
||||
for _, item := range items {
|
||||
itemMap, ok := item.(map[string]any)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if itemMap["type"] == itemType {
|
||||
return itemMap
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user