Files
bifrost/core/providers/openai/chat.go
Beyhan Oğur 880f412e2c first commit
2026-04-26 21:52:23 +03:00

193 lines
7.2 KiB
Go

package openai
import (
"strings"
"github.com/maximhq/bifrost/core/providers/utils"
"github.com/maximhq/bifrost/core/schemas"
)
// ToBifrostChatRequest converts an OpenAI chat request to Bifrost format
func (req *OpenAIChatRequest) ToBifrostChatRequest(ctx *schemas.BifrostContext) *schemas.BifrostChatRequest {
provider, model := schemas.ParseModelString(req.Model, utils.CheckAndSetDefaultProvider(ctx, schemas.OpenAI))
return &schemas.BifrostChatRequest{
Provider: provider,
Model: model,
Input: ConvertOpenAIMessagesToBifrostMessages(req.Messages),
Params: &req.ChatParameters,
Fallbacks: schemas.ParseFallbacks(req.Fallbacks),
}
}
// ToOpenAIChatRequest converts a Bifrost chat completion request to OpenAI format
func ToOpenAIChatRequest(ctx *schemas.BifrostContext, bifrostReq *schemas.BifrostChatRequest) *OpenAIChatRequest {
if bifrostReq == nil || bifrostReq.Input == nil {
return nil
}
openaiReq := &OpenAIChatRequest{
Model: bifrostReq.Model,
Messages: ConvertBifrostMessagesToOpenAIMessages(bifrostReq.Input),
}
if bifrostReq.Params != nil {
openaiReq.ChatParameters = *bifrostReq.Params
if openaiReq.ChatParameters.MaxCompletionTokens != nil && *openaiReq.ChatParameters.MaxCompletionTokens < MinMaxCompletionTokens {
openaiReq.ChatParameters.MaxCompletionTokens = schemas.Ptr(MinMaxCompletionTokens)
}
// Drop user field if it exceeds OpenAI's 64 character limit
openaiReq.ChatParameters.User = SanitizeUserField(openaiReq.ChatParameters.User)
openaiReq.ExtraParams = bifrostReq.Params.ExtraParams
// Normalize tool parameters for deterministic JSON serialization (improves prompt caching)
if len(openaiReq.ChatParameters.Tools) > 0 {
normalizedTools := make([]schemas.ChatTool, len(openaiReq.ChatParameters.Tools))
for i, tool := range openaiReq.ChatParameters.Tools {
normalizedTools[i] = tool
if tool.Function != nil && tool.Function.Parameters != nil {
funcCopy := *tool.Function
funcCopy.Parameters = tool.Function.Parameters.Normalized()
normalizedTools[i].Function = &funcCopy
}
}
openaiReq.ChatParameters.Tools = normalizedTools
}
}
switch bifrostReq.Provider {
case schemas.OpenAI, schemas.Azure:
return openaiReq
case schemas.XAI:
openaiReq.filterOpenAISpecificParameters()
openaiReq.applyXAICompatibility(bifrostReq.Model)
return openaiReq
case schemas.Gemini:
openaiReq.filterOpenAISpecificParameters()
// Removing extra parameters that are not supported by Gemini
openaiReq.ServiceTier = nil
return openaiReq
case schemas.Mistral:
openaiReq.filterOpenAISpecificParameters()
openaiReq.applyMistralCompatibility()
return openaiReq
case schemas.Vertex:
openaiReq.filterOpenAISpecificParameters()
// Apply Mistral-specific transformations for Vertex Mistral models
if schemas.IsMistralModel(bifrostReq.Model) {
openaiReq.applyMistralCompatibility()
}
return openaiReq
case schemas.Fireworks:
// Fireworks uses prompt_cache_isolation_key for cache isolation on chat/completions.
// Preserve it before the generic filter strips prompt_cache_key.
if openaiReq.ChatParameters.PromptCacheKey != nil && openaiReq.PromptCacheIsolationKey == nil {
openaiReq.PromptCacheIsolationKey = openaiReq.ChatParameters.PromptCacheKey
}
// Fireworks supports predicted outputs; save before the filter strips them.
prediction := openaiReq.ChatParameters.Prediction
openaiReq.filterOpenAISpecificParameters()
openaiReq.ChatParameters.Prediction = prediction
return openaiReq
default:
// Check if provider is a custom provider
if isCustomProvider, ok := ctx.Value(schemas.BifrostContextKeyIsCustomProvider).(bool); ok && isCustomProvider {
return openaiReq
}
openaiReq.filterOpenAISpecificParameters()
return openaiReq
}
}
// Filter OpenAI Specific Parameters
func (req *OpenAIChatRequest) filterOpenAISpecificParameters() {
// Handle reasoning parameter: OpenAI uses effort-based reasoning
// Priority: effort (native) > max_tokens (estimated)
if req.ChatParameters.Reasoning != nil {
reasoningCopy := *req.ChatParameters.Reasoning
req.ChatParameters.Reasoning = &reasoningCopy
if req.ChatParameters.Reasoning.Effort != nil {
// Native field is provided, use it (and clear max_tokens)
effort := *req.ChatParameters.Reasoning.Effort
// Convert "minimal" to "low"; cap "xhigh"/"max" to "high" — OpenAI tops out at high.
switch effort {
case "minimal":
req.ChatParameters.Reasoning.Effort = schemas.Ptr("low")
case "xhigh", "max":
req.ChatParameters.Reasoning.Effort = schemas.Ptr("high")
}
// Clear max_tokens since OpenAI doesn't use it
req.ChatParameters.Reasoning.MaxTokens = nil
} else if req.ChatParameters.Reasoning.MaxTokens != nil {
// Estimate effort from max_tokens
maxTokens := *req.ChatParameters.Reasoning.MaxTokens
maxCompletionTokens := utils.GetMaxOutputTokensOrDefault(req.Model, DefaultCompletionMaxTokens)
if req.ChatParameters.MaxCompletionTokens != nil {
maxCompletionTokens = *req.ChatParameters.MaxCompletionTokens
}
effort := utils.GetReasoningEffortFromBudgetTokens(maxTokens, MinReasoningMaxTokens, maxCompletionTokens)
req.ChatParameters.Reasoning.Effort = schemas.Ptr(effort)
// Clear max_tokens since OpenAI doesn't use it
req.ChatParameters.Reasoning.MaxTokens = nil
}
}
if req.ChatParameters.Prediction != nil {
req.ChatParameters.Prediction = nil
}
if req.ChatParameters.PromptCacheKey != nil {
req.ChatParameters.PromptCacheKey = nil
}
if req.ChatParameters.PromptCacheRetention != nil {
req.ChatParameters.PromptCacheRetention = nil
}
if req.ChatParameters.Verbosity != nil {
req.ChatParameters.Verbosity = nil
}
if req.ChatParameters.Store != nil {
req.ChatParameters.Store = nil
}
if req.ChatParameters.WebSearchOptions != nil {
req.ChatParameters.WebSearchOptions = nil
}
}
// applyMistralCompatibility applies Mistral-specific transformations to the request
func (req *OpenAIChatRequest) applyMistralCompatibility() {
// Mistral uses max_tokens instead of max_completion_tokens
if req.MaxCompletionTokens != nil {
req.MaxTokens = req.MaxCompletionTokens
req.MaxCompletionTokens = nil
}
// Mistral does not support ToolChoiceStruct, only simple tool choice strings are supported
if req.ToolChoice != nil && req.ToolChoice.ChatToolChoiceStruct != nil {
req.ToolChoice.ChatToolChoiceStr = schemas.Ptr("any")
req.ToolChoice.ChatToolChoiceStruct = nil
}
}
// applyXAICompatibility applies xAI-specific transformations to the request
func (req *OpenAIChatRequest) applyXAICompatibility(model string) {
// Only apply filters if this is a grok reasoning model
if !schemas.IsGrokReasoningModel(model) {
return
}
req.ChatParameters.PresencePenalty = nil
// Only non-mini grok-3 models support frequency_penalty and stop
// grok-3-mini only supports reasoning_effort in reasoning mode
if !strings.Contains(model, "grok-3") || strings.Contains(model, "grok-3-mini") {
req.ChatParameters.FrequencyPenalty = nil
req.ChatParameters.Stop = nil
}
// Only grok-3-mini supports reasoning_effort
if req.ChatParameters.Reasoning != nil &&
!strings.Contains(model, "grok-3-mini") {
// Clear reasoning_effort for non-grok-3-mini models
req.ChatParameters.Reasoning.Effort = nil
}
}