@@ -3,7 +3,6 @@ package anthropic
3
3
import (
4
4
"encoding/json"
5
5
"fmt"
6
- "strings"
7
6
8
7
bifrost "github.com/maximhq/bifrost/core"
9
8
"github.com/maximhq/bifrost/core/schemas"
@@ -21,6 +20,7 @@ type AnthropicContentBlock struct {
21
20
Name * string `json:"name,omitempty"` // For tool_use content
22
21
Input interface {} `json:"input,omitempty"` // For tool_use content
23
22
Content AnthropicContent `json:"content,omitempty"` // For tool_result content
23
+ IsError * bool `json:"is_error,omitempty"` // For tool_result content
24
24
Source * AnthropicImageSource `json:"source,omitempty"` // For image content
25
25
}
26
26
@@ -63,17 +63,18 @@ type AnthropicToolChoice struct {
63
63
64
64
// AnthropicMessageRequest represents an Anthropic messages API request
65
65
type AnthropicMessageRequest struct {
66
- Model string `json:"model"`
67
- MaxTokens int `json:"max_tokens"`
68
- Messages []AnthropicMessage `json:"messages"`
69
- System * AnthropicContent `json:"system,omitempty"`
70
- Temperature * float64 `json:"temperature,omitempty"`
71
- TopP * float64 `json:"top_p,omitempty"`
72
- TopK * int `json:"top_k,omitempty"`
73
- StopSequences * []string `json:"stop_sequences,omitempty"`
74
- Stream * bool `json:"stream,omitempty"`
75
- Tools * []AnthropicTool `json:"tools,omitempty"`
76
- ToolChoice * AnthropicToolChoice `json:"tool_choice,omitempty"`
66
+ Model string `json:"model"`
67
+ MaxTokens int `json:"max_tokens"`
68
+ Messages []AnthropicMessage `json:"messages"`
69
+ System * AnthropicContent `json:"system,omitempty"`
70
+ Temperature * float64 `json:"temperature,omitempty"`
71
+ TopP * float64 `json:"top_p,omitempty"`
72
+ TopK * int `json:"top_k,omitempty"`
73
+ StopSequences * []string `json:"stop_sequences,omitempty"`
74
+ Stream * bool `json:"stream,omitempty"`
75
+ StreamOptions * schemas.StreamOptions `json:"stream_options,omitempty"`
76
+ Tools * []AnthropicTool `json:"tools,omitempty"`
77
+ ToolChoice * AnthropicToolChoice `json:"tool_choice,omitempty"`
77
78
}
78
79
79
80
// IsStreamingRequested implements the StreamingRequest interface
@@ -126,6 +127,23 @@ type AnthropicStreamResponse struct {
126
127
Usage * AnthropicUsage `json:"usage,omitempty"`
127
128
}
128
129
130
+ func (s * AnthropicStreamResponse ) ToSSE () string {
131
+ jsonData , err := json .Marshal (s )
132
+ if err != nil {
133
+ return "event: error\n data: {\" type\" : \" error\" , \" error\" : {\" type\" : \" internal_error\" , \" message\" : \" Failed to marshal stream response\" }}\n \n "
134
+ }
135
+ return fmt .Sprintf ("event: %s\n data: %s\n \n " , s .Type , string (jsonData ))
136
+ }
137
+
138
+ func (s * AnthropicStreamResponse ) SetModel (model string ) {
139
+ if s .Model != nil {
140
+ * s .Model = model
141
+ }
142
+ if s .Message != nil && s .Message .Model != "" {
143
+ s .Message .Model = model
144
+ }
145
+ }
146
+
129
147
// AnthropicStreamMessage represents the message structure in streaming events
130
148
type AnthropicStreamMessage struct {
131
149
ID string `json:"id"`
@@ -238,6 +256,8 @@ func (r *AnthropicMessageRequest) ConvertToBifrostRequest() *schemas.BifrostRequ
238
256
var toolCalls []schemas.ToolCall
239
257
var contentBlocks []schemas.ContentBlock
240
258
259
+ skipAppendMessage := false
260
+
241
261
for _ , content := range * msg .Content .ContentBlocks {
242
262
switch content .Type {
243
263
case "text" :
@@ -281,48 +301,69 @@ func (r *AnthropicMessageRequest) ConvertToBifrostRequest() *schemas.BifrostRequ
281
301
toolCalls = append (toolCalls , tc )
282
302
}
283
303
case "tool_result" :
284
- if content .ToolUseID != nil {
285
- bifrostMsg .ToolMessage = & schemas.ToolMessage {
286
- ToolCallID : content .ToolUseID ,
287
- }
288
- if content .Content .ContentStr != nil {
289
- contentBlocks = append (contentBlocks , schemas.ContentBlock {
290
- Type : schemas .ContentBlockTypeText ,
291
- Text : content .Content .ContentStr ,
292
- })
293
- } else if content .Content .ContentBlocks != nil {
294
- for _ , block := range * content .Content .ContentBlocks {
295
- if block .Text != nil {
296
- contentBlocks = append (contentBlocks , schemas.ContentBlock {
297
- Type : schemas .ContentBlockTypeText ,
298
- Text : block .Text ,
299
- })
300
- } else if block .Source != nil {
301
- contentBlocks = append (contentBlocks , schemas.ContentBlock {
302
- Type : schemas .ContentBlockTypeImage ,
303
- ImageURL : & schemas.ImageURLStruct {
304
- URL : func () string {
305
- if block .Source .Data != nil {
306
- mime := "image/png"
307
- if block .Source .MediaType != nil && * block .Source .MediaType != "" {
308
- mime = * block .Source .MediaType
309
- }
310
- return "data:" + mime + ";base64," + * block .Source .Data
311
- }
312
- if block .Source .URL != nil {
313
- return * block .Source .URL
304
+ if content .ToolUseID == nil || * content .ToolUseID == "" {
305
+ continue
306
+ }
307
+
308
+ skipAppendMessage = true
309
+
310
+ bifrostMsg .Role = schemas .ModelChatMessageRoleTool
311
+ bifrostMsg .ToolMessage = & schemas.ToolMessage {
312
+ ToolCallID : content .ToolUseID ,
313
+ IsError : content .IsError ,
314
+ }
315
+ if content .Content .ContentStr != nil {
316
+ contentBlocks = append (contentBlocks , schemas.ContentBlock {
317
+ Type : schemas .ContentBlockTypeText ,
318
+ Text : content .Content .ContentStr ,
319
+ })
320
+ } else if content .Content .ContentBlocks != nil {
321
+ for _ , block := range * content .Content .ContentBlocks {
322
+ if block .Text != nil {
323
+ contentBlocks = append (contentBlocks , schemas.ContentBlock {
324
+ Type : schemas .ContentBlockTypeText ,
325
+ Text : block .Text ,
326
+ })
327
+ } else if block .Source != nil {
328
+ contentBlocks = append (contentBlocks , schemas.ContentBlock {
329
+ Type : schemas .ContentBlockTypeImage ,
330
+ ImageURL : & schemas.ImageURLStruct {
331
+ URL : func () string {
332
+ if block .Source .Data != nil {
333
+ mime := "image/png"
334
+ if block .Source .MediaType != nil && * block .Source .MediaType != "" {
335
+ mime = * block .Source .MediaType
314
336
}
315
- return ""
316
- }()},
317
- })
318
- }
337
+ return "data:" + mime + ";base64," + * block .Source .Data
338
+ }
339
+ if block .Source .URL != nil {
340
+ return * block .Source .URL
341
+ }
342
+ return ""
343
+ }()},
344
+ })
319
345
}
320
346
}
321
- bifrostMsg .Role = schemas .ModelChatMessageRoleTool
322
347
}
348
+
349
+ if len (contentBlocks ) > 0 {
350
+ blocks := make ([]schemas.ContentBlock , len (contentBlocks ))
351
+ copy (blocks , contentBlocks )
352
+ bifrostMsg .Content = schemas.MessageContent {
353
+ ContentBlocks : & blocks ,
354
+ }
355
+ messages = append (messages , bifrostMsg )
356
+ bifrostMsg = schemas.BifrostMessage {}
357
+ contentBlocks = contentBlocks [:0 ]
358
+ }
359
+ continue
323
360
}
324
361
}
325
362
363
+ if skipAppendMessage {
364
+ continue
365
+ }
366
+
326
367
// Concatenate all text contents
327
368
if len (contentBlocks ) > 0 {
328
369
bifrostMsg .Content = schemas.MessageContent {
@@ -360,6 +401,9 @@ func (r *AnthropicMessageRequest) ConvertToBifrostRequest() *schemas.BifrostRequ
360
401
if r .StopSequences != nil {
361
402
params .StopSequences = r .StopSequences
362
403
}
404
+ if r .StreamOptions != nil {
405
+ params .StreamOptions = r .StreamOptions
406
+ }
363
407
364
408
bifrostReq .Params = params
365
409
}
@@ -450,6 +494,9 @@ func DeriveAnthropicFromBifrostResponse(bifrostResp *schemas.BifrostResponse) *A
450
494
InputTokens : bifrostResp .Usage .PromptTokens ,
451
495
OutputTokens : bifrostResp .Usage .CompletionTokens ,
452
496
}
497
+ if bifrostResp .Usage .TokenDetails != nil {
498
+ anthropicResp .Usage .CacheReadInputTokens = bifrostResp .Usage .TokenDetails .CachedTokens
499
+ }
453
500
}
454
501
455
502
// Convert choices to content
@@ -521,9 +568,9 @@ func DeriveAnthropicFromBifrostResponse(bifrostResp *schemas.BifrostResponse) *A
521
568
}
522
569
523
570
// DeriveAnthropicStreamFromBifrostResponse converts a Bifrost streaming response to Anthropic SSE string format
524
- func DeriveAnthropicStreamFromBifrostResponse (bifrostResp * schemas.BifrostResponse , streamIndex int ) string {
571
+ func DeriveAnthropicStreamFromBifrostResponse (bifrostResp * schemas.BifrostResponse , streamIndex int ) [] * AnthropicStreamResponse {
525
572
if bifrostResp == nil {
526
- return ""
573
+ return nil
527
574
}
528
575
529
576
var streamRespList []* AnthropicStreamResponse
@@ -550,6 +597,13 @@ func DeriveAnthropicStreamFromBifrostResponse(bifrostResp *schemas.BifrostRespon
550
597
if bifrostResp .Usage .TokenDetails != nil {
551
598
usage .CacheReadInputTokens = bifrostResp .Usage .TokenDetails .CachedTokens
552
599
}
600
+ } else {
601
+ // Default to 1 token for input and output, e.g. for DeepSeek api.
602
+ // Return the actual usage in the final message delta.
603
+ usage = & AnthropicUsage {
604
+ InputTokens : 1 ,
605
+ OutputTokens : 1 ,
606
+ }
553
607
}
554
608
streamResp .Type = "message_start"
555
609
streamResp .Message = & AnthropicStreamMessage {
@@ -625,14 +679,19 @@ func DeriveAnthropicStreamFromBifrostResponse(bifrostResp *schemas.BifrostRespon
625
679
})
626
680
627
681
// Handle message delta
682
+ usage := & AnthropicUsage {
683
+ OutputTokens : bifrostResp .Usage .CompletionTokens ,
684
+ InputTokens : bifrostResp .Usage .PromptTokens ,
685
+ }
686
+ if bifrostResp .Usage .TokenDetails != nil {
687
+ usage .CacheReadInputTokens = bifrostResp .Usage .TokenDetails .CachedTokens
688
+ }
628
689
streamResp = & AnthropicStreamResponse {
629
690
Type : "message_delta" ,
630
691
Delta : & AnthropicStreamDelta {
631
692
StopReason : choice .FinishReason ,
632
693
},
633
- Usage : & AnthropicUsage {
634
- OutputTokens : bifrostResp .Usage .CompletionTokens ,
635
- },
694
+ Usage : usage ,
636
695
}
637
696
}
638
697
@@ -668,24 +727,17 @@ func DeriveAnthropicStreamFromBifrostResponse(bifrostResp *schemas.BifrostRespon
668
727
669
728
}
670
729
671
- var sb strings. Builder
730
+ result := make ([] * AnthropicStreamResponse , 0 , len ( streamRespList ))
672
731
for _ , streamResp := range streamRespList {
673
732
// Ignore empty stream responses
674
733
if streamResp .Type == "" {
675
734
continue
676
735
}
677
736
678
- // Marshal to JSON and format as SSE
679
- jsonData , err := json .Marshal (streamResp )
680
- if err != nil {
681
- return ""
682
- }
683
-
684
- // Format as Anthropic SSE
685
- sb .WriteString (fmt .Sprintf ("event: %s\n data: %s\n \n " , streamResp .Type , jsonData ))
737
+ result = append (result , streamResp )
686
738
}
687
739
688
- return sb . String ()
740
+ return result
689
741
}
690
742
691
743
// DeriveAnthropicErrorFromBifrostError derives a AnthropicMessageError from a BifrostError
0 commit comments