From bff6f6679bd4ba4defff95ff4f1a8c88bf7e1f4f Mon Sep 17 00:00:00 2001 From: Luis Pater Date: Mon, 1 Sep 2025 10:20:50 +0800 Subject: [PATCH] Refactor translator packages for OpenAI Chat Completions - Renamed `openai` packages to `chat_completions` across translator modules. - Introduced `openai_responses_handlers` with handlers for `/v1/models` and OpenAI-compatible chat completions endpoints. - Updated constants and registry identifiers for OpenAI response type. - Simplified request/response conversions and added detailed retry/error handling. - Added `golang.org/x/crypto` for additional cryptographic functions. --- .../gemini_openai-responses_request.go | 5 - .../openai_openai-responses_request.go | 183 ++++++++++++++++++ ...go => openai_openai-responses_response.go} | 0 3 files changed, 183 insertions(+), 5 deletions(-) delete mode 100644 internal/translator/openai/openai/responses/gemini_openai-responses_request.go create mode 100644 internal/translator/openai/openai/responses/openai_openai-responses_request.go rename internal/translator/openai/openai/responses/{gemini_openai-responses_response.go => openai_openai-responses_response.go} (100%) diff --git a/internal/translator/openai/openai/responses/gemini_openai-responses_request.go b/internal/translator/openai/openai/responses/gemini_openai-responses_request.go deleted file mode 100644 index b186369f..00000000 --- a/internal/translator/openai/openai/responses/gemini_openai-responses_request.go +++ /dev/null @@ -1,5 +0,0 @@ -package responses - -func ConvertOpenAIResponsesRequestToOpenAIChatCompletions(modelName string, rawJSON []byte, stream bool) []byte { - return nil -} diff --git a/internal/translator/openai/openai/responses/openai_openai-responses_request.go b/internal/translator/openai/openai/responses/openai_openai-responses_request.go new file mode 100644 index 00000000..e9cecbb2 --- /dev/null +++ b/internal/translator/openai/openai/responses/openai_openai-responses_request.go @@ -0,0 +1,183 @@ +package responses + +import ( + "github.com/tidwall/gjson" + "github.com/tidwall/sjson" +) + +// ConvertOpenAIResponsesRequestToOpenAIChatCompletions converts OpenAI responses format to OpenAI chat completions format. +// It transforms the OpenAI responses API format (with instructions and input array) into the standard +// OpenAI chat completions format (with messages array and system content). +// +// The conversion handles: +// 1. Model name and streaming configuration +// 2. Instructions to system message conversion +// 3. Input array to messages array transformation +// 4. Tool definitions and tool choice conversion +// 5. Function calls and function results handling +// 6. Generation parameters mapping (max_tokens, reasoning, etc.) +// +// Parameters: +// - modelName: The name of the model to use for the request +// - rawJSON: The raw JSON request data in OpenAI responses format +// - stream: A boolean indicating if the request is for a streaming response +// +// Returns: +// - []byte: The transformed request data in OpenAI chat completions format +func ConvertOpenAIResponsesRequestToOpenAIChatCompletions(modelName string, rawJSON []byte, stream bool) []byte { + // Base OpenAI chat completions template with default values + out := `{"model":"","messages":[],"stream":false}` + + root := gjson.ParseBytes(rawJSON) + + // Set model name + out, _ = sjson.Set(out, "model", modelName) + + // Set stream configuration + out, _ = sjson.Set(out, "stream", stream) + + // Map generation parameters from responses format to chat completions format + if maxTokens := root.Get("max_output_tokens"); maxTokens.Exists() { + out, _ = sjson.Set(out, "max_tokens", maxTokens.Int()) + } + + if parallelToolCalls := root.Get("parallel_tool_calls"); parallelToolCalls.Exists() { + out, _ = sjson.Set(out, "parallel_tool_calls", parallelToolCalls.Bool()) + } + + // Convert instructions to system message + if instructions := root.Get("instructions"); instructions.Exists() { + systemMessage := `{"role":"system","content":""}` + systemMessage, _ = sjson.Set(systemMessage, "content", instructions.String()) + out, _ = sjson.SetRaw(out, "messages.-1", systemMessage) + } + + // Convert input array to messages + if input := root.Get("input"); input.Exists() && input.IsArray() { + input.ForEach(func(_, item gjson.Result) bool { + itemType := item.Get("type").String() + + switch itemType { + case "message": + // Handle regular message conversion + role := item.Get("role").String() + message := `{"role":"","content":""}` + message, _ = sjson.Set(message, "role", role) + + if content := item.Get("content"); content.Exists() && content.IsArray() { + var messageContent string + var toolCalls []interface{} + + content.ForEach(func(_, contentItem gjson.Result) bool { + contentType := contentItem.Get("type").String() + + switch contentType { + case "input_text": + text := contentItem.Get("text").String() + if messageContent != "" { + messageContent += "\n" + text + } else { + messageContent = text + } + case "output_text": + text := contentItem.Get("text").String() + if messageContent != "" { + messageContent += "\n" + text + } else { + messageContent = text + } + } + return true + }) + + if messageContent != "" { + message, _ = sjson.Set(message, "content", messageContent) + } + + if len(toolCalls) > 0 { + message, _ = sjson.Set(message, "tool_calls", toolCalls) + } + } + + out, _ = sjson.SetRaw(out, "messages.-1", message) + + case "function_call": + // Handle function call conversion to assistant message with tool_calls + assistantMessage := `{"role":"assistant","tool_calls":[]}` + + toolCall := `{"id":"","type":"function","function":{"name":"","arguments":""}}` + + if callId := item.Get("call_id"); callId.Exists() { + toolCall, _ = sjson.Set(toolCall, "id", callId.String()) + } + + if name := item.Get("name"); name.Exists() { + toolCall, _ = sjson.Set(toolCall, "function.name", name.String()) + } + + if arguments := item.Get("arguments"); arguments.Exists() { + toolCall, _ = sjson.Set(toolCall, "function.arguments", arguments.String()) + } + + assistantMessage, _ = sjson.SetRaw(assistantMessage, "tool_calls.0", toolCall) + out, _ = sjson.SetRaw(out, "messages.-1", assistantMessage) + + case "function_call_output": + // Handle function call output conversion to tool message + toolMessage := `{"role":"tool","tool_call_id":"","content":""}` + + if callId := item.Get("call_id"); callId.Exists() { + toolMessage, _ = sjson.Set(toolMessage, "tool_call_id", callId.String()) + } + + if output := item.Get("output"); output.Exists() { + toolMessage, _ = sjson.Set(toolMessage, "content", output.String()) + } + + out, _ = sjson.SetRaw(out, "messages.-1", toolMessage) + } + + return true + }) + } + + // Convert tools from responses format to chat completions format + if tools := root.Get("tools"); tools.Exists() && tools.IsArray() { + var chatCompletionsTools []interface{} + + tools.ForEach(func(_, tool gjson.Result) bool { + chatTool := `{"type":"function","function":{}}` + + // Convert tool structure from responses format to chat completions format + function := `{"name":"","description":"","parameters":{}}` + + if name := tool.Get("name"); name.Exists() { + function, _ = sjson.Set(function, "name", name.String()) + } + + if description := tool.Get("description"); description.Exists() { + function, _ = sjson.Set(function, "description", description.String()) + } + + if parameters := tool.Get("parameters"); parameters.Exists() { + function, _ = sjson.SetRaw(function, "parameters", parameters.Raw) + } + + chatTool, _ = sjson.SetRaw(chatTool, "function", function) + chatCompletionsTools = append(chatCompletionsTools, gjson.Parse(chatTool).Value()) + + return true + }) + + if len(chatCompletionsTools) > 0 { + out, _ = sjson.Set(out, "tools", chatCompletionsTools) + } + } + + // Convert tool_choice if present + if toolChoice := root.Get("tool_choice"); toolChoice.Exists() { + out, _ = sjson.Set(out, "tool_choice", toolChoice.String()) + } + + return []byte(out) +} diff --git a/internal/translator/openai/openai/responses/gemini_openai-responses_response.go b/internal/translator/openai/openai/responses/openai_openai-responses_response.go similarity index 100% rename from internal/translator/openai/openai/responses/gemini_openai-responses_response.go rename to internal/translator/openai/openai/responses/openai_openai-responses_response.go