Skip to main content

Overview

The Complexity Router analyzes incoming requests and assigns a tier - Simple, Medium, Complex, or Reasoning - when the latest user message contains a clear complexity signal. The result is exposed as a flat string variable (complexity_tier) in Bifrost’s CEL routing engine, so you can write routing rules like:
This lets you route simple greetings to a fast, cheap model and deep reasoning tasks to a frontier model — automatically, with no changes to your application code. When the latest user message does not match a configured signal, Bifrost leaves complexity_tier unknown and keeps the request on its existing routing path instead of guessing. The algorithm is fast and deterministic: it runs entirely in-process using pre-compiled literal and stemmed keyword matching, adds less than 1 ms to request latency, and makes zero external calls. Complexity Router Configuration

How it works

Scoring dimensions

Classified requests produce a score between 0.0 and 1.0. The analyzer starts with a weighted score across five dimensions detected by scanning the last user message: Simple indicators are a small downward nudge, not an override. A simple-only request can classify as Simple with a 0.00 score, while a request that also has strong code, technical, or reasoning signals can still rise into a higher tier.

System prompt contribution

When the latest user message already has a code, technical, or reasoning signal, the system prompt is scanned for code and technical signals. Its contribution is weighted at 25% of the user-message signal for those dimensions. This provides soft lexical context — for example, a system prompt describing a coding assistant nudges code scores up — but it never creates a classification by itself.

Conversation context blending

For multi-turn conversations, prior context is used only when the latest user message has its own code, technical, or reasoning signal, or when the latest message is a configured continuation phrase such as “do it”, “try again”, “continue”, or “go ahead” and the prior-context score is at least 0.20. In those cases, the score blends the current message with history from up to the last 10 user turns (recency-weighted: earlier turns count less):
  • Default blend: 60% last message + 40% conversation history
  • Continuation blend: 35% last message + 65% conversation history
An explicit continuation phrase inherits most of its score from prior context rather than being classified on its own. A low-signal latest message that is not an explicit continuation does not inherit old context; complexity_tier remains unknown and routing falls through to the original model. The final score is max(last_message_score, weighted_blend) — the current message always sets a floor.

Reasoning override

When two or more reasoning keywords are detected in the last user message, the tier is forced to Reasoning regardless of the numeric score. The same override applies when one strong reasoning keyword appears alongside strong code or technical signals. This handles prompts like “step by step, explain why the authentication flow fails” that would score moderately on each individual dimension but clearly require deep reasoning.

Tier classification

The final score maps to a tier using configurable boundaries (defaults shown): Complexity Analyzer Pipeline

Configuration

Tier boundaries

Adjust where the score thresholds fall to match your traffic and model lineup.
Navigate to Complexity Router in the sidebar.The Complexity Spectrum bar updates live as you type boundary values, so you can see how your traffic would be distributed before saving.Complexity Router Tier Configuration
  1. Enter a value between 0 and 1 for each boundary.
  2. Boundaries must be strictly increasing: simple_medium < medium_complex < complex_reasoning.
  3. Click Save changes to apply immediately (hot-reloaded, no restart required).
  4. Click Restore defaults to reset all boundaries and keyword lists to factory values.

Keyword lists

Each list controls a different part of the scoring signal. Understanding what they do helps you tune routing for your domain.
The default keyword lists are tuned for common request patterns and are a good starting point for most deployments. For domain-specific traffic, add or remove keywords based on the prompts your users actually send so the tiers match your routing strategy. Bifrost stores and displays the exact keywords you configure; internally, normal word-based keywords and phrases also match common word forms, such as debug, debugging, and debugged. Punctuation-heavy terms such as ci/cd still use literal matching.
Complexity Router Keyword ListsType a keyword or phrase and press Enter to add it. Click the × on any tag to remove it. The entry count is shown next to each list label.

Routing with complexity_tier

