sign in
ProblemRouter
Sign inStart a search
1 · Search2 · API3 · Observability

Stop measuring prompts.
Start routing problems.

Eval tools tell you your prompt scores 72%. ProblemRouter routes the problem to the prompt and model that score 91% — at half the cost. And then, if you want, it keeps it that way.

Start a search →See how it serves
Routes across every model
OpenAI Anthropic Google Meta Mistral DeepSeek xAI Amazon
We have no model to sell you — a cheaper one is allowed to win.
Layer one · Search

You describe the problem. It writes the prompts.

It drafts candidate prompts, runs each one against your examples on every model worth trying, and keeps spending your budget wherever the results are still improving. Cheap checks first; full runs only for the candidates still standing.

1Start — or resume — your problem
Classify each incoming support ticket into one of six categories, from the subject and body.
Input / output shape
input { subject: text, body: text }
output { category: enum(6) }
You never write a prompt. ProblemRouter generates and optimizes the prompts itself — your description is the problem, not the prompt.
Budget$ 20stops when spent, resumes free
Route this problem
2Configurations we found
searching
$/1k reqs ↑
$1.70
$0.80
$0
84%
87%
90%
gpt-4o
mistral-large/ 1
llama-8b
gpt-4o-mini/ 3
gemini-flash
llama-70b/ 2
claude-haiku/ 1
claude-sonnet
accuracy →
frontier (ringed)dominated — faded
searching — evaluating configurations…
Ringed = nothing else we tried beats it on both accuracy and cost.
1 · SEARCH
PRODUCESand this is the whole product, if you want it to be
A configuration
You triage support tickets. From the {{subject}} and {{body}}, weigh intent over keywords and return exactly one of: billing, bug, account, feature, refund, other. When ambiguous, prefer the user's primary ask.
anthropic/claude-haiku · temp 0 · top-p 191% · $0.0004 / call
↓ WHICH 2 · THE API RUNSpromote it and it serves — there is no separate deploy step
Layer two · API

One URL. Your input in, your typed output back.

The JSON you POST is exactly the input shape you declared; the JSON you get back is exactly the output shape. Everything of ours travels in the headers — never in your payload. Nothing to install, no library, no proxy.

YOUR APPLICATIONwherever it already runs · nothing of ours inside it
REQUESTwhat you send
POST /v1/run/triage_tickets
authorization: bearer fmp_live_…
content-type: application/json
{ "subject": "charged twice",
  "body": "I was billed on the 3rd…" }
↑ exactly the input shape you declared — no wrapper, no reserved keys, no field your own data is forbidden from using.
RESPONSEwhat you get · 11 ms
200  { "category": "billing" }
x-fmp-configuration: cfg_4f1c9a
x-fmp-cache: hit   x-fmp-version: live
x-fmp-cost-credits: 0.04
↑ exactly the output shape, checked against it before you get it. Everything of ours is a header.
WHAT IT PASSES THROUGH

CACHE61%

In-process first, then a shared one. Most requests never reach a model. The key includes a fingerprint of the configuration, so two versions can never serve each other’s answers.

A/B TESTING90 / 10

A stable hash of the caller sends most traffic to what’s live and the rest to a version you’re trying. The same caller always lands on the same one, so nobody flips mid-session.

PROVIDER372 ms

Whichever model won your search. The spend is held before the call and settled against the real cost afterwards, so an empty wallet fails before any tokens are bought.

FALLBACK×2 + 1

Two retries on a transient failure, then a different provider that satisfies the same output shape. Your user still gets an answer — and it still counts as a failure in your numbers.

BEFORE ANY MODEL IS CALLEDThe key is checked, the rate limit is checked, and the body is checked against your input shape — 401 · 402 · 422 are answered in under a millisecond, without reaching a provider. And after the response has gone, in another process: the trace row, the cache write, and settling the cost.
2 · THE API
PRODUCESwritten after your bytes have gone — never on your caller’s clock
Requests and responses
One row per request, always: the input, the output, which configuration produced it, which version served it, whether it came from cache, how long each stage took, tokens, cost, and the error if there was one.
↓ WHICH 3 · OBSERVABILITY READSa sample of it, scored — and the interesting part kept
Layer three · Observability

A sample is scored. The interesting part is kept.

The scorer only ever sees the input and the output — production has no answer key. Put every week’s traffic, every verdict it handed down, and every deploy on one axis, and the whole quarter reads in a single picture.

SIXTEEN WEEKS OF ONE ENDPOINT not scored scored well middlingscored badly average score
ROLLING THE NEW VERSION OUT10% → 50% → 90% → 100%4008001,2000responses scored, per week0.950.750.55average scorebelow the floor —the next search starts here10%50%90%100%the floor you set — 0.600.910.93week 1week 16The new version takes 10% of callers first, and onlyadvances if it scores no worse. It rolls back if not.Volume never moved. What moved was the mix: the hatched band grew for nine weeks while the line fell, and shrank again once abetter configuration was serving. Every badly- and middling-scored response was kept — they are what the search that fixed this ran against.
The block above each bar is the ~22,800 responses a week we didn’t score — off scale, drawn broken, so the sample that was scored stays readable.
3 · OBSERVABILITY
PRODUCESversion 5 — stamped, so its scores stay comparable to version 4
A dataset
A quarter of what your users actually asked — including the cases your original examples never contained, and a held-back set that is collected and never trained on.
It arrives on the “Bring data” tab you already know — or presses Resume for you, if you let it.
↑ WHICH IS WHERE 1 · SEARCH BEGANand the loop closes
You bring the problem. We bring the rest.

You can stop at layer one — copy the prompt and go, it runs anywhere.

Start a search →