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
3 changes: 2 additions & 1 deletion core/bifrost.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"fmt"
"math/rand"
"slices"
"strings"
"sync"
"time"

Expand Down Expand Up @@ -229,7 +230,7 @@ func (bifrost *Bifrost) SelectKeyFromProviderForModel(providerKey schemas.ModelP
// filter out keys which dont support the model
var supportedKeys []schemas.Key
for _, key := range keys {
if slices.Contains(key.Models, model) {
if slices.Contains(key.Models, model) && strings.TrimSpace(key.Value) != "" {
supportedKeys = append(supportedKeys, key)
}
}
Expand Down
4 changes: 4 additions & 0 deletions core/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ require (
github.com/aws/aws-sdk-go-v2 v1.36.3
github.com/aws/aws-sdk-go-v2/config v1.29.14
github.com/goccy/go-json v0.10.5
github.com/stretchr/testify v1.10.0
github.com/valyala/fasthttp v1.60.0
golang.org/x/oauth2 v0.30.0
)
Expand All @@ -26,8 +27,11 @@ require (
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.30.1 // indirect
github.com/aws/aws-sdk-go-v2/service/sts v1.33.19 // indirect
github.com/aws/smithy-go v1.22.3 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/klauspost/compress v1.18.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
golang.org/x/net v0.39.0 // indirect
golang.org/x/text v0.24.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
Comment thread
coderabbitai[bot] marked this conversation as resolved.
)
10 changes: 10 additions & 0 deletions core/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,18 @@ github.com/aws/aws-sdk-go-v2/service/sts v1.33.19 h1:1XuUZ8mYJw9B6lzAkXhqHlJd/Xv
github.com/aws/aws-sdk-go-v2/service/sts v1.33.19/go.mod h1:cQnB8CUnxbMU82JvlqjKR2HBOm3fe9pWorWBza6MBJ4=
github.com/aws/smithy-go v1.22.3 h1:Z//5NuZCSW6R4PhQ93hShNbyBbn8BWCmCVCt+Q8Io5k=
github.com/aws/smithy-go v1.22.3/go.mod h1:t1ufH5HMublsJYulve2RKmHDC15xu1f26kHCp/HgceI=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasthttp v1.60.0 h1:kBRYS0lOhVJ6V+bYN8PqAHELKHtXqwq9zNMLKx1MBsw=
Expand All @@ -46,3 +52,7 @@ golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI=
golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU=
golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0=
golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
195 changes: 147 additions & 48 deletions core/providers/anthropic.go
Original file line number Diff line number Diff line change
Expand Up @@ -278,7 +278,7 @@ func (provider *AnthropicProvider) TextCompletion(ctx context.Context, model, ke
// It formats the request, sends it to Anthropic, and processes the response.
// Returns a BifrostResponse containing the completion results or an error if the request fails.
func (provider *AnthropicProvider) ChatCompletion(ctx context.Context, model, key string, messages []schemas.BifrostMessage, params *schemas.ModelParameters) (*schemas.BifrostResponse, *schemas.BifrostError) {
formattedMessages, preparedParams := prepareAnthropicChatRequest(model, messages, params)
formattedMessages, preparedParams := prepareAnthropicChatRequest(messages, params)

// Merge additional parameters
requestBody := mergeConfig(map[string]interface{}{
Expand Down Expand Up @@ -317,12 +317,32 @@ func (provider *AnthropicProvider) ChatCompletion(ctx context.Context, model, ke
return bifrostResponse, nil
}

func prepareAnthropicChatRequest(model string, messages []schemas.BifrostMessage, params *schemas.ModelParameters) ([]map[string]interface{}, map[string]interface{}) {
// buildAnthropicImageSourceMap creates the "source" map for an Anthropic image content part.
func buildAnthropicImageSourceMap(imgContent *schemas.ImageContent) map[string]interface{} {
if imgContent == nil || imgContent.Type == nil {
return nil
}

sourceMap := map[string]interface{}{
"type": *imgContent.Type, // "base64" or "url"
}

if *imgContent.Type == "url" {
sourceMap["url"] = imgContent.URL
} else {
if imgContent.MediaType != nil {
sourceMap["media_type"] = *imgContent.MediaType
}
sourceMap["data"] = imgContent.URL // URL field is used for base64 data string
}
return sourceMap
}
Comment thread
Pratham-Mishra04 marked this conversation as resolved.

func prepareAnthropicChatRequest(messages []schemas.BifrostMessage, params *schemas.ModelParameters) ([]map[string]interface{}, map[string]interface{}) {
// Add system messages if present
var systemMessages []BedrockAnthropicSystemMessage
for _, msg := range messages {
if msg.Role == schemas.ModelChatMessageRoleSystem {
//TODO handling image inputs here
if msg.Content != nil {
systemMessages = append(systemMessages, BedrockAnthropicSystemMessage{
Text: *msg.Content,
Expand All @@ -334,57 +354,61 @@ func prepareAnthropicChatRequest(model string, messages []schemas.BifrostMessage
// Format messages for Anthropic API
var formattedMessages []map[string]interface{}
for _, msg := range messages {
if msg.Role != schemas.ModelChatMessageRoleSystem {
if (msg.UserMessage != nil && msg.UserMessage.ImageContent != nil) || (msg.ToolMessage != nil && msg.ToolMessage.ImageContent != nil) {
var messageImageContent schemas.ImageContent
if msg.UserMessage != nil && msg.UserMessage.ImageContent != nil {
messageImageContent = *msg.UserMessage.ImageContent
} else if msg.ToolMessage != nil && msg.ToolMessage.ImageContent != nil {
messageImageContent = *msg.ToolMessage.ImageContent
}

var content []map[string]interface{}

imageContent := map[string]interface{}{
"type": "image",
"source": map[string]interface{}{
"type": messageImageContent.Type,
},
}
var content []interface{}

// Handle different image source types
if messageImageContent.Type != nil && *messageImageContent.Type == "url" {
imageContent["source"].(map[string]interface{})["url"] = messageImageContent.URL
} else {
imageContent["source"].(map[string]interface{})["media_type"] = messageImageContent.MediaType
imageContent["source"].(map[string]interface{})["data"] = messageImageContent.URL
if msg.Role != schemas.ModelChatMessageRoleSystem {
if msg.Role == schemas.ModelChatMessageRoleTool && msg.ToolMessage != nil && msg.ToolMessage.ToolCallID != nil {
toolCallResult := map[string]interface{}{
"type": "tool_result",
"tool_use_id": *msg.ToolMessage.ToolCallID,
}

content = append(content, imageContent)
var toolCallResultContent []map[string]interface{}

// Add text content if present
if msg.Content != nil {
content = append(content, map[string]interface{}{
toolCallResultContent = append(toolCallResultContent, map[string]interface{}{
"type": "text",
"text": *msg.Content,
})
}

// Add thinking content if present in AssistantMessage
if msg.AssistantMessage != nil && msg.AssistantMessage.Thought != nil {
content = append(content, map[string]interface{}{
"type": "thinking",
"thinking": *msg.AssistantMessage.Thought,
})
if (msg.UserMessage != nil && msg.UserMessage.ImageContent != nil) || (msg.ToolMessage != nil && msg.ToolMessage.ImageContent != nil) {
var messageImageContent schemas.ImageContent
if msg.UserMessage != nil && msg.UserMessage.ImageContent != nil {
messageImageContent = *msg.UserMessage.ImageContent
} else if msg.ToolMessage != nil && msg.ToolMessage.ImageContent != nil {
messageImageContent = *msg.ToolMessage.ImageContent
}

imageSource := buildAnthropicImageSourceMap(&messageImageContent)
if imageSource != nil {
toolCallResultContent = append(toolCallResultContent, map[string]interface{}{
"type": "image",
"source": imageSource,
})
}
}

formattedMessages = append(formattedMessages, map[string]interface{}{
"role": msg.Role,
"content": content,
})
toolCallResult["content"] = toolCallResultContent

content = append(content, toolCallResult)
} else {
// Handle non-image messages
var content []map[string]interface{}
if (msg.UserMessage != nil && msg.UserMessage.ImageContent != nil) || (msg.ToolMessage != nil && msg.ToolMessage.ImageContent != nil) {
var messageImageContent schemas.ImageContent
if msg.UserMessage != nil && msg.UserMessage.ImageContent != nil {
messageImageContent = *msg.UserMessage.ImageContent
} else if msg.ToolMessage != nil && msg.ToolMessage.ImageContent != nil {
messageImageContent = *msg.ToolMessage.ImageContent
}

imageSource := buildAnthropicImageSourceMap(&messageImageContent)
if imageSource != nil {
content = append(content, map[string]interface{}{
"type": "image",
"source": imageSource,
})
}
}

// Add text content if present
if msg.Content != nil && *msg.Content != "" {
Expand Down Expand Up @@ -428,14 +452,13 @@ func prepareAnthropicChatRequest(model string, messages []schemas.BifrostMessage
}
}
}
}

// Always use content block structure
if len(content) > 0 {
formattedMessages = append(formattedMessages, map[string]interface{}{
"role": msg.Role,
"content": content,
})
}
if len(content) > 0 {
formattedMessages = append(formattedMessages, map[string]interface{}{
"role": msg.Role,
"content": content,
})
}
}
}
Expand All @@ -456,6 +479,21 @@ func prepareAnthropicChatRequest(model string, messages []schemas.BifrostMessage
preparedParams["tools"] = tools
}

// Transform tool choice if present
if params != nil && params.ToolChoice != nil {
switch toolChoice := params.ToolChoice.Type; toolChoice {
case schemas.ToolChoiceTypeFunction:
preparedParams["tool_choice"] = map[string]interface{}{
"type": "tool",
"name": params.ToolChoice.Function.Name,
}
default:
preparedParams["tool_choice"] = map[string]interface{}{
"type": toolChoice,
}
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Comment thread
Pratham-Mishra04 marked this conversation as resolved.
}

if len(systemMessages) > 0 {
var messages []string
for _, message := range systemMessages {
Expand All @@ -465,6 +503,67 @@ func prepareAnthropicChatRequest(model string, messages []schemas.BifrostMessage
preparedParams["system"] = strings.Join(messages, " ")
}

// Post-process formattedMessages for tool call results
processedFormattedMessages := []map[string]interface{}{} // Use a new slice
i := 0
for i < len(formattedMessages) {
currentMsg := formattedMessages[i]
currentRole, roleOk := getRoleFromMessage(currentMsg)

if !roleOk || currentRole == "" {
// If role is of an unexpected type, missing, or empty, treat as non-tool message
processedFormattedMessages = append(processedFormattedMessages, currentMsg)
i++
continue
}

if currentRole == schemas.ModelChatMessageRoleTool {
// Content of a tool message is the toolCallResult map
// Initialize accumulatedToolResults with the content of the current tool message.
var accumulatedToolResults []interface{}

// Safely extract content from current message
if content, ok := currentMsg["content"].([]interface{}); ok {
accumulatedToolResults = content
} else {
// If content is not the expected type, skip this message
processedFormattedMessages = append(processedFormattedMessages, currentMsg)
i++
continue
}

// Look ahead for more sequential tool messages
j := i + 1
for j < len(formattedMessages) {
nextMsg := formattedMessages[j]
nextRole, nextRoleOk := getRoleFromMessage(nextMsg)

if !nextRoleOk || nextRole == "" || nextRole != schemas.ModelChatMessageRoleTool {
break // Not a sequential tool message or role is invalid/missing/empty
}

// Safely extract content from next message
if nextContent, ok := nextMsg["content"].([]interface{}); ok {
accumulatedToolResults = append(accumulatedToolResults, nextContent...)
}
j++
}
Comment thread
Pratham-Mishra04 marked this conversation as resolved.

// Create a new message with role User and accumulated content
mergedMsg := map[string]interface{}{
"role": schemas.ModelChatMessageRoleUser, // Final role is User
"content": accumulatedToolResults,
}
processedFormattedMessages = append(processedFormattedMessages, mergedMsg)
i = j // Advance main loop index past all merged messages
} else {
// Not a tool message, add it as is
processedFormattedMessages = append(processedFormattedMessages, currentMsg)
i++
}
}
formattedMessages = processedFormattedMessages // Update with processed messages

return formattedMessages, preparedParams
}

Expand Down
17 changes: 1 addition & 16 deletions core/providers/azure.go
Original file line number Diff line number Diff line change
Expand Up @@ -297,22 +297,7 @@ func (provider *AzureProvider) TextCompletion(ctx context.Context, model, key, t
// It formats the request, sends it to Azure, and processes the response.
// Returns a BifrostResponse containing the completion results or an error if the request fails.
func (provider *AzureProvider) ChatCompletion(ctx context.Context, model, key string, messages []schemas.BifrostMessage, params *schemas.ModelParameters) (*schemas.BifrostResponse, *schemas.BifrostError) {
preparedParams := prepareParams(params)

// Format messages for Azure API
var formattedMessages []map[string]interface{}
for _, msg := range messages {
message := map[string]interface{}{
"role": msg.Role,
}

// Only add content if it's not nil
if msg.Content != nil {
message["content"] = *msg.Content
}

formattedMessages = append(formattedMessages, message)
}
formattedMessages, preparedParams := prepareOpenAIChatRequest(messages, params)

// Merge additional parameters
requestBody := mergeConfig(map[string]interface{}{
Expand Down
Loading