Celon

Smart Routing

Smart routing

Not every request needs a frontier model. Celon scores a prompt’s difficulty in a few milliseconds and sends it to the cheapest tier that still holds the quality line.

celon/auto — difficulty-based automatic routing

Request with model: "celon/auto" and a rule-based classifier (no LLM call, <5ms on CPU) computes a difficulty score in the 0–1 range. The score is a weighted sum of the features below.

FeatureWeightDescription
promptSize+0.20Prompt token count (saturates to 0.5 at 1k tokens)
codePresence+0.25Code fences (```), code tokens, file extensions
reasoningSignals+0.40Reasoning keywords — analyze, design, prove, debug, optimize, trade-off … (matched in Korean and English)
structuredOutput+0.15Whether tools / tool_choice / a JSON response_format is used
simplicitySignals−0.25Summarize, classify, translate, extract keywords, and very short prompts (lowers the score)
conversationDepth+0.10Multi-turn conversation depth (accumulated context)

Score → tier mapping (with the default balanced dial): score ≥ 0.62 → Tier 1, score ≥ 0.30 → Tier 2, and anything below that → Tier 3. The computed score is reported as classifier_score in the response metadata.

Intent dials

The same classifier, but every workload wants a different trade-off. Pick one of four presets with celon.dial (per request) or the key’s routing policy (routing.dial). The applied dial is reported in the dial response metadata and the x-celon-dial header.

DialHow it worksRecommended workloads
balancedThe default. Thresholds 0.62 / 0.30, sticky weight 0.5 — the balance point between cost and quality.General-purpose backends · internal assistants · default integrations
strict-qualityLowers the thresholds to 0.45 / 0.18 to make promotion to higher tiers easier, and always applies sticky routing. Conservative about downgrading.Customer-facing chatbots · legal, medical, and other work where a wrong answer is costly
max-savingsRaises the thresholds to 0.85 / 0.55 and always forces the cheapest candidate (the only exception being extreme-difficulty queries beyond the anchor).High-volume batch · offline pipelines · tagging and classification
low-latencyReorders candidates to prefer latencyClass fast first, and always prioritizes cache-warm (sticky) providers. Thresholds are the same as the default.Real-time UIs · autocomplete · voice and streaming frontends

Purpose routers — intent as a model name

Instead of picking a dial yourself, you can name a purpose router id as the model string. celon/code for coding workloads, celon/quality when quality is non-negotiable (always frontier), celon/nitro when speed is everything (ranked by measured throughput), and celon/floor when cost comes first (ranked by real-time lowest price) — all of them are virtual model ids, so any OpenAI SDK applies them through a single model string.

Router idPurposeSelectionCandidate set
celon/codeDev Coding-focused — hard code goes to the frontier, lighter code to a chain of strong coding models.tieredT2: qwen/qwen3-235b → moonshot/kimi-k2.6 · T3: zhipu/glm-5.2 → deepseek/deepseek-v3.2 → meta-llama/llama-4-scout
celon/qualityTop quality Always uses frontier (Tier-1) models only. No difficulty classification — frontier chain + fallback.frontierT1: anthropic/claude-fable-5 → openai/gpt-5.2 → anthropic/claude-opus-4.8
celon/nitroTop speed Picks the fastest model by measured throughput (tokens/sec) and latency. No difficulty tiers.nitroEntire text catalog (modality, context, and availability constraints only)
celon/floorLowest price Picks the cheapest model by live pricing. No difficulty tiers.floorEntire text catalog (modality, context, and availability constraints only)

Savings-target routing

Specify celon.anchor (a quality ceiling) together with celon.savings_target (a target savings rate, in the 0–0.8 range) and the router automatically tunes how aggressively it routes downward so the realized savings converge on the target. For example, 0.4 targets “40% cheaper than always using the anchor alone.”

request.json
{
  "model": "celon/auto",
  "messages": [{ "role": "user", "content": "Summarize this quarter's metrics" }],
  "celon": {
    "anchor": "anthropic/claude-fable-5",
    "savings_target": 0.4
  }
}

The feedback loop is grounded in the usage ledger — it aggregates the realized savings rate (against the anchor baseline) from the key’s actual billing records, then routes downward more aggressively when it falls short of the target and pulls back more conservatively when it overshoots. Because the basis is the realized value left in the ledger rather than an estimate, cache hits and fallback costs are reflected exactly as they happened.

The four-stage constraint-aware pipeline

A celon/auto routing decision is not a single difficulty score — it runs through a four-stage pipeline that applies the request’s constraints in order. Each stage’s verdict is traced in the pipeline array of the response metadata.

StageNameBehavior
1Modality filterRemoves any candidate that doesn’t support a capability the request needs — image input, tool calling, a JSON schema, and so on (modality:pass).
2Context / compressionExcludes a candidate when the prompt exceeds its context window, and when context_compression: true, trims the middle (a lost-in-the-middle trim) to fit a cheaper model (context:fit).
3Sticky cache routingConversations that share a session_id prefer the provider whose prompt cache is warm — the lower the cache re-read price (for example Anthropic and DeepSeek at 0.1×), the greater the weight (sticky:hit, header x-celon-sticky).
4Difficulty + anchor fallbackApplies the difficulty score and the dial’s thresholds to the remaining candidates to settle on a tier. When an anchor model is set, even an extreme-difficulty query never climbs above the anchor (difficulty:0.41→tier2).

The three tiers

The models in each tier are listed in fallback order — if the first model fails, it automatically switches to the next. Prices are per 1M tokens (input / output).

TierCharacterModels (fallback order)Price ($/1M in / out)
Tier 1Frontier — hard reasoning and agents, the last line of defense against confident-wrong failuresanthropic/claude-fable-5 → openai/gpt-5.2$10 / $50 · $1.75 / $14
Tier 2High-performance open weights — everyday work, summarization, RAG answersmeta-llama/llama-4-maverick → qwen/qwen3-235b$0.15 / $0.60 · $0.09 / $0.60
Tier 3Ultra-fast and low-cost — classification, extraction, simple Q&A, high-volume batchdeepseek/deepseek-v3.2 → zhipu/glm-5.2 → meta-llama/llama-4-scout$0.27 / $0.40 · $0.85 / $2.68 · $0.08 / $0.30

Dynamic price tracking

The table above is a snapshot. Actual billing rates sync periodically from the LiteLLM and OpenRouter price feeds (every 6 hours by default), and every change is recorded in the model_pricing history table. Read the current rates with GET /admin/pricing, force an immediate re-sync with POST /admin/pricing/refresh, or pin a specific model’s rate by hand with PUT /admin/pricing/override.

Naming a model explicitly

Catalog ids use the vendor/model form — for example anthropic/claude-fable-5, openai/gpt-5.2, deepseek/deepseek-v3.2. List them all with GET /v1/models. Name a model and the request bypasses the classifier and passes straight through (classifier_score: null); an id that isn’t in the catalog is rejected with 404 model_not_found. The celon.tier hint is ignored for explicit-model requests.

Per-request celon options

The celon object in the request body lets you control routing and caching behavior per request. Every field is optional.

request.json
{
  "model": "celon/auto",
  "messages": [{ "role": "user", "content": "Design the core API" }],
  "celon": {
    "dial": "strict-quality",
    "anchor": "anthropic/claude-fable-5",
    "savings_target": 0.3,
    "session_id": "conv_8f2a91",
    "context_compression": true,
    "no_cache": true,
    "fallbacks": ["meta-llama/llama-4-maverick", "deepseek/deepseek-v3.2"]
  }
}
FieldTypeDescription
dialbalanced | strict-quality | max-savings | low-latencyThe intent dial for this request. When omitted, the key’s configured dial, then balanced.
anchorstringThe anchor (ceiling) model id — even an extreme-difficulty query never falls back above this model. Takes precedence over the key’s routing.anchorModel.
savings_targetnumber (0–0.8)Target savings rate against the anchor — uses the realized savings rate from the ledger as feedback to auto-tune routing strength. See Savings-target routing.
session_idstringSticky-routing session id — conversations sharing an id are pinned to the provider whose prompt cache is warm (check the x-celon-sticky header).
context_compressionbooleanTrims the middle of an over-long prompt (a lost-in-the-middle trim) to fit the context window of a cheaper model.
tier1 | 2 | 3celon/auto only — skips the classifier and forces the given tier.
no_cachebooleanDisables both cache lookup and storage for this request.
no_pii_maskbooleanPasses this one request through without masking even when PII masking is enabled on the key.
fallbacksstring[]Explicit-model requests only — a chain of catalog ids appended after the named model. With celon/auto the tier chain already acts as the fallback.
shadowbooleanShadow (validation) mode — the response is served by the anchor (else the frontier baseline) while the router's pick and its estimated cost are only recorded in the celon.shadow response meta (would_model · would_tier · would_cost_usd) and the x-celon-shadow header. Use it to validate the router on real traffic risk-free. Overrides the key's routing.shadow; auto routing only.

How the fallback chain behaves

  • Retry conditions — if an upstream returns 429 or 5xx, or a network/timeout error occurs, it automatically switches to the next candidate. Any other 4xx fails immediately.
  • Switch order — it first tries the same model’s alternate providers (altProviders, e.g. DeepInfra → Novita), then moves on to the next candidate model.
  • Tracing — the attempted path is recorded in the attempts array of the response metadata and the x-celon-attempts header.
  • When all fail — it responds with 502 upstream_unavailable.
  • Budget downgrade — when a key is over budget and its policy is downgrade, celon/auto requests are forced to Tier 3. See Governance.

Response metadata

Every response carries the routing outcome in the celon body field and the x-celon-* headers.

response.json
{
  "id": "chatcmpl-…",
  "object": "chat.completion",
  "model": "meta-llama/llama-4-maverick",
  "choices": [ … ],
  "usage": { "prompt_tokens": 21, "completion_tokens": 96, "total_tokens": 117 },
  "celon": {
    "model": "meta-llama/llama-4-maverick",
    "provider": "deepinfra",
    "tier": 2,
    "classifier_score": 0.41,
    "cache_hit": null,
    "cost_usd": 0.000122,
    "saved_usd": 0.003886,
    "latency_ms": 842,
    "pii_masked": 0,
    "attempts": [],
    "dial": "balanced",
    "sticky": true,
    "pipeline": ["modality:pass", "context:fit", "sticky:hit", "difficulty:0.41→tier2"],
    "guardrails": { "mode": "mask", "findings": 0, "blocked": false }
  }
}
HeaderValue
x-celon-modelThe catalog model id that actually served the request
x-celon-tierThe routed tier (0–3, when applicable)
x-celon-cacheexact | semantic | miss
x-celon-cost-usdThe cost of this request (USD, 6 decimal places)
x-celon-saved-usdSavings against the Tier 1 baseline (USD)
x-celon-attemptsThe list of failed attempts (only when a fallback occurred)
x-celon-dialThe applied intent dial — balanced · strict-quality · max-savings · low-latency
x-celon-sticky1 — only when sticky routing pinned to a cache-warm provider
x-celon-guardrail-findingsThe number of guardrail findings (omitted when 0) — see Tiered guardrails

With streaming the cost isn’t known up front, so only x-celon-model · x-celon-tier · x-celon-dial · x-celon-sticky · x-celon-attempts go out as headers at the start; the stream’s final data chunk (choices: []) carries the full usage and celon metadata, followed by data: [DONE].