Prompt-aware model choice for Bifrost, through its Complexity Router. Bifrost asks Nadir which tier a request belongs to, and your own routing rules pick the model. There is no plugin to install and no application code to change.
Info
What it adds
Bifrost's Complexity Router publishes a tier, complexity_tier, that routing rules can match. It ships its own classifiers, and it can also ask a decision model. Nadir plugs in as that decision model.
- Bifrost sends the recent user messages and its tier question to
POST /v1/decisions, Nadir's endpoint in OpenAI's Decisions API shape. - Nadir's classifier answers
SIMPLE,MEDIUMorCOMPLEX, with a confidence. It generates no text. - A Bifrost routing rule maps each tier to a provider and model, so keys, load balancing, fallbacks and logs all stay in Bifrost.
Provider keys stay in Bifrost and the completion never passes through Nadir. Only the decision does.
Set it up
1. Give Bifrost a Nadir key
Create a key under API Keys in the Nadir dashboard and expose it to Bifrost as NADIR_API_KEY.
NADIR_API_KEY=ndr_...
Use a key even to try it. Bifrost treats a keyless custom provider as serving every model, so ordinary chat traffic could be sent to it.
2. Add Nadir as a decision provider
{
"providers": {
"nadir": {
"keys": [
{
"name": "nadir-key",
"value": "env.NADIR_API_KEY",
"models": ["nadir-bucket"],
"weight": 1
}
],
"network_config": {
"base_url": "https://api.getnadir.com"
},
"custom_provider_config": {
"base_provider_type": "openai",
"allowed_requests": {
"list_models": false,
"decisions": true
}
}
}
}
}In the Web UI this is Models, Model Providers, Add New Provider, with Base Format OpenAI, the same base URL, Decisions on, List Models off, and a key whose Allowed Models is nadir-bucket.
- Give the bare origin. Bifrost appends
/v1/decisionsitself. - Keep List Models off. Nadir's
/v1/modelslists the chat models it proxies. With listing on, Bifrost would treat this provider as serving them. - Keep the key's models to one name. The name itself is not checked, and Nadir echoes it back.
nadir-bucketis a convention.
3. Make it the Complexity Router's classifier
{
"governance": {
"complexity_analyzer_config": {
"classifier": "decision",
"decision": {
"provider": "nadir",
"model": "nadir-bucket",
"previous_message_count": 1
},
"keywords": {
"simple_keywords": ["fix the grammar in this sentence."],
"medium_keywords": ["apply a standard method to this technical question."],
"complex_keywords": ["design a solution under several competing constraints."]
}
}
}
}Bifrost requires the three keywords lists in config.json even when a decision model classifies. Without them it logs keyword lists must be non-empty at startup, drops the whole block, and no request gets a tier.
4. Route each tier
The router only publishes the tier. A rule per tier decides where it goes. Classification runs only for requests that a rule referencing complexity_tier evaluates.
[
{
"id": "tier-simple",
"name": "Simple",
"enabled": true,
"cel_expression": "complexity_tier == \"SIMPLE\"",
"targets": [{ "provider": "anthropic", "model": "claude-haiku-4-5", "weight": 1 }],
"scope": "global",
"priority": 0
},
{
"id": "tier-medium",
"name": "Medium",
"enabled": true,
"cel_expression": "complexity_tier == \"MEDIUM\"",
"targets": [{ "provider": "anthropic", "model": "claude-sonnet-5", "weight": 1 }],
"scope": "global",
"priority": 1
},
{
"id": "tier-complex",
"name": "Complex",
"enabled": true,
"cel_expression": "complexity_tier == \"COMPLEX\"",
"targets": [{ "provider": "anthropic", "model": "claude-opus-5", "weight": 1 }],
"scope": "global",
"priority": 2
}
]Then send requests as you already do. When a rule matches, Bifrost replaces the requested model with that rule's target.
Tier guidance does not apply
Bifrost lets you edit each tier's definition, signals and examples, and sends them with every decision request. They steer a prompted model. Nadir's classifier is trained, so it accepts the guidance and ignores it. To change which model serves which prompts, change the routing rules' targets.
What reaches Nadir
Each classification sends the latest user message, plus as many earlier user messages as previous_message_count allows, the tier question and the tier guidance. No system prompt, no assistant turns, no tools and no provider keys. Nadir classifies the user text of those messages together.
A keyed decision is logged to your Nadir account. By default the log row includes the prompt text, so the dashboard can show it back to you. Turn prompt storage off under Settings, or per key, and Nadir stores a sha256: digest in its place. Routing is unaffected either way.
Guarantees
- Opt-in. Bifrost calls Nadir only when a routing rule references
complexity_tier. Other requests are untouched. - Fail-open. A timeout, a non-2xx response or an unreachable host publishes no tier, and the request continues through Bifrost's normal routing. With session-aware routing on, a session that already has a tier keeps it.
- Bounded. Each classification waits at most
decision.timeout, 1.5 seconds by default. If requests log the tier as skipped, raise it.
Seeing the decision
Bifrost's request log records complexity_tier and complexity_mechanism, which reads decision when Nadir answered and skipped when no tier was published. The routing decision log names the model and confidence.
Decision model complexity: tier=SIMPLE confidence=0.87 model=nadir/nadir-bucket
What has been validated
The October 22, 2026 validation run used Bifrost built from its dev branch, with Nadir as the decision model against api.getnadir.com, three tier rules, and a local mock provider as every rule's target. Every request named the same model, and Nadir's tier moved it: a short arithmetic question and a greeting went to the simple target, a Go refactor across handlers and a distributed-systems design question to the complex target. The mock supplied the completions; this did not validate real model inference.
With the keyed configuration above and a deliberately invalid key, the decision call reached Nadir with the key and got HTTP 401, no tier was published, and the request was served on the model it named. In the same setup, an unprefixed chat request never reached the Nadir provider. With the base URL pointed at a dead port, requests still completed.
Not yet tested: a valid key end to end, a packaged Bifrost release, and the Web UI path.