Skip to content

Commit 45367d7

Browse files
cursoragentchristopher
andcommitted
Merge origin/master into agent delegation branch
Co-authored-by: christopher <christopher@mattermost.com>
2 parents 982a321 + aaa3e4f commit 45367d7

72 files changed

Lines changed: 3564 additions & 937 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

bifrost/bifrost.go

Lines changed: 55 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1124,9 +1124,15 @@ type toolCallBuffer struct {
11241124
arguments strings.Builder
11251125
}
11261126

1127+
// thinkingBlockedBySchema reports whether extended thinking must be dropped
1128+
// for this request: Anthropic rejects thinking combined with structured output.
1129+
func (b *LLM) thinkingBlockedBySchema(cfg llm.LanguageModelConfig) bool {
1130+
return b.provider == schemas.Anthropic && cfg.JSONOutputFormat != nil
1131+
}
1132+
11271133
// buildChatReasoning creates a ChatReasoning configuration if reasoning is enabled.
11281134
func (b *LLM) buildChatReasoning(cfg llm.LanguageModelConfig) *schemas.ChatReasoning {
1129-
if !b.reasoningEnabled || cfg.ReasoningDisabled {
1135+
if !b.reasoningEnabled || cfg.ReasoningDisabled || b.thinkingBlockedBySchema(cfg) {
11301136
return nil
11311137
}
11321138

@@ -1171,7 +1177,7 @@ func (b *LLM) calculateThinkingBudget(maxGeneratedTokens int) int {
11711177

11721178
// convertToBifrostRequest converts our CompletionRequest to Bifrost's format.
11731179
func (b *LLM) convertToBifrostRequest(request llm.CompletionRequest, cfg llm.LanguageModelConfig) *schemas.BifrostChatRequest {
1174-
messages := b.convertMessages(request.Posts)
1180+
messages := b.convertMessages(request.Posts, cfg)
11751181
tools := b.convertTools(request, cfg)
11761182

11771183
req := &schemas.BifrostChatRequest{
@@ -1198,6 +1204,9 @@ func (b *LLM) convertToBifrostRequest(request llm.CompletionRequest, cfg llm.Lan
11981204
if cfg.JSONOutputFormat != nil {
11991205
params.ResponseFormat = buildChatResponseFormat(cfg.JSONOutputFormat)
12001206
}
1207+
if b.promptCachingEnabled() {
1208+
params.CacheControl = &schemas.CacheControl{Type: schemas.CacheControlTypeEphemeral}
1209+
}
12011210
req.Params = params
12021211

12031212
// Attach fallback chain so Bifrost retries with alternative providers on failure.
@@ -1207,7 +1216,7 @@ func (b *LLM) convertToBifrostRequest(request llm.CompletionRequest, cfg llm.Lan
12071216
}
12081217

12091218
// convertMessages converts llm.Post messages to Bifrost ChatMessage format.
1210-
func (b *LLM) convertMessages(posts []llm.Post) []schemas.ChatMessage {
1219+
func (b *LLM) convertMessages(posts []llm.Post, cfg llm.LanguageModelConfig) []schemas.ChatMessage {
12111220
messages := make([]schemas.ChatMessage, 0, len(posts))
12121221

12131222
for _, post := range posts {
@@ -1255,7 +1264,11 @@ func (b *LLM) convertMessages(posts []llm.Post) []schemas.ChatMessage {
12551264
// signature arrived, we persist partial reasoning for display only; do
12561265
// not replay it to Anthropic as an unsigned thinking block. Other
12571266
// providers may accept unsigned reasoning, so preserve it for them.
1258-
if post.Reasoning != "" && (b.provider != schemas.Anthropic || post.ReasoningSignature != "") {
1267+
// Also skip replay when thinking is disabled for this request:
1268+
// Anthropic rejects input thinking blocks when thinking is off.
1269+
if post.Reasoning != "" &&
1270+
(b.provider != schemas.Anthropic || post.ReasoningSignature != "") &&
1271+
!b.thinkingBlockedBySchema(cfg) {
12591272
if msg.ChatAssistantMessage == nil {
12601273
msg.ChatAssistantMessage = &schemas.ChatAssistantMessage{}
12611274
}
@@ -1590,6 +1603,15 @@ func (b *LLM) shouldUseResponsesAPI(cfg llm.LanguageModelConfig) bool {
15901603
if b.useResponsesAPI {
15911604
return true
15921605
}
1606+
1607+
// Direct OpenAI always sets useResponsesAPI during service construction.
1608+
// A false value for OpenAI-base or Azure providers therefore represents an
1609+
// explicit operator choice to use Chat Completions and must not be overridden
1610+
// by native tool configuration.
1611+
if b.provider == schemas.OpenAI || b.provider == schemas.Azure {
1612+
return false
1613+
}
1614+
15931615
if b.providerSupportsNativeTools() && len(b.enabledNativeTools) > 0 {
15941616
return true
15951617
}
@@ -1599,6 +1621,27 @@ func (b *LLM) shouldUseResponsesAPI(cfg llm.LanguageModelConfig) bool {
15991621
return false
16001622
}
16011623

1624+
// promptCachingEnabled reports whether to request Anthropic automatic prompt
1625+
// caching (top-level cache_control). Anthropic caches nothing unless asked,
1626+
// so without this every turn re-bills the full system prompt, tool schemas,
1627+
// and history at the base input rate. OpenAI-family and Gemini cache prompt
1628+
// prefixes automatically and need no marker. Bifrost forwards the field
1629+
// unstripped to non-Anthropic providers, so it is only attached when the
1630+
// primary and every fallback are Anthropic; a mixed chain would 400 the
1631+
// fallback request.
1632+
func (b *LLM) promptCachingEnabled() bool {
1633+
if b.provider != schemas.Anthropic {
1634+
return false
1635+
}
1636+
for _, fb := range b.fallbacks {
1637+
if fb.Provider != schemas.Anthropic &&
1638+
!strings.HasPrefix(string(fb.Provider), string(schemas.Anthropic)+"::") {
1639+
return false
1640+
}
1641+
}
1642+
return true
1643+
}
1644+
16021645
// isNativeToolEnabled checks if a native tool is enabled by name.
16031646
func (b *LLM) isNativeToolEnabled(name string) bool {
16041647
for _, t := range b.enabledNativeTools {
@@ -1810,7 +1853,7 @@ func (b *LLM) convertToResponsesTools(request llm.CompletionRequest, cfg llm.Lan
18101853

18111854
// buildResponsesReasoning creates a ResponsesParametersReasoning configuration if reasoning is enabled.
18121855
func (b *LLM) buildResponsesReasoning(cfg llm.LanguageModelConfig) *schemas.ResponsesParametersReasoning {
1813-
if !b.reasoningEnabled || cfg.ReasoningDisabled {
1856+
if !b.reasoningEnabled || cfg.ReasoningDisabled || b.thinkingBlockedBySchema(cfg) {
18141857
return nil
18151858
}
18161859

@@ -1889,6 +1932,13 @@ func (b *LLM) convertToBifrostResponsesRequest(request llm.CompletionRequest, cf
18891932
}
18901933
params.Text = textConfig
18911934
}
1935+
// The Anthropic provider reads cache_control from ExtraParams on the
1936+
// Responses path (there is no typed field on ResponsesParameters).
1937+
if b.promptCachingEnabled() {
1938+
params.ExtraParams = map[string]interface{}{
1939+
"cache_control": &schemas.CacheControl{Type: schemas.CacheControlTypeEphemeral},
1940+
}
1941+
}
18921942
req.Params = params
18931943

18941944
// Attach fallback chain so Bifrost retries with alternative providers on failure.

0 commit comments

Comments
 (0)