Skip to content
Closed
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
50 changes: 47 additions & 3 deletions core/providers/anthropic.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ type AnthropicStreamEvent struct {
Index *int `json:"index,omitempty"`
ContentBlock *AnthropicContentBlock `json:"content_block,omitempty"`
Delta *AnthropicDelta `json:"delta,omitempty"`
Usage *schemas.LLMUsage `json:"usage,omitempty"`
Usage *AnthropicUsage `json:"usage,omitempty"`
Error *AnthropicStreamError `json:"error,omitempty"`
}

Expand All @@ -84,7 +84,35 @@ type AnthropicStreamMessage struct {
Model string `json:"model"`
StopReason *string `json:"stop_reason"`
StopSequence *string `json:"stop_sequence"`
Usage *schemas.LLMUsage `json:"usage"`
Usage *AnthropicUsage `json:"usage"`
}

// AnthropicUsage represents usage information in Anthropic format
type AnthropicUsage struct {
InputTokens int `json:"input_tokens,omitempty"`
OutputTokens int `json:"output_tokens"`
CacheCreationInputTokens int `json:"cache_creation_input_tokens,omitempty"`
CacheReadInputTokens int `json:"cache_read_input_tokens,omitempty"`
}

func (u *AnthropicUsage) ToLLMUsage() *schemas.LLMUsage {
if u == nil {
return nil
}

llmUsage := &schemas.LLMUsage{
PromptTokens: u.InputTokens,
CompletionTokens: u.OutputTokens,
TotalTokens: u.InputTokens + u.OutputTokens,
}

if u.CacheReadInputTokens > 0 {
llmUsage.TokenDetails = &schemas.TokenDetails{
CachedTokens: u.CacheReadInputTokens,
}
}

return llmUsage
}
Comment on lines +90 to 116
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot Jul 23, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Consider including cache creation tokens in TokenDetails.

The ToLLMUsage() method only includes CacheReadInputTokens in the TokenDetails but not CacheCreationInputTokens. If the schema supports it, consider including both cache token types for complete usage tracking.


🏁 Script executed:

#!/bin/bash
# Description: Check if TokenDetails struct supports cache creation tokens

# Look for TokenDetails struct definition to see what fields it supports
ast-grep --pattern 'type TokenDetails struct {
  $$$
}'

Length of output: 299


TokenDetails missing cache creation tokens support

The TokenDetails struct (core/schemas/bifrost.go) only defines:

  • CachedTokens int
  • AudioTokens int

It doesn’t have a field for cache-creation tokens, so CacheCreationInputTokens cannot be included in ToLLMUsage() as-is.

If you intend to track creation tokens, please:

• Add a new field in TokenDetails, e.g.

type TokenDetails struct {
    CachedTokens        int `json:"cached_tokens,omitempty"`
    AudioTokens         int `json:"audio_tokens,omitempty"`
    CreatedCachedTokens int `json:"created_cached_tokens,omitempty"`
}

• Update ToLLMUsage() (core/providers/anthropic.go) accordingly:

 if u.CacheReadInputTokens > 0 {
     llmUsage.TokenDetails = &schemas.TokenDetails{
         CachedTokens: u.CacheReadInputTokens,
+        CreatedCachedTokens: u.CacheCreationInputTokens,
     }
 }

If you don’t need to track creation tokens, you can ignore CacheCreationInputTokens.

🤖 Prompt for AI Agents
In core/providers/anthropic.go lines 90 to 116, the ToLLMUsage method sets
TokenDetails with CachedTokens but does not handle CacheCreationInputTokens
because the TokenDetails struct lacks a field for creation tokens. To fix this,
first add a new field like CreatedCachedTokens int
`json:"created_cached_tokens,omitempty"` to the TokenDetails struct in
core/schemas/bifrost.go. Then update the ToLLMUsage method to include
CacheCreationInputTokens by setting CreatedCachedTokens in TokenDetails when
CacheCreationInputTokens is greater than zero, ensuring both cached and creation
tokens are tracked properly.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@fbzhong we can add this new field to the schema if required

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!


// AnthropicContentBlock represents a content block in Anthropic responses.
Expand Down Expand Up @@ -487,6 +515,10 @@ func prepareAnthropicChatRequest(messages []schemas.BifrostMessage, params *sche
"tool_use_id": *msg.ToolMessage.ToolCallID,
}

