Repository navigation
Replies: 1 comment 1 reply
|
Thanks for the proposal. My preferences on the six questions are:
I would also appreciate some clarification on two aspects:
|
1 reply
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
Hi all. I'd like to propose letting a router escalate to a stronger candidate when the cheaper model's answer is not good enough, not only when the call fails. This is the "cascade" pattern. I want to agree on the shape here before opening a PR.
Today
A
MODEL_ROUTER(#897) picks one candidate per request with rules, an LLM judge, or a custom executor. Withfallback(true)the remaining candidates are tried in declaration order, but only after the selected model fails, after its retries or immediately for a pre-call failure such as an unresolved resource. The loop does check a response for truncation, a missing structured output and the like, but a successful answer is final however poor it is, and tool rounds stay on the selected model. In short: routing decides before the call, and nothing checks the quality of a successful answer after it.How others do it
RoutingChatModel, Flink Agents' LLM judge (#897)Gateways (LiteLLM, OpenRouter, Portkey) declare the fallback list explicitly, cap it at a few models, and trigger on errors; Portkey's guardrail status codes are the one quality trigger. Every system above judges a complete answer, none a streamed one. #1324 proposes a LiteLLM Router integration for routing and failover among deployments; this proposal is about answer quality across models and is complementary.
Three findings shaped the design:
Proposal
An optional escalation policy next to
fallback. Fallback keeps handling errors. Escalation handles answers that pass but score badly.Names are illustrative. This example sets no
strategy(...), which is required today; I propose it becomes optional when tiers are set, so every request starts on the first tier. A strategy may still pick the starting tier. A low confidence, or a structural problem, re-sends the original request tobig, withbig's own prompt and tools; the cheap attempt is discarded.bigis the last tier, so its answer is final.Signals are checked after each model round and before any tool call from that round is dispatched, so a discarded attempt has no side effects. The structural signal is always on;
whenandoradd signals to it. Responses the loop already treats as failures (truncation, content filter, missed output schema) stay on the fallback path, and failure wins when both apply.confidencefield. When the request uses structured output the field is added to the schema and stripped from the returned content; otherwise it is read from a trailing JSON block. An ordering signal, not a probability. Exact mechanics are question 5.DECISION_MODEL([api][java][python] Add DecisionModel resources and Event/Action calls #1264) gets the request plus the answer and one predicate question;probability_truebelow the threshold escalates. A direct call underdurableExecutewith idescalate:<router>:<candidate>, not through the decision action, so ids do not collide. On tool rounds the route is direct, so the router name comes from the routing metadata parked for the request. Picking the starting tier with a decision model is [Feature] Decision-model routing strategy for MODEL_ROUTER #1325.CustomRoutingExecutor.Tiers are explicit, cheapest first, at least two, each a candidate; candidates left out stay fallback-only. Tier count bounds the hops,
maxEscalations(n)can cap lower. Two tiers, one hop, is the recommended default. The last tier always accepts on quality. If it fails after its retries, I propose fallback skips candidates that already answered this request, which is a change from today's fixed order; what happens with none left is question 6. Declaration order inof(...)keeps meaning fallback order.Tool loop: I propose that once a request escalates it stays on the stronger model (question 3). Switching back per round breaks prefix caching. Today tool rounds run under the plain
chatid with no fallback; that stays as it is.Replay: each initial attempt already has its own durable id,
chat:<router>:<candidate>. Confidence and decision-model verdicts are recorded beside it; structural checks are recomputed.Observability and cost: a
ModelRoutingEventwith sourceescalation, the signal and score. In themodel_routingblock,decision_sourcerecords the last step that changed the model, andescalation_attemptedis kept next tofallback_attempted, with the discarded attempt's tokens. Todayfallback_attemptedis derived from the final model differing from the selected one; with escalation it has to come from an actual fallback attempt. Two metrics next toroutingDecisionLatencyMs:routingEscalationCountandroutingEscalatedTokens. An escalated request pays the cheap call, the strong call and any judge or decision call, so the cascade costs more than calling the strong model directly once the escalation rate exceeds roughly (big minus small minus judge) over big. The docs should show that, and #1065, the per-agent cost budget, is where a ceiling belongs.Scope: Java first. Python follows the parity work in #1062; registering a
MODEL_ROUTERfrom Python raises today and the routing event is Java-only.Questions
escalationpolicy with its own tiers, or widenfallbackwith a quality trigger and reuse declaration order? I lean separate, so the error path does not change.strategy(...)become optional when tiers are set?escalation_failed? I lean fail, so callers never get an answer the policy rejected.References: OpenRouter: confidence thresholds, OpenRouter: Jev-verified cascade, Vals AI: Jev evaluation, Bedrock Intelligent Prompt Routing, LangChain4j model router, Portkey guardrails, NVIDIA NeMo Switchyard escalation router, FrugalGPT, AutoMix, Bouchard, Is escalation worth it?, Gupta et al., LM cascades.
All reactions