Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
735f72b
fix: private network toggle in custom provider form
Jun 8, 2026
fed162e
fix: private network toggle in custom provider form (#4160)
akshaydeo Jun 9, 2026
030f8d3
feat: add `PreRequestHook` to `LLMPlugin` interface for once-per-requ…
Pratham-Mishra04 Jun 9, 2026
27b1da5
refactor: migrate governance routing from `HTTPTransportPreHook` to `…
Pratham-Mishra04 Jun 9, 2026
5282def
refactor: extract provider resolution into `modelcatalogresolver` Pre…
Pratham-Mishra04 Jun 9, 2026
88ac570
docs: add `PreRequestHook` routing phase to plugin lifecycle and sequ…
Pratham-Mishra04 Jun 9, 2026
1799a3d
feat: add routing allowlist enforcement via `BifrostContextKeyRouting…
Pratham-Mishra04 Jun 9, 2026
4bddf40
refactor: promote `KeyAliases` value type from `string` to `AliasConf…
Pratham-Mishra04 Jun 9, 2026
84634a9
feat: add per-alias Azure endpoint, API version, and Anthropic versio…
Pratham-Mishra04 Jun 9, 2026
de4b699
feat: add alias-level region/ARN overrides and context-aware model fa…
Pratham-Mishra04 Jun 9, 2026
7f2150f
feat: add per-alias Vertex project/region overrides and context-aware…
Pratham-Mishra04 Jun 9, 2026
6d6f96c
feat: add per-alias `ReplicateAliasCfg.UseDeploymentsEndpoint` overri…
Pratham-Mishra04 Jun 9, 2026
f2444cf
feat: replace flat aliases table with rich `DeploymentsTable` support…
Pratham-Mishra04 Jun 9, 2026
f2a448e
feat: add `RoutingInfo` to response/error extra fields with fallback …
Pratham-Mishra04 Jun 9, 2026
076263d
refactor: replace `resolvePricing` flat args with `RoutingInfo` and a…
Pratham-Mishra04 Jun 9, 2026
bf6cd1d
docs: expand alias object schema, `routing_info` response shape, and …
Pratham-Mishra04 Jun 9, 2026
83e4180
feat: add `keyconfig.Store` for per-key config aggregation with allow…
Pratham-Mishra04 Jun 9, 2026
7ee0920
feat: add `live` model catalog cache store with filtered/unfiltered e…
Pratham-Mishra04 Jun 9, 2026
d923cda
feat: adds datasheet store to modelcatalog (#4191)
Pratham-Mishra04 Jun 9, 2026
aa4a44c
feat: scope `ListModels` to single key when `KeyID` is set, add `filt…
Pratham-Mishra04 Jun 9, 2026
c168fed
refactor: replace aggregated model discovery shims with per-key live …
Pratham-Mishra04 Jun 9, 2026
e4446c5
feat: add `core` routing engine audit trail for retry and fallback tr…
Pratham-Mishra04 Jun 9, 2026
23a767c
feat: add adaptive routing settings sub-route with sidebar nav and en…
Pratham-Mishra04 Jun 9, 2026
45ceff4
tests: adds routing test harness
Pratham-Mishra04 Jun 8, 2026
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
413 changes: 364 additions & 49 deletions core/bifrost.go

Large diffs are not rendered by default.

89 changes: 44 additions & 45 deletions core/bifrost_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -789,51 +789,6 @@ func (t *countingTracer) CompleteAndFlushTrace(_ string) {
t.flushed.Add(1)
}

func TestFilterProvidersByContext(t *testing.T) {
providers := []schemas.ModelProvider{
schemas.OpenAI,
schemas.Anthropic,
schemas.Mistral,
}

t.Run("no context filter keeps all providers", func(t *testing.T) {
filtered := filterProvidersByContext(nil, providers)
if len(filtered) != len(providers) {
t.Fatalf("expected all providers, got %v", filtered)
}
})

t.Run("available providers restrict list models fanout", func(t *testing.T) {
ctx := schemas.NewBifrostContext(context.Background(), schemas.NoDeadline)
ctx.SetValue(schemas.BifrostContextKeyAvailableProviders, []schemas.ModelProvider{schemas.Anthropic})

filtered := filterProvidersByContext(ctx, providers)
if len(filtered) != 1 || filtered[0] != schemas.Anthropic {
t.Fatalf("expected only anthropic, got %v", filtered)
}
})

t.Run("empty available providers denies all providers", func(t *testing.T) {
ctx := schemas.NewBifrostContext(context.Background(), schemas.NoDeadline)
ctx.SetValue(schemas.BifrostContextKeyAvailableProviders, []schemas.ModelProvider{})

filtered := filterProvidersByContext(ctx, providers)
if len(filtered) != 0 {
t.Fatalf("expected no providers, got %v", filtered)
}
})

t.Run("malformed available providers fails closed", func(t *testing.T) {
ctx := schemas.NewBifrostContext(context.Background(), schemas.NoDeadline)
ctx.SetValue(schemas.BifrostContextKeyAvailableProviders, "openai")

filtered := filterProvidersByContext(ctx, providers)
if len(filtered) != 0 {
t.Fatalf("expected no providers for malformed context value, got %v", filtered)
}
})
}

func TestRunStreamPreHooks_FinalChunkFlushesTrace(t *testing.T) {
ctx := schemas.NewBifrostContext(context.Background(), schemas.NoDeadline)
account := NewMockAccount()
Expand Down Expand Up @@ -2757,3 +2712,47 @@ func TestPluginPipelineStreamingRace(t *testing.T) {

wg.Wait()
}

// TestFilterKeysByID covers the KeyID scoping path for ListModels requests:
// a hit returns the single matching key, a miss returns an empty slice
// (which the caller surfaces as "no key found"), and the input slice must
// not be mutated.
func TestFilterKeysByID(t *testing.T) {
keys := []schemas.Key{
{ID: "k1"},
{ID: "k2"},
{ID: "k3"},
}

t.Run("match returns single key", func(t *testing.T) {
got := filterKeysByID(keys, "k2")
if len(got) != 1 || got[0].ID != "k2" {
t.Fatalf("filterKeysByID(_, k2) = %+v, want one key with ID=k2", got)
}
})

t.Run("no match returns empty slice", func(t *testing.T) {
got := filterKeysByID(keys, "does-not-exist")
if len(got) != 0 {
t.Fatalf("filterKeysByID(_, missing) = %+v, want empty", got)
}
})

t.Run("empty target returns empty slice", func(t *testing.T) {
got := filterKeysByID(keys, "")
if len(got) != 0 {
t.Fatalf("filterKeysByID(_, \"\") = %+v, want empty", got)
}
})

t.Run("input slice is not mutated", func(t *testing.T) {
before := make([]schemas.Key, len(keys))
copy(before, keys)
_ = filterKeysByID(keys, "k1")
for i := range keys {
if keys[i].ID != before[i].ID {
t.Fatalf("input mutated at index %d: got %q, want %q", i, keys[i].ID, before[i].ID)
}
}
})
}
54 changes: 27 additions & 27 deletions core/internal/llmtests/account.go
Original file line number Diff line number Diff line change
Expand Up @@ -241,12 +241,12 @@ func (account *ComprehensiveTestAccount) GetKeysForProvider(ctx context.Context,
{
Models: []string{"*"},
Weight: 1.0,
Aliases: map[string]string{
"claude-3.7-sonnet": "us.anthropic.claude-3-7-sonnet-20250219-v1:0",
"claude-4-sonnet": "global.anthropic.claude-sonnet-4-20250514-v1:0",
"claude-4.5-sonnet": "global.anthropic.claude-sonnet-4-5-20250929-v1:0",
"claude-4.6-sonnet": "global.anthropic.claude-sonnet-4-6",
"claude-4.5-haiku": "global.anthropic.claude-haiku-4-5-20251001-v1:0",
Aliases: schemas.KeyAliases{
"claude-3.7-sonnet": {ModelID: "us.anthropic.claude-3-7-sonnet-20250219-v1:0"},
"claude-4-sonnet": {ModelID: "global.anthropic.claude-sonnet-4-20250514-v1:0"},
"claude-4.5-sonnet": {ModelID: "global.anthropic.claude-sonnet-4-5-20250929-v1:0"},
"claude-4.6-sonnet": {ModelID: "global.anthropic.claude-sonnet-4-6"},
"claude-4.5-haiku": {ModelID: "global.anthropic.claude-haiku-4-5-20251001-v1:0"},
},
BedrockKeyConfig: &schemas.BedrockKeyConfig{
AccessKey: *schemas.NewEnvVar("env.AWS_ACCESS_KEY_ID"),
Expand All @@ -259,13 +259,13 @@ func (account *ComprehensiveTestAccount) GetKeysForProvider(ctx context.Context,
{
Models: []string{"*"},
Weight: 1.0,
Aliases: map[string]string{
"claude-3.5-sonnet": "anthropic.claude-3-5-sonnet-20240620-v1:0",
"claude-3.7-sonnet": "us.anthropic.claude-3-7-sonnet-20250219-v1:0",
"claude-4-sonnet": "global.anthropic.claude-sonnet-4-20250514-v1:0",
"claude-4.5-sonnet": "global.anthropic.claude-sonnet-4-5-20250929-v1:0",
"claude-4.6-sonnet": "global.anthropic.claude-sonnet-4-6",
"claude-4.5-haiku": "global.anthropic.claude-haiku-4-5-20251001-v1:0",
Aliases: schemas.KeyAliases{
"claude-3.5-sonnet": {ModelID: "anthropic.claude-3-5-sonnet-20240620-v1:0"},
"claude-3.7-sonnet": {ModelID: "us.anthropic.claude-3-7-sonnet-20250219-v1:0"},
"claude-4-sonnet": {ModelID: "global.anthropic.claude-sonnet-4-20250514-v1:0"},
"claude-4.5-sonnet": {ModelID: "global.anthropic.claude-sonnet-4-5-20250929-v1:0"},
"claude-4.6-sonnet": {ModelID: "global.anthropic.claude-sonnet-4-6"},
"claude-4.5-haiku": {ModelID: "global.anthropic.claude-haiku-4-5-20251001-v1:0"},
},
BedrockKeyConfig: &schemas.BedrockKeyConfig{
AccessKey: *schemas.NewEnvVar("env.AWS_ACCESS_KEY_ID"),
Expand Down Expand Up @@ -303,13 +303,13 @@ func (account *ComprehensiveTestAccount) GetKeysForProvider(ctx context.Context,
Models: []string{"*"},
Weight: 1.0,
Aliases: schemas.KeyAliases{
"gpt-4o": "gpt-4o",
"gpt-4o-backup": "gpt-4o-3",
"claude-opus-4-5": "claude-opus-4-5",
"o1": "o1",
"gpt-image-1": "gpt-image-1",
"text-embedding-ada-002": "text-embedding-ada-002",
"sora-2": "sora-2",
"gpt-4o": {ModelID: "gpt-4o"},
"gpt-4o-backup": {ModelID: "gpt-4o-3"},
"claude-opus-4-5": {ModelID: "claude-opus-4-5"},
"o1": {ModelID: "o1"},
"gpt-image-1": {ModelID: "gpt-image-1"},
"text-embedding-ada-002": {ModelID: "text-embedding-ada-002"},
"sora-2": {ModelID: "sora-2"},
},
AzureKeyConfig: &schemas.AzureKeyConfig{
Endpoint: *schemas.NewEnvVar("env.AZURE_ENDPOINT"),
Expand All @@ -324,10 +324,10 @@ func (account *ComprehensiveTestAccount) GetKeysForProvider(ctx context.Context,
Models: []string{"*"},
Weight: 1.0,
Aliases: schemas.KeyAliases{
"whisper": "whisper",
"whisper-1": "whisper",
"gpt-4o-mini-tts": "gpt-4o-mini-tts",
"gpt-4o-mini-audio-preview": "gpt-4o-mini-audio-preview",
"whisper": {ModelID: "whisper"},
"whisper-1": {ModelID: "whisper"},
"gpt-4o-mini-tts": {ModelID: "gpt-4o-mini-tts"},
"gpt-4o-mini-audio-preview": {ModelID: "gpt-4o-mini-audio-preview"},
},
AzureKeyConfig: &schemas.AzureKeyConfig{
Endpoint: *schemas.NewEnvVar("env.AZURE_ENDPOINT"),
Expand Down Expand Up @@ -365,9 +365,9 @@ func (account *ComprehensiveTestAccount) GetKeysForProvider(ctx context.Context,
Models: []string{"claude-sonnet-4-5", "claude-4.5-haiku", "claude-opus-4-5"},
Weight: 1.0,
Aliases: schemas.KeyAliases{
"claude-sonnet-4-5": "claude-sonnet-4-5",
"claude-4.5-haiku": "claude-haiku-4-5@20251001",
"claude-opus-4-5": "claude-opus-4-5",
"claude-sonnet-4-5": {ModelID: "claude-sonnet-4-5"},
"claude-4.5-haiku": {ModelID: "claude-haiku-4-5@20251001"},
"claude-opus-4-5": {ModelID: "claude-opus-4-5"},
},
VertexKeyConfig: &schemas.VertexKeyConfig{
ProjectID: *schemas.NewEnvVar("env.VERTEX_PROJECT_ID"),
Expand Down
2 changes: 1 addition & 1 deletion core/providers/anthropic/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import (
"github.com/maximhq/bifrost/core/schemas"
)

func (response *AnthropicListModelsResponse) ToBifrostListModelsResponse(providerKey schemas.ModelProvider, allowedModels schemas.WhiteList, blacklistedModels schemas.BlackList, aliases map[string]string, unfiltered bool) *schemas.BifrostListModelsResponse {
func (response *AnthropicListModelsResponse) ToBifrostListModelsResponse(providerKey schemas.ModelProvider, allowedModels schemas.WhiteList, blacklistedModels schemas.BlackList, aliases schemas.KeyAliases, unfiltered bool) *schemas.BifrostListModelsResponse {
if response == nil {
return nil
}
Expand Down
2 changes: 1 addition & 1 deletion core/providers/anthropic/responses.go
Original file line number Diff line number Diff line change
Expand Up @@ -2187,7 +2187,7 @@ func ToAnthropicResponsesStreamResponse(ctx *schemas.BifrostContext, bifrostResp

// ToBifrostResponsesRequest converts an Anthropic message request to Bifrost format
func (req *AnthropicMessageRequest) ToBifrostResponsesRequest(ctx *schemas.BifrostContext) *schemas.BifrostResponsesRequest {
provider, model := schemas.ParseModelString(req.Model, providerUtils.CheckAndSetDefaultProvider(ctx, schemas.Anthropic))
provider, model := schemas.ParseModelString(req.Model, "")

bifrostReq := &schemas.BifrostResponsesRequest{
Provider: provider,
Expand Down
2 changes: 1 addition & 1 deletion core/providers/anthropic/text.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ func (req *AnthropicTextRequest) ToBifrostTextCompletionRequest(ctx *schemas.Bif
return nil
}

provider, model := schemas.ParseModelString(req.Model, providerUtils.CheckAndSetDefaultProvider(ctx, schemas.Anthropic))
provider, model := schemas.ParseModelString(req.Model, "")

bifrostReq := &schemas.BifrostTextCompletionRequest{
Provider: provider,
Expand Down
Loading
Loading