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
100 changes: 98 additions & 2 deletions core/internal/llmtests/provider_feature_support_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -219,8 +219,8 @@ func TestProviderToolValidation(t *testing.T) {
name: "Vertex/mixed_supported_and_unsupported",
provider: schemas.Vertex,
tools: []schemas.ResponsesTool{
{Type: schemas.ResponsesToolTypeWebSearch}, // allowed
{Type: schemas.ResponsesToolTypeFunction}, // allowed
{Type: schemas.ResponsesToolTypeWebSearch}, // allowed
{Type: schemas.ResponsesToolTypeFunction}, // allowed
{Type: schemas.ResponsesToolTypeCodeInterpreter}, // rejected
},
expectErr: true,
Expand Down Expand Up @@ -1036,6 +1036,102 @@ func TestProviderFeatureMapCompleteness(t *testing.T) {
}
}

// TestAnthropicCompatibleThirdPartyProvidersRejectServerTools covers the
// surfaces that speak the Anthropic Messages wire format in front of models and
// infrastructure that are not Anthropic's own. Anthropic executes its server
// tools on its own infrastructure, so these hosts cannot run them and the
// request must be stripped before it goes out. Fireworks is the reported case:
// it answers a forwarded web_search tool with a 400.
//
// Fireworks documents one carve-out, asserted separately below: it implements
// the client-side tool-search and deferred-loading wire format, so tool_search
// survives there and is dropped on the self-hosted pair.
//
// These providers are deliberately excluded from TestProviderFeatureMapCompleteness
// above, whose closing assertions require blanket client-tool support.
func TestAnthropicCompatibleThirdPartyProvidersRejectServerTools(t *testing.T) {
t.Parallel()

// Unsupported on all three, whatever the host.
serverTools := []schemas.ResponsesToolType{
schemas.ResponsesToolTypeWebSearch,
schemas.ResponsesToolTypeWebSearchPreview,
schemas.ResponsesToolTypeWebFetch,
schemas.ResponsesToolTypeCodeInterpreter,
schemas.ResponsesToolTypeComputerUsePreview,
schemas.ResponsesToolTypeMCP,
schemas.ResponsesToolTypeLocalShell,
schemas.ResponsesToolTypeMemory,
}

for _, provider := range []schemas.ModelProvider{schemas.Fireworks, schemas.VLLM, schemas.SGL} {
t.Run(string(provider), func(t *testing.T) {
features, ok := anthropic.ProviderFeatures[provider]
require.True(t, ok, "%s must be in ProviderFeatures, or the validators fall back to keep-everything", provider)

assert.False(t, features.WebSearch, "%s must not advertise WebSearch", provider)
assert.False(t, features.WebFetch, "%s must not advertise WebFetch", provider)
assert.False(t, features.CodeExecution, "%s must not advertise CodeExecution", provider)
assert.False(t, features.MCP, "%s must not advertise MCP", provider)
assert.False(t, features.ComputerUse, "%s must not advertise ComputerUse", provider)
assert.False(t, features.Bash, "%s must not advertise Bash", provider)
assert.False(t, features.Memory, "%s must not advertise Memory", provider)
assert.False(t, features.TextEditor, "%s must not advertise TextEditor", provider)

caps := schemas.ResolveModelCaps(provider, "accounts/fireworks/models/deepseek-v4p1-flash")
for _, toolType := range serverTools {
keep, dropped := anthropic.ValidateResponsesToolsForProvider(
[]schemas.ResponsesTool{{Type: toolType}}, caps,
)
assert.Empty(t, keep, "%s should drop %s", provider, toolType)
assert.Equal(t, []string{string(toolType)}, dropped, "%s should report %s as dropped", provider, toolType)
}

// Function tools are the whole point of the endpoint and must survive.
keep, dropped := anthropic.ValidateResponsesToolsForProvider(
[]schemas.ResponsesTool{{Type: schemas.ResponsesToolTypeFunction}}, caps,
)
assert.Len(t, keep, 1, "%s must keep function tools", provider)
assert.Empty(t, dropped)
})
}
}