if msg.ToolMessage.IsError != nil {
toolCallResult["is_error"] = *msg.ToolMessage.IsError
}

var toolCallResultContent []map[string]interface{}

if msg.Content.ContentStr != nil {
Expand Down Expand Up @@ -871,6 +903,7 @@ func handleAnthropicStreaming(
// Track minimal state needed for response format
var messageID string
var modelName string
var usage *AnthropicUsage

// Track SSE event parsing state
var eventType string
Expand Down Expand Up @@ -899,6 +932,8 @@ func handleAnthropicStreaming(
continue
}

// logger.Debug(fmt.Sprintf("Received event: %s, %s", eventType, eventData))

Comment on lines +935 to +936
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick (assertive)

Consider removing commented debug logging.

If debug logging is no longer needed, consider removing it entirely rather than leaving it commented out.

-			// logger.Debug(fmt.Sprintf("Received event: %s, %s", eventType, eventData))
-
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// logger.Debug(fmt.Sprintf("Received event: %s, %s", eventType, eventData))
🤖 Prompt for AI Agents
In core/providers/anthropic.go around lines 931 to 932, there is a commented-out
debug logging statement. Since it is no longer needed, remove this commented
line entirely to keep the code clean and maintainable.

// Handle different event types
switch eventType {
case "message_start":
Expand All @@ -910,6 +945,7 @@ func handleAnthropicStreaming(
if event.Message != nil {
messageID = event.Message.ID
modelName = event.Message.Model
usage = event.Message.Usage
}

case "content_block_start":
Expand Down Expand Up @@ -948,6 +984,7 @@ func handleAnthropicStreaming(
},
},
},
Usage: usage.ToLLMUsage(),
ExtraFields: schemas.BifrostResponseExtraFields{
Provider: providerType,
},
Expand Down Expand Up @@ -986,6 +1023,7 @@ func handleAnthropicStreaming(
},
},
},
Usage: usage.ToLLMUsage(),
ExtraFields: schemas.BifrostResponseExtraFields{
Provider: providerType,
},
Expand Down Expand Up @@ -1027,6 +1065,7 @@ func handleAnthropicStreaming(
},
},
},
Usage: usage.ToLLMUsage(),
ExtraFields: schemas.BifrostResponseExtraFields{
Provider: providerType,
},
Expand Down Expand Up @@ -1065,6 +1104,7 @@ func handleAnthropicStreaming(
},
},
},
Usage: usage.ToLLMUsage(),
ExtraFields: schemas.BifrostResponseExtraFields{
Provider: providerType,
},
Expand Down Expand Up @@ -1096,6 +1136,7 @@ func handleAnthropicStreaming(
},
},
},
Usage: usage.ToLLMUsage(),
ExtraFields: schemas.BifrostResponseExtraFields{
Provider: providerType,
},
Expand Down Expand Up @@ -1128,14 +1169,16 @@ func handleAnthropicStreaming(
}

// Handle delta changes to the top-level message
if event.Usage != nil && usage != nil {
usage.OutputTokens = event.Usage.OutputTokens
}

