Search

↑↓ to move↵ to openesc to closetop 6 documentation results

Book a demoTry TokenTriage

In development · Explore the product preview. Join the launch list or book a walkthrough.

Browse docs

Choosing a prompt routing signal

Choose between complexity, topic matching, and estimated cost before you write a routing rule.

ExplanationLast updated

On this page

Some requests give you useful clues before a model sees them. A short factual question, a legal drafting request, and a large code review may each deserve a different model.

TokenTriage exposes those clues as signals: values computed from the request before it is routed. This page helps you choose one of three prompt signals, add it to a rules node, and check the resulting decision without adding another model call.

You need at least two tiers and a working policy. If you do not have those yet, start with Turn routing on. The examples use tiers named cheap, mid, and frontier; replace them with the names from your config.

Choose a signal

Start with the difference you can observe reliably in your own traffic.

Your routing goal Signal Main limitation
separate broadly simple and difficult prompts signal.complexity short, difficult prompts can look simple
keep recognizable subject areas on specific tiers signal.semantic.<topic> matches shared wording, not meaning
avoid large requests on a high-cost tier signal.est_cost assumes a fixed response length

All three signals are computed locally, so none adds a second upstream call. Complexity is available on every request. Topic matching and estimated cost run only when you configure their scorers. If a signal is unavailable, its comparison is false and the request follows the default branch.

The thresholds below are examples, not recommended production values. Start in shadow, observe your own requests, and adjust the threshold before you let the rule change traffic.

signal.complexity — estimated difficulty

Use complexity when a broad simple-versus-difficult split describes your workload. A lower score means the prompt looks more like something your lower-cost tier can handle.

routing:
  default_tier: frontier
  policy:
    version: 1
    params:
      simple_cut:
        value: 0.35
        calibrate: { signal: complexity, direction: below }
    root:
      rules:
        - id: simple-to-cheap
          when: "signal.complexity < $simple_cut and not request.has_tools"
          to: { tier: cheap }
      default: { tier: frontier }

This example sends prompts below 0.35 to cheap. Requests that declare tools stay on frontier, because an error can compound across a tool loop. Remove that condition only if you have evaluated tool-using requests separately.

The score comes from fixed patterns in the prompt, including length, multi-step structure, question patterns, and code or reasoning vocabulary. It is a local heuristic, not a model call and not a prediction of answer quality.

Main limitation. A short question about a subtle API contract can receive a low score. Use another signal when difficult prompts in your workload are often terse.

How the complexity score is weighted

The classifier combines seven fixed inputs. Code-related vocabulary contributes 0.30; reasoning phrases and technical vocabulary contribute 0.25 each; length contributes 0.10; multi-step structure contributes 0.03; and question marks contribute 0.02. Matching a simple pattern lowers the result, and the final score is clamped to [0,1].

Length is estimated from characters. Prompts below 15 estimated tokens pull the score down, while prompts above 400 estimated tokens push it up. See the hot-path benchmarks for measured classifier overhead.

signal.semantic.<topic> — recognizable topic wording

Use topic matching when each route has a stable vocabulary: legal drafting, billing support, or another subject you can describe with representative phrases. Despite the scorer’s semantic name, it matches words rather than general meaning.

routing:
  default_tier: mid
  policy:
    version: 1
    scorers:
      semantic:
        budget_ms: 200
        topics:
          legal:
            exemplars: ["draft a contract clause", "review this NDA"]
          support:
            exemplars: ["track my shipment", "billing question"]
    params:
      topic_conf: { value: 0.8 }
    root:
      rules:
        - id: legal-to-frontier
          when: "signal.semantic.legal >= $topic_conf"
          to: { tier: frontier }
        - id: support-to-cheap
          when: "signal.semantic.support >= $topic_conf"
          to: { tier: cheap }
      default: { tier: mid }

Each topic receives a score from 0 to 1. A higher score means the prompt shares more distinctive words with at least one of that topic’s example phrases. The condition owns the threshold; there is no separate threshold setting on the scorer.

The scorer’s default time budget is 1 millisecond. The example raises it to 200, but that is a starting value rather than a production recommendation. Choose a budget that fits your latency target, then confirm in shadow that the topic signal is present under load. When the scorer exceeds its budget, its signals are omitted and the request follows default.

Main limitation. “Terminate the agreement” and “cancel the contract” share no content words, so they can score near zero against each other. Use another signal when paraphrases or multilingual prompts are common.

Technical matching and tokenizer details

Each example phrase becomes a term frequency–inverse document frequency (TF-IDF) vector. The prompt becomes one too, and the topic score is the highest cosine similarity against any of its examples. Common English words are removed before matching.

Words are split on ASCII word characters, so text written in a non-Latin alphabet will not match Latin-alphabet examples. Words not present in the topic examples are dropped before the query is normalized. See Selectors and scorers for the full configuration reference.

signal.est_cost — estimated request cost

Use estimated cost when prompt size is a useful predictor of spend. The scorer answers one specific question: what would this request cost if the configured reference tier served it?

routing:
  default_tier: frontier
  policy:
    version: 1
    scorers:
      est_cost:
        reference_tier: frontier
    params:
      frontier_cost_limit: { value: 0.02 }
    root:
      rules:
        - id: pricey-to-cheap
          when: "signal.est_cost >= $frontier_cost_limit"
          to: { tier: cheap }
      default: { tier: frontier }

This example sends a request to cheap when its estimated cost on frontier is at least $0.02. Set that threshold from your own price and quality targets, and verify the lower-cost tier on representative requests before enforcing the rule.

The scorer estimates input tokens from prompt bytes, adds an assumed 256 output tokens, and applies the reference tier’s catalog rates. It is computed locally and does not make a network or model call.

Main limitation. The output estimate is constant because the response does not exist yet. If one request returns a sentence and another returns a file, the signal mostly distinguishes their prompt sizes rather than their final bills.

If the reference tier has no catalog price, the signal is omitted and the request follows default. A reference_tier that does not name a configured tier prevents the policy from loading.

Estimated-cost formula and settings
signal.est_cost = (prompt_bytes / chars_per_token) × input_rate
                + est_output_tokens × output_rate

reference_tier is required. By default, the estimate assumes 256 output tokens and four prompt bytes per input token. Rates are resolved from the reference tier’s provider, catalog prefix, and region. See Selectors and scorers for the complete settings and behavior.

Verify one request before routing

A matching rule and a useful rule are different claims. Keep the top-level mode set to shadow, then validate the edited config:

$ tokentriage validate --config ./tokentriage.yaml

After you send one representative request through your normal client, explain its recorded decision using the request ID:

$ tokentriage policy explain --req-id <request-id> --log ~/.tokentriage/decisions.jsonl

Look for both the rule ID in path and the expected value in read:

Signal Expected read Expected matched rule
complexity signal.complexity = ... rules[simple-to-cheap] for a low score
topic signal.semantic.legal = ... rules[legal-to-frontier] for a strong legal match
estimated cost signal.est_cost = ... rules[pricey-to-cheap] above the cost limit

If the signal is absent, the comparison is false. Check the scorer configuration, its time budget, or the reference tier’s price before changing the default branch. Routing explanation fields covers the rest of the output.

What these signals cannot tell you

These signals describe a request before an answer exists. None proves that a lower-cost tier will produce an acceptable answer. Measure that separately before moving from shadow to route.

Use Route on context when the caller, time, or remaining budget should decide. Use Cheapest that qualifies to compare tiers against runtime criteria. Use Cascades when the decision depends on inspecting an answer.