// TestFireworksToolSearchCarveOut pins the one server-tool family Fireworks
// documents as working. Their compatibility page says "Tool search discovery
// and deferred tool loading are supported", translating "the client-side
// tool-search discovery and deferred-loading wire format only" and covering
// "both Anthropic-native tool_search_tool_* tool names and clients that name
// their discovery tool ToolSearch". The ToolSearch flag gates the tool type and
// tool.defer_loading together, so turning it off would strip a pattern the
// endpoint implements. vLLM and SGLang document nothing here and stay
// fail-closed.
//
// Source: https://docs.fireworks.ai/tools-sdks/anthropic-compatibility
func TestFireworksToolSearchCarveOut(t *testing.T) {
t.Parallel()

fwCaps := schemas.ResolveModelCaps(schemas.Fireworks, "accounts/fireworks/models/deepseek-v4p1-flash")
keep, dropped := anthropic.ValidateResponsesToolsForProvider(
[]schemas.ResponsesTool{{Type: schemas.ResponsesToolTypeToolSearch}}, fwCaps,
)
assert.Len(t, keep, 1, "Fireworks implements the client-side tool-search wire format, so the tool must survive")
assert.Empty(t, dropped)

// service_tier: "priority" is documented on the same page.
assert.True(t, anthropic.ProviderFeatures[schemas.Fireworks].ServiceTier,
"Fireworks documents service_tier: priority, so the field must not be stripped")

for _, provider := range []schemas.ModelProvider{schemas.VLLM, schemas.SGL} {
caps := schemas.ResolveModelCaps(provider, "meta-llama/Llama-3.2-1B-Instruct")
keep, dropped := anthropic.ValidateResponsesToolsForProvider(
[]schemas.ResponsesTool{{Type: schemas.ResponsesToolTypeToolSearch}}, caps,
)
assert.Empty(t, keep, "%s documents no tool-search support, so it stays fail-closed", provider)
assert.Equal(t, []string{string(schemas.ResponsesToolTypeToolSearch)}, dropped)
}
}

// TestComputerUseVersionAndBetaHeaderEndToEnd verifies the full pipeline:
// bifrost tool → anthropic tool version → correct beta header
func TestComputerUseVersionAndBetaHeaderEndToEnd(t *testing.T) {
Expand Down
54 changes: 54 additions & 0 deletions core/providers/anthropic/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -343,6 +343,60 @@ var ProviderFeatures = map[schemas.ModelProvider]ProviderFeatureSupport{
InterleavedThinking: true,
ServiceTier: true,
},
// Fireworks' Anthropic-compatible Messages endpoint (cite: FW-compat,
// https://docs.fireworks.ai/tools-sdks/anthropic-compatibility), reached
// through the use_anthropic_endpoints key/alias toggle.
//
// FW-compat's "Unsupported features" list is the source for every cell here:
// - "Server-side execution of tool families such as code execution,
// memory, web fetch, and web search is not supported" -> WebSearch,
// WebFetch, CodeExecution, Memory off. Forwarding one is a hard 400:
// 'tools: server-side web search ("web_search_20250305") is not
// supported on this endpoint'.
// - "Fields such as caller and container are not supported" ->
// ContainerBasic off (caller is not a flag; allowed_callers rides
// AdvancedToolUse below).
// - "eager_input_streaming, cache_control, allowed_callers, and
// input_examples are not supported" -> EagerInputStreaming,
// AdvancedToolUse, InputExamples off. PromptCachingScope is off too,
// though note Bifrost only strips cache_control.scope, so the rest of
// cache_control still reaches an endpoint that rejects it.
// - "The output_config.speed option is not supported yet" -> FastMode off.
// - inference_geo is documented as deprecated there -> InferenceGeo off.
//
// ToolSearch is ON despite server-side tool search being unsupported:
// FW-compat carves it out with "Tool search discovery and deferred tool
// loading are supported", translating "the client-side tool-search
// discovery and deferred-loading wire format only" and covering "both
// Anthropic-native tool_search_tool_* tool names and clients that name
// their discovery tool ToolSearch". This flag gates the tool type and
// tool.defer_loading together, so turning it off would break a pattern the
// endpoint implements. ServiceTier is ON per FW-compat's service_tier:
// "priority".
//
// Everything not named above is undocumented on FW-compat and stays off,
// fail-closed, matching how this map already treats undocumented Vertex and
// Bedrock features. Function tools, tool_choice and thinking are never gated
// here and keep working. Per-model overrides go through the capability
// datasheet; beta headers stay controllable through
// network_config.beta_header_overrides.
schemas.Fireworks: {
ToolSearch: true,
ServiceTier: true,
},
// Self-hosted vLLM and SGLang, reached through the same toggle.
//
// Neither project documents Anthropic server or client tools on its
// /v1/messages surface, so unlike Fireworks above these cells are
// fail-closed inference rather than citation. Supporting evidence:
// SGLang's own report that the endpoint rejects built-in web_search_*
// tools (sgl-project/sglang#22655), and vLLM's Anthropic layer being an
// adapter onto an OpenAI ChatCompletionRequest, a shape with no
// representation for Anthropic server tools. Revisit per project if either
// starts documenting support; a single deployment can already opt back in
// through the capability datasheet.
schemas.VLLM: {},
schemas.SGL: {},
}