// Send usage information immediately if present
if event.Usage != nil {
streamResponse := &schemas.BifrostResponse{
ID: messageID,
Object: "chat.completion.chunk",
Model: modelName,
Usage: event.Usage,
Choices: []schemas.BifrostResponseChoice{
{
Index: 0,
Expand All @@ -1145,6 +1188,7 @@ func handleAnthropicStreaming(
FinishReason: event.Delta.StopReason,
},
},
Usage: usage.ToLLMUsage(),
ExtraFields: schemas.BifrostResponseExtraFields{
Provider: providerType,
},
Expand Down
41 changes: 36 additions & 5 deletions core/providers/openai.go
Original file line number Diff line number Diff line change
Expand Up @@ -222,12 +222,23 @@ func prepareOpenAIChatRequest(messages []schemas.BifrostMessage, params *schemas
for _, msg := range messages {
if msg.Role == schemas.ModelChatMessageRoleAssistant {
assistantMessage := map[string]interface{}{
"role": msg.Role,
"content": msg.Content,
"role": msg.Role,
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not needed, bifrost content blocks follow openai's format only so we can directly embed the content

}
if msg.AssistantMessage != nil && msg.AssistantMessage.ToolCalls != nil {
assistantMessage["tool_calls"] = *msg.AssistantMessage.ToolCalls
}
if msg.Content.ContentStr != nil {
assistantMessage["content"] = *msg.Content.ContentStr
} else if msg.Content.ContentBlocks != nil && len(*msg.Content.ContentBlocks) > 0 {
var sb strings.Builder
for _, block := range *msg.Content.ContentBlocks {
if block.Text != nil && *block.Text != "" {
sb.WriteString(*block.Text)
sb.WriteString(" ")
}
}
assistantMessage["content"] = sb.String()
}
Comment on lines +230 to +241
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Extract duplicated content serialization logic into a helper function.

The content serialization logic is duplicated between assistant messages and tool messages. This violates the DRY principle and makes maintenance harder.

Extract the common logic into a helper function:

+// serializeMessageContent converts MessageContent to a string representation
+func serializeMessageContent(content schemas.MessageContent) string {
+    if content.ContentStr != nil {
+        return *content.ContentStr
+    } else if content.ContentBlocks != nil && len(*content.ContentBlocks) > 0 {
+        var sb strings.Builder
+        for _, block := range *content.ContentBlocks {
+            if block.Text != nil && *block.Text != "" {
+                sb.WriteString(*block.Text)
+                sb.WriteString(" ")
+            } else if block.ImageURL != nil {
+                sb.WriteString(block.ImageURL.URL)
+                sb.WriteString(" ")
+            }
+        }
+        return sb.String()
+    }
+    return ""
+}

 func prepareOpenAIChatRequest(messages []schemas.BifrostMessage, params *schemas.ModelParameters) ([]map[string]interface{}, map[string]interface{}) {
     // ... existing code ...
     
             if msg.AssistantMessage != nil && msg.AssistantMessage.ToolCalls != nil {
                 assistantMessage["tool_calls"] = *msg.AssistantMessage.ToolCalls
             }
-            if msg.Content.ContentStr != nil {
-                assistantMessage["content"] = *msg.Content.ContentStr
-            } else if msg.Content.ContentBlocks != nil && len(*msg.Content.ContentBlocks) > 0 {
-                var sb strings.Builder
-                for _, block := range *msg.Content.ContentBlocks {
-                    if block.Text != nil && *block.Text != "" {
-                        sb.WriteString(*block.Text)
-                        sb.WriteString(" ")
-                    }
-                }
-                assistantMessage["content"] = sb.String()
-            }
+            assistantMessage["content"] = serializeMessageContent(msg.Content)
             
             // ... later in the code ...
             
-            content := message["content"]
-            if contentBlocks, ok := content.([]schemas.ContentBlock); ok {
-                var sb strings.Builder
-                for _, block := range contentBlocks {
-                    if block.Text != nil && *block.Text != "" {
-                        sb.WriteString(*block.Text)
-                        sb.WriteString(" ")
-                    } else if block.ImageURL != nil {
-                        sb.WriteString(block.ImageURL.URL)
-                        sb.WriteString(" ")
-                    }
-                }
-                message["content"] = sb.String()
-            }
+            message["content"] = serializeMessageContent(msg.Content)

Also applies to: 268-281

🤖 Prompt for AI Agents
In core/providers/openai.go around lines 230 to 241 and also lines 268 to 281,
the content serialization logic for assistant and tool messages is duplicated.
To fix this, extract the repeated logic that checks ContentStr and
ContentBlocks, concatenates text blocks, and returns the serialized content into
a single helper function. Replace the duplicated code in both places by calling
this new helper function to improve code reuse and maintainability.

formattedMessages = append(formattedMessages, assistantMessage)
} else {
message := map[string]interface{}{
Expand All @@ -250,6 +261,24 @@ func prepareOpenAIChatRequest(messages []schemas.BifrostMessage, params *schemas

if msg.ToolMessage != nil && msg.ToolMessage.ToolCallID != nil {
message["tool_call_id"] = *msg.ToolMessage.ToolCallID
if msg.IsError != nil {
message["is_error"] = *msg.IsError
}

content := message["content"]
if contentBlocks, ok := content.([]schemas.ContentBlock); ok {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here

var sb strings.Builder
for _, block := range contentBlocks {
if block.Text != nil && *block.Text != "" {
sb.WriteString(*block.Text)
sb.WriteString(" ")
} else if block.ImageURL != nil {
sb.WriteString(block.ImageURL.URL)
sb.WriteString(" ")
}
}
message["content"] = sb.String()
}
Comment on lines +264 to +281
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix incorrect field access for IsError.

The code accesses msg.IsError directly, but based on the schema in core/schemas/bifrost.go, IsError is a field of the ToolMessage struct. It should be accessed as msg.ToolMessage.IsError.

Apply this fix:

-				if msg.IsError != nil {
-					message["is_error"] = *msg.IsError
+				if msg.ToolMessage.IsError != nil {
+					message["is_error"] = *msg.ToolMessage.IsError
				}

The rest of the content block processing logic looks correct for converting content blocks to a plain string format suitable for OpenAI's API.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if msg.IsError != nil {
message["is_error"] = *msg.IsError
}
content := message["content"]
if contentBlocks, ok := content.([]schemas.ContentBlock); ok {
var sb strings.Builder
for _, block := range contentBlocks {
if block.Text != nil && *block.Text != "" {
sb.WriteString(*block.Text)
sb.WriteString(" ")
} else if block.ImageURL != nil {
sb.WriteString(block.ImageURL.URL)
sb.WriteString(" ")
}
}
message["content"] = sb.String()
}
if msg.ToolMessage.IsError != nil {
message["is_error"] = *msg.ToolMessage.IsError
}
content := message["content"]
if contentBlocks, ok := content.([]schemas.ContentBlock); ok {
var sb strings.Builder
for _, block := range contentBlocks {
if block.Text != nil && *block.Text != "" {
sb.WriteString(*block.Text)
sb.WriteString(" ")
} else if block.ImageURL != nil {
sb.WriteString(block.ImageURL.URL)
sb.WriteString(" ")
}
}
message["content"] = sb.String()
}
🤖 Prompt for AI Agents
In core/providers/openai.go around lines 264 to 281, the code incorrectly
accesses the IsError field directly from msg, but IsError is actually a field
within the embedded ToolMessage struct. Update the code to access IsError as
msg.ToolMessage.IsError instead of msg.IsError to correctly reference the field.
The rest of the content block processing logic can remain unchanged.

}

formattedMessages = append(formattedMessages, message)
Expand Down Expand Up @@ -565,6 +594,9 @@ func handleOpenAIStreaming(

// Handle usage-only chunks (when stream_options include_usage is true)
if len(response.Choices) == 0 && response.Usage != nil {
// Empty choices array.
response.Choices = []schemas.BifrostResponseChoice{}

// This is a usage information chunk at the end of stream
if params != nil {
response.ExtraFields.Params = *params
Expand All @@ -590,9 +622,7 @@ func handleOpenAIStreaming(
response.ExtraFields.Provider = providerType

processAndSendResponse(ctx, postHookRunner, &response, responseChan)

// End stream processing after finish reason
break
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dont break here, listen for [DONE] for breaking

continue
}

// Handle regular content chunks
Expand All @@ -603,6 +633,7 @@ func handleOpenAIStreaming(
response.ExtraFields.Provider = providerType

processAndSendResponse(ctx, postHookRunner, &response, responseChan)
continue
}
}

Expand Down
115 changes: 88 additions & 27 deletions core/schemas/bifrost.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@ const (
DefaultInitialPoolSize = 100
)

// StreamOptions represents the options for streaming requests.
type StreamOptions struct {
IncludeUsage bool `json:"include_usage"`
}

// BifrostConfig represents the configuration for initializing a Bifrost instance.
// It contains the necessary components for setting up the system including account details,
// plugins, logging, and initial pool size.
Expand Down Expand Up @@ -161,19 +166,20 @@ type Fallback struct {
// your request to the model. Bifrost follows a standard set of parameters which
// mapped to the provider's parameters.
type ModelParameters struct {
ToolChoice *ToolChoice `json:"tool_choice,omitempty"` // Whether to call a tool
Tools *[]Tool `json:"tools,omitempty"` // Tools to use
Temperature *float64 `json:"temperature,omitempty"` // Controls randomness in the output
TopP *float64 `json:"top_p,omitempty"` // Controls diversity via nucleus sampling
TopK *int `json:"top_k,omitempty"` // Controls diversity via top-k sampling
MaxTokens *int `json:"max_tokens,omitempty"` // Maximum number of tokens to generate
StopSequences *[]string `json:"stop_sequences,omitempty"` // Sequences that stop generation
PresencePenalty *float64 `json:"presence_penalty,omitempty"` // Penalizes repeated tokens
FrequencyPenalty *float64 `json:"frequency_penalty,omitempty"` // Penalizes frequent tokens
ParallelToolCalls *bool `json:"parallel_tool_calls,omitempty"` // Enables parallel tool calls
EncodingFormat *string `json:"encoding_format,omitempty"` // Format for embedding output (e.g., "float", "base64")
Dimensions *int `json:"dimensions,omitempty"` // Number of dimensions for embedding output
User *string `json:"user,omitempty"` // User identifier for tracking
ToolChoice *ToolChoice `json:"tool_choice,omitempty"` // Whether to call a tool
Tools *[]Tool `json:"tools,omitempty"` // Tools to use
Temperature *float64 `json:"temperature,omitempty"` // Controls randomness in the output
TopP *float64 `json:"top_p,omitempty"` // Controls diversity via nucleus sampling
TopK *int `json:"top_k,omitempty"` // Controls diversity via top-k sampling
MaxTokens *int `json:"max_tokens,omitempty"` // Maximum number of tokens to generate
StopSequences *[]string `json:"stop_sequences,omitempty"` // Sequences that stop generation
PresencePenalty *float64 `json:"presence_penalty,omitempty"` // Penalizes repeated tokens
FrequencyPenalty *float64 `json:"frequency_penalty,omitempty"` // Penalizes frequent tokens
ParallelToolCalls *bool `json:"parallel_tool_calls,omitempty"` // Enables parallel tool calls
EncodingFormat *string `json:"encoding_format,omitempty"` // Format for embedding output (e.g., "float", "base64")
Dimensions *int `json:"dimensions,omitempty"` // Number of dimensions for embedding output
User *string `json:"user,omitempty"` // User identifier for tracking
StreamOptions *StreamOptions `json:"stream_options,omitempty"` // Stream options for streaming requests
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this streamOptions openai format or they directly take the include_usage parameter?

// Dynamic parameters that can be provider-specific, they are directly
// added to the request as is.
ExtraParams map[string]interface{} `json:"-"`
Expand Down Expand Up @@ -351,6 +357,7 @@ type ContentBlock struct {
// ToolMessage represents a message from a tool
type ToolMessage struct {
ToolCallID *string `json:"tool_call_id,omitempty"`
IsError *bool `json:"is_error,omitempty"`
}

// AssistantMessage represents a message from an assistant
Expand All @@ -371,18 +378,41 @@ type ImageURLStruct struct {

// BifrostResponse represents the complete result from any bifrost request.
type BifrostResponse struct {
ID string `json:"id,omitempty"`
Object string `json:"object,omitempty"` // text.completion, chat.completion, or embedding
Choices []BifrostResponseChoice `json:"choices,omitempty"`
Embedding [][]float32 `json:"data,omitempty"` // Maps to "data" field in provider responses (e.g., OpenAI embedding format)
Speech *BifrostSpeech `json:"speech,omitempty"` // Maps to "speech" field in provider responses (e.g., OpenAI speech format)
Transcribe *BifrostTranscribe `json:"transcribe,omitempty"` // Maps to "transcribe" field in provider responses (e.g., OpenAI transcription format)
Model string `json:"model,omitempty"`
Created int `json:"created,omitempty"` // The Unix timestamp (in seconds).
ServiceTier *string `json:"service_tier,omitempty"`
SystemFingerprint *string `json:"system_fingerprint,omitempty"`
Usage *LLMUsage `json:"usage,omitempty"`
ExtraFields BifrostResponseExtraFields `json:"extra_fields"`
ID string `json:"id,omitempty"`
Object string `json:"object,omitempty"` // text.completion, chat.completion, or embedding
Choices []BifrostResponseChoice `json:"choices,omitempty"`
Embedding [][]float32 `json:"data,omitempty"` // Maps to "data" field in provider responses (e.g., OpenAI embedding format)
Speech *BifrostSpeech `json:"speech,omitempty"` // Maps to "speech" field in provider responses (e.g., OpenAI speech format)
Transcribe *BifrostTranscribe `json:"transcribe,omitempty"` // Maps to "transcribe" field in provider responses (e.g., OpenAI transcription format)
Model string `json:"model,omitempty"`
Created int `json:"created,omitempty"` // The Unix timestamp (in seconds).
ServiceTier *string `json:"service_tier,omitempty"`
SystemFingerprint *string `json:"system_fingerprint,omitempty"`
Usage *LLMUsage `json:"usage,omitempty"`
PromptFilterResults *[]PromptFilterResult `json:"prompt_filter_results,omitempty"` // Azure OpenAI Service
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[]PromptFilterResult is already a pointer so we can remove the * here.

ExtraFields BifrostResponseExtraFields `json:"extra_fields"`
}

// FilterResult represents the result of a content filter.
type FilterResult struct {
Filtered bool `json:"filtered"`
Severity bool `json:"severity"`
}

// ContentFilterResult represents the result of a content filter.
type ContentFilterResult struct {
HateSpeech FilterResult `json:"hate_speech,omitempty"`
SelfHarm FilterResult `json:"self_harm,omitempty"`
Sexual FilterResult `json:"sexual,omitempty"`
Violence FilterResult `json:"violence,omitempty"`
Jailbreak FilterResult `json:"jailbreak,omitempty"`
Profanity FilterResult `json:"profanity,omitempty"`
}

// PromptFilterResult represents the result of a prompt filter.
type PromptFilterResult struct {
PromptIndex int `json:"prompt_index"`
ContentFilterResults *ContentFilterResult `json:"content_filter_results"`
}

// LLMUsage represents token usage information
Expand All @@ -394,6 +424,36 @@ type LLMUsage struct {
CompletionTokensDetails *CompletionTokensDetails `json:"completion_tokens_details,omitempty"`
}

func (u *LLMUsage) Clone() *LLMUsage {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you please specify where this is being used? I guess in most cases we can directly give out LLMUsage obj, but lmk if there's any specific use case for this

if u == nil {
return nil
}

ret := &LLMUsage{
PromptTokens: u.PromptTokens,
CompletionTokens: u.CompletionTokens,
TotalTokens: u.TotalTokens,
}

if u.TokenDetails != nil {
ret.TokenDetails = &TokenDetails{
CachedTokens: u.TokenDetails.CachedTokens,
AudioTokens: u.TokenDetails.AudioTokens,
}
}

if u.CompletionTokensDetails != nil {
ret.CompletionTokensDetails = &CompletionTokensDetails{
ReasoningTokens: u.CompletionTokensDetails.ReasoningTokens,
AudioTokens: u.CompletionTokensDetails.AudioTokens,
AcceptedPredictionTokens: u.CompletionTokensDetails.AcceptedPredictionTokens,
RejectedPredictionTokens: u.CompletionTokensDetails.RejectedPredictionTokens,
}
}

return ret
}

type AudioLLMUsage struct {
InputTokens int `json:"input_tokens"`
InputTokensDetails *AudioTokenDetails `json:"input_tokens_details,omitempty"`
Expand Down Expand Up @@ -494,8 +554,9 @@ type Annotation struct {
// IMPORTANT: Only one of BifrostNonStreamResponseChoice or BifrostStreamResponseChoice
// should be non-nil at a time.
type BifrostResponseChoice struct {
Index int `json:"index"`
FinishReason *string `json:"finish_reason,omitempty"`
Index int `json:"index"`
FinishReason *string `json:"finish_reason,omitempty"`
ContentFilterResults *ContentFilterResult `json:"content_filter_results,omitempty"` // Azure OpenAI Service or DeepSeek
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We also have ContentFilterResult on outer response, can we have we only in one place to avoid confusion? We can internally parse the providers which return filter results the other way


*BifrostNonStreamResponseChoice
*BifrostStreamResponseChoice
Expand Down
4 changes: 2 additions & 2 deletions transports/bifrost-http/integrations/anthropic/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,8 @@ func NewAnthropicRouter(client *bifrost.Bifrost) *AnthropicRouter {
return DeriveAnthropicErrorFromBifrostError(err)
},
StreamConfig: &integrations.StreamConfig{
ResponseConverter: func(resp *schemas.BifrostResponse) (interface{}, error) {
return DeriveAnthropicStreamFromBifrostResponse(resp), nil
ResponseConverter: func(resp *schemas.BifrostResponse, streamIndex int) (interface{}, error) {
return DeriveAnthropicStreamFromBifrostResponse(resp, streamIndex), nil
},
ErrorConverter: func(err *schemas.BifrostError) interface{} {
return DeriveAnthropicStreamFromBifrostError(err)
Expand Down
Loading