vern-llm-hedge 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE.md ADDED
@@ -0,0 +1,7 @@
1
+ Copyright 2026 LakBud
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4
+
5
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6
+
7
+ THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,70 @@
1
- # Temporary Holding Version
1
+ <p align="center">
2
+ <img src="https://raw.githubusercontent.com/LakBud/vernLLM/main/apps/docs/public/extensions/vernllm-hedge.png" alt="vern-llm-hedge logo" width="96" />
3
+ </p>
2
4
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
5
+ <h1 align="center">vern-llm-hedge</h1>
6
+
7
+ <p align="center">Cuts the slow tail of <a href="https://github.com/LakBud/vernLLM/tree/main/packages/vern-llm">vern-llm</a> latency by sending a slow call to the next target too and keeping the first answer.</p>
8
+
9
+ A middleware that waits for the delay a target usually needs, and if the call is still open, starts the same call on another target. The first success is kept and the core cancels the other. It never changes what a call returns or throws, and a call it cannot hedge runs exactly as it would without it.
10
+
11
+ ## Usage
12
+
13
+ ```ts
14
+ import { VernLLM } from 'vern-llm';
15
+ import { loggerMiddleware } from 'vern-llm-logger';
16
+ import { hedgeDimension, hedgeMiddleware, promptSizeDimension } from 'vern-llm-hedge';
17
+
18
+ const logger = loggerMiddleware({
19
+ dimensions: { size: promptSizeDimension, hedge: hedgeDimension },
20
+ });
21
+
22
+ const hedge = hedgeMiddleware({ stats: logger.stats });
23
+
24
+ const llm = new VernLLM({
25
+ client,
26
+ model: 'main',
27
+ fallback: [{ client: backupClient, model: 'backup-model', name: 'backup' }],
28
+ middleware: [logger, hedge],
29
+ });
30
+ ```
31
+
32
+ The delay is the p95 of the logger's `attemptMs` for the first target, the stream mode and the prompt size bucket. With no samples there is no delay and no hedge, unless `delay.fallbackMs` is set. Name the logger dimension `size`, since the hedge reads the series under that name. A number or a function as `delay` needs no `stats`.
33
+
34
+ A token bucket keeps extra calls near 5% of eligible calls (`budget: { ratio: 0.05, burst: 10 }`). The bucket starts empty and the token is taken when the delay passes, so calls that answer in time cost nothing.
35
+
36
+ `targets(ctx, request)` says where the hedge may go and defaults to `nextTargets(ctx)`, the call's targets after the first. `when(ctx)` says whether a call may be hedged. Both take recipes, such as skipping calls a policy marked `duplicateSends: false`, or targets that cost more than twice the first:
37
+
38
+ ```ts
39
+ import { hedgeMiddleware, nextTargets } from 'vern-llm-hedge';
40
+ import { policyFlags } from 'vern-llm-policy';
41
+
42
+ // The same target again, for a single provider.
43
+ hedgeMiddleware({ delay: 800, targets: (ctx) => [ctx.targets[0]!.name] });
44
+
45
+ // Never hedge into a target that costs more than twice the first.
46
+ hedgeMiddleware({
47
+ stats: logger.stats,
48
+ targets: (ctx, request) => {
49
+ const first = usage.costFor(ctx.targets[0]!, request) ?? Infinity;
50
+ return nextTargets(ctx).filter((name) => {
51
+ const target = ctx.targets.find((t) => t.name === name)!;
52
+ return (usage.costFor(target, request) ?? Infinity) <= 2 * first;
53
+ });
54
+ },
55
+ });
56
+
57
+ // Keep content that must not go to two providers on one.
58
+ hedgeMiddleware({
59
+ stats: logger.stats,
60
+ when: (ctx) => policyFlags(ctx).duplicateSends !== false,
61
+ });
62
+ ```
63
+
64
+ Each call reports one `hedge.decision` with an outcome: `won`, `lost`, `not_needed`, `failed`, `aborted`, `budget`, `unavailable`, `no_target`, `not_allowed`, `paused`, `cached`, `no_data` or `deadline`. `hedgeDimension` splits the logger's counts and latency by outcome, and `hedgeLogFields(ctx)` puts the outcome on the call's record.
65
+
66
+ `maxHedges` (up to 4) sends more than one hedge, each after one more delay. `adapt: true` raises the delay of a first target whose hedges rarely win, pauses it when that stops helping, and reports a target whose hedges often win as a `hedge.adapt` event. `cacheAware: true` waits longer, or skips the hedge, when the first target served the prompt's prefix from its provider cache recently, since a hedge elsewhere starts cold. All three are off by default.
67
+
68
+ Place it at its default position `10`, inside the logger, guard, policy, usage and router. Breakers, limiters and middleware hooks apply to the extra call like any other attempt. For streams the race is on the first chunk, and the losing stream is cancelled when the winner opens.
69
+
70
+ `vern-llm` is a peer dependency and there is no other runtime dependency.
package/dist/index.cjs ADDED
@@ -0,0 +1,2 @@
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});let e=require("vern-llm");const t=`hedge.decision`,n=`hedge.adapt`,r=[1e3,4e3,16e3,64e3],i=[`xs`,`s`,`m`,`l`];function a(e){for(let[t,n]of r.entries())if(e<n)return i[t];return`xl`}function o(t){let n=(0,e.defaultEstimateTokens)(t)-(t.max_tokens??0);return a(Math.max(0,n))}const s=/* @__PURE__ */ new Set([`won`,`lost`,`not_needed`,`failed`,`aborted`,`budget`,`unavailable`,`no_target`,`not_allowed`,`paused`,`cached`,`no_data`,`deadline`]),c=e=>{let t=e.usage?.promptTokens;return typeof t==`number`&&Number.isFinite(t)&&t>=0?a(t):void 0},l=e=>{let n=e.custom?.[t],r=n?.[n.length-1];if(typeof r!=`object`||!r)return;let i=r.outcome;return s.has(i)?i:void 0};var u=class{config;baseQuantile;targets=/* @__PURE__ */ new Map;constructor(e,t){this.config=e,this.baseQuantile=t}paused(e,t){return t<(this.targets.get(e)?.pausedUntil??0)}quantileFor(e,t){let n=this.targets.get(e)?.boost??0;return Math.min(t+n,this.ceiling(t))}ceiling(e){return Math.max(e,this.config.maxQuantile)}record(e,t,n){let r=this.targets.get(e);if(r||(r={results:[],boost:0,pausedUntil:0,slow:!1,sinceSlow:0},this.targets.set(e,r)),r.results.push(t),r.results.length>this.config.window&&r.results.shift(),r.results.length<this.config.minHedges)return;let i=r.results.length,a=r.results.filter(Boolean).length/i;if(a<this.config.lowWinRate){r.slow=!1,r.results=[];let t=this.baseQuantile,o=t===void 0?void 0:Math.min(t+r.boost+this.config.quantileStep,this.ceiling(t));return t===void 0||o===void 0||o<=t+r.boost?(r.boost=0,r.pausedUntil=n+this.config.pauseMs,{action:`pause`,target:e,winRate:a,hedges:i}):(r.boost=o-t,{action:`raise_quantile`,target:e,winRate:a,hedges:i,quantile:o})}if(a>this.config.highWinRate)return r.slow&&++r.sinceSlow<this.config.minHedges?void 0:(r.slow=!0,r.sinceSlow=0,{action:`slow`,target:e,winRate:a,hedges:i});r.slow=!1}},d=class{ratio;burst;tokens=0;constructor(e,t){this.ratio=e,this.burst=t}add(){this.tokens=Math.min(this.burst,this.tokens+this.ratio)}refund(){this.tokens=Math.min(this.burst,this.tokens+1)}take(){return this.tokens<.999999999?!1:(this.tokens=Math.max(0,this.tokens-1),!0)}};function f(e){let t=3735928559,n=1103547991;for(let r=0;r<e.length;r++){let i=e.charCodeAt(r);t=Math.imul(t^i,2654435761),n=Math.imul(n^i,1597334677)}return t=Math.imul(t^t>>>16,2246822507)^Math.imul(n^n>>>13,3266489909),n=Math.imul(n^n>>>16,2246822507)^Math.imul(t^t>>>13,3266489909),(4294967296*(2097151&n)+(t>>>0)).toString(36)}function p(e){return typeof e==`string`?e:Array.isArray(e)?e.map(e=>{let t=e?.text;return typeof t==`string`?t:``}).join(``):``}var m=class{config;seen=/* @__PURE__ */ new Map;constructor(e){this.config=e}keyOf(e){let t=``;for(let n of e.messages)if(t+=`${n.role}:${p(n.content)}\n`,t.length>=this.config.prefixChars)break;return f(t.slice(0,this.config.prefixChars))}remember(e,t,n){let r=`${e}|${t}`;this.seen.delete(r),this.seen.set(r,n),this.seen.size>1e4&&this.seen.delete(this.seen.keys().next().value)}warm(e,t,n){let r=this.seen.get(`${e}|${t}`);return r!==void 0&&n-r>=0&&n-r<this.config.ttlMs}get size(){return this.seen.size}},h=class{source;ttlMs;entries=/* @__PURE__ */ new Map;constructor(e,t){this.source=e,this.ttlMs=t}quantile(e,t,n){let r=`${JSON.stringify(Object.entries(e).sort(([e],[t])=>e<t?-1:1))}|${t}`;if(this.ttlMs>0){let e=this.entries.get(r);if(e&&n-e.at>=0&&n-e.at<this.ttlMs)return e.value}let i=this.source.quantile(e,t,`attemptMs`);return this.ttlMs>0&&(this.entries.size>=512&&this.entries.clear(),this.entries.set(r,{at:n,value:i})),i}get size(){return this.entries.size}};function g(e){return typeof e==`number`&&Number.isFinite(e)&&e>0}function _(e,t,n){let{delay:r}=e;if(r.kind===`fixed`)return{for:()=>r.ms};if(r.kind===`function`)return{for(e,n){let i=t(`delay`,()=>r.fn(e,n),void 0);if(i!==void 0){if(g(i))return i;t.warnOnce(`delay-value`,`delay returned a value that is not a positive, finite number`)}}};let i=new h(e.stats,e.statsTtlMs);return{for(e,a){let s=e.targets[0]?.name;if(s===void 0)return;let c={provider:s,streaming:String(e.streaming===!0)},l=n?n.quantileFor(s,r.quantile):r.quantile,u=Date.now(),d;try{d=i.quantile({...c,size:o(a)},l,u)??i.quantile(c,l,u)}catch(e){t.warnOnce(`stats`,`stats.quantile threw, so calls without a fallbackMs are not hedged: ${e instanceof Error?e.message:String(e)}`)}let f=g(d)?d:r.fallbackMs;return f===void 0?void 0:Math.max(f,r.minMs)}}}function v(t){let n=t===`silent`?void 0:t??new e.ConsoleLogger(!1),r=/* @__PURE__ */ new Set,i=(e,t)=>{if(n)try{n.error(`[VernLLM] hedge: ${e} failed`,{message:t instanceof Error?t.message:String(t),stack:t instanceof Error?t.stack:void 0})}catch{}},a=((e,t,n)=>{try{return t()}catch(t){return i(e,t),n}});return a.report=i,a.warn=e=>{if(n)try{n.warn(`[VernLLM] hedge: ${e}`)}catch{}},a.warnOnce=(e,t)=>{r.has(e)||(r.add(e),a.warn(t))},a}function y({cacheAware:e,guard:t,prefixKey:n}){if(e)return(r,i)=>t(`cacheEvent`,()=>{if(r.kind!==`usage`||!((r.usage.cacheReadTokens??0)>0))return;let t=i.state.get(n);t!==void 0&&e.remember(r.usage.provider??`primary`,t,Date.now())},void 0)}function b(e,t,n){if(!Array.isArray(t))return n.warnOnce(`targets-shape`,`targets must return an array of target names`),[];let r=new Set(e.targets.map(e=>e.name));return[...new Set(t.filter(e=>r.has(e)))]}function x({config:e,guard:t,adapt:n,cacheAware:r,delay:i,prefixKey:a},o,s,c,l){if(!c)return{kind:`skip`,outcome:`unavailable`};let u=t(`targets`,()=>b(s,e.targets(s,o),t),[]);if(u.length===0)return{kind:`skip`,outcome:`no_target`};if(!t(`when`,()=>!!e.when(s),!1))return{kind:`skip`,outcome:`not_allowed`};let d=s.targets[0].name;if(n?.paused(d,l))return{kind:`skip`,outcome:`paused`};let f=!1;if(r){let e=t(`cacheKey`,()=>r.keyOf(o),void 0);e!==void 0&&(t(`cacheState`,()=>s.state.set(a,e),void 0),f=r.warm(d,e,l))}if(f&&e.cacheAware.action===`skip`)return{kind:`skip`,outcome:`cached`};let p=t(`delay`,()=>i.for(s,o),void 0);if(p===void 0)return{kind:`skip`,outcome:`no_data`};let m=f?p*e.cacheAware.delayFactor:p;return s.deadlineAt!==void 0&&l+m>=s.deadlineAt?{kind:`skip`,outcome:`deadline`,delayMs:m}:{kind:`hedge`,targets:u,first:d,delayMs:m}}const S=(0,e.createStateKey)(`hedge.decision`),C=e=>e>1?{hedges:e}:{};function w(e,n,r){let i={v:1,outcome:r.outcome,...r.delayMs!==void 0&&{delayMs:Math.round(r.delayMs)},...r.target!==void 0&&{target:r.target},...r.hedges!==void 0&&{hedges:r.hedges},...r.winner!==void 0&&{winner:r.winner},...r.ms!==void 0&&{ms:Math.round(r.ms)}};n(`state`,()=>e.state.set(S,i),void 0),n(`emit`,()=>e.emit(t,i),void 0)}function T(e,t,n){return new Promise(r=>{if(n?.aborted){r(`settled`);return}let i=e=>{clearTimeout(o),n?.removeEventListener(`abort`,a),r(e)},a=()=>i(`settled`),o=setTimeout(()=>i(`elapsed`),Math.min(t,2147483647));n?.addEventListener(`abort`,a,{once:!0}),e.then(()=>i(`settled`),()=>i(`settled`))})}function E(e){return new Promise((t,n)=>{let r=0,i;e.forEach((a,o)=>{a.then(e=>t({result:e,winner:o}),t=>{o===0&&(i=t),r++,r===e.length&&n(i)})})})}const D=e=>e.catch(()=>new Promise(()=>{}));async function O({config:e,budget:t},n,r,i,a,o){let s=[i],c=i;for(let l=1;l<=e.maxHedges;l++){if(await T(c,o,n.signal)===`settled`){if(l>1)break;return{kind:`alone`,outcome:await k(n,i)}}let u=a.slice(l-1);if(u.length===0)break;let d=r.again;if(!d){if(l===1)return{kind:`alone`,outcome:`unavailable`};break}if(!t.take()){if(l===1)return{kind:`alone`,outcome:`budget`};break}let f;try{f=d({targets:u})}catch(e){t.refund(),f=Promise.reject(e)}s.push(f),l<e.maxHedges&&(c=Promise.race([i,...s.slice(1).map(D)]))}return{kind:`raced`,chains:s}}async function k(e,t){return e.signal?.aborted?`aborted`:t.then(()=>`not_needed`,()=>`failed`)}async function A({adapt:e,guard:t},r,i,a,o,s,c,l){let u=a.length-1,d;try{d=await E(a)}catch(e){throw i({outcome:`lost`,delayMs:c,target:o[0],...C(u)}),e}let f=e?.record(s,d.winner>0,Date.now());return f&&t(`adapt`,()=>r.emit(n,f),void 0),i({outcome:d.winner>0?`won`:`lost`,delayMs:c,target:o[0],ms:Date.now()-l,...C(u),...u>1&&d.winner>0&&{winner:o[d.winner-1]}}),d.result}function j(e){let{guard:t,budget:n}=e;return async(r,i,a)=>{let o=Date.now(),s=e=>w(a,t,e),c=(e,t,n)=>t.finally(()=>s({outcome:e,...n!==void 0&&{delayMs:n}})),l=x(e,r,a,i.again!==void 0,o);if(l.kind===`skip`)return c(l.outcome,i(),l.delayMs);let{targets:u,first:d,delayMs:f}=l;n.add();let p=i(),m=await O(e,a,i,p,u,f);return m.kind===`alone`?c(m.outcome,p,f):A(e,a,s,m.chains,u,d,f,o)}}function M(e){return e.targets.slice(1).map(e=>e.name)}function N(e){throw Error(`hedgeMiddleware: ${e}`)}function P(e){return typeof e==`number`&&Number.isFinite(e)}function F(e){return P(e)&&e>0}function I(e){if(e===void 0||e===!1)return;e!==!0&&(typeof e!=`object`||!e||Array.isArray(e))&&N(`adapt must be true or an object`);let t=e===!0?{}:e,n={window:t.window??50,minHedges:t.minHedges??20,lowWinRate:t.lowWinRate??.1,highWinRate:t.highWinRate??.5,quantileStep:t.quantileStep??.02,maxQuantile:t.maxQuantile??.99,pauseMs:t.pauseMs??3e5};return(!Number.isInteger(n.window)||n.window<1)&&N(`adapt.window must be an integer of at least 1`),(!Number.isInteger(n.minHedges)||n.minHedges<1||n.minHedges>n.window)&&N(`adapt.minHedges must be an integer from 1 to adapt.window`),(!P(n.lowWinRate)||n.lowWinRate<0||n.lowWinRate>=1)&&N(`adapt.lowWinRate must be a number from 0 up to 1`),(!P(n.highWinRate)||n.highWinRate<=n.lowWinRate||n.highWinRate>1)&&N(`adapt.highWinRate must be a number above adapt.lowWinRate and at most 1`),(!P(n.quantileStep)||n.quantileStep<=0||n.quantileStep>=1)&&N(`adapt.quantileStep must be a number above 0 and below 1`),(!P(n.maxQuantile)||n.maxQuantile<=0||n.maxQuantile>=1)&&N(`adapt.maxQuantile must be a number above 0 and below 1`),F(n.pauseMs)||N(`adapt.pauseMs must be a positive, finite number of milliseconds`),n}function L(e){if(e===void 0||e===!1)return;e!==!0&&(typeof e!=`object`||!e||Array.isArray(e))&&N(`cacheAware must be true or an object`);let t=e===!0?{}:e,n={ttlMs:t.ttlMs??3e5,prefixChars:t.prefixChars??2e3,action:t.action??`delay`,delayFactor:t.delayFactor??2};return F(n.ttlMs)||N(`cacheAware.ttlMs must be a positive, finite number of milliseconds`),(!Number.isInteger(n.prefixChars)||n.prefixChars<1)&&N(`cacheAware.prefixChars must be an integer of at least 1`),n.action!==`delay`&&n.action!==`skip`&&N(`cacheAware.action must be 'delay' or 'skip'`),(!P(n.delayFactor)||n.delayFactor<=1)&&N(`cacheAware.delayFactor must be a number above 1`),n}function R(e){return e===void 0?{kind:`quantile`,quantile:.95,minMs:0,fallbackMs:void 0}:typeof e==`function`?{kind:`function`,fn:e}:typeof e==`number`?(F(e)||N(`delay must be a positive, finite number of milliseconds`),{kind:`fixed`,ms:e}):((typeof e!=`object`||!e||Array.isArray(e))&&N(`delay must be a number, a function or { quantile, minMs?, fallbackMs? }`),(!P(e.quantile)||e.quantile<=0||e.quantile>=1)&&N(`delay.quantile must be a number above 0 and below 1`),e.minMs!==void 0&&(!P(e.minMs)||e.minMs<0)&&N(`delay.minMs must be a number of at least 0`),e.fallbackMs!==void 0&&!F(e.fallbackMs)&&N(`delay.fallbackMs must be a positive, finite number of milliseconds`),{kind:`quantile`,quantile:e.quantile,minMs:e.minMs??0,fallbackMs:e.fallbackMs})}function z(e){e===void 0||typeof e==`object`&&e||N(`budget must be an object: { ratio?, burst? }`);let t=e?.ratio??.05,n=e?.burst??10;return(!P(t)||t<=0||t>1)&&N(`budget.ratio must be a number above 0 and at most 1`),(!P(n)||n<1)&&N(`budget.burst must be a number of at least 1`),{ratio:t,burst:n}}function B(e={}){let t=R(e.delay);e.stats!==void 0&&typeof e.stats?.quantile!=`function`&&N(`stats must have a quantile function`),t.kind===`quantile`&&e.stats===void 0&&N(`stats must have a quantile function when delay is a quantile (pass logger.stats), or set delay to a number or a function`),e.targets!==void 0&&typeof e.targets!=`function`&&N(`targets must be a function`),e.when!==void 0&&typeof e.when!=`function`&&N(`when must be a function`);let n=e.maxHedges??1;(!Number.isInteger(n)||n<1||n>4)&&N(`maxHedges must be an integer from 1 to 4`);let r=e.statsTtlMs??1e3;return(!P(r)||r<0)&&N(`statsTtlMs must be a number of at least 0`),{stats:e.stats,delay:t,budget:z(e.budget),maxHedges:n,adapt:I(e.adapt),cacheAware:L(e.cacheAware),targets:e.targets??M,when:e.when??(()=>!0),statsTtlMs:r,logger:e.logger}}const V=(0,e.createMiddlewareRef)(`hedge`);function H(t={}){let n=B(t),r=v(n.logger),i=n.adapt?new u(n.adapt,n.delay.kind===`quantile`?n.delay.quantile:void 0):void 0,a={config:n,guard:r,budget:new d(n.budget.ratio,n.budget.burst),adapt:i,cacheAware:n.cacheAware?new m(n.cacheAware):void 0,delay:_(n,r,i),prefixKey:(0,e.createStateKey)(`hedge.prefix`)},o=y(a);return{name:`hedge`,ref:V,position:10,reruns:!0,...o&&{onEvent:o},wrap:j(a)}}function U(e){let t=e.state.get(S);if(t!==void 0)return{hedge:t.outcome,...t.delayMs!==void 0&&{hedge_delay_ms:t.delayMs}}}exports.HEDGE_ADAPT_EVENT=n,exports.HEDGE_DECISION_EVENT=t,exports.hedgeDimension=l,exports.hedgeLogFields=U,exports.hedgeMiddleware=H,exports.hedgeMiddlewareRef=V,exports.nextTargets=M,exports.promptSizeDimension=c;
2
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.cjs","names":["defaultEstimateTokens","MAX_ENTRIES","ConsoleLogger","createStateKey","createMiddlewareRef","createStateKey","vern_llm","HEDGE_DECISION_EVENT","HEDGE_ADAPT_EVENT","EDGES","NAMES","bucketOfTokens","index","edge","promptTokens","bucketOfRequest","prompt","request","OUTCOMES","promptSizeDimension","tokens","record","hedgeDimension","events","last","outcome","Adapt","config","baseQuantile","now","target","boost","base","state","won","hedges","winRate","next","HedgeBudget","ratio","burst","hash","h1","h2","i","text","code","textOf","content","block","CacheAware","message","id","key","at","StatsCache","source","ttlMs","filter","a","b","q","hit","value","usable","createDelay","delay","ms","guard","ctx","cache","first","mode","quantile","adapt","learned","\"size\"","error","createGuard","logger","option","warned","report","operation","fn","fallback","createCacheEvent","cacheAware","event","prefixKey","usableTargets","names","allowed","name","plan","hasAgain","targets","callStart","warm","delayMs","hedgeDecisionKey","hedgesOf","sent","decision","raceDelay","signal","resolve","finish","timer","onAbort","main","firstSuccess","failures","mainError","chains","chain","winner","reject","untilAnswered","sendHedges","anySettled","hedge","whyAlone","rest","again","budget","settleRace","sentCount","raced","adapted","createWrap","deps","alone","chosen","nextTargets","invalid","isNumber","isPositive","resolveAdapt","given","resolved","resolveCacheAware","resolveDelay","resolveBudget","normalizeOptions","options","maxHedges","statsTtlMs","hedgeMiddlewareRef","hedgeMiddleware","onEvent","hedgeLogFields"],"sources":["../src/events.ts","../src/internal/size.utils.ts","../src/dimensions.ts","../src/internal/adapt.ts","../src/internal/budget.ts","../src/internal/cacheAware.ts","../src/internal/delay/statsCache.ts","../src/internal/delay/delay.utils.ts","../src/internal/guard.utils.ts","../src/internal/middleware/cacheEvent.utils.ts","../src/internal/middleware/usableTargets.utils.ts","../src/internal/middleware/plan.utils.ts","../src/stateKeys.ts","../src/internal/middleware/record.utils.ts","../src/internal/race.utils.ts","../src/internal/middleware/sendHedges.utils.ts","../src/internal/middleware/settleRace.utils.ts","../src/internal/middleware/wrap.utils.ts","../src/targets.ts","../src/internal/options/validate.utils.ts","../src/internal/options/resolveAdapt.utils.ts","../src/internal/options/resolveCacheAware.utils.ts","../src/internal/options/resolveDelay.utils.ts","../src/internal/options/normalizeOptions.utils.ts","../src/hedgeMiddleware.ts","../src/logFields.ts"],"sourcesContent":["/** Name of the custom event carrying each call's decision. */\nexport const HEDGE_DECISION_EVENT = 'hedge.decision';\n\n/** Name of the custom event for a change the win rate caused. */\nexport const HEDGE_ADAPT_EVENT = 'hedge.adapt';\n","import { defaultEstimateTokens, type WireCallRequest } from 'vern-llm';\n\n/**\n * The logger dimension the hedge reads for a prompt's size. Register it under this name:\n * `dimensions: { size: promptSizeDimension }`.\n */\nexport const SIZE_DIMENSION = 'size';\n\nexport type SizeBucket = 'xs' | 's' | 'm' | 'l' | 'xl';\n\n/** Upper edges, in prompt tokens: under 1k, 4k, 16k and 64k, and everything above. */\nconst EDGES = [1_000, 4_000, 16_000, 64_000] as const;\nconst NAMES = ['xs', 's', 'm', 'l'] as const;\n\nexport function bucketOfTokens(promptTokens: number): SizeBucket {\n for (const [index, edge] of EDGES.entries()) {\n if (promptTokens < edge) return NAMES[index]!;\n }\n return 'xl';\n}\n\n/**\n * The bucket of a request that has not been sent yet. It is an estimate, so a prompt near an edge\n * may land beside the bucket the logger later records it in. The core's estimate reserves `max_tokens`\n * for the answer, which the logger's recorded prompt tokens never include, so it is taken off.\n */\nexport function bucketOfRequest(request: Readonly<WireCallRequest>): SizeBucket {\n const prompt = defaultEstimateTokens(request) - (request.max_tokens ?? 0);\n return bucketOfTokens(Math.max(0, prompt));\n}\n","import { HEDGE_DECISION_EVENT } from './events.js';\nimport { bucketOfTokens, type SizeBucket } from './internal/size.utils.js';\n\nimport type { HedgeOutcome } from './types/index.js';\n\n/** The part of the logger's call record these read, so no import of the logger is needed. */\ninterface CallRecord {\n usage?: { promptTokens?: number };\n custom?: Record<string, unknown[]>;\n}\n\nconst OUTCOMES: ReadonlySet<unknown> = new Set<HedgeOutcome>([\n 'won',\n 'lost',\n 'not_needed',\n 'failed',\n 'aborted',\n 'budget',\n 'unavailable',\n 'no_target',\n 'not_allowed',\n 'paused',\n 'cached',\n 'no_data',\n 'deadline',\n]);\n\n/**\n * The prompt's size as a logger dimension, so a long prompt's slow first token never sets the\n * delay for a short one: `dimensions: { size: promptSizeDimension }`. Buckets by powers of four\n * from 1 000 prompt tokens: `xs` under 1k, `s` under 4k, `m` under 16k, `l` under 64k and `xl`\n * above. The hedge reads it under the name `size`. A record with no usage yields nothing.\n */\nexport const promptSizeDimension = (record: CallRecord): SizeBucket | undefined => {\n const tokens = record.usage?.promptTokens;\n return typeof tokens === 'number' && Number.isFinite(tokens) && tokens >= 0\n ? bucketOfTokens(tokens)\n : undefined;\n};\n\n/**\n * How the call's hedge went, as a logger dimension: `dimensions: { hedge: hedgeDimension }`\n * splits the logger's counts and percentiles by `won`, `lost`, `not_needed`, `failed`, `aborted`, `budget` and the skip\n * reasons. It reads the call's last `hedge.decision`, so the logger must keep `hedge.*` events\n * (`customEvents: ['hedge.*']`). A call with no decision yields nothing.\n */\nexport const hedgeDimension = (record: CallRecord): HedgeOutcome | undefined => {\n const events = record.custom?.[HEDGE_DECISION_EVENT];\n const last = events?.[events.length - 1];\n if (typeof last !== 'object' || last === null) return undefined;\n\n const outcome = (last as { outcome?: unknown }).outcome;\n return OUTCOMES.has(outcome) ? (outcome as HedgeOutcome) : undefined;\n};\n","import type { AdaptEvent, ResolvedAdapt } from '../types/index.js';\n\ninterface TargetState {\n /** The last `window` hedged calls of this first target: whether the hedge won. */\n results: boolean[];\n /** Added to the base quantile. */\n boost: number;\n pausedUntil: number;\n /** Whether `slow` was reported, until the win rate falls back. */\n slow: boolean;\n /** Hedged calls since `slow` was last reported, still above the high win rate. */\n sinceSlow: number;\n}\n\n/**\n * Watches how often a first target's hedges win, and answers in two ways. Rarely winning means\n * the delay is too short for that target: wait for a higher quantile, then stop hedging it for a\n * while. Often winning means the first target is the problem, which is reported, and again every\n * `minHedges` hedged calls while it stays so.\n */\nexport class Adapt {\n private readonly targets = new Map<string, TargetState>();\n\n /**\n * @param baseQuantile the configured quantile, or `undefined` when the delay is a number or a\n * function, which has no quantile to raise.\n */\n constructor(\n private readonly config: ResolvedAdapt,\n private readonly baseQuantile: number | undefined,\n ) {}\n\n /** Whether this target is out of hedging for now. */\n paused(target: string, now: number): boolean {\n return now < (this.targets.get(target)?.pausedUntil ?? 0);\n }\n\n /** The quantile to wait for on this target: the base, raised by what the win rate asked for. */\n quantileFor(target: string, base: number): number {\n const boost = this.targets.get(target)?.boost ?? 0;\n return Math.min(base + boost, this.ceiling(base));\n }\n\n /** The highest quantile a target may be raised to, never below the one configured. */\n private ceiling(base: number): number {\n return Math.max(base, this.config.maxQuantile);\n }\n\n /** Counts a hedged call. Returns the one change it caused, if any. */\n record(target: string, won: boolean, now: number): AdaptEvent | undefined {\n let state = this.targets.get(target);\n if (!state) {\n state = { results: [], boost: 0, pausedUntil: 0, slow: false, sinceSlow: 0 };\n this.targets.set(target, state);\n }\n\n state.results.push(won);\n if (state.results.length > this.config.window) state.results.shift();\n if (state.results.length < this.config.minHedges) return undefined;\n\n const hedges = state.results.length;\n const winRate = state.results.filter(Boolean).length / hedges;\n\n if (winRate < this.config.lowWinRate) {\n state.slow = false;\n state.results = [];\n\n const base = this.baseQuantile;\n // The quantile after one more step, held at the configured ceiling. With nothing to raise,\n // or no room left, stop hedging this target for a while and start over afterwards.\n const next =\n base === undefined\n ? undefined\n : Math.min(base + state.boost + this.config.quantileStep, this.ceiling(base));\n if (base === undefined || next === undefined || next <= base + state.boost) {\n state.boost = 0;\n state.pausedUntil = now + this.config.pauseMs;\n return { action: 'pause', target, winRate, hedges };\n }\n\n state.boost = next - base;\n return { action: 'raise_quantile', target, winRate, hedges, quantile: next };\n }\n\n if (winRate > this.config.highWinRate) {\n // Reported again every `minHedges` hedged calls while it lasts, since whoever listens may\n // hold the target back only for a while, and the target is first again once that ends.\n if (state.slow && ++state.sinceSlow < this.config.minHedges) return undefined;\n state.slow = true;\n state.sinceSlow = 0;\n return { action: 'slow', target, winRate, hedges };\n }\n\n state.slow = false;\n return undefined;\n }\n}\n","/** Absorbs the float error of adding `ratio` many times, so 20 calls at 0.05 make one token. */\nconst EPSILON = 1e-9;\n\n/**\n * A token bucket that eligible calls refill and hedges drain. It starts empty, so hedging never\n * exceeds about `ratio` of calls, even when every call is slow, and a cold start never hedges.\n */\nexport class HedgeBudget {\n private tokens = 0;\n\n constructor(\n private readonly ratio: number,\n private readonly burst: number,\n ) {}\n\n /** One eligible call: adds `ratio` tokens, up to `burst`. */\n add(): void {\n this.tokens = Math.min(this.burst, this.tokens + this.ratio);\n }\n\n /** Gives back a token that was taken for a hedge that could not be sent. */\n refund(): void {\n this.tokens = Math.min(this.burst, this.tokens + 1);\n }\n\n /** Takes one token if there is one. */\n take(): boolean {\n if (this.tokens < 1 - EPSILON) return false;\n this.tokens = Math.max(0, this.tokens - 1);\n return true;\n }\n}\n","import type { ResolvedCacheAware } from '../types/index.js';\nimport type { WireCallRequest } from 'vern-llm';\n\n/** Most remembered prefixes. The oldest are dropped first. */\nconst MAX_ENTRIES = 10_000;\n\n/** A fast, non cryptographic 53 bit hash. A collision only changes a heuristic. */\nfunction hash(text: string): string {\n let h1 = 0xdeadbeef;\n let h2 = 0x41c6ce57;\n for (let i = 0; i < text.length; i++) {\n const code = text.charCodeAt(i);\n h1 = Math.imul(h1 ^ code, 2654435761);\n h2 = Math.imul(h2 ^ code, 1597334677);\n }\n h1 = Math.imul(h1 ^ (h1 >>> 16), 2246822507) ^ Math.imul(h2 ^ (h2 >>> 13), 3266489909);\n h2 = Math.imul(h2 ^ (h2 >>> 16), 2246822507) ^ Math.imul(h1 ^ (h1 >>> 13), 3266489909);\n return (4294967296 * (2097151 & h2) + (h1 >>> 0)).toString(36);\n}\n\n/** The text of one message, whatever its content shape. Anything that is not text is skipped. */\nfunction textOf(content: unknown): string {\n if (typeof content === 'string') return content;\n if (!Array.isArray(content)) return '';\n return content\n .map((block: unknown) => {\n const text = (block as { text?: unknown } | null)?.text;\n return typeof text === 'string' ? text : '';\n })\n .join('');\n}\n\n/**\n * Remembers which targets recently served which prompt prefix from their cache. A provider's\n * prompt cache only helps a request that starts the same way, so the prefix is what is keyed.\n */\nexport class CacheAware {\n /** `target|prefix` to when the target last served that prefix from its cache. */\n private readonly seen = new Map<string, number>();\n\n constructor(private readonly config: ResolvedCacheAware) {}\n\n /** A key for how the request starts: the same for requests sharing their first characters. */\n keyOf(request: Readonly<WireCallRequest>): string {\n let text = '';\n for (const message of request.messages) {\n text += `${message.role}:${textOf(message.content)}\\n`;\n if (text.length >= this.config.prefixChars) break;\n }\n return hash(text.slice(0, this.config.prefixChars));\n }\n\n /** The target served this prefix from its cache just now. */\n remember(target: string, key: string, now: number): void {\n const id = `${target}|${key}`;\n // Deleted first, so a refreshed entry moves to the end and the oldest is dropped first.\n this.seen.delete(id);\n this.seen.set(id, now);\n if (this.seen.size > MAX_ENTRIES) this.seen.delete(this.seen.keys().next().value!);\n }\n\n /** Whether the target served this prefix from its cache within `ttlMs`. */\n warm(target: string, key: string, now: number): boolean {\n const at = this.seen.get(`${target}|${key}`);\n return at !== undefined && now - at >= 0 && now - at < this.config.ttlMs;\n }\n\n get size(): number {\n return this.seen.size;\n }\n}\n","import type { StatsSource } from '../../types/index.js';\n\n/** Most distinct reads kept. A chain has a handful of targets, so this is only a safety cap. */\nconst MAX_ENTRIES = 512;\n\n/** The one metric that means \"time to the first answer\" for plain and streamed calls alike. */\nconst METRIC = 'attemptMs';\n\ninterface Entry {\n at: number;\n value: number | undefined;\n}\n\n/**\n * Reuses stats reads for `ttlMs`, which keeps the logger's quantile merge off the hot path.\n * A read that throws is not cached, so it is tried again on the next call.\n */\nexport class StatsCache {\n private readonly entries = new Map<string, Entry>();\n\n constructor(\n private readonly source: StatsSource,\n private readonly ttlMs: number,\n ) {}\n\n quantile(filter: Readonly<Record<string, string>>, q: number, now: number): number | undefined {\n const key = `${JSON.stringify(Object.entries(filter).sort(([a], [b]) => (a < b ? -1 : 1)))}|${q}`;\n\n if (this.ttlMs > 0) {\n const hit = this.entries.get(key);\n if (hit && now - hit.at >= 0 && now - hit.at < this.ttlMs) return hit.value;\n }\n\n const value = this.source.quantile(filter, q, METRIC);\n if (this.ttlMs > 0) {\n if (this.entries.size >= MAX_ENTRIES) this.entries.clear();\n this.entries.set(key, { at: now, value });\n }\n return value;\n }\n\n get size(): number {\n return this.entries.size;\n }\n}\n","import { bucketOfRequest, SIZE_DIMENSION } from '../size.utils.js';\nimport { StatsCache } from './statsCache.js';\n\nimport type { ResolvedConfig } from '../../types/index.js';\nimport type { Adapt } from '../adapt.js';\nimport type { Guard } from '../guard.utils.js';\nimport type { PreDispatchContext, WireCallRequest } from 'vern-llm';\n\n/** How long to wait before hedging this call, or `undefined` when there is no basis for one. */\nexport interface Delay {\n for(ctx: PreDispatchContext, request: Readonly<WireCallRequest>): number | undefined;\n}\n\nfunction usable(value: unknown): value is number {\n return typeof value === 'number' && Number.isFinite(value) && value > 0;\n}\n\nexport function createDelay(config: ResolvedConfig, guard: Guard, adapt?: Adapt): Delay {\n const { delay } = config;\n\n if (delay.kind === 'fixed') return { for: () => delay.ms };\n\n if (delay.kind === 'function') {\n return {\n for(ctx, request) {\n const ms = guard('delay', () => delay.fn(ctx, request), undefined);\n if (ms === undefined) return undefined;\n if (usable(ms)) return ms;\n guard.warnOnce(\n 'delay-value',\n 'delay returned a value that is not a positive, finite number',\n );\n return undefined;\n },\n };\n }\n\n const cache = new StatsCache(config.stats!, config.statsTtlMs);\n\n return {\n for(ctx, request) {\n const first = ctx.targets[0]?.name;\n if (first === undefined) return undefined;\n\n // The target's own series for this stream mode, narrowed to the prompt's size bucket when\n // the logger records `promptSizeDimension`. A bucket below `minSamples` reads as unknown.\n const mode = { provider: first, streaming: String(ctx.streaming === true) };\n const quantile = adapt ? adapt.quantileFor(first, delay.quantile) : delay.quantile;\n const now = Date.now();\n\n let learned: number | undefined;\n try {\n learned =\n cache.quantile({ ...mode, [SIZE_DIMENSION]: bucketOfRequest(request) }, quantile, now) ??\n cache.quantile(mode, quantile, now);\n } catch (error) {\n guard.warnOnce(\n 'stats',\n `stats.quantile threw, so calls without a fallbackMs are not hedged: ${\n error instanceof Error ? error.message : String(error)\n }`,\n );\n }\n\n const ms = usable(learned) ? learned : delay.fallbackMs;\n return ms === undefined ? undefined : Math.max(ms, delay.minMs);\n },\n };\n}\n","import { ConsoleLogger, type Logger } from 'vern-llm';\n\nimport type { LoggerOption } from '../types/index.js';\n\n/** Runs `fn`, and returns `fallback` after logging if it throws. */\nexport interface Guard {\n <T>(operation: string, fn: () => T, fallback: T): T;\n /** Logs a failure in the same shape without running anything. */\n report(operation: string, error: unknown): void;\n /** Logs a configuration warning, not a failure. */\n warn(message: string): void;\n /** Like `warn`, but each `key` only once for the life of the hedge. */\n warnOnce(key: string, message: string): void;\n}\n\nexport function createGuard(option: LoggerOption | undefined): Guard {\n const logger: Logger | undefined =\n option === 'silent' ? undefined : (option ?? new ConsoleLogger(false));\n const warned = new Set<string>();\n\n const report = (operation: string, error: unknown): void => {\n if (!logger) return;\n\n try {\n logger.error(`[VernLLM] hedge: ${operation} failed`, {\n message: error instanceof Error ? error.message : String(error),\n stack: error instanceof Error ? error.stack : undefined,\n });\n } catch {\n // A logger that throws must not be able to break a call.\n }\n };\n\n const guard = (<T>(operation: string, fn: () => T, fallback: T): T => {\n try {\n return fn();\n } catch (error) {\n report(operation, error);\n return fallback;\n }\n }) as Guard;\n\n guard.report = report;\n guard.warn = (message) => {\n if (!logger) return;\n try {\n logger.warn(`[VernLLM] hedge: ${message}`);\n } catch {\n // Same as `report`: a throwing logger must not break a call.\n }\n };\n guard.warnOnce = (key, message) => {\n if (warned.has(key)) return;\n warned.add(key);\n guard.warn(message);\n };\n return guard;\n}\n","import type { HedgeDeps } from './deps.js';\nimport type { MiddlewareContext, VernLLMEvent, VernLLMMiddleware } from 'vern-llm';\n\ntype OnEvent = NonNullable<VernLLMMiddleware['onEvent']>;\n\n/**\n * The `onEvent` hook of cache awareness: a target that reports cache reads has this call's prefix\n * in its cache for a while. `undefined` when cache awareness is off, so no hook is registered.\n */\nexport function createCacheEvent({ cacheAware, guard, prefixKey }: HedgeDeps): OnEvent | undefined {\n if (!cacheAware) return undefined;\n\n return (event: VernLLMEvent, ctx: MiddlewareContext) =>\n guard(\n 'cacheEvent',\n () => {\n if (event.kind !== 'usage' || !((event.usage.cacheReadTokens ?? 0) > 0)) return;\n const key = ctx.state.get(prefixKey);\n if (key !== undefined) {\n cacheAware.remember(event.usage.provider ?? 'primary', key, Date.now());\n }\n },\n undefined,\n );\n}\n","import type { Guard } from '../guard.utils.js';\nimport type { PreDispatchContext } from 'vern-llm';\n\n/**\n * The names `targets` returned that the call may actually use: strings the order contains, each\n * once. The core rejects an unknown or repeated name, which would spend a budget token on a chain\n * that fails at once.\n */\nexport function usableTargets(ctx: PreDispatchContext, names: unknown, guard: Guard): string[] {\n if (!Array.isArray(names)) {\n guard.warnOnce('targets-shape', 'targets must return an array of target names');\n return [];\n }\n\n const allowed = new Set(ctx.targets.map((target) => target.name));\n return [...new Set(names.filter((name): name is string => allowed.has(name)))];\n}\n","import { usableTargets } from './usableTargets.utils.js';\n\nimport type { SkipReason } from '../../types/index.js';\nimport type { HedgeDeps } from './deps.js';\nimport type { PreDispatchContext, WireCallRequest } from 'vern-llm';\n\n/** Whether to run the call as it is, and why, or to race it. */\nexport type Plan =\n | { kind: 'skip'; outcome: SkipReason; delayMs?: number }\n | { kind: 'hedge'; targets: string[]; first: string; delayMs: number };\n\n/**\n * Decides before anything is sent whether a call may be hedged, and how long to wait. The order\n * of the checks is the order of the outcomes: the first one that says no is reported.\n */\nexport function plan(\n { config, guard, adapt, cacheAware, delay, prefixKey }: HedgeDeps,\n request: Readonly<WireCallRequest>,\n ctx: PreDispatchContext,\n hasAgain: boolean,\n callStart: number,\n): Plan {\n if (!hasAgain) return { kind: 'skip', outcome: 'unavailable' };\n\n const targets = guard(\n 'targets',\n () => usableTargets(ctx, config.targets(ctx, request), guard),\n [],\n );\n if (targets.length === 0) return { kind: 'skip', outcome: 'no_target' };\n\n if (!guard('when', () => Boolean(config.when(ctx)), false)) {\n return { kind: 'skip', outcome: 'not_allowed' };\n }\n\n // The first target is the one the call is sent to, and the one whose hedges are counted.\n const first = ctx.targets[0]!.name;\n if (adapt?.paused(first, callStart)) return { kind: 'skip', outcome: 'paused' };\n\n // A target holding this prefix in its cache answers cheaply and soon; a hedge elsewhere would\n // start cold. The prefix is kept for the usage events of this call.\n let warm = false;\n if (cacheAware) {\n const key = guard('cacheKey', () => cacheAware.keyOf(request), undefined);\n if (key !== undefined) {\n guard('cacheState', () => ctx.state.set(prefixKey, key), undefined);\n warm = cacheAware.warm(first, key, callStart);\n }\n }\n if (warm && config.cacheAware!.action === 'skip') return { kind: 'skip', outcome: 'cached' };\n\n const learned = guard('delay', () => delay.for(ctx, request), undefined);\n if (learned === undefined) return { kind: 'skip', outcome: 'no_data' };\n const delayMs = warm ? learned * config.cacheAware!.delayFactor : learned;\n\n if (ctx.deadlineAt !== undefined && callStart + delayMs >= ctx.deadlineAt) {\n return { kind: 'skip', outcome: 'deadline', delayMs };\n }\n\n return { kind: 'hedge', targets, first, delayMs };\n}\n","import { createStateKey } from 'vern-llm';\n\nimport type { HedgeDecisionEvent } from './types/index.js';\n\n/**\n * Module level on purpose: the hedge writes it and `hedgeLogFields` reads it without being handed\n * the instance, the way the other extensions' fields travel.\n */\nexport const hedgeDecisionKey = createStateKey<HedgeDecisionEvent>('hedge.decision');\n","import { HEDGE_DECISION_EVENT } from '../../events.js';\nimport { hedgeDecisionKey } from '../../stateKeys.js';\n\nimport type { HedgeDecisionEvent } from '../../types/index.js';\nimport type { Guard } from '../guard.utils.js';\nimport type { JsonValue, PreDispatchContext } from 'vern-llm';\n\n/** What a decision carries besides `v`, so the call site names only what it knows. */\nexport type Decision = Omit<HedgeDecisionEvent, 'v'>;\n\n/** The hedge count for the event, only when it is more than one. */\nexport const hedgesOf = (sent: number): Pick<Decision, 'hedges'> =>\n sent > 1 ? { hedges: sent } : {};\n\n/** Writes the decision where the log fields and the logger read it, once, and never throws. */\nexport function record(ctx: PreDispatchContext, guard: Guard, decision: Decision): void {\n const event: HedgeDecisionEvent = {\n v: 1,\n outcome: decision.outcome,\n ...(decision.delayMs !== undefined && { delayMs: Math.round(decision.delayMs) }),\n ...(decision.target !== undefined && { target: decision.target }),\n ...(decision.hedges !== undefined && { hedges: decision.hedges }),\n ...(decision.winner !== undefined && { winner: decision.winner }),\n ...(decision.ms !== undefined && { ms: Math.round(decision.ms) }),\n };\n\n guard('state', () => ctx.state.set(hedgeDecisionKey, event), undefined);\n guard('emit', () => ctx.emit(HEDGE_DECISION_EVENT, event as unknown as JsonValue), undefined);\n}\n","import type { CallResult } from 'vern-llm';\n\n/** The longest delay `setTimeout` takes; a larger one fires at once. */\nconst MAX_TIMER_MS = 2_147_483_647;\n\n/**\n * Waits for `delayMs`, or for `main` to settle either way, or for `signal` to abort, whichever\n * comes first. `'settled'` covers the last two, since neither leaves anything to hedge. The timer\n * and the abort listener are always cleaned up.\n */\nexport function raceDelay(\n main: Promise<unknown>,\n delayMs: number,\n signal: AbortSignal | undefined,\n): Promise<'settled' | 'elapsed'> {\n return new Promise((resolve) => {\n if (signal?.aborted) {\n resolve('settled');\n return;\n }\n\n const finish = (outcome: 'settled' | 'elapsed'): void => {\n clearTimeout(timer);\n signal?.removeEventListener('abort', onAbort);\n resolve(outcome);\n };\n const onAbort = (): void => finish('settled');\n const timer = setTimeout(() => finish('elapsed'), Math.min(delayMs, MAX_TIMER_MS));\n\n signal?.addEventListener('abort', onAbort, { once: true });\n main.then(\n () => finish('settled'),\n () => finish('settled'),\n );\n });\n}\n\n/** The chain whose result was kept: `0` for the main chain, `1` and up for the hedges, in order. */\nexport interface Raced {\n result: CallResult;\n winner: number;\n}\n\n/**\n * The first chain to succeed, the main chain first in `chains`. A chain that fails leaves the\n * others to answer; when all fail, the main chain's error is the one thrown. The result is exactly\n * what the winner produced, so the core can match it to its chain and cancel the others. When\n * several have already answered, the lowest index wins.\n */\nexport function firstSuccess(chains: readonly Promise<CallResult>[]): Promise<Raced> {\n return new Promise((resolve, reject) => {\n let failures = 0;\n let mainError: unknown;\n\n chains.forEach((chain, winner) => {\n chain.then(\n (result) => resolve({ result, winner }),\n (error: unknown) => {\n if (winner === 0) mainError = error;\n failures++;\n if (failures === chains.length) reject(mainError);\n },\n );\n });\n });\n}\n","import { raceDelay } from '../race.utils.js';\n\nimport type { HedgeDeps } from './deps.js';\nimport type { Decision } from './record.utils.js';\nimport type { CallResult, PreDispatchContext, WrapNext } from 'vern-llm';\n\n/** Chains to race, or why the main chain alone is kept. */\nexport type Sent =\n | { kind: 'alone'; outcome: Decision['outcome'] }\n | { kind: 'raced'; chains: Promise<CallResult>[] };\n\n/** Settles when the chain answers, and never when it fails. */\nconst untilAnswered = (chain: Promise<CallResult>): Promise<unknown> =>\n chain.catch(() => new Promise<never>(() => {}));\n\n/**\n * One iteration per hedge. The first waits for the delay the main chain has been running; each\n * later one waits another delay, and none is sent once a chain has settled.\n */\nexport async function sendHedges(\n { config, budget }: HedgeDeps,\n ctx: PreDispatchContext,\n next: WrapNext,\n main: Promise<CallResult>,\n targets: readonly string[],\n delayMs: number,\n): Promise<Sent> {\n const chains: Promise<CallResult>[] = [main];\n let anySettled: Promise<unknown> = main;\n\n for (let hedge = 1; hedge <= config.maxHedges; hedge++) {\n if ((await raceDelay(anySettled, delayMs, ctx.signal)) === 'settled') {\n if (hedge > 1) break;\n return { kind: 'alone', outcome: await whyAlone(ctx, main) };\n }\n\n // Each hedge goes one name further along `targets` and keeps the rest as its fallback.\n const rest = targets.slice(hedge - 1);\n if (rest.length === 0) break;\n\n // Read again: other layers may have used up the call's reruns while the delay ran.\n const again = next.again;\n if (!again) {\n if (hedge === 1) return { kind: 'alone', outcome: 'unavailable' };\n break;\n }\n\n // The token is taken when the delay passes, so calls that answer in time cost nothing.\n if (!budget.take()) {\n if (hedge === 1) return { kind: 'alone', outcome: 'budget' };\n break;\n }\n\n let sent: Promise<CallResult>;\n try {\n sent = again({ targets: rest });\n } catch (error) {\n budget.refund();\n sent = Promise.reject(error);\n }\n chains.push(sent);\n // A hedge that fails is not a reason to stop: only the main chain settling, or any chain\n // answering, ends the wait.\n if (hedge < config.maxHedges) {\n anySettled = Promise.race([main, ...chains.slice(1).map(untilAnswered)]);\n }\n }\n\n return { kind: 'raced', chains };\n}\n\n/** Why the main chain ended before the delay did. */\nasync function whyAlone(\n ctx: PreDispatchContext,\n main: Promise<CallResult>,\n): Promise<Decision['outcome']> {\n if (ctx.signal?.aborted) return 'aborted';\n return main.then(\n () => 'not_needed' as const,\n () => 'failed' as const,\n );\n}\n","import { HEDGE_ADAPT_EVENT } from '../../events.js';\nimport { firstSuccess, type Raced } from '../race.utils.js';\nimport { hedgesOf, type Decision } from './record.utils.js';\n\nimport type { HedgeDeps } from './deps.js';\nimport type { CallResult, JsonValue, PreDispatchContext } from 'vern-llm';\n\n/**\n * Keeps the first chain to succeed, counts the call for the win rate, and reports it. When every\n * chain failed, the main chain's error is thrown, as if no hedge had been sent.\n */\nexport async function settleRace(\n { adapt, guard }: HedgeDeps,\n ctx: PreDispatchContext,\n report: (decision: Decision) => void,\n chains: readonly Promise<CallResult>[],\n targets: readonly string[],\n first: string,\n delayMs: number,\n callStart: number,\n): Promise<CallResult> {\n const sentCount = chains.length - 1;\n let raced: Raced;\n try {\n raced = await firstSuccess(chains);\n } catch (error) {\n report({ outcome: 'lost', delayMs, target: targets[0], ...hedgesOf(sentCount) });\n throw error;\n }\n\n // A call where every chain failed says nothing about whether the hedge helps, so only calls\n // that were answered count.\n const adapted = adapt?.record(first, raced.winner > 0, Date.now());\n if (adapted) {\n guard('adapt', () => ctx.emit(HEDGE_ADAPT_EVENT, adapted as unknown as JsonValue), undefined);\n }\n\n report({\n outcome: raced.winner > 0 ? 'won' : 'lost',\n delayMs,\n target: targets[0],\n ms: Date.now() - callStart,\n ...hedgesOf(sentCount),\n // The winning hedge's first target; its fallback may be the one that answered.\n ...(sentCount > 1 && raced.winner > 0 && { winner: targets[raced.winner - 1] }),\n });\n // Unchanged, so the core finds the chain it came from and cancels the others.\n return raced.result;\n}\n","import { plan } from './plan.utils.js';\nimport { record, type Decision } from './record.utils.js';\nimport { sendHedges } from './sendHedges.utils.js';\nimport { settleRace } from './settleRace.utils.js';\n\nimport type { HedgeDeps } from './deps.js';\nimport type { CallResult, VernLLMMiddleware } from 'vern-llm';\n\ntype Wrap = NonNullable<VernLLMMiddleware['wrap']>;\n\n/**\n * The `wrap` hook: plan, run the call, send the hedges that are due, and keep the first answer.\n * Every path ends in exactly one report, so a call has one decision.\n */\nexport function createWrap(deps: HedgeDeps): Wrap {\n const { guard, budget } = deps;\n\n return async (request, next, ctx) => {\n const callStart = Date.now();\n const report = (decision: Decision): void => record(ctx, guard, decision);\n\n // Runs the one chain as if the hedge were not there, and reports why once it settles. The\n // chain's own result or error passes through unchanged.\n const alone = (\n outcome: Decision['outcome'],\n chain: Promise<CallResult>,\n delayMs?: number,\n ): Promise<CallResult> =>\n chain.finally(() => report({ outcome, ...(delayMs !== undefined && { delayMs }) }));\n\n const chosen = plan(deps, request, ctx, next.again !== undefined, callStart);\n if (chosen.kind === 'skip') return alone(chosen.outcome, next(), chosen.delayMs);\n const { targets, first, delayMs } = chosen;\n\n // Eligible calls only, so skipped calls never build up a burst.\n budget.add();\n\n const main = next();\n const sent = await sendHedges(deps, ctx, next, main, targets, delayMs);\n if (sent.kind === 'alone') return alone(sent.outcome, main, delayMs);\n\n return settleRace(deps, ctx, report, sent.chains, targets, first, delayMs, callStart);\n };\n}\n","import type { PreDispatchContext } from 'vern-llm';\n\n/**\n * The call's targets after the first, in order: where a hedge goes by default. Inside the router\n * that is the router's second choice, already narrowed by policy. The rest of the order stays\n * behind it, so the hedge has its own fallback.\n */\nexport function nextTargets(ctx: Pick<PreDispatchContext, 'targets'>): readonly string[] {\n return ctx.targets.slice(1).map((target) => target.name);\n}\n","export function invalid(message: string): never {\n throw new Error(`hedgeMiddleware: ${message}`);\n}\n\nexport function isNumber(value: unknown): value is number {\n return typeof value === 'number' && Number.isFinite(value);\n}\n\nexport function isPositive(value: unknown): value is number {\n return isNumber(value) && value > 0;\n}\n","import { invalid, isNumber, isPositive } from './validate.utils.js';\n\nimport type { AdaptOptions, HedgeOptions, ResolvedAdapt } from '../../types/index.js';\n\n/** Fills every default of the win rate adaptation and validates it. `undefined` when it is off. */\nexport function resolveAdapt(adapt: HedgeOptions['adapt']): ResolvedAdapt | undefined {\n if (adapt === undefined || adapt === false) return undefined;\n if (adapt !== true && (typeof adapt !== 'object' || adapt === null || Array.isArray(adapt))) {\n invalid('adapt must be true or an object');\n }\n\n const given: AdaptOptions = adapt === true ? {} : adapt;\n const resolved: ResolvedAdapt = {\n window: given.window ?? 50,\n minHedges: given.minHedges ?? 20,\n lowWinRate: given.lowWinRate ?? 0.1,\n highWinRate: given.highWinRate ?? 0.5,\n quantileStep: given.quantileStep ?? 0.02,\n maxQuantile: given.maxQuantile ?? 0.99,\n pauseMs: given.pauseMs ?? 300_000,\n };\n\n if (!Number.isInteger(resolved.window) || resolved.window < 1) {\n invalid('adapt.window must be an integer of at least 1');\n }\n if (\n !Number.isInteger(resolved.minHedges) ||\n resolved.minHedges < 1 ||\n resolved.minHedges > resolved.window\n ) {\n invalid('adapt.minHedges must be an integer from 1 to adapt.window');\n }\n if (!isNumber(resolved.lowWinRate) || resolved.lowWinRate < 0 || resolved.lowWinRate >= 1) {\n invalid('adapt.lowWinRate must be a number from 0 up to 1');\n }\n if (\n !isNumber(resolved.highWinRate) ||\n resolved.highWinRate <= resolved.lowWinRate ||\n resolved.highWinRate > 1\n ) {\n invalid('adapt.highWinRate must be a number above adapt.lowWinRate and at most 1');\n }\n if (\n !isNumber(resolved.quantileStep) ||\n resolved.quantileStep <= 0 ||\n resolved.quantileStep >= 1\n ) {\n invalid('adapt.quantileStep must be a number above 0 and below 1');\n }\n if (!isNumber(resolved.maxQuantile) || resolved.maxQuantile <= 0 || resolved.maxQuantile >= 1) {\n invalid('adapt.maxQuantile must be a number above 0 and below 1');\n }\n if (!isPositive(resolved.pauseMs)) {\n invalid('adapt.pauseMs must be a positive, finite number of milliseconds');\n }\n return resolved;\n}\n","import { invalid, isNumber, isPositive } from './validate.utils.js';\n\nimport type { CacheAwareOptions, HedgeOptions, ResolvedCacheAware } from '../../types/index.js';\n\n/** Fills every default of the cache awareness and validates it. `undefined` when it is off. */\nexport function resolveCacheAware(\n option: HedgeOptions['cacheAware'],\n): ResolvedCacheAware | undefined {\n if (option === undefined || option === false) return undefined;\n if (option !== true && (typeof option !== 'object' || option === null || Array.isArray(option))) {\n invalid('cacheAware must be true or an object');\n }\n\n const given: CacheAwareOptions = option === true ? {} : option;\n const resolved: ResolvedCacheAware = {\n ttlMs: given.ttlMs ?? 300_000,\n prefixChars: given.prefixChars ?? 2_000,\n action: given.action ?? 'delay',\n delayFactor: given.delayFactor ?? 2,\n };\n\n if (!isPositive(resolved.ttlMs)) {\n invalid('cacheAware.ttlMs must be a positive, finite number of milliseconds');\n }\n if (!Number.isInteger(resolved.prefixChars) || resolved.prefixChars < 1) {\n invalid('cacheAware.prefixChars must be an integer of at least 1');\n }\n if (resolved.action !== 'delay' && resolved.action !== 'skip') {\n invalid(\"cacheAware.action must be 'delay' or 'skip'\");\n }\n if (!isNumber(resolved.delayFactor) || resolved.delayFactor <= 1) {\n invalid('cacheAware.delayFactor must be a number above 1');\n }\n return resolved;\n}\n","import { invalid, isNumber, isPositive } from './validate.utils.js';\n\nimport type { HedgeOptions, ResolvedDelay } from '../../types/index.js';\n\n/** The delay as one of three shapes, with every default filled in. */\nexport function resolveDelay(delay: HedgeOptions['delay']): ResolvedDelay {\n if (delay === undefined)\n return { kind: 'quantile', quantile: 0.95, minMs: 0, fallbackMs: undefined };\n\n if (typeof delay === 'function') return { kind: 'function', fn: delay };\n\n if (typeof delay === 'number') {\n if (!isPositive(delay)) invalid('delay must be a positive, finite number of milliseconds');\n return { kind: 'fixed', ms: delay };\n }\n\n if (typeof delay !== 'object' || delay === null || Array.isArray(delay)) {\n invalid('delay must be a number, a function or { quantile, minMs?, fallbackMs? }');\n }\n\n if (!isNumber(delay.quantile) || delay.quantile <= 0 || delay.quantile >= 1) {\n invalid('delay.quantile must be a number above 0 and below 1');\n }\n if (delay.minMs !== undefined && (!isNumber(delay.minMs) || delay.minMs < 0)) {\n invalid('delay.minMs must be a number of at least 0');\n }\n if (delay.fallbackMs !== undefined && !isPositive(delay.fallbackMs)) {\n invalid('delay.fallbackMs must be a positive, finite number of milliseconds');\n }\n\n return {\n kind: 'quantile',\n quantile: delay.quantile,\n minMs: delay.minMs ?? 0,\n fallbackMs: delay.fallbackMs,\n };\n}\n","import { nextTargets } from '../../targets.js';\nimport { resolveAdapt } from './resolveAdapt.utils.js';\nimport { resolveCacheAware } from './resolveCacheAware.utils.js';\nimport { resolveDelay } from './resolveDelay.utils.js';\nimport { invalid, isNumber } from './validate.utils.js';\n\nimport type { HedgeOptions, ResolvedConfig } from '../../types/index.js';\n\nfunction resolveBudget(budget: HedgeOptions['budget']): ResolvedConfig['budget'] {\n if (budget !== undefined && (typeof budget !== 'object' || budget === null)) {\n invalid('budget must be an object: { ratio?, burst? }');\n }\n\n const ratio = budget?.ratio ?? 0.05;\n const burst = budget?.burst ?? 10;\n\n if (!isNumber(ratio) || ratio <= 0 || ratio > 1) {\n invalid('budget.ratio must be a number above 0 and at most 1');\n }\n if (!isNumber(burst) || burst < 1) invalid('budget.burst must be a number of at least 1');\n\n return { ratio, burst };\n}\n\n/** Validates the options once, so a mistake fails at construction instead of mid call. */\nexport function normalizeOptions(options: HedgeOptions = {}): ResolvedConfig {\n const delay = resolveDelay(options.delay);\n\n if (options.stats !== undefined && typeof options.stats?.quantile !== 'function') {\n invalid('stats must have a quantile function');\n }\n if (delay.kind === 'quantile' && options.stats === undefined) {\n invalid(\n 'stats must have a quantile function when delay is a quantile (pass logger.stats), ' +\n 'or set delay to a number or a function',\n );\n }\n\n if (options.targets !== undefined && typeof options.targets !== 'function') {\n invalid('targets must be a function');\n }\n if (options.when !== undefined && typeof options.when !== 'function') {\n invalid('when must be a function');\n }\n\n const maxHedges = options.maxHedges ?? 1;\n if (!Number.isInteger(maxHedges) || maxHedges < 1 || maxHedges > 4) {\n invalid('maxHedges must be an integer from 1 to 4');\n }\n\n const statsTtlMs = options.statsTtlMs ?? 1_000;\n if (!isNumber(statsTtlMs) || statsTtlMs < 0) {\n invalid('statsTtlMs must be a number of at least 0');\n }\n\n return {\n stats: options.stats,\n delay,\n budget: resolveBudget(options.budget),\n maxHedges,\n adapt: resolveAdapt(options.adapt),\n cacheAware: resolveCacheAware(options.cacheAware),\n targets: options.targets ?? nextTargets,\n when: options.when ?? (() => true),\n statsTtlMs,\n logger: options.logger,\n };\n}\n","import { createMiddlewareRef, createStateKey, type VernLLMMiddleware } from 'vern-llm';\n\nimport { Adapt } from './internal/adapt.js';\nimport { HedgeBudget } from './internal/budget.js';\nimport { CacheAware } from './internal/cacheAware.js';\nimport { createDelay } from './internal/delay/delay.utils.js';\nimport { createGuard } from './internal/guard.utils.js';\nimport { createCacheEvent } from './internal/middleware/cacheEvent.utils.js';\nimport { createWrap } from './internal/middleware/wrap.utils.js';\nimport { normalizeOptions } from './internal/options/normalizeOptions.utils.js';\n\nimport type { HedgeDeps } from './internal/middleware/deps.js';\nimport type { HedgeOptions } from './types/index.js';\n\n/** One module level ref, so registering two instances hits the core's duplicate ref check. */\nexport const hedgeMiddlewareRef = createMiddlewareRef('hedge');\n\n/**\n * When a call's first answer is slower than that target usually is, sends the same call to the\n * next target too, keeps whichever answers first, and lets the core cancel the other.\n */\nexport function hedgeMiddleware(options: HedgeOptions = {}): VernLLMMiddleware {\n const config = normalizeOptions(options);\n const guard = createGuard(config.logger);\n const adapt = config.adapt\n ? new Adapt(config.adapt, config.delay.kind === 'quantile' ? config.delay.quantile : undefined)\n : undefined;\n\n const deps: HedgeDeps = {\n config,\n guard,\n budget: new HedgeBudget(config.budget.ratio, config.budget.burst),\n adapt,\n cacheAware: config.cacheAware ? new CacheAware(config.cacheAware) : undefined,\n delay: createDelay(config, guard, adapt),\n prefixKey: createStateKey<string>('hedge.prefix'),\n };\n const onEvent = createCacheEvent(deps);\n\n return {\n name: 'hedge',\n ref: hedgeMiddlewareRef,\n\n // Inside the logger, guard, policy, usage and router, which should see one call, and outside\n // the attempts it races.\n position: 10,\n\n // Gives `wrap` `next.again()`, and has the core cancel the chain that is not returned.\n reruns: true,\n\n // A target that reports cache reads has this call's prefix in its cache for a while.\n ...(onEvent && { onEvent }),\n\n wrap: createWrap(deps),\n };\n}\n","import { hedgeDecisionKey } from './stateKeys.js';\n\nimport type { JsonValue, MiddlewareStateBag } from 'vern-llm';\n\n/** The part of a context the reader needs, so any hook's `ctx` fits. */\ninterface WithState {\n state: MiddlewareStateBag;\n}\n\n/**\n * Fields for a log record: `hedge` (the outcome) and `hedge_delay_ms` when a delay was computed,\n * such as `{ hedge: 'won', hedge_delay_ms: 840 }` or `{ hedge: 'budget' }`. `undefined` when the\n * hedge did not run. Pass it to the logger's `enrich` option.\n */\nexport function hedgeLogFields(ctx: WithState): Record<string, JsonValue> | undefined {\n const decision = ctx.state.get(hedgeDecisionKey);\n if (decision === undefined) return undefined;\n\n return {\n hedge: decision.outcome,\n ...(decision.delayMs !== undefined && { hedge_delay_ms: decision.delayMs }),\n };\n}\n"],"mappings":"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"}
@@ -0,0 +1,264 @@
1
+ import { JsonValue, Logger, MiddlewareStateBag, PreDispatchContext, VernLLMMiddleware, WireCallRequest } from "vern-llm";
2
+ type SizeBucket = 'xs' | 's' | 'm' | 'l' | 'xl';
3
+ /** Same shape as `VernLLMOptions.logger`. */
4
+ type LoggerOption = Logger | 'silent';
5
+ /**
6
+ * Rolling stats, such as `logger.stats` from `vern-llm-logger`. The logger matches this
7
+ * structurally. The hedge asks only for the `attemptMs` quantile.
8
+ */
9
+ interface StatsSource {
10
+ quantile(filter: Readonly<Record<string, string>>, q: number, metric?: string): number | undefined;
11
+ }
12
+ /** A delay learned from the first target's own recent latency. */
13
+ interface DelayQuantile {
14
+ /** The quantile of `attemptMs` to wait for, strictly between 0 and 1. Typically `0.95`. */
15
+ quantile: number;
16
+ /** The delay is never shorter than this. Default `0`. */
17
+ minMs?: number;
18
+ /** Used while the logger has too few samples. Without it, a call with no data is not hedged. */
19
+ fallbackMs?: number;
20
+ }
21
+ /**
22
+ * Your own delay for this call, in milliseconds. `undefined` skips the hedge for this call. A
23
+ * result that is not a positive, finite number counts as `undefined`.
24
+ */
25
+ type DelayFunction = (ctx: PreDispatchContext, request: Readonly<WireCallRequest>) => number | undefined;
26
+ /** A quantile of the first target's `attemptMs`, a fixed number of milliseconds, or a function. */
27
+ type DelayOption = DelayQuantile | number | DelayFunction;
28
+ /**
29
+ * Reads how often each first target's hedges win, over its last `window` hedged calls, once it
30
+ * has at least `minHedges`.
31
+ */
32
+ interface AdaptOptions {
33
+ /** How many recent hedged calls to judge by. Default `50`. */
34
+ window?: number;
35
+ /** The fewest hedged calls to judge by, at most `window`. Default `20`. */
36
+ minHedges?: number;
37
+ /**
38
+ * Below this win rate the hedge is too early for that target: its quantile is raised by
39
+ * `quantileStep` up to `maxQuantile`, and once it cannot go higher the target is not hedged for
40
+ * `pauseMs`. Default `0.1`.
41
+ */
42
+ lowWinRate?: number;
43
+ /**
44
+ * Above this win rate the first target is the problem, which is reported as a `hedge.adapt`
45
+ * event with action `slow`, and again every `minHedges` hedged calls while it stays above.
46
+ * Default `0.5`, and above `lowWinRate`.
47
+ */
48
+ highWinRate?: number;
49
+ /** How much each raise adds to the quantile. Default `0.02`. */
50
+ quantileStep?: number;
51
+ /** The highest quantile a raise reaches, below 1. Default `0.99`. */
52
+ maxQuantile?: number;
53
+ /** How long a paused target is not hedged, in milliseconds. Default `300000`. */
54
+ pauseMs?: number;
55
+ }
56
+ /**
57
+ * Notices when the first target has recently served a prompt's prefix from its provider cache,
58
+ * since a hedge to another provider starts cold and pays full price for that prefix. It learns
59
+ * this from the `cacheReadTokens` the target reported on earlier calls, in this process. It is a
60
+ * heuristic: the prefix is the role and text of the first `prefixChars` characters, so the model,
61
+ * tools, other parameters and images are not part of it, and the usage event's `provider` is taken
62
+ * to be the target's name.
63
+ */
64
+ interface CacheAwareOptions {
65
+ /** How long a target is assumed to keep a prefix cached, in milliseconds. Default `300000`. */
66
+ ttlMs?: number;
67
+ /** How many leading characters of the prompt identify its prefix. Default `2000`. */
68
+ prefixChars?: number;
69
+ /**
70
+ * `'delay'` (default) waits `delayFactor` times as long before hedging such a call. `'skip'`
71
+ * does not hedge it, with outcome `cached`.
72
+ */
73
+ action?: 'delay' | 'skip';
74
+ /** How much longer `'delay'` waits, above 1. Default `2`. */
75
+ delayFactor?: number;
76
+ }
77
+ /** Caps hedges at about `ratio` of eligible calls, whatever the latency does. */
78
+ interface BudgetOptions {
79
+ /** Tokens each eligible call adds, above 0 and at most 1. Default `0.05`. */
80
+ ratio?: number;
81
+ /** The most tokens held, at least 1. Default `10`. */
82
+ burst?: number;
83
+ }
84
+ /**
85
+ * Where the hedge may go, in order. Names the call's order does not contain are ignored, and an
86
+ * empty result skips the hedge. `nextTargets` is the default.
87
+ */
88
+ type TargetsFunction = (ctx: PreDispatchContext, request: Readonly<WireCallRequest>) => readonly string[];
89
+ /** Whether this call may be hedged at all. A throw counts as `false`. */
90
+ type WhenFunction = (ctx: PreDispatchContext) => boolean;
91
+ interface HedgeOptions {
92
+ /**
93
+ * Rolling stats with `quantile(filter, q, metric)`; `logger.stats` fits. Required unless `delay`
94
+ * is a number or a function.
95
+ */
96
+ stats?: StatsSource;
97
+ /** Default `{ quantile: 0.95 }`. */
98
+ delay?: DelayOption;
99
+ /** Default `{ ratio: 0.05, burst: 10 }`. The bucket starts empty. */
100
+ budget?: BudgetOptions;
101
+ /**
102
+ * The most hedges one call may send, from 1 to 4, which is the core's limit on reruns. Each
103
+ * waits one more delay than the one before, goes to the next name in `targets`, and takes a
104
+ * budget token. No more are sent once the main chain has settled or any chain has answered.
105
+ * Default `1`.
106
+ */
107
+ maxHedges?: number;
108
+ /**
109
+ * Adjusts each first target's delay by how often its hedges win. `true` takes the defaults of
110
+ * {@link AdaptOptions}. Off by default. The tracking is in this process only.
111
+ */
112
+ adapt?: boolean | AdaptOptions;
113
+ /**
114
+ * Waits longer, or does not hedge, when the first target has this prompt's prefix in its
115
+ * provider cache. `true` takes the defaults of {@link CacheAwareOptions}. Off by default.
116
+ */
117
+ cacheAware?: boolean | CacheAwareOptions;
118
+ /** Default `nextTargets`: the call's targets after the first. */
119
+ targets?: TargetsFunction;
120
+ /** Default: every call. */
121
+ when?: WhenFunction;
122
+ /** How long a stats read is reused, in milliseconds. Default `1000`. */
123
+ statsTtlMs?: number;
124
+ /** Where the hedge reports its own failures and configuration warnings. */
125
+ logger?: LoggerOption;
126
+ }
127
+ /** The delay after validation: one of three shapes, with every default filled in. */
128
+ type ResolvedDelay = {
129
+ kind: 'fixed';
130
+ ms: number;
131
+ } | {
132
+ kind: 'function';
133
+ fn: DelayFunction;
134
+ } | {
135
+ kind: 'quantile';
136
+ quantile: number;
137
+ minMs: number;
138
+ fallbackMs: number | undefined;
139
+ };
140
+ /** The cache awareness with every default filled in. */
141
+ type ResolvedCacheAware = Required<CacheAwareOptions>;
142
+ /** The win rate adaptation with every default filled in. */
143
+ type ResolvedAdapt = Required<AdaptOptions>;
144
+ interface ResolvedConfig {
145
+ stats: StatsSource | undefined;
146
+ delay: ResolvedDelay;
147
+ budget: {
148
+ ratio: number;
149
+ burst: number;
150
+ };
151
+ maxHedges: number;
152
+ adapt: ResolvedAdapt | undefined;
153
+ cacheAware: ResolvedCacheAware | undefined;
154
+ targets: TargetsFunction;
155
+ when: WhenFunction;
156
+ statsTtlMs: number;
157
+ logger: LoggerOption | undefined;
158
+ }
159
+ /** Why a call was not hedged at all. */
160
+ type SkipReason = 'unavailable' | 'no_target' | 'not_allowed' | 'paused' | 'cached' | 'no_data' | 'deadline';
161
+ /**
162
+ * How a call went.
163
+ *
164
+ * - `won`: a hedge was sent and its answer was kept.
165
+ * - `lost`: a hedge was sent and the main chain's outcome was kept, an error included.
166
+ * - `not_needed`: the main chain answered before the delay.
167
+ * - `failed`: the main chain failed before the delay, so there was nothing to hedge.
168
+ * - `aborted`: the caller cancelled the call before the delay passed.
169
+ * - `budget`: the delay passed and the budget had no token.
170
+ * - Any {@link SkipReason}: the call was never eligible.
171
+ */
172
+ type HedgeOutcome = 'won' | 'lost' | 'not_needed' | 'failed' | 'aborted' | 'budget' | SkipReason;
173
+ /**
174
+ * The custom event `hedge.decision`, emitted once per call when the hedge settles. Names and
175
+ * numbers only, so it is safe on spans and in logs.
176
+ */
177
+ interface HedgeDecisionEvent {
178
+ v: 1;
179
+ outcome: HedgeOutcome;
180
+ /** The delay the call waited for, when one was computed. */
181
+ delayMs?: number;
182
+ /** Where the hedge went first, when one was sent. */
183
+ target?: string;
184
+ /** How many hedges were sent, only when more than one. */
185
+ hedges?: number;
186
+ /**
187
+ * The first target of the hedge whose answer was kept, only when more than one hedge was sent
188
+ * and one won. A hedge keeps the rest of the order as its fallback, so the answer itself may
189
+ * have come from a later target.
190
+ */
191
+ winner?: string;
192
+ /** From the start of the call to the kept answer, when a hedge was sent and one answered. */
193
+ ms?: number;
194
+ }
195
+ /**
196
+ * The custom event `hedge.adapt`, emitted when the win rate changes what the hedge does for a
197
+ * first target.
198
+ *
199
+ * - `raise_quantile`: hedges rarely won, so the target now waits for `quantile`.
200
+ * - `pause`: hedges still rarely won at the highest quantile, so the target is not hedged for a while.
201
+ * - `slow`: hedges often won, so the first target itself is slow. Reported again every `minHedges`
202
+ * hedged calls while the rate stays high.
203
+ */
204
+ interface AdaptEvent {
205
+ action: 'raise_quantile' | 'pause' | 'slow';
206
+ /** The first target. */
207
+ target: string;
208
+ /** Hedges that won over the hedged calls judged, from 0 to 1. */
209
+ winRate: number;
210
+ /** How many hedged calls it was judged over. */
211
+ hedges: number;
212
+ /** The new quantile, for `raise_quantile`. */
213
+ quantile?: number;
214
+ }
215
+ /** The part of the logger's call record these read, so no import of the logger is needed. */
216
+ interface CallRecord {
217
+ usage?: {
218
+ promptTokens?: number;
219
+ };
220
+ custom?: Record<string, unknown[]>;
221
+ }
222
+ /**
223
+ * The prompt's size as a logger dimension, so a long prompt's slow first token never sets the
224
+ * delay for a short one: `dimensions: { size: promptSizeDimension }`. Buckets by powers of four
225
+ * from 1 000 prompt tokens: `xs` under 1k, `s` under 4k, `m` under 16k, `l` under 64k and `xl`
226
+ * above. The hedge reads it under the name `size`. A record with no usage yields nothing.
227
+ */
228
+ export declare const promptSizeDimension: (record: CallRecord) => SizeBucket | undefined;
229
+ /**
230
+ * How the call's hedge went, as a logger dimension: `dimensions: { hedge: hedgeDimension }`
231
+ * splits the logger's counts and percentiles by `won`, `lost`, `not_needed`, `failed`, `aborted`, `budget` and the skip
232
+ * reasons. It reads the call's last `hedge.decision`, so the logger must keep `hedge.*` events
233
+ * (`customEvents: ['hedge.*']`). A call with no decision yields nothing.
234
+ */
235
+ export declare const hedgeDimension: (record: CallRecord) => HedgeOutcome | undefined;
236
+ /** Name of the custom event carrying each call's decision. */
237
+ export declare const HEDGE_DECISION_EVENT = "hedge.decision";
238
+ /** Name of the custom event for a change the win rate caused. */
239
+ export declare const HEDGE_ADAPT_EVENT = "hedge.adapt";
240
+ /** One module level ref, so registering two instances hits the core's duplicate ref check. */
241
+ export declare const hedgeMiddlewareRef: import("vern-llm").MiddlewareRef;
242
+ /**
243
+ * When a call's first answer is slower than that target usually is, sends the same call to the
244
+ * next target too, keeps whichever answers first, and lets the core cancel the other.
245
+ */
246
+ export declare function hedgeMiddleware(options?: HedgeOptions): VernLLMMiddleware;
247
+ /** The part of a context the reader needs, so any hook's `ctx` fits. */
248
+ interface WithState {
249
+ state: MiddlewareStateBag;
250
+ }
251
+ /**
252
+ * Fields for a log record: `hedge` (the outcome) and `hedge_delay_ms` when a delay was computed,
253
+ * such as `{ hedge: 'won', hedge_delay_ms: 840 }` or `{ hedge: 'budget' }`. `undefined` when the
254
+ * hedge did not run. Pass it to the logger's `enrich` option.
255
+ */
256
+ export declare function hedgeLogFields(ctx: WithState): Record<string, JsonValue> | undefined;
257
+ /**
258
+ * The call's targets after the first, in order: where a hedge goes by default. Inside the router
259
+ * that is the router's second choice, already narrowed by policy. The rest of the order stays
260
+ * behind it, so the hedge has its own fallback.
261
+ */
262
+ export declare function nextTargets(ctx: Pick<PreDispatchContext, 'targets'>): readonly string[];
263
+ export type { AdaptEvent, AdaptOptions, BudgetOptions, CacheAwareOptions, DelayFunction, DelayOption, DelayQuantile, HedgeDecisionEvent, HedgeOptions, HedgeOutcome, LoggerOption, ResolvedAdapt, ResolvedCacheAware, ResolvedConfig, ResolvedDelay, SkipReason, StatsSource, TargetsFunction, WhenFunction };
264
+ //# sourceMappingURL=index.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.cts","names":[],"sources":["../src/internal/size.utils.ts","../src/types/options.ts","../src/types/resolvedConfig.ts","../src/types/decision.ts","../src/dimensions.ts","../src/events.ts","../src/hedgeMiddleware.ts","../src/logFields.ts","../src/targets.ts"],"mappings":";KAQY;;KCLA,eAAe;;;;;UAMV;EACf,SACE,QAAQ,SAAS,yBACjB,WACA;;;UAKa;;EAEf;;EAEA;;EAEA;;;;;;KAOU,iBACV,KAAK,oBACL,SAAS,SAAS;;KAIR,cAAc,yBAAyB;;;;;UAMlC;;EAEf;;EAEA;;;;;;EAMA;;;;;;EAMA;;EAEA;;EAEA;;EAEA;;;;;;;;;;UAWe;;EAEf;;EAEA;;;;;EAKA;;EAEA;;;UAIe;;EAEf;;EAEA;;;;;;KAOU,mBACV,KAAK,oBACL,SAAS,SAAS;;KAIR,gBAAgB,KAAK;UAEhB;;;;;EAKf,QAAQ;;EAER,QAAQ;;EAER,SAAS;;;;;;;EAOT;;;;;EAKA,kBAAkB;;;;;EAKlB,uBAAuB;;EAEvB,UAAU;;EAEV,OAAO;;EAEP;;EAEA,SAAS;;;KCrIC;EACN;EAAe;;EACf;EAAkB,IAAI;;EACtB;EAAkB;EAAkB;EAAe;;;KAG7C,qBAAqB,SAAS;;KAG9B,gBAAgB,SAAS;UAEpB;EACf,OAAO;EACP,OAAO;EACP;IAAU;IAAe;;EACzB;EACA,OAAO;EACP,YAAY;EACZ,SAAS;EACT,MAAM;EACN;EACA,QAAQ;;;KC/BE;;;;;;;;;;;;KAoBA,iFAOR;;;;;UAMa;EACf;EACA,SAAS;;EAET;;EAEA;;EAEA;;;;;;EAMA;;EAEA;;;;;;;;;;;UAYe;EACf;;EAEA;;EAEA;;EAEA;;EAEA;;;UCjEQ;EACR;IAAU;;EACV,SAAS;;;;;;;;qBAyBE,sBAAmB,QAAY,eAAa;;;;;;;qBAa5C,iBAAc,QAAY,eAAa;;qBC7CvC;;qBAGA;;qBCWA,uCAAkB;;;;;wBAMf,gBAAgB,UAAS,eAAoB;;UChBnD;EACR,OAAO;;;;;;;wBAQO,eAAe,KAAK,YAAY,eAAe;;;;;;wBCP/C,YAAY,KAAK,KAAK"}
@@ -0,0 +1,264 @@
1
+ import { JsonValue, Logger, MiddlewareStateBag, PreDispatchContext, VernLLMMiddleware, WireCallRequest } from "vern-llm";
2
+ type SizeBucket = 'xs' | 's' | 'm' | 'l' | 'xl';
3
+ /** Same shape as `VernLLMOptions.logger`. */
4
+ type LoggerOption = Logger | 'silent';
5
+ /**
6
+ * Rolling stats, such as `logger.stats` from `vern-llm-logger`. The logger matches this
7
+ * structurally. The hedge asks only for the `attemptMs` quantile.
8
+ */
9
+ interface StatsSource {
10
+ quantile(filter: Readonly<Record<string, string>>, q: number, metric?: string): number | undefined;
11
+ }
12
+ /** A delay learned from the first target's own recent latency. */
13
+ interface DelayQuantile {
14
+ /** The quantile of `attemptMs` to wait for, strictly between 0 and 1. Typically `0.95`. */
15
+ quantile: number;
16
+ /** The delay is never shorter than this. Default `0`. */
17
+ minMs?: number;
18
+ /** Used while the logger has too few samples. Without it, a call with no data is not hedged. */
19
+ fallbackMs?: number;
20
+ }
21
+ /**
22
+ * Your own delay for this call, in milliseconds. `undefined` skips the hedge for this call. A
23
+ * result that is not a positive, finite number counts as `undefined`.
24
+ */
25
+ type DelayFunction = (ctx: PreDispatchContext, request: Readonly<WireCallRequest>) => number | undefined;
26
+ /** A quantile of the first target's `attemptMs`, a fixed number of milliseconds, or a function. */
27
+ type DelayOption = DelayQuantile | number | DelayFunction;
28
+ /**
29
+ * Reads how often each first target's hedges win, over its last `window` hedged calls, once it
30
+ * has at least `minHedges`.
31
+ */
32
+ interface AdaptOptions {
33
+ /** How many recent hedged calls to judge by. Default `50`. */
34
+ window?: number;
35
+ /** The fewest hedged calls to judge by, at most `window`. Default `20`. */
36
+ minHedges?: number;
37
+ /**
38
+ * Below this win rate the hedge is too early for that target: its quantile is raised by
39
+ * `quantileStep` up to `maxQuantile`, and once it cannot go higher the target is not hedged for
40
+ * `pauseMs`. Default `0.1`.
41
+ */
42
+ lowWinRate?: number;
43
+ /**
44
+ * Above this win rate the first target is the problem, which is reported as a `hedge.adapt`
45
+ * event with action `slow`, and again every `minHedges` hedged calls while it stays above.
46
+ * Default `0.5`, and above `lowWinRate`.
47
+ */
48
+ highWinRate?: number;
49
+ /** How much each raise adds to the quantile. Default `0.02`. */
50
+ quantileStep?: number;
51
+ /** The highest quantile a raise reaches, below 1. Default `0.99`. */
52
+ maxQuantile?: number;
53
+ /** How long a paused target is not hedged, in milliseconds. Default `300000`. */
54
+ pauseMs?: number;
55
+ }
56
+ /**
57
+ * Notices when the first target has recently served a prompt's prefix from its provider cache,
58
+ * since a hedge to another provider starts cold and pays full price for that prefix. It learns
59
+ * this from the `cacheReadTokens` the target reported on earlier calls, in this process. It is a
60
+ * heuristic: the prefix is the role and text of the first `prefixChars` characters, so the model,
61
+ * tools, other parameters and images are not part of it, and the usage event's `provider` is taken
62
+ * to be the target's name.
63
+ */
64
+ interface CacheAwareOptions {
65
+ /** How long a target is assumed to keep a prefix cached, in milliseconds. Default `300000`. */
66
+ ttlMs?: number;
67
+ /** How many leading characters of the prompt identify its prefix. Default `2000`. */
68
+ prefixChars?: number;
69
+ /**
70
+ * `'delay'` (default) waits `delayFactor` times as long before hedging such a call. `'skip'`
71
+ * does not hedge it, with outcome `cached`.
72
+ */
73
+ action?: 'delay' | 'skip';
74
+ /** How much longer `'delay'` waits, above 1. Default `2`. */
75
+ delayFactor?: number;
76
+ }
77
+ /** Caps hedges at about `ratio` of eligible calls, whatever the latency does. */
78
+ interface BudgetOptions {
79
+ /** Tokens each eligible call adds, above 0 and at most 1. Default `0.05`. */
80
+ ratio?: number;
81
+ /** The most tokens held, at least 1. Default `10`. */
82
+ burst?: number;
83
+ }
84
+ /**
85
+ * Where the hedge may go, in order. Names the call's order does not contain are ignored, and an
86
+ * empty result skips the hedge. `nextTargets` is the default.
87
+ */
88
+ type TargetsFunction = (ctx: PreDispatchContext, request: Readonly<WireCallRequest>) => readonly string[];
89
+ /** Whether this call may be hedged at all. A throw counts as `false`. */
90
+ type WhenFunction = (ctx: PreDispatchContext) => boolean;
91
+ interface HedgeOptions {
92
+ /**
93
+ * Rolling stats with `quantile(filter, q, metric)`; `logger.stats` fits. Required unless `delay`
94
+ * is a number or a function.
95
+ */
96
+ stats?: StatsSource;
97
+ /** Default `{ quantile: 0.95 }`. */
98
+ delay?: DelayOption;
99
+ /** Default `{ ratio: 0.05, burst: 10 }`. The bucket starts empty. */
100
+ budget?: BudgetOptions;
101
+ /**
102
+ * The most hedges one call may send, from 1 to 4, which is the core's limit on reruns. Each
103
+ * waits one more delay than the one before, goes to the next name in `targets`, and takes a
104
+ * budget token. No more are sent once the main chain has settled or any chain has answered.
105
+ * Default `1`.
106
+ */
107
+ maxHedges?: number;
108
+ /**
109
+ * Adjusts each first target's delay by how often its hedges win. `true` takes the defaults of
110
+ * {@link AdaptOptions}. Off by default. The tracking is in this process only.
111
+ */
112
+ adapt?: boolean | AdaptOptions;
113
+ /**
114
+ * Waits longer, or does not hedge, when the first target has this prompt's prefix in its
115
+ * provider cache. `true` takes the defaults of {@link CacheAwareOptions}. Off by default.
116
+ */
117
+ cacheAware?: boolean | CacheAwareOptions;
118
+ /** Default `nextTargets`: the call's targets after the first. */
119
+ targets?: TargetsFunction;
120
+ /** Default: every call. */
121
+ when?: WhenFunction;
122
+ /** How long a stats read is reused, in milliseconds. Default `1000`. */
123
+ statsTtlMs?: number;
124
+ /** Where the hedge reports its own failures and configuration warnings. */
125
+ logger?: LoggerOption;
126
+ }
127
+ /** The delay after validation: one of three shapes, with every default filled in. */
128
+ type ResolvedDelay = {
129
+ kind: 'fixed';
130
+ ms: number;
131
+ } | {
132
+ kind: 'function';
133
+ fn: DelayFunction;
134
+ } | {
135
+ kind: 'quantile';
136
+ quantile: number;
137
+ minMs: number;
138
+ fallbackMs: number | undefined;
139
+ };
140
+ /** The cache awareness with every default filled in. */
141
+ type ResolvedCacheAware = Required<CacheAwareOptions>;
142
+ /** The win rate adaptation with every default filled in. */
143
+ type ResolvedAdapt = Required<AdaptOptions>;
144
+ interface ResolvedConfig {
145
+ stats: StatsSource | undefined;
146
+ delay: ResolvedDelay;
147
+ budget: {
148
+ ratio: number;
149
+ burst: number;
150
+ };
151
+ maxHedges: number;
152
+ adapt: ResolvedAdapt | undefined;
153
+ cacheAware: ResolvedCacheAware | undefined;
154
+ targets: TargetsFunction;
155
+ when: WhenFunction;
156
+ statsTtlMs: number;
157
+ logger: LoggerOption | undefined;
158
+ }
159
+ /** Why a call was not hedged at all. */
160
+ type SkipReason = 'unavailable' | 'no_target' | 'not_allowed' | 'paused' | 'cached' | 'no_data' | 'deadline';
161
+ /**
162
+ * How a call went.
163
+ *
164
+ * - `won`: a hedge was sent and its answer was kept.
165
+ * - `lost`: a hedge was sent and the main chain's outcome was kept, an error included.
166
+ * - `not_needed`: the main chain answered before the delay.
167
+ * - `failed`: the main chain failed before the delay, so there was nothing to hedge.
168
+ * - `aborted`: the caller cancelled the call before the delay passed.
169
+ * - `budget`: the delay passed and the budget had no token.
170
+ * - Any {@link SkipReason}: the call was never eligible.
171
+ */
172
+ type HedgeOutcome = 'won' | 'lost' | 'not_needed' | 'failed' | 'aborted' | 'budget' | SkipReason;
173
+ /**
174
+ * The custom event `hedge.decision`, emitted once per call when the hedge settles. Names and
175
+ * numbers only, so it is safe on spans and in logs.
176
+ */
177
+ interface HedgeDecisionEvent {
178
+ v: 1;
179
+ outcome: HedgeOutcome;
180
+ /** The delay the call waited for, when one was computed. */
181
+ delayMs?: number;
182
+ /** Where the hedge went first, when one was sent. */
183
+ target?: string;
184
+ /** How many hedges were sent, only when more than one. */
185
+ hedges?: number;
186
+ /**
187
+ * The first target of the hedge whose answer was kept, only when more than one hedge was sent
188
+ * and one won. A hedge keeps the rest of the order as its fallback, so the answer itself may
189
+ * have come from a later target.
190
+ */
191
+ winner?: string;
192
+ /** From the start of the call to the kept answer, when a hedge was sent and one answered. */
193
+ ms?: number;
194
+ }
195
+ /**
196
+ * The custom event `hedge.adapt`, emitted when the win rate changes what the hedge does for a
197
+ * first target.
198
+ *
199
+ * - `raise_quantile`: hedges rarely won, so the target now waits for `quantile`.
200
+ * - `pause`: hedges still rarely won at the highest quantile, so the target is not hedged for a while.
201
+ * - `slow`: hedges often won, so the first target itself is slow. Reported again every `minHedges`
202
+ * hedged calls while the rate stays high.
203
+ */
204
+ interface AdaptEvent {
205
+ action: 'raise_quantile' | 'pause' | 'slow';
206
+ /** The first target. */
207
+ target: string;
208
+ /** Hedges that won over the hedged calls judged, from 0 to 1. */
209
+ winRate: number;
210
+ /** How many hedged calls it was judged over. */
211
+ hedges: number;
212
+ /** The new quantile, for `raise_quantile`. */
213
+ quantile?: number;
214
+ }
215
+ /** The part of the logger's call record these read, so no import of the logger is needed. */
216
+ interface CallRecord {
217
+ usage?: {
218
+ promptTokens?: number;
219
+ };
220
+ custom?: Record<string, unknown[]>;
221
+ }
222
+ /**
223
+ * The prompt's size as a logger dimension, so a long prompt's slow first token never sets the
224
+ * delay for a short one: `dimensions: { size: promptSizeDimension }`. Buckets by powers of four
225
+ * from 1 000 prompt tokens: `xs` under 1k, `s` under 4k, `m` under 16k, `l` under 64k and `xl`
226
+ * above. The hedge reads it under the name `size`. A record with no usage yields nothing.
227
+ */
228
+ export declare const promptSizeDimension: (record: CallRecord) => SizeBucket | undefined;
229
+ /**
230
+ * How the call's hedge went, as a logger dimension: `dimensions: { hedge: hedgeDimension }`
231
+ * splits the logger's counts and percentiles by `won`, `lost`, `not_needed`, `failed`, `aborted`, `budget` and the skip
232
+ * reasons. It reads the call's last `hedge.decision`, so the logger must keep `hedge.*` events
233
+ * (`customEvents: ['hedge.*']`). A call with no decision yields nothing.
234
+ */
235
+ export declare const hedgeDimension: (record: CallRecord) => HedgeOutcome | undefined;
236
+ /** Name of the custom event carrying each call's decision. */
237
+ export declare const HEDGE_DECISION_EVENT = "hedge.decision";
238
+ /** Name of the custom event for a change the win rate caused. */
239
+ export declare const HEDGE_ADAPT_EVENT = "hedge.adapt";
240
+ /** One module level ref, so registering two instances hits the core's duplicate ref check. */
241
+ export declare const hedgeMiddlewareRef: import("vern-llm").MiddlewareRef;
242
+ /**
243
+ * When a call's first answer is slower than that target usually is, sends the same call to the
244
+ * next target too, keeps whichever answers first, and lets the core cancel the other.
245
+ */
246
+ export declare function hedgeMiddleware(options?: HedgeOptions): VernLLMMiddleware;
247
+ /** The part of a context the reader needs, so any hook's `ctx` fits. */
248
+ interface WithState {
249
+ state: MiddlewareStateBag;
250
+ }
251
+ /**
252
+ * Fields for a log record: `hedge` (the outcome) and `hedge_delay_ms` when a delay was computed,
253
+ * such as `{ hedge: 'won', hedge_delay_ms: 840 }` or `{ hedge: 'budget' }`. `undefined` when the
254
+ * hedge did not run. Pass it to the logger's `enrich` option.
255
+ */
256
+ export declare function hedgeLogFields(ctx: WithState): Record<string, JsonValue> | undefined;
257
+ /**
258
+ * The call's targets after the first, in order: where a hedge goes by default. Inside the router
259
+ * that is the router's second choice, already narrowed by policy. The rest of the order stays
260
+ * behind it, so the hedge has its own fallback.
261
+ */
262
+ export declare function nextTargets(ctx: Pick<PreDispatchContext, 'targets'>): readonly string[];
263
+ export type { AdaptEvent, AdaptOptions, BudgetOptions, CacheAwareOptions, DelayFunction, DelayOption, DelayQuantile, HedgeDecisionEvent, HedgeOptions, HedgeOutcome, LoggerOption, ResolvedAdapt, ResolvedCacheAware, ResolvedConfig, ResolvedDelay, SkipReason, StatsSource, TargetsFunction, WhenFunction };
264
+ //# sourceMappingURL=index.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../src/internal/size.utils.ts","../src/types/options.ts","../src/types/resolvedConfig.ts","../src/types/decision.ts","../src/dimensions.ts","../src/events.ts","../src/hedgeMiddleware.ts","../src/logFields.ts","../src/targets.ts"],"mappings":";KAQY;;KCLA,eAAe;;;;;UAMV;EACf,SACE,QAAQ,SAAS,yBACjB,WACA;;;UAKa;;EAEf;;EAEA;;EAEA;;;;;;KAOU,iBACV,KAAK,oBACL,SAAS,SAAS;;KAIR,cAAc,yBAAyB;;;;;UAMlC;;EAEf;;EAEA;;;;;;EAMA;;;;;;EAMA;;EAEA;;EAEA;;EAEA;;;;;;;;;;UAWe;;EAEf;;EAEA;;;;;EAKA;;EAEA;;;UAIe;;EAEf;;EAEA;;;;;;KAOU,mBACV,KAAK,oBACL,SAAS,SAAS;;KAIR,gBAAgB,KAAK;UAEhB;;;;;EAKf,QAAQ;;EAER,QAAQ;;EAER,SAAS;;;;;;;EAOT;;;;;EAKA,kBAAkB;;;;;EAKlB,uBAAuB;;EAEvB,UAAU;;EAEV,OAAO;;EAEP;;EAEA,SAAS;;;KCrIC;EACN;EAAe;;EACf;EAAkB,IAAI;;EACtB;EAAkB;EAAkB;EAAe;;;KAG7C,qBAAqB,SAAS;;KAG9B,gBAAgB,SAAS;UAEpB;EACf,OAAO;EACP,OAAO;EACP;IAAU;IAAe;;EACzB;EACA,OAAO;EACP,YAAY;EACZ,SAAS;EACT,MAAM;EACN;EACA,QAAQ;;;KC/BE;;;;;;;;;;;;KAoBA,iFAOR;;;;;UAMa;EACf;EACA,SAAS;;EAET;;EAEA;;EAEA;;;;;;EAMA;;EAEA;;;;;;;;;;;UAYe;EACf;;EAEA;;EAEA;;EAEA;;EAEA;;;UCjEQ;EACR;IAAU;;EACV,SAAS;;;;;;;;qBAyBE,sBAAmB,QAAY,eAAa;;;;;;;qBAa5C,iBAAc,QAAY,eAAa;;qBC7CvC;;qBAGA;;qBCWA,uCAAkB;;;;;wBAMf,gBAAgB,UAAS,eAAoB;;UChBnD;EACR,OAAO;;;;;;;wBAQO,eAAe,KAAK,YAAY,eAAe;;;;;;wBCP/C,YAAY,KAAK,KAAK"}
package/dist/index.mjs ADDED
@@ -0,0 +1,2 @@
1
+ import{ConsoleLogger as e,createMiddlewareRef as t,createStateKey as n,defaultEstimateTokens as r}from"vern-llm";const i=`hedge.decision`,a=`hedge.adapt`,o=[1e3,4e3,16e3,64e3],s=[`xs`,`s`,`m`,`l`];function c(e){for(let[t,n]of o.entries())if(e<n)return s[t];return`xl`}function l(e){let t=r(e)-(e.max_tokens??0);return c(Math.max(0,t))}const u=/* @__PURE__ */ new Set([`won`,`lost`,`not_needed`,`failed`,`aborted`,`budget`,`unavailable`,`no_target`,`not_allowed`,`paused`,`cached`,`no_data`,`deadline`]),d=e=>{let t=e.usage?.promptTokens;return typeof t==`number`&&Number.isFinite(t)&&t>=0?c(t):void 0},f=e=>{let t=e.custom?.[i],n=t?.[t.length-1];if(typeof n!=`object`||!n)return;let r=n.outcome;return u.has(r)?r:void 0};var p=class{config;baseQuantile;targets=/* @__PURE__ */ new Map;constructor(e,t){this.config=e,this.baseQuantile=t}paused(e,t){return t<(this.targets.get(e)?.pausedUntil??0)}quantileFor(e,t){let n=this.targets.get(e)?.boost??0;return Math.min(t+n,this.ceiling(t))}ceiling(e){return Math.max(e,this.config.maxQuantile)}record(e,t,n){let r=this.targets.get(e);if(r||(r={results:[],boost:0,pausedUntil:0,slow:!1,sinceSlow:0},this.targets.set(e,r)),r.results.push(t),r.results.length>this.config.window&&r.results.shift(),r.results.length<this.config.minHedges)return;let i=r.results.length,a=r.results.filter(Boolean).length/i;if(a<this.config.lowWinRate){r.slow=!1,r.results=[];let t=this.baseQuantile,o=t===void 0?void 0:Math.min(t+r.boost+this.config.quantileStep,this.ceiling(t));return t===void 0||o===void 0||o<=t+r.boost?(r.boost=0,r.pausedUntil=n+this.config.pauseMs,{action:`pause`,target:e,winRate:a,hedges:i}):(r.boost=o-t,{action:`raise_quantile`,target:e,winRate:a,hedges:i,quantile:o})}if(a>this.config.highWinRate)return r.slow&&++r.sinceSlow<this.config.minHedges?void 0:(r.slow=!0,r.sinceSlow=0,{action:`slow`,target:e,winRate:a,hedges:i});r.slow=!1}},m=class{ratio;burst;tokens=0;constructor(e,t){this.ratio=e,this.burst=t}add(){this.tokens=Math.min(this.burst,this.tokens+this.ratio)}refund(){this.tokens=Math.min(this.burst,this.tokens+1)}take(){return this.tokens<.999999999?!1:(this.tokens=Math.max(0,this.tokens-1),!0)}};function h(e){let t=3735928559,n=1103547991;for(let r=0;r<e.length;r++){let i=e.charCodeAt(r);t=Math.imul(t^i,2654435761),n=Math.imul(n^i,1597334677)}return t=Math.imul(t^t>>>16,2246822507)^Math.imul(n^n>>>13,3266489909),n=Math.imul(n^n>>>16,2246822507)^Math.imul(t^t>>>13,3266489909),(4294967296*(2097151&n)+(t>>>0)).toString(36)}function g(e){return typeof e==`string`?e:Array.isArray(e)?e.map(e=>{let t=e?.text;return typeof t==`string`?t:``}).join(``):``}var _=class{config;seen=/* @__PURE__ */ new Map;constructor(e){this.config=e}keyOf(e){let t=``;for(let n of e.messages)if(t+=`${n.role}:${g(n.content)}\n`,t.length>=this.config.prefixChars)break;return h(t.slice(0,this.config.prefixChars))}remember(e,t,n){let r=`${e}|${t}`;this.seen.delete(r),this.seen.set(r,n),this.seen.size>1e4&&this.seen.delete(this.seen.keys().next().value)}warm(e,t,n){let r=this.seen.get(`${e}|${t}`);return r!==void 0&&n-r>=0&&n-r<this.config.ttlMs}get size(){return this.seen.size}},v=class{source;ttlMs;entries=/* @__PURE__ */ new Map;constructor(e,t){this.source=e,this.ttlMs=t}quantile(e,t,n){let r=`${JSON.stringify(Object.entries(e).sort(([e],[t])=>e<t?-1:1))}|${t}`;if(this.ttlMs>0){let e=this.entries.get(r);if(e&&n-e.at>=0&&n-e.at<this.ttlMs)return e.value}let i=this.source.quantile(e,t,`attemptMs`);return this.ttlMs>0&&(this.entries.size>=512&&this.entries.clear(),this.entries.set(r,{at:n,value:i})),i}get size(){return this.entries.size}};function y(e){return typeof e==`number`&&Number.isFinite(e)&&e>0}function b(e,t,n){let{delay:r}=e;if(r.kind===`fixed`)return{for:()=>r.ms};if(r.kind===`function`)return{for(e,n){let i=t(`delay`,()=>r.fn(e,n),void 0);if(i!==void 0){if(y(i))return i;t.warnOnce(`delay-value`,`delay returned a value that is not a positive, finite number`)}}};let i=new v(e.stats,e.statsTtlMs);return{for(e,a){let o=e.targets[0]?.name;if(o===void 0)return;let s={provider:o,streaming:String(e.streaming===!0)},c=n?n.quantileFor(o,r.quantile):r.quantile,u=Date.now(),d;try{d=i.quantile({...s,size:l(a)},c,u)??i.quantile(s,c,u)}catch(e){t.warnOnce(`stats`,`stats.quantile threw, so calls without a fallbackMs are not hedged: ${e instanceof Error?e.message:String(e)}`)}let f=y(d)?d:r.fallbackMs;return f===void 0?void 0:Math.max(f,r.minMs)}}}function x(t){let n=t===`silent`?void 0:t??new e(!1),r=/* @__PURE__ */ new Set,i=(e,t)=>{if(n)try{n.error(`[VernLLM] hedge: ${e} failed`,{message:t instanceof Error?t.message:String(t),stack:t instanceof Error?t.stack:void 0})}catch{}},a=((e,t,n)=>{try{return t()}catch(t){return i(e,t),n}});return a.report=i,a.warn=e=>{if(n)try{n.warn(`[VernLLM] hedge: ${e}`)}catch{}},a.warnOnce=(e,t)=>{r.has(e)||(r.add(e),a.warn(t))},a}function S({cacheAware:e,guard:t,prefixKey:n}){if(e)return(r,i)=>t(`cacheEvent`,()=>{if(r.kind!==`usage`||!((r.usage.cacheReadTokens??0)>0))return;let t=i.state.get(n);t!==void 0&&e.remember(r.usage.provider??`primary`,t,Date.now())},void 0)}function C(e,t,n){if(!Array.isArray(t))return n.warnOnce(`targets-shape`,`targets must return an array of target names`),[];let r=new Set(e.targets.map(e=>e.name));return[...new Set(t.filter(e=>r.has(e)))]}function w({config:e,guard:t,adapt:n,cacheAware:r,delay:i,prefixKey:a},o,s,c,l){if(!c)return{kind:`skip`,outcome:`unavailable`};let u=t(`targets`,()=>C(s,e.targets(s,o),t),[]);if(u.length===0)return{kind:`skip`,outcome:`no_target`};if(!t(`when`,()=>!!e.when(s),!1))return{kind:`skip`,outcome:`not_allowed`};let d=s.targets[0].name;if(n?.paused(d,l))return{kind:`skip`,outcome:`paused`};let f=!1;if(r){let e=t(`cacheKey`,()=>r.keyOf(o),void 0);e!==void 0&&(t(`cacheState`,()=>s.state.set(a,e),void 0),f=r.warm(d,e,l))}if(f&&e.cacheAware.action===`skip`)return{kind:`skip`,outcome:`cached`};let p=t(`delay`,()=>i.for(s,o),void 0);if(p===void 0)return{kind:`skip`,outcome:`no_data`};let m=f?p*e.cacheAware.delayFactor:p;return s.deadlineAt!==void 0&&l+m>=s.deadlineAt?{kind:`skip`,outcome:`deadline`,delayMs:m}:{kind:`hedge`,targets:u,first:d,delayMs:m}}const T=n(`hedge.decision`),E=e=>e>1?{hedges:e}:{};function D(e,t,n){let r={v:1,outcome:n.outcome,...n.delayMs!==void 0&&{delayMs:Math.round(n.delayMs)},...n.target!==void 0&&{target:n.target},...n.hedges!==void 0&&{hedges:n.hedges},...n.winner!==void 0&&{winner:n.winner},...n.ms!==void 0&&{ms:Math.round(n.ms)}};t(`state`,()=>e.state.set(T,r),void 0),t(`emit`,()=>e.emit(i,r),void 0)}function O(e,t,n){return new Promise(r=>{if(n?.aborted){r(`settled`);return}let i=e=>{clearTimeout(o),n?.removeEventListener(`abort`,a),r(e)},a=()=>i(`settled`),o=setTimeout(()=>i(`elapsed`),Math.min(t,2147483647));n?.addEventListener(`abort`,a,{once:!0}),e.then(()=>i(`settled`),()=>i(`settled`))})}function k(e){return new Promise((t,n)=>{let r=0,i;e.forEach((a,o)=>{a.then(e=>t({result:e,winner:o}),t=>{o===0&&(i=t),r++,r===e.length&&n(i)})})})}const A=e=>e.catch(()=>new Promise(()=>{}));async function j({config:e,budget:t},n,r,i,a,o){let s=[i],c=i;for(let l=1;l<=e.maxHedges;l++){if(await O(c,o,n.signal)===`settled`){if(l>1)break;return{kind:`alone`,outcome:await M(n,i)}}let u=a.slice(l-1);if(u.length===0)break;let d=r.again;if(!d){if(l===1)return{kind:`alone`,outcome:`unavailable`};break}if(!t.take()){if(l===1)return{kind:`alone`,outcome:`budget`};break}let f;try{f=d({targets:u})}catch(e){t.refund(),f=Promise.reject(e)}s.push(f),l<e.maxHedges&&(c=Promise.race([i,...s.slice(1).map(A)]))}return{kind:`raced`,chains:s}}async function M(e,t){return e.signal?.aborted?`aborted`:t.then(()=>`not_needed`,()=>`failed`)}async function N({adapt:e,guard:t},n,r,i,o,s,c,l){let u=i.length-1,d;try{d=await k(i)}catch(e){throw r({outcome:`lost`,delayMs:c,target:o[0],...E(u)}),e}let f=e?.record(s,d.winner>0,Date.now());return f&&t(`adapt`,()=>n.emit(a,f),void 0),r({outcome:d.winner>0?`won`:`lost`,delayMs:c,target:o[0],ms:Date.now()-l,...E(u),...u>1&&d.winner>0&&{winner:o[d.winner-1]}}),d.result}function P(e){let{guard:t,budget:n}=e;return async(r,i,a)=>{let o=Date.now(),s=e=>D(a,t,e),c=(e,t,n)=>t.finally(()=>s({outcome:e,...n!==void 0&&{delayMs:n}})),l=w(e,r,a,i.again!==void 0,o);if(l.kind===`skip`)return c(l.outcome,i(),l.delayMs);let{targets:u,first:d,delayMs:f}=l;n.add();let p=i(),m=await j(e,a,i,p,u,f);return m.kind===`alone`?c(m.outcome,p,f):N(e,a,s,m.chains,u,d,f,o)}}function F(e){return e.targets.slice(1).map(e=>e.name)}function I(e){throw Error(`hedgeMiddleware: ${e}`)}function L(e){return typeof e==`number`&&Number.isFinite(e)}function R(e){return L(e)&&e>0}function z(e){if(e===void 0||e===!1)return;e!==!0&&(typeof e!=`object`||!e||Array.isArray(e))&&I(`adapt must be true or an object`);let t=e===!0?{}:e,n={window:t.window??50,minHedges:t.minHedges??20,lowWinRate:t.lowWinRate??.1,highWinRate:t.highWinRate??.5,quantileStep:t.quantileStep??.02,maxQuantile:t.maxQuantile??.99,pauseMs:t.pauseMs??3e5};return(!Number.isInteger(n.window)||n.window<1)&&I(`adapt.window must be an integer of at least 1`),(!Number.isInteger(n.minHedges)||n.minHedges<1||n.minHedges>n.window)&&I(`adapt.minHedges must be an integer from 1 to adapt.window`),(!L(n.lowWinRate)||n.lowWinRate<0||n.lowWinRate>=1)&&I(`adapt.lowWinRate must be a number from 0 up to 1`),(!L(n.highWinRate)||n.highWinRate<=n.lowWinRate||n.highWinRate>1)&&I(`adapt.highWinRate must be a number above adapt.lowWinRate and at most 1`),(!L(n.quantileStep)||n.quantileStep<=0||n.quantileStep>=1)&&I(`adapt.quantileStep must be a number above 0 and below 1`),(!L(n.maxQuantile)||n.maxQuantile<=0||n.maxQuantile>=1)&&I(`adapt.maxQuantile must be a number above 0 and below 1`),R(n.pauseMs)||I(`adapt.pauseMs must be a positive, finite number of milliseconds`),n}function B(e){if(e===void 0||e===!1)return;e!==!0&&(typeof e!=`object`||!e||Array.isArray(e))&&I(`cacheAware must be true or an object`);let t=e===!0?{}:e,n={ttlMs:t.ttlMs??3e5,prefixChars:t.prefixChars??2e3,action:t.action??`delay`,delayFactor:t.delayFactor??2};return R(n.ttlMs)||I(`cacheAware.ttlMs must be a positive, finite number of milliseconds`),(!Number.isInteger(n.prefixChars)||n.prefixChars<1)&&I(`cacheAware.prefixChars must be an integer of at least 1`),n.action!==`delay`&&n.action!==`skip`&&I(`cacheAware.action must be 'delay' or 'skip'`),(!L(n.delayFactor)||n.delayFactor<=1)&&I(`cacheAware.delayFactor must be a number above 1`),n}function V(e){return e===void 0?{kind:`quantile`,quantile:.95,minMs:0,fallbackMs:void 0}:typeof e==`function`?{kind:`function`,fn:e}:typeof e==`number`?(R(e)||I(`delay must be a positive, finite number of milliseconds`),{kind:`fixed`,ms:e}):((typeof e!=`object`||!e||Array.isArray(e))&&I(`delay must be a number, a function or { quantile, minMs?, fallbackMs? }`),(!L(e.quantile)||e.quantile<=0||e.quantile>=1)&&I(`delay.quantile must be a number above 0 and below 1`),e.minMs!==void 0&&(!L(e.minMs)||e.minMs<0)&&I(`delay.minMs must be a number of at least 0`),e.fallbackMs!==void 0&&!R(e.fallbackMs)&&I(`delay.fallbackMs must be a positive, finite number of milliseconds`),{kind:`quantile`,quantile:e.quantile,minMs:e.minMs??0,fallbackMs:e.fallbackMs})}function H(e){e===void 0||typeof e==`object`&&e||I(`budget must be an object: { ratio?, burst? }`);let t=e?.ratio??.05,n=e?.burst??10;return(!L(t)||t<=0||t>1)&&I(`budget.ratio must be a number above 0 and at most 1`),(!L(n)||n<1)&&I(`budget.burst must be a number of at least 1`),{ratio:t,burst:n}}function U(e={}){let t=V(e.delay);e.stats!==void 0&&typeof e.stats?.quantile!=`function`&&I(`stats must have a quantile function`),t.kind===`quantile`&&e.stats===void 0&&I(`stats must have a quantile function when delay is a quantile (pass logger.stats), or set delay to a number or a function`),e.targets!==void 0&&typeof e.targets!=`function`&&I(`targets must be a function`),e.when!==void 0&&typeof e.when!=`function`&&I(`when must be a function`);let n=e.maxHedges??1;(!Number.isInteger(n)||n<1||n>4)&&I(`maxHedges must be an integer from 1 to 4`);let r=e.statsTtlMs??1e3;return(!L(r)||r<0)&&I(`statsTtlMs must be a number of at least 0`),{stats:e.stats,delay:t,budget:H(e.budget),maxHedges:n,adapt:z(e.adapt),cacheAware:B(e.cacheAware),targets:e.targets??F,when:e.when??(()=>!0),statsTtlMs:r,logger:e.logger}}const W=t(`hedge`);function G(e={}){let t=U(e),r=x(t.logger),i=t.adapt?new p(t.adapt,t.delay.kind===`quantile`?t.delay.quantile:void 0):void 0,a={config:t,guard:r,budget:new m(t.budget.ratio,t.budget.burst),adapt:i,cacheAware:t.cacheAware?new _(t.cacheAware):void 0,delay:b(t,r,i),prefixKey:n(`hedge.prefix`)},o=S(a);return{name:`hedge`,ref:W,position:10,reruns:!0,...o&&{onEvent:o},wrap:P(a)}}function K(e){let t=e.state.get(T);if(t!==void 0)return{hedge:t.outcome,...t.delayMs!==void 0&&{hedge_delay_ms:t.delayMs}}}export{a as HEDGE_ADAPT_EVENT,i as HEDGE_DECISION_EVENT,f as hedgeDimension,K as hedgeLogFields,G as hedgeMiddleware,W as hedgeMiddlewareRef,F as nextTargets,d as promptSizeDimension};
2
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.mjs","names":["MAX_ENTRIES","HEDGE_DECISION_EVENT","HEDGE_ADAPT_EVENT","EDGES","NAMES","bucketOfTokens","index","edge","promptTokens","bucketOfRequest","prompt","defaultEstimateTokens","request","OUTCOMES","promptSizeDimension","tokens","record","hedgeDimension","events","last","outcome","Adapt","config","baseQuantile","now","target","boost","base","state","won","hedges","winRate","next","HedgeBudget","ratio","burst","hash","h1","h2","i","text","code","textOf","content","block","CacheAware","message","id","key","at","StatsCache","source","ttlMs","filter","a","b","q","hit","value","usable","createDelay","delay","ms","guard","ctx","cache","first","mode","quantile","adapt","learned","\"size\"","error","createGuard","logger","option","ConsoleLogger","warned","report","operation","fn","fallback","createCacheEvent","cacheAware","event","prefixKey","usableTargets","names","allowed","name","plan","hasAgain","targets","callStart","warm","delayMs","hedgeDecisionKey","createStateKey","hedgesOf","sent","decision","raceDelay","signal","resolve","finish","timer","onAbort","main","firstSuccess","failures","mainError","chains","chain","winner","reject","untilAnswered","sendHedges","anySettled","hedge","whyAlone","rest","again","budget","settleRace","sentCount","raced","adapted","createWrap","deps","alone","chosen","nextTargets","invalid","isNumber","isPositive","resolveAdapt","given","resolved","resolveCacheAware","resolveDelay","resolveBudget","normalizeOptions","options","maxHedges","statsTtlMs","hedgeMiddlewareRef","createMiddlewareRef","hedgeMiddleware","onEvent","hedgeLogFields"],"sources":["../src/events.ts","../src/internal/size.utils.ts","../src/dimensions.ts","../src/internal/adapt.ts","../src/internal/budget.ts","../src/internal/cacheAware.ts","../src/internal/delay/statsCache.ts","../src/internal/delay/delay.utils.ts","../src/internal/guard.utils.ts","../src/internal/middleware/cacheEvent.utils.ts","../src/internal/middleware/usableTargets.utils.ts","../src/internal/middleware/plan.utils.ts","../src/stateKeys.ts","../src/internal/middleware/record.utils.ts","../src/internal/race.utils.ts","../src/internal/middleware/sendHedges.utils.ts","../src/internal/middleware/settleRace.utils.ts","../src/internal/middleware/wrap.utils.ts","../src/targets.ts","../src/internal/options/validate.utils.ts","../src/internal/options/resolveAdapt.utils.ts","../src/internal/options/resolveCacheAware.utils.ts","../src/internal/options/resolveDelay.utils.ts","../src/internal/options/normalizeOptions.utils.ts","../src/hedgeMiddleware.ts","../src/logFields.ts"],"sourcesContent":["/** Name of the custom event carrying each call's decision. */\nexport const HEDGE_DECISION_EVENT = 'hedge.decision';\n\n/** Name of the custom event for a change the win rate caused. */\nexport const HEDGE_ADAPT_EVENT = 'hedge.adapt';\n","import { defaultEstimateTokens, type WireCallRequest } from 'vern-llm';\n\n/**\n * The logger dimension the hedge reads for a prompt's size. Register it under this name:\n * `dimensions: { size: promptSizeDimension }`.\n */\nexport const SIZE_DIMENSION = 'size';\n\nexport type SizeBucket = 'xs' | 's' | 'm' | 'l' | 'xl';\n\n/** Upper edges, in prompt tokens: under 1k, 4k, 16k and 64k, and everything above. */\nconst EDGES = [1_000, 4_000, 16_000, 64_000] as const;\nconst NAMES = ['xs', 's', 'm', 'l'] as const;\n\nexport function bucketOfTokens(promptTokens: number): SizeBucket {\n for (const [index, edge] of EDGES.entries()) {\n if (promptTokens < edge) return NAMES[index]!;\n }\n return 'xl';\n}\n\n/**\n * The bucket of a request that has not been sent yet. It is an estimate, so a prompt near an edge\n * may land beside the bucket the logger later records it in. The core's estimate reserves `max_tokens`\n * for the answer, which the logger's recorded prompt tokens never include, so it is taken off.\n */\nexport function bucketOfRequest(request: Readonly<WireCallRequest>): SizeBucket {\n const prompt = defaultEstimateTokens(request) - (request.max_tokens ?? 0);\n return bucketOfTokens(Math.max(0, prompt));\n}\n","import { HEDGE_DECISION_EVENT } from './events.js';\nimport { bucketOfTokens, type SizeBucket } from './internal/size.utils.js';\n\nimport type { HedgeOutcome } from './types/index.js';\n\n/** The part of the logger's call record these read, so no import of the logger is needed. */\ninterface CallRecord {\n usage?: { promptTokens?: number };\n custom?: Record<string, unknown[]>;\n}\n\nconst OUTCOMES: ReadonlySet<unknown> = new Set<HedgeOutcome>([\n 'won',\n 'lost',\n 'not_needed',\n 'failed',\n 'aborted',\n 'budget',\n 'unavailable',\n 'no_target',\n 'not_allowed',\n 'paused',\n 'cached',\n 'no_data',\n 'deadline',\n]);\n\n/**\n * The prompt's size as a logger dimension, so a long prompt's slow first token never sets the\n * delay for a short one: `dimensions: { size: promptSizeDimension }`. Buckets by powers of four\n * from 1 000 prompt tokens: `xs` under 1k, `s` under 4k, `m` under 16k, `l` under 64k and `xl`\n * above. The hedge reads it under the name `size`. A record with no usage yields nothing.\n */\nexport const promptSizeDimension = (record: CallRecord): SizeBucket | undefined => {\n const tokens = record.usage?.promptTokens;\n return typeof tokens === 'number' && Number.isFinite(tokens) && tokens >= 0\n ? bucketOfTokens(tokens)\n : undefined;\n};\n\n/**\n * How the call's hedge went, as a logger dimension: `dimensions: { hedge: hedgeDimension }`\n * splits the logger's counts and percentiles by `won`, `lost`, `not_needed`, `failed`, `aborted`, `budget` and the skip\n * reasons. It reads the call's last `hedge.decision`, so the logger must keep `hedge.*` events\n * (`customEvents: ['hedge.*']`). A call with no decision yields nothing.\n */\nexport const hedgeDimension = (record: CallRecord): HedgeOutcome | undefined => {\n const events = record.custom?.[HEDGE_DECISION_EVENT];\n const last = events?.[events.length - 1];\n if (typeof last !== 'object' || last === null) return undefined;\n\n const outcome = (last as { outcome?: unknown }).outcome;\n return OUTCOMES.has(outcome) ? (outcome as HedgeOutcome) : undefined;\n};\n","import type { AdaptEvent, ResolvedAdapt } from '../types/index.js';\n\ninterface TargetState {\n /** The last `window` hedged calls of this first target: whether the hedge won. */\n results: boolean[];\n /** Added to the base quantile. */\n boost: number;\n pausedUntil: number;\n /** Whether `slow` was reported, until the win rate falls back. */\n slow: boolean;\n /** Hedged calls since `slow` was last reported, still above the high win rate. */\n sinceSlow: number;\n}\n\n/**\n * Watches how often a first target's hedges win, and answers in two ways. Rarely winning means\n * the delay is too short for that target: wait for a higher quantile, then stop hedging it for a\n * while. Often winning means the first target is the problem, which is reported, and again every\n * `minHedges` hedged calls while it stays so.\n */\nexport class Adapt {\n private readonly targets = new Map<string, TargetState>();\n\n /**\n * @param baseQuantile the configured quantile, or `undefined` when the delay is a number or a\n * function, which has no quantile to raise.\n */\n constructor(\n private readonly config: ResolvedAdapt,\n private readonly baseQuantile: number | undefined,\n ) {}\n\n /** Whether this target is out of hedging for now. */\n paused(target: string, now: number): boolean {\n return now < (this.targets.get(target)?.pausedUntil ?? 0);\n }\n\n /** The quantile to wait for on this target: the base, raised by what the win rate asked for. */\n quantileFor(target: string, base: number): number {\n const boost = this.targets.get(target)?.boost ?? 0;\n return Math.min(base + boost, this.ceiling(base));\n }\n\n /** The highest quantile a target may be raised to, never below the one configured. */\n private ceiling(base: number): number {\n return Math.max(base, this.config.maxQuantile);\n }\n\n /** Counts a hedged call. Returns the one change it caused, if any. */\n record(target: string, won: boolean, now: number): AdaptEvent | undefined {\n let state = this.targets.get(target);\n if (!state) {\n state = { results: [], boost: 0, pausedUntil: 0, slow: false, sinceSlow: 0 };\n this.targets.set(target, state);\n }\n\n state.results.push(won);\n if (state.results.length > this.config.window) state.results.shift();\n if (state.results.length < this.config.minHedges) return undefined;\n\n const hedges = state.results.length;\n const winRate = state.results.filter(Boolean).length / hedges;\n\n if (winRate < this.config.lowWinRate) {\n state.slow = false;\n state.results = [];\n\n const base = this.baseQuantile;\n // The quantile after one more step, held at the configured ceiling. With nothing to raise,\n // or no room left, stop hedging this target for a while and start over afterwards.\n const next =\n base === undefined\n ? undefined\n : Math.min(base + state.boost + this.config.quantileStep, this.ceiling(base));\n if (base === undefined || next === undefined || next <= base + state.boost) {\n state.boost = 0;\n state.pausedUntil = now + this.config.pauseMs;\n return { action: 'pause', target, winRate, hedges };\n }\n\n state.boost = next - base;\n return { action: 'raise_quantile', target, winRate, hedges, quantile: next };\n }\n\n if (winRate > this.config.highWinRate) {\n // Reported again every `minHedges` hedged calls while it lasts, since whoever listens may\n // hold the target back only for a while, and the target is first again once that ends.\n if (state.slow && ++state.sinceSlow < this.config.minHedges) return undefined;\n state.slow = true;\n state.sinceSlow = 0;\n return { action: 'slow', target, winRate, hedges };\n }\n\n state.slow = false;\n return undefined;\n }\n}\n","/** Absorbs the float error of adding `ratio` many times, so 20 calls at 0.05 make one token. */\nconst EPSILON = 1e-9;\n\n/**\n * A token bucket that eligible calls refill and hedges drain. It starts empty, so hedging never\n * exceeds about `ratio` of calls, even when every call is slow, and a cold start never hedges.\n */\nexport class HedgeBudget {\n private tokens = 0;\n\n constructor(\n private readonly ratio: number,\n private readonly burst: number,\n ) {}\n\n /** One eligible call: adds `ratio` tokens, up to `burst`. */\n add(): void {\n this.tokens = Math.min(this.burst, this.tokens + this.ratio);\n }\n\n /** Gives back a token that was taken for a hedge that could not be sent. */\n refund(): void {\n this.tokens = Math.min(this.burst, this.tokens + 1);\n }\n\n /** Takes one token if there is one. */\n take(): boolean {\n if (this.tokens < 1 - EPSILON) return false;\n this.tokens = Math.max(0, this.tokens - 1);\n return true;\n }\n}\n","import type { ResolvedCacheAware } from '../types/index.js';\nimport type { WireCallRequest } from 'vern-llm';\n\n/** Most remembered prefixes. The oldest are dropped first. */\nconst MAX_ENTRIES = 10_000;\n\n/** A fast, non cryptographic 53 bit hash. A collision only changes a heuristic. */\nfunction hash(text: string): string {\n let h1 = 0xdeadbeef;\n let h2 = 0x41c6ce57;\n for (let i = 0; i < text.length; i++) {\n const code = text.charCodeAt(i);\n h1 = Math.imul(h1 ^ code, 2654435761);\n h2 = Math.imul(h2 ^ code, 1597334677);\n }\n h1 = Math.imul(h1 ^ (h1 >>> 16), 2246822507) ^ Math.imul(h2 ^ (h2 >>> 13), 3266489909);\n h2 = Math.imul(h2 ^ (h2 >>> 16), 2246822507) ^ Math.imul(h1 ^ (h1 >>> 13), 3266489909);\n return (4294967296 * (2097151 & h2) + (h1 >>> 0)).toString(36);\n}\n\n/** The text of one message, whatever its content shape. Anything that is not text is skipped. */\nfunction textOf(content: unknown): string {\n if (typeof content === 'string') return content;\n if (!Array.isArray(content)) return '';\n return content\n .map((block: unknown) => {\n const text = (block as { text?: unknown } | null)?.text;\n return typeof text === 'string' ? text : '';\n })\n .join('');\n}\n\n/**\n * Remembers which targets recently served which prompt prefix from their cache. A provider's\n * prompt cache only helps a request that starts the same way, so the prefix is what is keyed.\n */\nexport class CacheAware {\n /** `target|prefix` to when the target last served that prefix from its cache. */\n private readonly seen = new Map<string, number>();\n\n constructor(private readonly config: ResolvedCacheAware) {}\n\n /** A key for how the request starts: the same for requests sharing their first characters. */\n keyOf(request: Readonly<WireCallRequest>): string {\n let text = '';\n for (const message of request.messages) {\n text += `${message.role}:${textOf(message.content)}\\n`;\n if (text.length >= this.config.prefixChars) break;\n }\n return hash(text.slice(0, this.config.prefixChars));\n }\n\n /** The target served this prefix from its cache just now. */\n remember(target: string, key: string, now: number): void {\n const id = `${target}|${key}`;\n // Deleted first, so a refreshed entry moves to the end and the oldest is dropped first.\n this.seen.delete(id);\n this.seen.set(id, now);\n if (this.seen.size > MAX_ENTRIES) this.seen.delete(this.seen.keys().next().value!);\n }\n\n /** Whether the target served this prefix from its cache within `ttlMs`. */\n warm(target: string, key: string, now: number): boolean {\n const at = this.seen.get(`${target}|${key}`);\n return at !== undefined && now - at >= 0 && now - at < this.config.ttlMs;\n }\n\n get size(): number {\n return this.seen.size;\n }\n}\n","import type { StatsSource } from '../../types/index.js';\n\n/** Most distinct reads kept. A chain has a handful of targets, so this is only a safety cap. */\nconst MAX_ENTRIES = 512;\n\n/** The one metric that means \"time to the first answer\" for plain and streamed calls alike. */\nconst METRIC = 'attemptMs';\n\ninterface Entry {\n at: number;\n value: number | undefined;\n}\n\n/**\n * Reuses stats reads for `ttlMs`, which keeps the logger's quantile merge off the hot path.\n * A read that throws is not cached, so it is tried again on the next call.\n */\nexport class StatsCache {\n private readonly entries = new Map<string, Entry>();\n\n constructor(\n private readonly source: StatsSource,\n private readonly ttlMs: number,\n ) {}\n\n quantile(filter: Readonly<Record<string, string>>, q: number, now: number): number | undefined {\n const key = `${JSON.stringify(Object.entries(filter).sort(([a], [b]) => (a < b ? -1 : 1)))}|${q}`;\n\n if (this.ttlMs > 0) {\n const hit = this.entries.get(key);\n if (hit && now - hit.at >= 0 && now - hit.at < this.ttlMs) return hit.value;\n }\n\n const value = this.source.quantile(filter, q, METRIC);\n if (this.ttlMs > 0) {\n if (this.entries.size >= MAX_ENTRIES) this.entries.clear();\n this.entries.set(key, { at: now, value });\n }\n return value;\n }\n\n get size(): number {\n return this.entries.size;\n }\n}\n","import { bucketOfRequest, SIZE_DIMENSION } from '../size.utils.js';\nimport { StatsCache } from './statsCache.js';\n\nimport type { ResolvedConfig } from '../../types/index.js';\nimport type { Adapt } from '../adapt.js';\nimport type { Guard } from '../guard.utils.js';\nimport type { PreDispatchContext, WireCallRequest } from 'vern-llm';\n\n/** How long to wait before hedging this call, or `undefined` when there is no basis for one. */\nexport interface Delay {\n for(ctx: PreDispatchContext, request: Readonly<WireCallRequest>): number | undefined;\n}\n\nfunction usable(value: unknown): value is number {\n return typeof value === 'number' && Number.isFinite(value) && value > 0;\n}\n\nexport function createDelay(config: ResolvedConfig, guard: Guard, adapt?: Adapt): Delay {\n const { delay } = config;\n\n if (delay.kind === 'fixed') return { for: () => delay.ms };\n\n if (delay.kind === 'function') {\n return {\n for(ctx, request) {\n const ms = guard('delay', () => delay.fn(ctx, request), undefined);\n if (ms === undefined) return undefined;\n if (usable(ms)) return ms;\n guard.warnOnce(\n 'delay-value',\n 'delay returned a value that is not a positive, finite number',\n );\n return undefined;\n },\n };\n }\n\n const cache = new StatsCache(config.stats!, config.statsTtlMs);\n\n return {\n for(ctx, request) {\n const first = ctx.targets[0]?.name;\n if (first === undefined) return undefined;\n\n // The target's own series for this stream mode, narrowed to the prompt's size bucket when\n // the logger records `promptSizeDimension`. A bucket below `minSamples` reads as unknown.\n const mode = { provider: first, streaming: String(ctx.streaming === true) };\n const quantile = adapt ? adapt.quantileFor(first, delay.quantile) : delay.quantile;\n const now = Date.now();\n\n let learned: number | undefined;\n try {\n learned =\n cache.quantile({ ...mode, [SIZE_DIMENSION]: bucketOfRequest(request) }, quantile, now) ??\n cache.quantile(mode, quantile, now);\n } catch (error) {\n guard.warnOnce(\n 'stats',\n `stats.quantile threw, so calls without a fallbackMs are not hedged: ${\n error instanceof Error ? error.message : String(error)\n }`,\n );\n }\n\n const ms = usable(learned) ? learned : delay.fallbackMs;\n return ms === undefined ? undefined : Math.max(ms, delay.minMs);\n },\n };\n}\n","import { ConsoleLogger, type Logger } from 'vern-llm';\n\nimport type { LoggerOption } from '../types/index.js';\n\n/** Runs `fn`, and returns `fallback` after logging if it throws. */\nexport interface Guard {\n <T>(operation: string, fn: () => T, fallback: T): T;\n /** Logs a failure in the same shape without running anything. */\n report(operation: string, error: unknown): void;\n /** Logs a configuration warning, not a failure. */\n warn(message: string): void;\n /** Like `warn`, but each `key` only once for the life of the hedge. */\n warnOnce(key: string, message: string): void;\n}\n\nexport function createGuard(option: LoggerOption | undefined): Guard {\n const logger: Logger | undefined =\n option === 'silent' ? undefined : (option ?? new ConsoleLogger(false));\n const warned = new Set<string>();\n\n const report = (operation: string, error: unknown): void => {\n if (!logger) return;\n\n try {\n logger.error(`[VernLLM] hedge: ${operation} failed`, {\n message: error instanceof Error ? error.message : String(error),\n stack: error instanceof Error ? error.stack : undefined,\n });\n } catch {\n // A logger that throws must not be able to break a call.\n }\n };\n\n const guard = (<T>(operation: string, fn: () => T, fallback: T): T => {\n try {\n return fn();\n } catch (error) {\n report(operation, error);\n return fallback;\n }\n }) as Guard;\n\n guard.report = report;\n guard.warn = (message) => {\n if (!logger) return;\n try {\n logger.warn(`[VernLLM] hedge: ${message}`);\n } catch {\n // Same as `report`: a throwing logger must not break a call.\n }\n };\n guard.warnOnce = (key, message) => {\n if (warned.has(key)) return;\n warned.add(key);\n guard.warn(message);\n };\n return guard;\n}\n","import type { HedgeDeps } from './deps.js';\nimport type { MiddlewareContext, VernLLMEvent, VernLLMMiddleware } from 'vern-llm';\n\ntype OnEvent = NonNullable<VernLLMMiddleware['onEvent']>;\n\n/**\n * The `onEvent` hook of cache awareness: a target that reports cache reads has this call's prefix\n * in its cache for a while. `undefined` when cache awareness is off, so no hook is registered.\n */\nexport function createCacheEvent({ cacheAware, guard, prefixKey }: HedgeDeps): OnEvent | undefined {\n if (!cacheAware) return undefined;\n\n return (event: VernLLMEvent, ctx: MiddlewareContext) =>\n guard(\n 'cacheEvent',\n () => {\n if (event.kind !== 'usage' || !((event.usage.cacheReadTokens ?? 0) > 0)) return;\n const key = ctx.state.get(prefixKey);\n if (key !== undefined) {\n cacheAware.remember(event.usage.provider ?? 'primary', key, Date.now());\n }\n },\n undefined,\n );\n}\n","import type { Guard } from '../guard.utils.js';\nimport type { PreDispatchContext } from 'vern-llm';\n\n/**\n * The names `targets` returned that the call may actually use: strings the order contains, each\n * once. The core rejects an unknown or repeated name, which would spend a budget token on a chain\n * that fails at once.\n */\nexport function usableTargets(ctx: PreDispatchContext, names: unknown, guard: Guard): string[] {\n if (!Array.isArray(names)) {\n guard.warnOnce('targets-shape', 'targets must return an array of target names');\n return [];\n }\n\n const allowed = new Set(ctx.targets.map((target) => target.name));\n return [...new Set(names.filter((name): name is string => allowed.has(name)))];\n}\n","import { usableTargets } from './usableTargets.utils.js';\n\nimport type { SkipReason } from '../../types/index.js';\nimport type { HedgeDeps } from './deps.js';\nimport type { PreDispatchContext, WireCallRequest } from 'vern-llm';\n\n/** Whether to run the call as it is, and why, or to race it. */\nexport type Plan =\n | { kind: 'skip'; outcome: SkipReason; delayMs?: number }\n | { kind: 'hedge'; targets: string[]; first: string; delayMs: number };\n\n/**\n * Decides before anything is sent whether a call may be hedged, and how long to wait. The order\n * of the checks is the order of the outcomes: the first one that says no is reported.\n */\nexport function plan(\n { config, guard, adapt, cacheAware, delay, prefixKey }: HedgeDeps,\n request: Readonly<WireCallRequest>,\n ctx: PreDispatchContext,\n hasAgain: boolean,\n callStart: number,\n): Plan {\n if (!hasAgain) return { kind: 'skip', outcome: 'unavailable' };\n\n const targets = guard(\n 'targets',\n () => usableTargets(ctx, config.targets(ctx, request), guard),\n [],\n );\n if (targets.length === 0) return { kind: 'skip', outcome: 'no_target' };\n\n if (!guard('when', () => Boolean(config.when(ctx)), false)) {\n return { kind: 'skip', outcome: 'not_allowed' };\n }\n\n // The first target is the one the call is sent to, and the one whose hedges are counted.\n const first = ctx.targets[0]!.name;\n if (adapt?.paused(first, callStart)) return { kind: 'skip', outcome: 'paused' };\n\n // A target holding this prefix in its cache answers cheaply and soon; a hedge elsewhere would\n // start cold. The prefix is kept for the usage events of this call.\n let warm = false;\n if (cacheAware) {\n const key = guard('cacheKey', () => cacheAware.keyOf(request), undefined);\n if (key !== undefined) {\n guard('cacheState', () => ctx.state.set(prefixKey, key), undefined);\n warm = cacheAware.warm(first, key, callStart);\n }\n }\n if (warm && config.cacheAware!.action === 'skip') return { kind: 'skip', outcome: 'cached' };\n\n const learned = guard('delay', () => delay.for(ctx, request), undefined);\n if (learned === undefined) return { kind: 'skip', outcome: 'no_data' };\n const delayMs = warm ? learned * config.cacheAware!.delayFactor : learned;\n\n if (ctx.deadlineAt !== undefined && callStart + delayMs >= ctx.deadlineAt) {\n return { kind: 'skip', outcome: 'deadline', delayMs };\n }\n\n return { kind: 'hedge', targets, first, delayMs };\n}\n","import { createStateKey } from 'vern-llm';\n\nimport type { HedgeDecisionEvent } from './types/index.js';\n\n/**\n * Module level on purpose: the hedge writes it and `hedgeLogFields` reads it without being handed\n * the instance, the way the other extensions' fields travel.\n */\nexport const hedgeDecisionKey = createStateKey<HedgeDecisionEvent>('hedge.decision');\n","import { HEDGE_DECISION_EVENT } from '../../events.js';\nimport { hedgeDecisionKey } from '../../stateKeys.js';\n\nimport type { HedgeDecisionEvent } from '../../types/index.js';\nimport type { Guard } from '../guard.utils.js';\nimport type { JsonValue, PreDispatchContext } from 'vern-llm';\n\n/** What a decision carries besides `v`, so the call site names only what it knows. */\nexport type Decision = Omit<HedgeDecisionEvent, 'v'>;\n\n/** The hedge count for the event, only when it is more than one. */\nexport const hedgesOf = (sent: number): Pick<Decision, 'hedges'> =>\n sent > 1 ? { hedges: sent } : {};\n\n/** Writes the decision where the log fields and the logger read it, once, and never throws. */\nexport function record(ctx: PreDispatchContext, guard: Guard, decision: Decision): void {\n const event: HedgeDecisionEvent = {\n v: 1,\n outcome: decision.outcome,\n ...(decision.delayMs !== undefined && { delayMs: Math.round(decision.delayMs) }),\n ...(decision.target !== undefined && { target: decision.target }),\n ...(decision.hedges !== undefined && { hedges: decision.hedges }),\n ...(decision.winner !== undefined && { winner: decision.winner }),\n ...(decision.ms !== undefined && { ms: Math.round(decision.ms) }),\n };\n\n guard('state', () => ctx.state.set(hedgeDecisionKey, event), undefined);\n guard('emit', () => ctx.emit(HEDGE_DECISION_EVENT, event as unknown as JsonValue), undefined);\n}\n","import type { CallResult } from 'vern-llm';\n\n/** The longest delay `setTimeout` takes; a larger one fires at once. */\nconst MAX_TIMER_MS = 2_147_483_647;\n\n/**\n * Waits for `delayMs`, or for `main` to settle either way, or for `signal` to abort, whichever\n * comes first. `'settled'` covers the last two, since neither leaves anything to hedge. The timer\n * and the abort listener are always cleaned up.\n */\nexport function raceDelay(\n main: Promise<unknown>,\n delayMs: number,\n signal: AbortSignal | undefined,\n): Promise<'settled' | 'elapsed'> {\n return new Promise((resolve) => {\n if (signal?.aborted) {\n resolve('settled');\n return;\n }\n\n const finish = (outcome: 'settled' | 'elapsed'): void => {\n clearTimeout(timer);\n signal?.removeEventListener('abort', onAbort);\n resolve(outcome);\n };\n const onAbort = (): void => finish('settled');\n const timer = setTimeout(() => finish('elapsed'), Math.min(delayMs, MAX_TIMER_MS));\n\n signal?.addEventListener('abort', onAbort, { once: true });\n main.then(\n () => finish('settled'),\n () => finish('settled'),\n );\n });\n}\n\n/** The chain whose result was kept: `0` for the main chain, `1` and up for the hedges, in order. */\nexport interface Raced {\n result: CallResult;\n winner: number;\n}\n\n/**\n * The first chain to succeed, the main chain first in `chains`. A chain that fails leaves the\n * others to answer; when all fail, the main chain's error is the one thrown. The result is exactly\n * what the winner produced, so the core can match it to its chain and cancel the others. When\n * several have already answered, the lowest index wins.\n */\nexport function firstSuccess(chains: readonly Promise<CallResult>[]): Promise<Raced> {\n return new Promise((resolve, reject) => {\n let failures = 0;\n let mainError: unknown;\n\n chains.forEach((chain, winner) => {\n chain.then(\n (result) => resolve({ result, winner }),\n (error: unknown) => {\n if (winner === 0) mainError = error;\n failures++;\n if (failures === chains.length) reject(mainError);\n },\n );\n });\n });\n}\n","import { raceDelay } from '../race.utils.js';\n\nimport type { HedgeDeps } from './deps.js';\nimport type { Decision } from './record.utils.js';\nimport type { CallResult, PreDispatchContext, WrapNext } from 'vern-llm';\n\n/** Chains to race, or why the main chain alone is kept. */\nexport type Sent =\n | { kind: 'alone'; outcome: Decision['outcome'] }\n | { kind: 'raced'; chains: Promise<CallResult>[] };\n\n/** Settles when the chain answers, and never when it fails. */\nconst untilAnswered = (chain: Promise<CallResult>): Promise<unknown> =>\n chain.catch(() => new Promise<never>(() => {}));\n\n/**\n * One iteration per hedge. The first waits for the delay the main chain has been running; each\n * later one waits another delay, and none is sent once a chain has settled.\n */\nexport async function sendHedges(\n { config, budget }: HedgeDeps,\n ctx: PreDispatchContext,\n next: WrapNext,\n main: Promise<CallResult>,\n targets: readonly string[],\n delayMs: number,\n): Promise<Sent> {\n const chains: Promise<CallResult>[] = [main];\n let anySettled: Promise<unknown> = main;\n\n for (let hedge = 1; hedge <= config.maxHedges; hedge++) {\n if ((await raceDelay(anySettled, delayMs, ctx.signal)) === 'settled') {\n if (hedge > 1) break;\n return { kind: 'alone', outcome: await whyAlone(ctx, main) };\n }\n\n // Each hedge goes one name further along `targets` and keeps the rest as its fallback.\n const rest = targets.slice(hedge - 1);\n if (rest.length === 0) break;\n\n // Read again: other layers may have used up the call's reruns while the delay ran.\n const again = next.again;\n if (!again) {\n if (hedge === 1) return { kind: 'alone', outcome: 'unavailable' };\n break;\n }\n\n // The token is taken when the delay passes, so calls that answer in time cost nothing.\n if (!budget.take()) {\n if (hedge === 1) return { kind: 'alone', outcome: 'budget' };\n break;\n }\n\n let sent: Promise<CallResult>;\n try {\n sent = again({ targets: rest });\n } catch (error) {\n budget.refund();\n sent = Promise.reject(error);\n }\n chains.push(sent);\n // A hedge that fails is not a reason to stop: only the main chain settling, or any chain\n // answering, ends the wait.\n if (hedge < config.maxHedges) {\n anySettled = Promise.race([main, ...chains.slice(1).map(untilAnswered)]);\n }\n }\n\n return { kind: 'raced', chains };\n}\n\n/** Why the main chain ended before the delay did. */\nasync function whyAlone(\n ctx: PreDispatchContext,\n main: Promise<CallResult>,\n): Promise<Decision['outcome']> {\n if (ctx.signal?.aborted) return 'aborted';\n return main.then(\n () => 'not_needed' as const,\n () => 'failed' as const,\n );\n}\n","import { HEDGE_ADAPT_EVENT } from '../../events.js';\nimport { firstSuccess, type Raced } from '../race.utils.js';\nimport { hedgesOf, type Decision } from './record.utils.js';\n\nimport type { HedgeDeps } from './deps.js';\nimport type { CallResult, JsonValue, PreDispatchContext } from 'vern-llm';\n\n/**\n * Keeps the first chain to succeed, counts the call for the win rate, and reports it. When every\n * chain failed, the main chain's error is thrown, as if no hedge had been sent.\n */\nexport async function settleRace(\n { adapt, guard }: HedgeDeps,\n ctx: PreDispatchContext,\n report: (decision: Decision) => void,\n chains: readonly Promise<CallResult>[],\n targets: readonly string[],\n first: string,\n delayMs: number,\n callStart: number,\n): Promise<CallResult> {\n const sentCount = chains.length - 1;\n let raced: Raced;\n try {\n raced = await firstSuccess(chains);\n } catch (error) {\n report({ outcome: 'lost', delayMs, target: targets[0], ...hedgesOf(sentCount) });\n throw error;\n }\n\n // A call where every chain failed says nothing about whether the hedge helps, so only calls\n // that were answered count.\n const adapted = adapt?.record(first, raced.winner > 0, Date.now());\n if (adapted) {\n guard('adapt', () => ctx.emit(HEDGE_ADAPT_EVENT, adapted as unknown as JsonValue), undefined);\n }\n\n report({\n outcome: raced.winner > 0 ? 'won' : 'lost',\n delayMs,\n target: targets[0],\n ms: Date.now() - callStart,\n ...hedgesOf(sentCount),\n // The winning hedge's first target; its fallback may be the one that answered.\n ...(sentCount > 1 && raced.winner > 0 && { winner: targets[raced.winner - 1] }),\n });\n // Unchanged, so the core finds the chain it came from and cancels the others.\n return raced.result;\n}\n","import { plan } from './plan.utils.js';\nimport { record, type Decision } from './record.utils.js';\nimport { sendHedges } from './sendHedges.utils.js';\nimport { settleRace } from './settleRace.utils.js';\n\nimport type { HedgeDeps } from './deps.js';\nimport type { CallResult, VernLLMMiddleware } from 'vern-llm';\n\ntype Wrap = NonNullable<VernLLMMiddleware['wrap']>;\n\n/**\n * The `wrap` hook: plan, run the call, send the hedges that are due, and keep the first answer.\n * Every path ends in exactly one report, so a call has one decision.\n */\nexport function createWrap(deps: HedgeDeps): Wrap {\n const { guard, budget } = deps;\n\n return async (request, next, ctx) => {\n const callStart = Date.now();\n const report = (decision: Decision): void => record(ctx, guard, decision);\n\n // Runs the one chain as if the hedge were not there, and reports why once it settles. The\n // chain's own result or error passes through unchanged.\n const alone = (\n outcome: Decision['outcome'],\n chain: Promise<CallResult>,\n delayMs?: number,\n ): Promise<CallResult> =>\n chain.finally(() => report({ outcome, ...(delayMs !== undefined && { delayMs }) }));\n\n const chosen = plan(deps, request, ctx, next.again !== undefined, callStart);\n if (chosen.kind === 'skip') return alone(chosen.outcome, next(), chosen.delayMs);\n const { targets, first, delayMs } = chosen;\n\n // Eligible calls only, so skipped calls never build up a burst.\n budget.add();\n\n const main = next();\n const sent = await sendHedges(deps, ctx, next, main, targets, delayMs);\n if (sent.kind === 'alone') return alone(sent.outcome, main, delayMs);\n\n return settleRace(deps, ctx, report, sent.chains, targets, first, delayMs, callStart);\n };\n}\n","import type { PreDispatchContext } from 'vern-llm';\n\n/**\n * The call's targets after the first, in order: where a hedge goes by default. Inside the router\n * that is the router's second choice, already narrowed by policy. The rest of the order stays\n * behind it, so the hedge has its own fallback.\n */\nexport function nextTargets(ctx: Pick<PreDispatchContext, 'targets'>): readonly string[] {\n return ctx.targets.slice(1).map((target) => target.name);\n}\n","export function invalid(message: string): never {\n throw new Error(`hedgeMiddleware: ${message}`);\n}\n\nexport function isNumber(value: unknown): value is number {\n return typeof value === 'number' && Number.isFinite(value);\n}\n\nexport function isPositive(value: unknown): value is number {\n return isNumber(value) && value > 0;\n}\n","import { invalid, isNumber, isPositive } from './validate.utils.js';\n\nimport type { AdaptOptions, HedgeOptions, ResolvedAdapt } from '../../types/index.js';\n\n/** Fills every default of the win rate adaptation and validates it. `undefined` when it is off. */\nexport function resolveAdapt(adapt: HedgeOptions['adapt']): ResolvedAdapt | undefined {\n if (adapt === undefined || adapt === false) return undefined;\n if (adapt !== true && (typeof adapt !== 'object' || adapt === null || Array.isArray(adapt))) {\n invalid('adapt must be true or an object');\n }\n\n const given: AdaptOptions = adapt === true ? {} : adapt;\n const resolved: ResolvedAdapt = {\n window: given.window ?? 50,\n minHedges: given.minHedges ?? 20,\n lowWinRate: given.lowWinRate ?? 0.1,\n highWinRate: given.highWinRate ?? 0.5,\n quantileStep: given.quantileStep ?? 0.02,\n maxQuantile: given.maxQuantile ?? 0.99,\n pauseMs: given.pauseMs ?? 300_000,\n };\n\n if (!Number.isInteger(resolved.window) || resolved.window < 1) {\n invalid('adapt.window must be an integer of at least 1');\n }\n if (\n !Number.isInteger(resolved.minHedges) ||\n resolved.minHedges < 1 ||\n resolved.minHedges > resolved.window\n ) {\n invalid('adapt.minHedges must be an integer from 1 to adapt.window');\n }\n if (!isNumber(resolved.lowWinRate) || resolved.lowWinRate < 0 || resolved.lowWinRate >= 1) {\n invalid('adapt.lowWinRate must be a number from 0 up to 1');\n }\n if (\n !isNumber(resolved.highWinRate) ||\n resolved.highWinRate <= resolved.lowWinRate ||\n resolved.highWinRate > 1\n ) {\n invalid('adapt.highWinRate must be a number above adapt.lowWinRate and at most 1');\n }\n if (\n !isNumber(resolved.quantileStep) ||\n resolved.quantileStep <= 0 ||\n resolved.quantileStep >= 1\n ) {\n invalid('adapt.quantileStep must be a number above 0 and below 1');\n }\n if (!isNumber(resolved.maxQuantile) || resolved.maxQuantile <= 0 || resolved.maxQuantile >= 1) {\n invalid('adapt.maxQuantile must be a number above 0 and below 1');\n }\n if (!isPositive(resolved.pauseMs)) {\n invalid('adapt.pauseMs must be a positive, finite number of milliseconds');\n }\n return resolved;\n}\n","import { invalid, isNumber, isPositive } from './validate.utils.js';\n\nimport type { CacheAwareOptions, HedgeOptions, ResolvedCacheAware } from '../../types/index.js';\n\n/** Fills every default of the cache awareness and validates it. `undefined` when it is off. */\nexport function resolveCacheAware(\n option: HedgeOptions['cacheAware'],\n): ResolvedCacheAware | undefined {\n if (option === undefined || option === false) return undefined;\n if (option !== true && (typeof option !== 'object' || option === null || Array.isArray(option))) {\n invalid('cacheAware must be true or an object');\n }\n\n const given: CacheAwareOptions = option === true ? {} : option;\n const resolved: ResolvedCacheAware = {\n ttlMs: given.ttlMs ?? 300_000,\n prefixChars: given.prefixChars ?? 2_000,\n action: given.action ?? 'delay',\n delayFactor: given.delayFactor ?? 2,\n };\n\n if (!isPositive(resolved.ttlMs)) {\n invalid('cacheAware.ttlMs must be a positive, finite number of milliseconds');\n }\n if (!Number.isInteger(resolved.prefixChars) || resolved.prefixChars < 1) {\n invalid('cacheAware.prefixChars must be an integer of at least 1');\n }\n if (resolved.action !== 'delay' && resolved.action !== 'skip') {\n invalid(\"cacheAware.action must be 'delay' or 'skip'\");\n }\n if (!isNumber(resolved.delayFactor) || resolved.delayFactor <= 1) {\n invalid('cacheAware.delayFactor must be a number above 1');\n }\n return resolved;\n}\n","import { invalid, isNumber, isPositive } from './validate.utils.js';\n\nimport type { HedgeOptions, ResolvedDelay } from '../../types/index.js';\n\n/** The delay as one of three shapes, with every default filled in. */\nexport function resolveDelay(delay: HedgeOptions['delay']): ResolvedDelay {\n if (delay === undefined)\n return { kind: 'quantile', quantile: 0.95, minMs: 0, fallbackMs: undefined };\n\n if (typeof delay === 'function') return { kind: 'function', fn: delay };\n\n if (typeof delay === 'number') {\n if (!isPositive(delay)) invalid('delay must be a positive, finite number of milliseconds');\n return { kind: 'fixed', ms: delay };\n }\n\n if (typeof delay !== 'object' || delay === null || Array.isArray(delay)) {\n invalid('delay must be a number, a function or { quantile, minMs?, fallbackMs? }');\n }\n\n if (!isNumber(delay.quantile) || delay.quantile <= 0 || delay.quantile >= 1) {\n invalid('delay.quantile must be a number above 0 and below 1');\n }\n if (delay.minMs !== undefined && (!isNumber(delay.minMs) || delay.minMs < 0)) {\n invalid('delay.minMs must be a number of at least 0');\n }\n if (delay.fallbackMs !== undefined && !isPositive(delay.fallbackMs)) {\n invalid('delay.fallbackMs must be a positive, finite number of milliseconds');\n }\n\n return {\n kind: 'quantile',\n quantile: delay.quantile,\n minMs: delay.minMs ?? 0,\n fallbackMs: delay.fallbackMs,\n };\n}\n","import { nextTargets } from '../../targets.js';\nimport { resolveAdapt } from './resolveAdapt.utils.js';\nimport { resolveCacheAware } from './resolveCacheAware.utils.js';\nimport { resolveDelay } from './resolveDelay.utils.js';\nimport { invalid, isNumber } from './validate.utils.js';\n\nimport type { HedgeOptions, ResolvedConfig } from '../../types/index.js';\n\nfunction resolveBudget(budget: HedgeOptions['budget']): ResolvedConfig['budget'] {\n if (budget !== undefined && (typeof budget !== 'object' || budget === null)) {\n invalid('budget must be an object: { ratio?, burst? }');\n }\n\n const ratio = budget?.ratio ?? 0.05;\n const burst = budget?.burst ?? 10;\n\n if (!isNumber(ratio) || ratio <= 0 || ratio > 1) {\n invalid('budget.ratio must be a number above 0 and at most 1');\n }\n if (!isNumber(burst) || burst < 1) invalid('budget.burst must be a number of at least 1');\n\n return { ratio, burst };\n}\n\n/** Validates the options once, so a mistake fails at construction instead of mid call. */\nexport function normalizeOptions(options: HedgeOptions = {}): ResolvedConfig {\n const delay = resolveDelay(options.delay);\n\n if (options.stats !== undefined && typeof options.stats?.quantile !== 'function') {\n invalid('stats must have a quantile function');\n }\n if (delay.kind === 'quantile' && options.stats === undefined) {\n invalid(\n 'stats must have a quantile function when delay is a quantile (pass logger.stats), ' +\n 'or set delay to a number or a function',\n );\n }\n\n if (options.targets !== undefined && typeof options.targets !== 'function') {\n invalid('targets must be a function');\n }\n if (options.when !== undefined && typeof options.when !== 'function') {\n invalid('when must be a function');\n }\n\n const maxHedges = options.maxHedges ?? 1;\n if (!Number.isInteger(maxHedges) || maxHedges < 1 || maxHedges > 4) {\n invalid('maxHedges must be an integer from 1 to 4');\n }\n\n const statsTtlMs = options.statsTtlMs ?? 1_000;\n if (!isNumber(statsTtlMs) || statsTtlMs < 0) {\n invalid('statsTtlMs must be a number of at least 0');\n }\n\n return {\n stats: options.stats,\n delay,\n budget: resolveBudget(options.budget),\n maxHedges,\n adapt: resolveAdapt(options.adapt),\n cacheAware: resolveCacheAware(options.cacheAware),\n targets: options.targets ?? nextTargets,\n when: options.when ?? (() => true),\n statsTtlMs,\n logger: options.logger,\n };\n}\n","import { createMiddlewareRef, createStateKey, type VernLLMMiddleware } from 'vern-llm';\n\nimport { Adapt } from './internal/adapt.js';\nimport { HedgeBudget } from './internal/budget.js';\nimport { CacheAware } from './internal/cacheAware.js';\nimport { createDelay } from './internal/delay/delay.utils.js';\nimport { createGuard } from './internal/guard.utils.js';\nimport { createCacheEvent } from './internal/middleware/cacheEvent.utils.js';\nimport { createWrap } from './internal/middleware/wrap.utils.js';\nimport { normalizeOptions } from './internal/options/normalizeOptions.utils.js';\n\nimport type { HedgeDeps } from './internal/middleware/deps.js';\nimport type { HedgeOptions } from './types/index.js';\n\n/** One module level ref, so registering two instances hits the core's duplicate ref check. */\nexport const hedgeMiddlewareRef = createMiddlewareRef('hedge');\n\n/**\n * When a call's first answer is slower than that target usually is, sends the same call to the\n * next target too, keeps whichever answers first, and lets the core cancel the other.\n */\nexport function hedgeMiddleware(options: HedgeOptions = {}): VernLLMMiddleware {\n const config = normalizeOptions(options);\n const guard = createGuard(config.logger);\n const adapt = config.adapt\n ? new Adapt(config.adapt, config.delay.kind === 'quantile' ? config.delay.quantile : undefined)\n : undefined;\n\n const deps: HedgeDeps = {\n config,\n guard,\n budget: new HedgeBudget(config.budget.ratio, config.budget.burst),\n adapt,\n cacheAware: config.cacheAware ? new CacheAware(config.cacheAware) : undefined,\n delay: createDelay(config, guard, adapt),\n prefixKey: createStateKey<string>('hedge.prefix'),\n };\n const onEvent = createCacheEvent(deps);\n\n return {\n name: 'hedge',\n ref: hedgeMiddlewareRef,\n\n // Inside the logger, guard, policy, usage and router, which should see one call, and outside\n // the attempts it races.\n position: 10,\n\n // Gives `wrap` `next.again()`, and has the core cancel the chain that is not returned.\n reruns: true,\n\n // A target that reports cache reads has this call's prefix in its cache for a while.\n ...(onEvent && { onEvent }),\n\n wrap: createWrap(deps),\n };\n}\n","import { hedgeDecisionKey } from './stateKeys.js';\n\nimport type { JsonValue, MiddlewareStateBag } from 'vern-llm';\n\n/** The part of a context the reader needs, so any hook's `ctx` fits. */\ninterface WithState {\n state: MiddlewareStateBag;\n}\n\n/**\n * Fields for a log record: `hedge` (the outcome) and `hedge_delay_ms` when a delay was computed,\n * such as `{ hedge: 'won', hedge_delay_ms: 840 }` or `{ hedge: 'budget' }`. `undefined` when the\n * hedge did not run. Pass it to the logger's `enrich` option.\n */\nexport function hedgeLogFields(ctx: WithState): Record<string, JsonValue> | undefined {\n const decision = ctx.state.get(hedgeDecisionKey);\n if (decision === undefined) return undefined;\n\n return {\n hedge: decision.outcome,\n ...(decision.delayMs !== undefined && { hedge_delay_ms: decision.delayMs }),\n };\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,85 @@
1
1
  {
2
2
  "name": "vern-llm-hedge",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "0.1.0",
4
+ "description": "Latency triggered hedging for vern-llm: when a target answers slower than usual, ask the next one too and keep the first answer",
5
+ "keywords": [
6
+ "vern-llm",
7
+ "hedge",
8
+ "hedging",
9
+ "tail-latency",
10
+ "fallback",
11
+ "llm"
12
+ ],
13
+ "type": "module",
14
+ "main": "./dist/index.cjs",
15
+ "module": "./dist/index.mjs",
16
+ "types": "./dist/index.d.mts",
17
+ "sideEffects": false,
18
+ "engines": {
19
+ "node": ">=22.22.0"
20
+ },
21
+ "exports": {
22
+ ".": {
23
+ "import": {
24
+ "types": "./dist/index.d.mts",
25
+ "default": "./dist/index.mjs"
26
+ },
27
+ "require": {
28
+ "types": "./dist/index.d.cts",
29
+ "default": "./dist/index.cjs"
30
+ }
31
+ }
32
+ },
33
+ "files": [
34
+ "dist"
35
+ ],
36
+ "peerDependencies": {
37
+ "vern-llm": "^3.1.0"
38
+ },
39
+ "devDependencies": {
40
+ "@types/node": "^26.6.6",
41
+ "@vitest/coverage-v8": "^5.0.5",
42
+ "publint": "^0.3.25",
43
+ "tsdown": "^0.23.4",
44
+ "typescript": "^7.0.2",
45
+ "unplugin-unused": "^0.6.0",
46
+ "vern-llm": "^3.1.0",
47
+ "vern-llm-guard": "^0.1.0",
48
+ "vern-llm-logger": "^0.1.0",
49
+ "vern-llm-policy": "^0.1.0",
50
+ "vern-llm-router": "^0.1.0",
51
+ "vern-llm-usage": "^0.1.0",
52
+ "vitest": "^5.0.5"
53
+ },
54
+ "license": "MIT",
55
+ "repository": {
56
+ "type": "git",
57
+ "url": "git+https://github.com/LakBud/vernLLM.git",
58
+ "directory": "packages/extensions/vern-llm-hedge"
59
+ },
60
+ "publishConfig": {
61
+ "access": "public",
62
+ "provenance": true
63
+ },
64
+ "scripts": {
65
+ "build": "tsdown",
66
+ "dev": "tsdown --watch",
67
+ "typecheck": "tsc -p tsconfig.json --noEmit",
68
+ "typecheck:test": "tsc -p tsconfig.test.json",
69
+ "lint": "oxlint .",
70
+ "lint:fix": "oxlint . --fix",
71
+ "test": "vitest run",
72
+ "test:unit": "vitest run --project unit",
73
+ "test:unit:coverage": "vitest run --project unit --coverage",
74
+ "test:int": "vitest run --project integration",
75
+ "test:int:coverage": "vitest run --project integration --coverage",
76
+ "test:watch": "vitest",
77
+ "test:coverage": "vitest run --coverage",
78
+ "bench:check": "node scripts/bench-check.ts",
79
+ "bench:update": "node scripts/bench-check.ts --update",
80
+ "bench": "vitest bench --run",
81
+ "smoke-test": "node scripts/smoke-test.ts",
82
+ "changeset": "changeset",
83
+ "version-packages": "changeset version"
84
+ }
6
85
  }