first commit
This commit is contained in:
192
core/providers/openai/chat.go
Normal file
192
core/providers/openai/chat.go
Normal file
@@ -0,0 +1,192 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user