/** * Cheap first, escalate on measured failure — and the number that says whether * that is a saving or a bill. * * "Route it to the cheaper model" has been a recommendation with a quality * question attached since 1.23. Outcomes answered the quality question. This * answers the one nobody asks out loud: **an escalation pays twice**, so a * ladder is only a saving below a specific escalation rate, and above it the * ladder costs more than never having built it. * * ## The arithmetic, stated rather than assumed * * Without a ladder, every call costs `dear`. With one, every call costs * `cheap`, and the escalated share pays `dear` **on top** — the cheap attempt * is not refunded. * * with a ladder: cheap + rate x dear * without one: dear * * Those are equal at `rate = (dear - cheap) / dear`. Below it the ladder saves; * above it the ladder is a more expensive way to get the same answers. A ladder * sold as a saving without that number is the same head-arithmetic error `plan` * was built to kill — and it is worse here, because the mistake compounds with * traffic and nobody notices until a quarter is over. * * ## The escalation signal is the caller's * * Never inferred from length, latency, refusal text, a stop reason or a retry. * The same refusal `outcome` makes, for a sharper reason: this is a **control * loop**, not a report. A report built on a guess prints a wrong number; a * control loop built on a guess sends real traffic to a more expensive model on * the strength of that guess, forever, and bills for it. * * No signal, no ladder. * * ## What this module does not do * * It does not execute anything. A ladder escalates *after* a failure is known, * which is after the answer came back and usually after something downstream * judged it — so the retry belongs to the caller's own loop, not to a proxy * sitting on one request. What lives here is the policy and the arithmetic that * says whether the policy is worth running, measured against what actually * happened. */ import type { OutcomeTally, OutcomeVocabulary } from './outcome.js'; import type { PricingCatalogue } from './pricing.js'; /** One workload's ladder, as the config declares it. */ export interface LadderPolicy { /** * Model ids, cheapest first. Two is the normal case; more is allowed and * priced pairwise, because a three-rung ladder that escalates twice pays * three times and the arithmetic has to say so. */ tiers: string[]; /** * The recorded outcome values that send the work up a tier. * * Declared, like the vocabulary itself. A value here that the vocabulary * never declared is a configuration error rather than a silent no-op, and * `validateLadder` says so. */ escalateOn: string[]; } export type LadderVerdict = /** Measured escalation is below break-even: the ladder saves money. */ 'saving' /** Measured escalation is above it: the ladder costs money. */ | 'costing' /** Within a hair of break-even, where the sign is not a claim worth making. */ | 'at-break-even' /** Not enough was measured to say. */ | 'cannot-tell'; export type LadderUnknown = 'no-outcomes-recorded' | 'no-escalation-values-declared' | 'tier-unpriced' | 'too-few-calls'; /** * Calls a workload needs before its escalation rate is treated as a rate. * * The same floor `per-outcome` uses for successes, and for the same reason: a * rate over fewer than ten observations moves more from one more observation * than from anything a team could do about it. A control loop switched on * because of nine calls is a control loop switched on for no reason. */ export declare const MIN_CALLS_FOR_LADDER = 10; /** * How close to break-even counts as break-even. * * Two percentage points. Inside that band the ladder's sign flips on ordinary * week-to-week variation, and reporting "saving" on Monday and "costing" on * Thursday from the same policy would teach a reader to ignore the figure. */ export declare const BREAK_EVEN_BAND = 0.02; export interface LadderArithmetic { /** What one call costs on the cheap tier. */ cheapUsd: number; /** What one call costs on the dear tier. */ dearUsd: number; /** * The escalation rate at which the ladder stops saving, 0-1. * * `(dear - cheap) / dear`. Null when either tier could not be priced — never * a zero, which would read as "any escalation at all loses money" and is a * completely different and much more alarming claim. */ breakEvenRate: number | null; } export interface LadderPosition { arithmetic: LadderArithmetic; /** The measured share of calls that escalated, 0-1, or null. */ measuredRate: number | null; verdict: LadderVerdict; /** Why, when the verdict is `cannot-tell`. A refusal never arrives bare. */ unknown: LadderUnknown | null; /** Calls behind the measured rate. */ calls: number; /** Calls whose recorded outcome was an escalation trigger. */ escalations: number; /** * What the ladder cost against what one tier alone would have, per call, or * null when it cannot be computed. Negative means the ladder is cheaper. */ deltaUsdPerCall: number | null; } /** * The cost of one call on a model, for a described shape of work. * * Deliberately takes the token shape rather than reading it from a log: the * break-even rate is a property of the *models and the work*, and somebody * should be able to compute it before running a single call through a ladder. */ export declare function ladderArithmetic(policy: LadderPolicy, shape: { inputTokens: number; outputTokens: number; }, catalogue: PricingCatalogue, on?: Date): LadderArithmetic; export declare function ladderPosition(policy: LadderPolicy, tally: OutcomeTally, shape: { inputTokens: number; outputTokens: number; }, vocabulary: OutcomeVocabulary | null, catalogue: PricingCatalogue, on?: Date): LadderPosition; export type LadderProblem = { kind: 'too-few-tiers'; tiers: number; } | { kind: 'unknown-model'; model: string; } | { kind: 'duplicate-tier'; model: string; } | { kind: 'tiers-not-cheapest-first'; model: string; after: string; } | { kind: 'escalate-on-undeclared'; value: string; } | { kind: 'escalate-on-a-success'; value: string; }; /** * Everything wrong with a ladder before it is ever run. * * Returned rather than thrown, so a caller can report all of it at once — a * config that has to be fixed one error per run is a config people give up on. * * The last two are the interesting ones. Escalating on a value the vocabulary * never declared is a ladder that silently never fires; escalating on a value * declared as a **success** is a ladder that pays twice for work that already * worked, which is the most expensive possible typo in this file. */ export declare function validateLadder(policy: LadderPolicy, vocabulary: OutcomeVocabulary | null, catalogue: PricingCatalogue, on?: Date): LadderProblem[]; //# sourceMappingURL=ladder.d.ts.map