feat: support Responses to Chat (#5787)
* fix(openai): harden Chat-to-Responses compatibility Add a shared Responses-to-Chat stream state machine and use it from the OpenAI relay path. Preserve assistant text alongside tool calls, bind tool argument deltas by output_index, map incomplete finish reasons, support reasoning/custom tool events, and buffer upstream SSE for non-stream Chat clients. Add deterministic service tests and relay SSE tests for the conversion path. Related to #5745. * refactor: rename openaicompat to relayconvert for improved clarity * feat(gemini): support responses request conversion * feat: add responses to chat conversion support * fix: harden responses chat conversion edge cases
This commit is contained in:
@@ -63,6 +63,7 @@ const (
|
||||
AdvancedCustomConverterAnthropicMessagesToOpenAIChatCompletions = "anthropic_messages_to_openai_chat_completions"
|
||||
AdvancedCustomConverterOpenAIChatCompletionsToAnthropicMessages = "openai_chat_completions_to_anthropic_messages"
|
||||
AdvancedCustomConverterOpenAIChatCompletionsToOpenAIResponses = "openai_chat_completions_to_openai_responses"
|
||||
AdvancedCustomConverterOpenAIResponsesToOpenAIChatCompletions = "openai_responses_to_openai_chat_completions"
|
||||
AdvancedCustomConverterGeminiGenerateContentToOpenAIChatCompletions = "gemini_generate_content_to_openai_chat_completions"
|
||||
AdvancedCustomConverterOpenAIChatCompletionsToGeminiGenerateContent = "openai_chat_completions_to_gemini_generate_content"
|
||||
)
|
||||
@@ -147,6 +148,7 @@ func IsAdvancedCustomConverterAllowed(converter string) bool {
|
||||
AdvancedCustomConverterAnthropicMessagesToOpenAIChatCompletions,
|
||||
AdvancedCustomConverterOpenAIChatCompletionsToAnthropicMessages,
|
||||
AdvancedCustomConverterOpenAIChatCompletionsToOpenAIResponses,
|
||||
AdvancedCustomConverterOpenAIResponsesToOpenAIChatCompletions,
|
||||
AdvancedCustomConverterGeminiGenerateContentToOpenAIChatCompletions,
|
||||
AdvancedCustomConverterOpenAIChatCompletionsToGeminiGenerateContent:
|
||||
return true
|
||||
@@ -240,6 +242,10 @@ func validateAdvancedCustomConverterPath(index int, incomingPath string, convert
|
||||
if incomingPath == "/v1/chat/completions" {
|
||||
return nil
|
||||
}
|
||||
case AdvancedCustomConverterOpenAIResponsesToOpenAIChatCompletions:
|
||||
if incomingPath == "/v1/responses" {
|
||||
return nil
|
||||
}
|
||||
case AdvancedCustomConverterGeminiGenerateContentToOpenAIChatCompletions:
|
||||
if strings.Contains(incomingPath, ":generateContent") || strings.Contains(incomingPath, ":streamGenerateContent") {
|
||||
return nil
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
package dto
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestAdvancedCustomValidateResponsesToChatConverterPath(t *testing.T) {
|
||||
valid := &AdvancedCustomConfig{
|
||||
Routes: []AdvancedCustomRoute{
|
||||
{
|
||||
IncomingPath: "/v1/responses",
|
||||
UpstreamPath: "/v1/chat/completions",
|
||||
Converter: AdvancedCustomConverterOpenAIResponsesToOpenAIChatCompletions,
|
||||
},
|
||||
},
|
||||
}
|
||||
require.NoError(t, valid.Validate())
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
incomingPath string
|
||||
}{
|
||||
{name: "chat completions", incomingPath: "/v1/chat/completions"},
|
||||
{name: "responses compact", incomingPath: "/v1/responses/compact"},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
config := &AdvancedCustomConfig{
|
||||
Routes: []AdvancedCustomRoute{
|
||||
{
|
||||
IncomingPath: tt.incomingPath,
|
||||
UpstreamPath: "/v1/chat/completions",
|
||||
Converter: AdvancedCustomConverterOpenAIResponsesToOpenAIChatCompletions,
|
||||
},
|
||||
},
|
||||
}
|
||||
err := config.Validate()
|
||||
require.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "converter does not match incoming_path")
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user