Skip to content
Open
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
1 change: 1 addition & 0 deletions .github/workflows/helm-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ jobs:
cd helm-charts
gh release create "helm-chart-v${{ steps.chart-version.outputs.version }}" \
bifrost-${{ steps.chart-version.outputs.version }}.tgz \
--target ${{ github.sha }} \
--title "Helm Chart v${{ steps.chart-version.outputs.version }}" \
--notes "Helm chart release for Bifrost v${{ steps.chart-version.outputs.version }}"
env:
Expand Down
17 changes: 14 additions & 3 deletions core/bifrost.go
Original file line number Diff line number Diff line change
Expand Up @@ -514,9 +514,20 @@ func (bifrost *Bifrost) ListAllModels(ctx *schemas.BifrostContext, req *schemas.

response, bifrostErr := bifrost.ListModelsRequest(providerCtx, providerRequest)
if bifrostErr != nil {
// Skip logging "no keys found" and "not supported" errors as they are expected when a provider is not configured
if !strings.Contains(bifrostErr.Error.Message, "no keys found") &&
!strings.Contains(bifrostErr.Error.Message, "not supported") {
// Some per-provider failures are expected when fanning out across all
// configured providers and must not be surfaced as a top-level error
errType := ""
if bifrostErr.Type != nil {
errType = *bifrostErr.Type
}
errMsg := ""
if bifrostErr.Error != nil {
errMsg = bifrostErr.Error.Message
}
isExpected := strings.Contains(errMsg, "no keys found") ||
strings.Contains(errMsg, "not supported") ||
errType == "provider_blocked"
if !isExpected {
providerErr = bifrostErr
bifrost.logger.Warn("failed to list models for provider %s: %s", providerKey, bifrostErr.GetErrorString())
}
Expand Down
2 changes: 1 addition & 1 deletion core/bifrost_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -711,7 +711,7 @@ func (ma *MockAccount) AddProviderWithBaseURL(provider schemas.ModelProvider, co
ma.configs[provider] = &schemas.ProviderConfig{
NetworkConfig: schemas.NetworkConfig{
BaseURL: baseURL,
DefaultRequestTimeoutInSeconds: 30,
DefaultRequestTimeoutInSeconds: 300,
MaxRetries: 3,
RetryBackoffInitial: 500 * time.Millisecond,
RetryBackoffMax: 5 * time.Second,
Expand Down
16 changes: 8 additions & 8 deletions core/providers/bedrock/transport_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -547,7 +547,7 @@ func generateTestCACert(t *testing.T) string {
func TestBedrockTransportHTTP2Config(t *testing.T) {
config := &schemas.ProviderConfig{
NetworkConfig: schemas.NetworkConfig{
DefaultRequestTimeoutInSeconds: 30,
DefaultRequestTimeoutInSeconds: 300,
MaxConnsPerHost: 5000,
EnforceHTTP2: true,
},
Expand All @@ -570,7 +570,7 @@ func TestBedrockTransportHTTP2Config(t *testing.T) {
func TestBedrockTransportCustomMaxConns(t *testing.T) {
config := &schemas.ProviderConfig{
NetworkConfig: schemas.NetworkConfig{
DefaultRequestTimeoutInSeconds: 30,
DefaultRequestTimeoutInSeconds: 300,
MaxConnsPerHost: 50,
},
}
Expand All @@ -590,7 +590,7 @@ func TestBedrockTransportCustomMaxConns(t *testing.T) {
func TestBedrockTransportDefaultMaxConns(t *testing.T) {
config := &schemas.ProviderConfig{
NetworkConfig: schemas.NetworkConfig{
DefaultRequestTimeoutInSeconds: 30,
DefaultRequestTimeoutInSeconds: 300,
// MaxConnsPerHost left as 0 — should default to 5000
},
}
Expand All @@ -612,7 +612,7 @@ func TestBedrockTransportDefaultMaxConns(t *testing.T) {
func TestBedrockTransportTLSInsecureSkipVerify(t *testing.T) {
config := &schemas.ProviderConfig{
NetworkConfig: schemas.NetworkConfig{
DefaultRequestTimeoutInSeconds: 30,
DefaultRequestTimeoutInSeconds: 300,
InsecureSkipVerify: true,
EnforceHTTP2: true,
},
Expand All @@ -636,7 +636,7 @@ func TestBedrockTransportTLSCACert(t *testing.T) {

config := &schemas.ProviderConfig{
NetworkConfig: schemas.NetworkConfig{
DefaultRequestTimeoutInSeconds: 30,
DefaultRequestTimeoutInSeconds: 300,
CACertPEM: schemas.NewEnvVar(testCACert),
EnforceHTTP2: true,
},
Expand All @@ -657,7 +657,7 @@ func TestBedrockTransportTLSCACert(t *testing.T) {
func TestBedrockTransportDefaultTLS(t *testing.T) {
config := &schemas.ProviderConfig{
NetworkConfig: schemas.NetworkConfig{
DefaultRequestTimeoutInSeconds: 30,
DefaultRequestTimeoutInSeconds: 300,
// No TLS settings — should use system defaults
},
}
Expand All @@ -677,7 +677,7 @@ func TestBedrockTransportDefaultTLS(t *testing.T) {
func TestBedrockTransportEnforceHTTP2(t *testing.T) {
config := &schemas.ProviderConfig{
NetworkConfig: schemas.NetworkConfig{
DefaultRequestTimeoutInSeconds: 30,
DefaultRequestTimeoutInSeconds: 300,
EnforceHTTP2: true,
},
}
Expand All @@ -696,7 +696,7 @@ func TestBedrockTransportEnforceHTTP2(t *testing.T) {
func TestBedrockTransportEnforceHTTP2Disabled(t *testing.T) {
config := &schemas.ProviderConfig{
NetworkConfig: schemas.NetworkConfig{
DefaultRequestTimeoutInSeconds: 30,
DefaultRequestTimeoutInSeconds: 300,
EnforceHTTP2: false,
},
}
Expand Down
2 changes: 1 addition & 1 deletion core/providers/fireworks/fireworks_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -431,7 +431,7 @@ func newTestFireworksProvider(t *testing.T, baseURL string) *fireworksprovider.F
provider, err := fireworksprovider.NewFireworksProvider(&schemas.ProviderConfig{
NetworkConfig: schemas.NetworkConfig{
BaseURL: baseURL,
DefaultRequestTimeoutInSeconds: 30,
DefaultRequestTimeoutInSeconds: 300,
},
}, bifrost.NewNoOpLogger())
if err != nil {
Expand Down
6 changes: 3 additions & 3 deletions core/providers/mistral/ocr_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -605,7 +605,7 @@ func TestOCRWithMockServer(t *testing.T) {
provider := NewMistralProvider(&schemas.ProviderConfig{
NetworkConfig: schemas.NetworkConfig{
BaseURL: server.URL,
DefaultRequestTimeoutInSeconds: 30,
DefaultRequestTimeoutInSeconds: 300,
},
}, &testLogger{})

Expand Down Expand Up @@ -639,7 +639,7 @@ func TestOCRNilInput(t *testing.T) {
provider := NewMistralProvider(&schemas.ProviderConfig{
NetworkConfig: schemas.NetworkConfig{
BaseURL: "https://api.mistral.ai",
DefaultRequestTimeoutInSeconds: 30,
DefaultRequestTimeoutInSeconds: 300,
},
}, &testLogger{})

Expand Down Expand Up @@ -686,7 +686,7 @@ func TestOCRRequestValidation(t *testing.T) {
provider := NewMistralProvider(&schemas.ProviderConfig{
NetworkConfig: schemas.NetworkConfig{
BaseURL: server.URL,
DefaultRequestTimeoutInSeconds: 30,
DefaultRequestTimeoutInSeconds: 300,
},
}, &testLogger{})

Expand Down
12 changes: 6 additions & 6 deletions core/providers/mistral/transcription_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -593,7 +593,7 @@ func TestTranscriptionWithMockServer(t *testing.T) {
provider := NewMistralProvider(&schemas.ProviderConfig{
NetworkConfig: schemas.NetworkConfig{
BaseURL: server.URL,
DefaultRequestTimeoutInSeconds: 30,
DefaultRequestTimeoutInSeconds: 300,
},
}, &testLogger{})

Expand Down Expand Up @@ -637,7 +637,7 @@ func TestTranscriptionNilInput(t *testing.T) {
provider := NewMistralProvider(&schemas.ProviderConfig{
NetworkConfig: schemas.NetworkConfig{
BaseURL: "https://api.mistral.ai",
DefaultRequestTimeoutInSeconds: 30,
DefaultRequestTimeoutInSeconds: 300,
},
}, &testLogger{})

Expand Down Expand Up @@ -787,7 +787,7 @@ func TestTranscriptionStreamWithMockServer(t *testing.T) {
provider := NewMistralProvider(&schemas.ProviderConfig{
NetworkConfig: schemas.NetworkConfig{
BaseURL: server.URL,
DefaultRequestTimeoutInSeconds: 30,
DefaultRequestTimeoutInSeconds: 300,
},
}, &testLogger{})

Expand Down Expand Up @@ -842,7 +842,7 @@ func TestTranscriptionStreamNilInput(t *testing.T) {
provider := NewMistralProvider(&schemas.ProviderConfig{
NetworkConfig: schemas.NetworkConfig{
BaseURL: "https://api.mistral.ai",
DefaultRequestTimeoutInSeconds: 30,
DefaultRequestTimeoutInSeconds: 300,
},
}, &testLogger{})

Expand Down Expand Up @@ -1272,7 +1272,7 @@ func TestTranscriptionStreamEdgeCases(t *testing.T) {
provider := NewMistralProvider(&schemas.ProviderConfig{
NetworkConfig: schemas.NetworkConfig{
BaseURL: server.URL,
DefaultRequestTimeoutInSeconds: 30,
DefaultRequestTimeoutInSeconds: 300,
},
}, &testLogger{})

Expand Down Expand Up @@ -1342,7 +1342,7 @@ func TestTranscriptionStreamContextCancellation(t *testing.T) {
provider := NewMistralProvider(&schemas.ProviderConfig{
NetworkConfig: schemas.NetworkConfig{
BaseURL: server.URL,
DefaultRequestTimeoutInSeconds: 30,
DefaultRequestTimeoutInSeconds: 300,
},
}, &testLogger{})

Expand Down
2 changes: 1 addition & 1 deletion core/providers/utils/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -2217,7 +2217,7 @@ func SetupStreamCancellation(ctx *schemas.BifrostContext, bodyStream io.Reader,
// before bifrost considers the connection stalled and closes it. This protects
// against providers that stop sending data but keep the TCP connection open
// (e.g., Azure TPM throttling).
const DefaultStreamIdleTimeout = 60 * time.Second
const DefaultStreamIdleTimeout = 120 * time.Second

// SetStreamIdleTimeoutIfEmpty sets the stream idle timeout on the context from
// the provider's network config, but only if no valid timeout is already present.
Expand Down
6 changes: 3 additions & 3 deletions core/schemas/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,20 +13,20 @@ const (
DefaultMaxRetries = 0
DefaultRetryBackoffInitial = 500 * time.Millisecond
DefaultRetryBackoffMax = 5 * time.Second
DefaultRequestTimeoutInSeconds = 30
DefaultRequestTimeoutInSeconds = 300
DefaultMaxConnDurationInSeconds = 300 // 5 minutes — forces connection recycling to prevent stale connections from NAT/LB silent drops
DefaultBufferSize = 5000
DefaultConcurrency = 1000
DefaultStreamBufferSize = 256
DefaultStreamIdleTimeoutInSeconds = 60 // Idle timeout per stream chunk — if no data for this many seconds, bifrost closes the connection
DefaultStreamIdleTimeoutInSeconds = 120 // Idle timeout per stream chunk — if no data for this many seconds, bifrost closes the connection
DefaultMaxConnsPerHost = 5000
MaxConnsPerHostUpperBound = 10000
DefaultMaxIdleConnsPerHost = 40
)

// Pre-defined errors for provider operations
const (
ErrProviderRequestTimedOut = "request timed out (default is 30 seconds). You can increase it by setting the default_request_timeout_in_seconds in the network_config or in UI - Providers > Provider Name > Network Config."
ErrProviderRequestTimedOut = "request timed out (default is 300 seconds). You can increase it by setting the default_request_timeout_in_seconds in the network_config or in UI - Providers > Provider Name > Network Config."
ErrRequestCancelled = "request cancelled by caller"
ErrRequestBodyConversion = "failed to convert bifrost request to the expected provider request body"
ErrProviderRequestMarshal = "failed to marshal request body to JSON"
Expand Down
2 changes: 1 addition & 1 deletion core/schemas/serialization_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1135,7 +1135,7 @@ func TestNetworkConfig_TLSFieldsRoundTrip(t *testing.T) {
// round-trips correctly through JSON marshaling.
func TestNetworkConfig_StreamIdleTimeoutRoundTrip(t *testing.T) {
nc := NetworkConfig{
DefaultRequestTimeoutInSeconds: 30,
DefaultRequestTimeoutInSeconds: 300,
StreamIdleTimeoutInSeconds: 120,
}

Expand Down
15 changes: 9 additions & 6 deletions docs/deployment-guides/config-json.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -12,23 +12,26 @@ icon: "file-code"

---

## Two Configuration Modes
## Configuration Sources

Bifrost supports **two mutually exclusive modes**. You cannot run both at the same time.
Bifrost stores runtime configuration in a config database by default, so settings can be edited through the Web UI or API. You can also provide a `config.json` file to seed or reconcile that database at startup. To run with only `config.json`, set `config_store.enabled: false`.

| Mode | When | Behaviour |
| Setup | When | Behaviour |
|------|------|-----------|
| **Web UI / database** | No `config.json`, or `config.json` with `config_store` enabled | Full UI available, configuration stored in SQLite or PostgreSQL |
| **File-based (`config.json`)** | `config.json` present, `config_store` disabled | UI disabled, all config loaded from file at startup, restart required for changes |
| **Web UI / database** | No `config.json` | Bifrost creates a default SQLite config store and runtime changes are saved through the UI or API |
| **DB-backed `config.json`** | `config.json` exists and `config_store` is omitted or enabled | File-backed sections seed or reconcile the config store at startup; UI/API edits remain available |
| **File-only `config.json`** | `config_store.enabled` is `false` | Config is loaded from file into memory at startup; config-backed UI/API changes are unavailable and file changes require restart |

<Note>
See [Setting Up](/quickstart/gateway/setting-up#two-configuration-modes) for a full explanation of both modes and how `config_store` bootstrapping works.
By default, DB-backed `config.json` uses `source_of_truth: "split"`: unchanged file-backed rows preserve UI/API edits, while changed file-backed rows are applied on the next startup. Use `source_of_truth: "config.json"` only when explicitly present file sections should replace matching DB state. See [Source of Truth & Reconciliation](/deployment-guides/config-json/source-of-truth) for the full rules, including missing-vs-empty section behavior.
</Note>

---

## Minimal Working Example

This example uses file-only configuration for the smallest self-contained setup.

```json
{
"$schema": "https://www.getbifrost.ai/schema",
Expand Down
4 changes: 4 additions & 0 deletions docs/deployment-guides/config-json/governance.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ icon: "shield-check"

The `governance` block lets you seed all governance resources directly in `config.json`. On startup, Bifrost loads these into the configuration store. This is the recommended approach for GitOps workflows where governance state is managed as code.

<Note>
In default split mode, file-backed governance resources seed or update the DB by hash while unrelated DB-only resources are preserved. With `source_of_truth: "config.json"`, only governance sub-sections that are explicitly present in the file are authoritative. Omit a sub-section to leave DB-managed rows alone; set it to an empty array only when you intend to remove stored rows for that sub-section. See [Source of Truth & Reconciliation](/deployment-guides/config-json/source-of-truth).
</Note>

<Note>
**Governance enforcement is always active** in OSS - you do not need a plugin entry to enable it. To require a virtual key on every inference request, set `client.enforce_auth_on_inference: true`. This is the global default, but a more specific inference-auth flag such as `governance.auth_config.disable_auth_on_inference` overrides it; if no specific override is set, `client.enforce_auth_on_inference` applies.
</Note>
Expand Down
4 changes: 4 additions & 0 deletions docs/deployment-guides/config-json/plugins.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ icon: "puzzle-piece"
**Telemetry, logging, and governance are auto-loaded built-ins** - they are always active and configured via the `client` block and dedicated top-level keys, not the `plugins` array.
</Note>

<Note>
In DB-backed deployments, plugin sync depends on the reconciliation mode. Split mode preserves DB plugins unless a file plugin has a higher `version` or changed placement/order. With `source_of_truth: "config.json"`, a present `plugins` array is authoritative; `plugins: []` removes stored opt-in plugins. See [Source of Truth & Reconciliation](/deployment-guides/config-json/source-of-truth).
</Note>

---

## Auto-Loaded Built-ins
Expand Down
4 changes: 4 additions & 0 deletions docs/deployment-guides/config-json/providers.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ icon: "plug"

All providers are configured under `providers` in `config.json`. Each provider entry contains a `keys` array where every key has a `name`, `value`, `models`, and `weight`, plus optional provider-specific config objects.

<Note>
In DB-backed deployments, provider entries from `config.json` are reconciled into the config store at startup. The default `source_of_truth: "split"` mode preserves UI/API edits while matching file-backed providers are unchanged. With `source_of_truth: "config.json"`, a present `providers` section is authoritative and prunes DB-only providers or keys. See [Source of Truth & Reconciliation](/deployment-guides/config-json/source-of-truth).
</Note>

**Supplying credentials:**

Use the `env.` prefix to reference environment variables - never put API keys directly in `config.json`:
Expand Down
24 changes: 24 additions & 0 deletions docs/deployment-guides/config-json/schema-reference.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ This page is a concise reference for every top-level key in `config.json`. Click
| Key | Type | Description | Guide |
|-----|------|-------------|-------|
| `$schema` | string | Schema URL for IDE validation. Set to `"https://www.getbifrost.ai/schema"` | - |
| `version` | integer | Compatibility switch for empty allow-list arrays. Omit for current v2 semantics. | [`version`](#version) |
| `source_of_truth` | string | Startup reconciliation mode for DB-backed `config.json`: `"split"` or `"config.json"` | [Source of Truth](/deployment-guides/config-json/source-of-truth) |
| `encryption_key` | string | Optional AES-256 key (derived via Argon2id). Accepts `env.VAR` prefix and is also read from `BIFROST_ENCRYPTION_KEY`. If omitted, data is stored in plaintext. | [Client](/deployment-guides/config-json/client#encryption-key) |
| `client` | object | Worker pool, logging, CORS, auth enforcement, header filtering, MCP, compat shims | [Client](/deployment-guides/config-json/client) |
| `providers` | object | LLM provider API keys, network settings, concurrency | [Providers](/deployment-guides/config-json/providers) |
Expand Down Expand Up @@ -48,6 +50,28 @@ Omitting `version` uses v2 semantics. Set `"version": 1` only if you are migrati

---

## `source_of_truth`

Controls how `config.json` is reconciled with the config store at startup.

| Value | Behaviour |
|-------|-----------|
| `"split"` *(default)* | File-backed rows seed or update the config store by hash, while unchanged file-backed rows preserve UI/API edits |
| `"config.json"` | Explicitly present file sections are authoritative and replace matching DB state on startup |

Missing and empty sections behave differently when `source_of_truth` is `"config.json"`. A missing section leaves DB rows untouched; a present empty section is authoritative and can prune matching DB rows.

```json
{
"source_of_truth": "config.json",
"plugins": []
}
```

The example above makes the `plugins` section present and empty, so stored plugins are removed on startup. See [Source of Truth & Reconciliation](/deployment-guides/config-json/source-of-truth) for section-by-section behavior.

---

## `client`

Controls the worker pool, logging pipeline, security, and SDK shims. All fields are optional.
Expand Down
Loading
Loading