import type { PricingCatalogue } from './pricing.js'; import type { UsageRecord } from './usage.js'; /** * Where the output spend concentrates. * * ## The biggest line, and nothing said anything actionable about it * * Output is over half of many real bills — **87%** on the support prompt this * repository measures itself against. `profile` could say that much and then * stopped, because the advice that follows from "output dominates" is about * answers rather than prompts, and the rules engine has nothing to offer there. * * But a total hides the shape, and the shape is the actionable part. Two bills * with identical output spend want opposite responses: * * - **A tail.** Six per cent of calls hold half the output spend. Those calls are * doing something the other ninety-four are not — a different path through the * prompt, a runaway with no `max_tokens`, a retrieval that returned a book. They * are a morning's work and they are worth finding. * - **Flat.** Forty-five per cent of calls hold half of it, which is what "evenly * spread" looks like. There is no tail to hunt; the answer length is inherent to * the task, and the only lever is asking every answer to be shorter. * * ## The split is derived, not chosen * * The figure reported is **the smallest group of calls that holds at least half the * output spend**. Half is the point that divides the spend in two — a median over * money rather than a threshold somebody picked — and the group is found by walking * the distribution down from the longest answers until half the spend is covered. * * "At least half" is meant literally. The walk stops on a bucket boundary, so the * group it names is a whole number of buckets and can overshoot; saying "half" * flat would be claiming a precision the histogram does not have. * * ## Bounded memory, exact statement * * The counts live in fixed buckets rather than a list of every call, because a * usage log is measured in megabytes. Every call inside an included bucket is at or * above that bucket's lower edge, so **"calls producing more than N tokens" is * exact** for the N this reports — it is only ever a bucket edge. */ /** How the output spend of one label-and-model slice is distributed. */ export interface OutputShape { label: string; model: string; modelName: string; calls: number; outputTokens: number; outputUsd: number; /** The bucket edge the heaviest group sits above. Always a bucket boundary. */ aboveTokens: number; /** How many calls are in that group. */ heavyCalls: number; /** Their share of the calls in this slice. */ heavyCallShare: number; /** Their share of this slice's output spend — at least a half, by construction. */ heavySpendShare: number; /** This slice's output spend as a fraction of the whole bill. */ shareOfBill: number; /** * The bucket ceiling that at least half the measured answers fit within. * * A ceiling by construction, never an interpolation: the histogram knows * which bucket the median call landed in, and the honest sentence is "half * the answers fit within N tokens" where N is that bucket's upper edge. * `null` only when the covering bucket is the open-ended last one, which has * no ceiling to name. */ medianWithinTokens: number | null; /** * The same ceiling for 95% of the measured answers — the number somebody * setting `max_tokens` actually wants. Measured on these calls, promised for * nothing. */ p95WithinTokens: number | null; } export interface OutputShapeOptions { catalogue: PricingCatalogue; on?: Date; /** Slices whose output is below this share of the bill are dropped. Default 5%. */ minShare?: number; } export interface OutputShapeTracker { add(record: UsageRecord): void; finish(totalUsd: number): OutputShape[]; } /** * An accumulator, fed in the pass a profile already makes. * * What it holds is bounded by the number of slices times the number of buckets any * of them actually touches, not by the size of the log. */ export declare function createOutputShapeTracker(options: OutputShapeOptions): OutputShapeTracker; /** The same measurement over a list of records, for a caller holding one. */ export declare function outputShapes(records: readonly UsageRecord[], totalUsd: number, options: OutputShapeOptions): OutputShape[]; //# sourceMappingURL=output-shape.d.ts.map