Once the analyzer is configured, use complexity_tier as a variable in any CEL routing rule expression. Bifrost evaluates it as a plain string. complexity_tier is not a special standalone rule type. In the Routing Rules builder, it behaves like any other field, so you can combine it with headers, request type, team/customer scope, budgets, and other predicates in the same rule or nested rule group.
Complexity Router only exposes complexity_tier; it does not create rules automatically. Add rules for the tiers you want to route. For deterministic four-tier routing, create rules for Simple, Medium, Complex, and Reasoning.

Available operators

Combining with other rule conditions

You can mix complexity with any other routing condition the CEL builder supports:

Setting up a complexity-based routing rule

The best first rollout is usually a single Reasoning rule. It is easy to validate, has the smallest blast radius, and leaves Simple, Medium, and Complex traffic on your existing routing path.
  1. Go to Routing Rules in the sidebar.
  2. Create a new rule and open the CEL builder.
  3. Add a condition: field = Complexity Tier, operator = =, value = Reasoning.
  4. Set the target provider and model to your strongest reasoning model.
  5. Save and enable the rule.
Once you are happy with the classifications, add complementary rules for Simple, Medium, and Complex if you want a full tier-based routing ladder. An example is shown below. Routing Rule with Complexity Tier

Use case examples

Start with a Reasoning carve-out

Route only frontier-worthy requests to your strongest model and let everything else keep using your existing routing:

Full four-tier ladder

Route every tier explicitly when you want deterministic model selection across the full spectrum:

Roll out to one team first

Test complexity routing with a single team before enabling it globally:

Observability

Complexity analysis is recorded in the routing log for every request where analysis ran and produced a tier. In the log detail view, look at the Routing Decision Logs section backed by routing_engine_logs. Routing Logs with Complexity Tier You will see log lines like Complexity: tier=REASONING score=0.38 words=25. If no configured complexity signal matches the latest user message, the routing log records that complexity analysis was skipped and routing continues on the existing path. This lets you audit how traffic is being distributed and spot mis-classifications to tune thresholds or keyword lists.

Troubleshooting

Rule not matching when complexity_tier is set

If the routing rule uses complexity_tier and the request is not matching, make sure the latest user message contains analyzable user text and at least one configured signal. A system prompt by itself is not enough — the analyzer needs a text-bearing user prompt to classify. If analysis is unavailable (for example the body could not be parsed, the user content is not text-only, or no configured complexity signal matches the latest user message), the complexity-dependent rule does not match and evaluation falls through to the next rule. This is intentional: complexity rules silently degrade rather than blocking requests.

Which request types are supported

Complexity routing currently runs only for text-bearing request families. Supported inputs include:
  • Chat Completions and other messages-style requests with text-only user content
  • Text Completions requests using prompt
  • Responses API requests using text-only input
  • Anthropic Messages, Bedrock Converse, and Gemini contents / systemInstruction shapes when they carry text-only user input
It does not run for:
  • Image generation, embeddings, rerank, OCR, audio/speech/transcription, video, or count-tokens requests
  • Chat or Responses requests where user content mixes text with image, file, or audio blocks
  • Requests that contain only system or developer text and no user text

All traffic classified as Reasoning

The reasoning keywords list is the most common cause. Check if any broad single-word terms were added (e.g. “explain”, “analyze”). The override gate fires when two or more strong reasoning keywords match — a broad list will trigger it on most prompts. Replace single-word terms with specific multi-word phrases.

Want to test threshold changes without affecting live traffic

Use the Discard changes button to revert unsaved edits, or Restore defaults to return to factory settings. Changes only take effect on save.

Next Steps

Routing Rules

Full reference for CEL expressions, scope hierarchy, and rule chaining

Virtual Keys

Scope complexity routing rules to specific teams, customers, or virtual keys

Budget & Limits

Combine complexity routing with budget limits for cost-optimal routing

Provider Routing

Understand how complexity routing fits into the full request routing pipeline