Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
136 changes: 99 additions & 37 deletions core/relay/adaptor/openai/chat.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ type chatCompletionStreamState struct {
currentToolCall *relaymodel.ToolCall
currentToolCallID string
toolCallArgs string
hasToolCall bool
}

func responseModelName(meta *meta.Meta) string {
Expand All @@ -43,6 +44,29 @@ func responseModelName(meta *meta.Meta) string {
return meta.ActualModel
}

func responseToChatFinishReason(response *relaymodel.Response) relaymodel.FinishReason {
if response == nil {
return relaymodel.FinishReasonStop
}

if response.Status != relaymodel.ResponseStatusIncomplete {
return relaymodel.FinishReasonStop
}

if response.IncompleteDetails == nil {
return relaymodel.FinishReasonStop
}

switch response.IncompleteDetails.Reason {
case "max_output_tokens":
return relaymodel.FinishReasonLength
case "content_filter":
return relaymodel.FinishReasonContentFilter
default:
return relaymodel.FinishReasonStop
}
}

// handleResponseCreated handles response.created event for ChatCompletion
func (s *chatCompletionStreamState) handleResponseCreated(
event *relaymodel.ResponseStreamEvent,
Expand Down Expand Up @@ -103,6 +127,7 @@ func (s *chatCompletionStreamState) handleOutputItemAdded(

// Track function calls
if event.Item.Type == relaymodel.InputItemTypeFunctionCall {
s.hasToolCall = true
s.currentToolCallID = event.Item.ID
s.currentToolCall = &relaymodel.ToolCall{
ID: event.Item.CallID,
Expand Down Expand Up @@ -232,6 +257,11 @@ func (s *chatCompletionStreamState) handleResponseCompleted(

chatUsage := event.Response.Usage.ToChatUsage()

finishReason := responseToChatFinishReason(event.Response)
if finishReason == relaymodel.FinishReasonStop && s.hasToolCall {
finishReason = relaymodel.FinishReasonToolCalls
}

return &relaymodel.ChatCompletionsStreamResponse{
ID: s.messageID,
Object: relaymodel.ChatCompletionChunkObject,
Expand All @@ -240,7 +270,7 @@ func (s *chatCompletionStreamState) handleResponseCompleted(
Choices: []*relaymodel.ChatCompletionsStreamResponseChoice{
{
Index: 0,
FinishReason: relaymodel.FinishReasonStop,
FinishReason: finishReason,
},
},
Usage: &chatUsage,
Expand Down Expand Up @@ -1235,50 +1265,79 @@ func ConvertResponsesToChatCompletionResponse(

// Convert output items to choices
for _, outputItem := range responsesResp.Output {
if outputItem.Type != "" && outputItem.Type != relaymodel.InputItemTypeMessage {
continue
}
switch outputItem.Type {
case "", relaymodel.InputItemTypeMessage:
role := outputItem.Role
if role == "" {
role = relaymodel.RoleAssistant
}

choice := relaymodel.TextResponseChoice{
Index: 0, // Responses API doesn't have index, default to 0
Message: relaymodel.Message{
Role: outputItem.Role,
Content: "",
},
}
choice := relaymodel.TextResponseChoice{
Index: len(chatResp.Choices),
Message: relaymodel.Message{
Role: role,
Content: "",
},
}

// Convert content
var (
contentParts []string
toolCalls []relaymodel.ToolCall
)
var contentParts []string
for _, content := range outputItem.Content {
if (content.Type == "text" || content.Type == "output_text") && content.Text != "" {
contentParts = append(contentParts, content.Text)
}
}

for _, content := range outputItem.Content {
if (content.Type == "text" || content.Type == "output_text") && content.Text != "" {
contentParts = append(contentParts, content.Text)
if len(contentParts) > 0 {
choice.Message.Content = strings.Join(contentParts, "\n")
}
// Add tool call conversion if needed in the future
}

if len(contentParts) > 0 {
choice.Message.Content = strings.Join(contentParts, "\n")
}
choice.FinishReason = responseToChatFinishReason(&responsesResp)
chatResp.Choices = append(chatResp.Choices, &choice)

if len(toolCalls) > 0 {
choice.Message.ToolCalls = toolCalls
}
case relaymodel.InputItemTypeFunctionCall:
toolCallID := outputItem.CallID
if toolCallID == "" {
toolCallID = outputItem.ID
}

finishReason := responseToChatFinishReason(&responsesResp)
if finishReason == relaymodel.FinishReasonStop {
finishReason = relaymodel.FinishReasonToolCalls
}

chatResp.Choices = append(chatResp.Choices, &relaymodel.TextResponseChoice{
Index: len(chatResp.Choices),
Message: relaymodel.Message{
Role: relaymodel.RoleAssistant,
ToolCalls: []relaymodel.ToolCall{
{
Index: 0,
ID: toolCallID,
Type: relaymodel.ToolChoiceTypeFunction,
Function: relaymodel.Function{
Name: outputItem.Name,
Arguments: outputItem.Arguments,
},
},
},
},
FinishReason: finishReason,
})

// Set finish reason based on status
switch responsesResp.Status {
case relaymodel.ResponseStatusCompleted:
choice.FinishReason = relaymodel.FinishReasonStop
case relaymodel.ResponseStatusIncomplete:
choice.FinishReason = relaymodel.FinishReasonLength
case relaymodel.ResponseStatusFailed:
choice.FinishReason = relaymodel.FinishReasonStop
default:
continue
}
}

chatResp.Choices = append(chatResp.Choices, &choice)
if len(chatResp.Choices) == 0 {
chatResp.Choices = append(chatResp.Choices, &relaymodel.TextResponseChoice{
Index: 0,
Message: relaymodel.Message{
Role: relaymodel.RoleAssistant,
Content: "",
},
FinishReason: responseToChatFinishReason(&responsesResp),
})
}

// Convert usage
Expand Down Expand Up @@ -1446,6 +1505,7 @@ func ConvertResponsesToChatCompletionStreamResponse(

for scanner.Scan() && !stopStream {
data := scanner.Bytes()

if !render.IsValidSSEData(data) {
continue
}
Expand Down Expand Up @@ -1492,7 +1552,9 @@ func ConvertResponsesToChatCompletionStreamResponse(
chatStreamResp = state.handleFunctionCallArgumentsDelta(&event)
case relaymodel.EventOutputItemDone:
state.handleOutputItemDone(&event)
case relaymodel.EventResponseCompleted, relaymodel.EventResponseDone:
case relaymodel.EventResponseCompleted,
relaymodel.EventResponseIncomplete,
relaymodel.EventResponseDone:
chatStreamResp = state.handleResponseCompleted(&event)
case relaymodel.EventResponseFailed, relaymodel.EventError:
if wroteStream {
Expand Down
Loading
Loading