// ==================== REQUEST TYPES ====================
Expand Down
44 changes: 44 additions & 0 deletions core/providers/anthropic/validateresponsestools_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,50 @@ func TestValidateResponsesToolsForProvider(t *testing.T) {
input: []schemas.ResponsesTool{fnTool, fnTool},
wantKeep: 2,
},
{
// The reported Fireworks failure: its Anthropic-compatible endpoint
// answers a server web_search tool with 400 "server-side web search
// is not supported on this endpoint", so the tool has to be dropped
// before the request leaves Bifrost.
name: "fireworks drops web_search",
provider: schemas.Fireworks,
input: []schemas.ResponsesTool{serverTool(schemas.ResponsesToolTypeWebSearch)},
wantKeep: 0,
wantDropped: []string{string(schemas.ResponsesToolTypeWebSearch)},
assertNotes: "Anthropic-hosted server tools do not exist on Fireworks",
},
{
name: "fireworks keeps the caller's function tools",
provider: schemas.Fireworks,
input: []schemas.ResponsesTool{fnTool, serverTool(schemas.ResponsesToolTypeWebSearch), fnTool},
wantKeep: 2,
wantDropped: []string{
string(schemas.ResponsesToolTypeWebSearch),
},
},
{
name: "vllm drops web_search",
provider: schemas.VLLM,
input: []schemas.ResponsesTool{serverTool(schemas.ResponsesToolTypeWebSearch)},
wantKeep: 0,
wantDropped: []string{string(schemas.ResponsesToolTypeWebSearch)},
},
{
name: "sgl drops web_search",
provider: schemas.SGL,
input: []schemas.ResponsesTool{serverTool(schemas.ResponsesToolTypeWebSearch)},
wantKeep: 0,
wantDropped: []string{string(schemas.ResponsesToolTypeWebSearch)},
},
{
// DeepSeek's endpoint does serve web search, so its entry keeps the
// tool. Guards the three entries above against a copy-paste that
// would silently disable a working feature.
name: "deepseek keeps web_search",
provider: schemas.DeepSeek,
input: []schemas.ResponsesTool{serverTool(schemas.ResponsesToolTypeWebSearch)},
wantKeep: 1,
},
{
name: "unknown provider keeps everything (forward-compat)",
provider: schemas.ModelProvider("custom-new-provider"),
Expand Down
Loading
Loading