llm-retry-kit 0.1.0 → 0.2.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/README.md +116 -61
- package/dist/backoff.d.ts +2 -1
- package/dist/backoff.d.ts.map +1 -1
- package/dist/backoff.js +28 -4
- package/dist/backoff.js.map +1 -1
- package/dist/budget.d.ts +2 -1
- package/dist/budget.d.ts.map +1 -1
- package/dist/budget.js +5 -2
- package/dist/budget.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/retry.d.ts +3 -1
- package/dist/retry.d.ts.map +1 -1
- package/dist/retry.js +208 -56
- package/dist/retry.js.map +1 -1
- package/dist/types.d.ts +40 -4
- package/dist/types.d.ts.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
# llm-retry-kit
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
fallback
|
|
3
|
+
Small resilience layer for production LLM calls. It gives you retries,
|
|
4
|
+
provider fallback, jittered exponential backoff, `Retry-After` handling,
|
|
5
|
+
budget tracking, cancellation, timeouts, and observability hooks.
|
|
5
6
|
|
|
6
7
|
## Install
|
|
7
8
|
|
|
@@ -18,11 +19,14 @@ import OpenAI from 'openai'
|
|
|
18
19
|
const openai = new OpenAI()
|
|
19
20
|
|
|
20
21
|
const result = await llmRetry({
|
|
21
|
-
fn: async () => {
|
|
22
|
-
const response = await openai.chat.completions.create(
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
22
|
+
fn: async ({ signal }) => {
|
|
23
|
+
const response = await openai.chat.completions.create(
|
|
24
|
+
{
|
|
25
|
+
model: 'gpt-4o-mini',
|
|
26
|
+
messages: [{ role: 'user', content: 'Hello!' }],
|
|
27
|
+
},
|
|
28
|
+
{ signal }
|
|
29
|
+
)
|
|
26
30
|
|
|
27
31
|
return {
|
|
28
32
|
data: response.choices[0]?.message.content ?? '',
|
|
@@ -35,60 +39,122 @@ const result = await llmRetry({
|
|
|
35
39
|
: undefined,
|
|
36
40
|
}
|
|
37
41
|
},
|
|
42
|
+
maxRetries: 3,
|
|
38
43
|
})
|
|
39
44
|
|
|
40
45
|
console.log(result.data)
|
|
41
|
-
console.log(result.
|
|
46
|
+
console.log(result.provider)
|
|
42
47
|
console.log(result.totalCostUSD)
|
|
43
48
|
```
|
|
44
49
|
|
|
45
|
-
## Fallback
|
|
50
|
+
## Provider Fallback Chain
|
|
51
|
+
|
|
52
|
+
Use `providers` when you want explicit model or vendor failover. Each provider
|
|
53
|
+
can have its own retry count and cost calculator.
|
|
46
54
|
|
|
47
55
|
```ts
|
|
48
|
-
|
|
56
|
+
const result = await llmRetry({
|
|
57
|
+
providers: [
|
|
58
|
+
{
|
|
59
|
+
name: 'openai:gpt-4o',
|
|
60
|
+
fn: async (context) => callOpenAI(context),
|
|
61
|
+
maxRetries: 2,
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
name: 'anthropic:sonnet',
|
|
65
|
+
fn: async (context) => callAnthropic(context),
|
|
66
|
+
maxRetries: 1,
|
|
67
|
+
},
|
|
68
|
+
],
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
console.log(result.provider)
|
|
72
|
+
console.log(result.usedFallback)
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
The older `fn` + `fallback` API still works:
|
|
49
76
|
|
|
77
|
+
```ts
|
|
50
78
|
const result = await llmRetry({
|
|
51
79
|
fn: async () => callPrimaryModel(),
|
|
52
80
|
fallback: async () => callFallbackModel(),
|
|
53
|
-
maxRetries: 3,
|
|
54
|
-
initialDelayMs: 1000,
|
|
55
|
-
maxDelayMs: 30000,
|
|
56
81
|
})
|
|
82
|
+
```
|
|
57
83
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
84
|
+
## Custom Retry Policy
|
|
85
|
+
|
|
86
|
+
```ts
|
|
87
|
+
const result = await llmRetry({
|
|
88
|
+
fn: myLLMCall,
|
|
89
|
+
shouldRetry: (error, context) => {
|
|
90
|
+
if (error.message.includes('quota exceeded')) return false
|
|
91
|
+
return context.retryAttempt < context.maxRetries
|
|
92
|
+
},
|
|
93
|
+
})
|
|
61
94
|
```
|
|
62
95
|
|
|
96
|
+
Without `shouldRetry`, the package retries common transient failures such as
|
|
97
|
+
HTTP `408`, `409`, `429`, `5xx`, Anthropic `529`, timeout, network, and
|
|
98
|
+
overload errors.
|
|
99
|
+
|
|
100
|
+
## Timeouts And Cancellation
|
|
101
|
+
|
|
102
|
+
```ts
|
|
103
|
+
const controller = new AbortController()
|
|
104
|
+
|
|
105
|
+
const result = await llmRetry({
|
|
106
|
+
fn: async ({ signal }) => myLLMCall({ signal }),
|
|
107
|
+
signal: controller.signal,
|
|
108
|
+
timeoutMs: 30_000,
|
|
109
|
+
})
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
`timeoutMs` aborts the wrapper and retry waits. Passing `signal` into your SDK
|
|
113
|
+
call lets the underlying HTTP request stop too, when the SDK supports it.
|
|
114
|
+
|
|
63
115
|
## Budget Tracking
|
|
64
116
|
|
|
65
|
-
|
|
66
|
-
function. It cannot know the exact cost of a future LLM call before that call
|
|
67
|
-
finishes, so the budget guard is best used to stop later retries or fallback
|
|
68
|
-
calls after tracked usage reaches the configured cap.
|
|
117
|
+
Simple mode:
|
|
69
118
|
|
|
70
119
|
```ts
|
|
71
120
|
const result = await llmRetry({
|
|
72
121
|
fn: myLLMCall,
|
|
73
122
|
maxCostUSD: 0.5,
|
|
74
123
|
costPer1kTokens: 0.002,
|
|
75
|
-
onBudgetExceeded: (spent, limit) => {
|
|
76
|
-
console.warn(`Budget exceeded: $${spent.toFixed(4)} / $${limit}`)
|
|
77
|
-
},
|
|
78
124
|
})
|
|
79
125
|
```
|
|
80
126
|
|
|
81
|
-
|
|
127
|
+
Provider pricing is often more nuanced than one flat token price, so production
|
|
128
|
+
apps should prefer `costCalculator`:
|
|
82
129
|
|
|
83
130
|
```ts
|
|
84
131
|
const result = await llmRetry({
|
|
85
132
|
fn: myLLMCall,
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
133
|
+
costCalculator: (usage) => {
|
|
134
|
+
const input = usage.promptTokens * 0.00000015
|
|
135
|
+
const output = usage.completionTokens * 0.0000006
|
|
136
|
+
return input + output
|
|
137
|
+
},
|
|
138
|
+
})
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
## Observability
|
|
142
|
+
|
|
143
|
+
```ts
|
|
144
|
+
await llmRetry({
|
|
145
|
+
fn: myLLMCall,
|
|
146
|
+
onAttempt: (context) => {
|
|
147
|
+
console.log(`Calling ${context.provider}, attempt ${context.attempt}`)
|
|
148
|
+
},
|
|
149
|
+
onRetry: (attempt, error, delayMs, context) => {
|
|
150
|
+
console.log(`${context.provider} failed: ${error.message}`)
|
|
151
|
+
console.log(`Retrying in ${delayMs}ms`)
|
|
152
|
+
},
|
|
153
|
+
onSuccess: (context) => {
|
|
154
|
+
console.log(`Cost so far: $${context.totalCostUSD}`)
|
|
155
|
+
},
|
|
156
|
+
onFailure: (error) => {
|
|
157
|
+
console.error(error)
|
|
92
158
|
},
|
|
93
159
|
})
|
|
94
160
|
```
|
|
@@ -99,28 +165,23 @@ const result = await llmRetry({
|
|
|
99
165
|
|
|
100
166
|
| Option | Type | Default | Description |
|
|
101
167
|
| --- | --- | --- | --- |
|
|
102
|
-
| `fn` | `() => Promise<LLMResponse<T>>` |
|
|
103
|
-
| `fallback` | `() => Promise<LLMResponse<T>>` | optional | Backup
|
|
104
|
-
| `
|
|
105
|
-
| `
|
|
106
|
-
| `
|
|
168
|
+
| `fn` | `(context) => Promise<LLMResponse<T>>` | optional | Primary LLM call for the simple API. |
|
|
169
|
+
| `fallback` | `(context) => Promise<LLMResponse<T>>` | optional | Backup LLM call for the simple API. |
|
|
170
|
+
| `providers` | `RetryProvider<T>[]` | optional | Explicit provider/model chain. |
|
|
171
|
+
| `maxRetries` | `number` | `3` | Retries after the first attempt. |
|
|
172
|
+
| `maxCostUSD` | `number` | optional | Maximum tracked cost before later attempts stop. |
|
|
173
|
+
| `costPer1kTokens` | `number` | `0.002` | Simple cost estimate. |
|
|
174
|
+
| `costCalculator` | `(usage, context) => number` | optional | Custom cost calculation. |
|
|
107
175
|
| `initialDelayMs` | `number` | `1000` | Initial retry delay. |
|
|
108
176
|
| `maxDelayMs` | `number` | `30000` | Maximum retry delay. |
|
|
109
|
-
| `
|
|
110
|
-
| `
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
usage?: {
|
|
118
|
-
promptTokens: number
|
|
119
|
-
completionTokens: number
|
|
120
|
-
totalTokens: number
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
```
|
|
177
|
+
| `timeoutMs` | `number` | optional | Abort wrapper after this time. |
|
|
178
|
+
| `signal` | `AbortSignal` | optional | External cancellation signal. |
|
|
179
|
+
| `shouldRetry` | `(error, context) => boolean \| Promise<boolean>` | optional | Override retry decisions. |
|
|
180
|
+
| `onAttempt` | `(context) => void` | optional | Called before each attempt. |
|
|
181
|
+
| `onRetry` | `(attempt, error, delayMs, context) => void` | optional | Called before retry wait. |
|
|
182
|
+
| `onSuccess` | `(context) => void` | optional | Called after a successful response. |
|
|
183
|
+
| `onFailure` | `(error) => void` | optional | Called before final failure is thrown. |
|
|
184
|
+
| `onBudgetExceeded` | `(spentUSD, limitUSD) => void` | optional | Called when budget is exhausted. |
|
|
124
185
|
|
|
125
186
|
### `RetryResult<T>`
|
|
126
187
|
|
|
@@ -128,24 +189,18 @@ const result = await llmRetry({
|
|
|
128
189
|
{
|
|
129
190
|
data: T
|
|
130
191
|
attempts: number
|
|
192
|
+
provider: string
|
|
131
193
|
usedFallback: boolean
|
|
132
194
|
totalCostUSD: number
|
|
133
195
|
totalTokens: number
|
|
134
196
|
}
|
|
135
197
|
```
|
|
136
198
|
|
|
137
|
-
##
|
|
138
|
-
|
|
139
|
-
The package retries common transient failures:
|
|
140
|
-
|
|
141
|
-
- HTTP `429`
|
|
142
|
-
- HTTP `5xx`
|
|
143
|
-
- timeout errors
|
|
144
|
-
- network connection errors
|
|
145
|
-
- overloaded server errors
|
|
199
|
+
## Notes
|
|
146
200
|
|
|
147
|
-
|
|
148
|
-
|
|
201
|
+
Budget tracking is based on the `usage` object returned by your function. A
|
|
202
|
+
wrapper cannot know the final cost of an in-flight LLM call before the provider
|
|
203
|
+
returns usage, so `maxCostUSD` is a guard for later attempts and fallback calls.
|
|
149
204
|
|
|
150
205
|
## License
|
|
151
206
|
|
package/dist/backoff.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export declare function calculateBackoff(attempt: number, initialDelayMs: number, maxDelayMs: number): number;
|
|
2
|
-
export declare function sleep(ms: number): Promise<void>;
|
|
2
|
+
export declare function sleep(ms: number, signal?: AbortSignal): Promise<void>;
|
|
3
3
|
export declare function isRetryableError(error: unknown): boolean;
|
|
4
4
|
export declare function extractRetryAfter(error: unknown): number | null;
|
|
5
|
+
export declare function createAbortError(): Error;
|
|
5
6
|
//# sourceMappingURL=backoff.d.ts.map
|
package/dist/backoff.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"backoff.d.ts","sourceRoot":"","sources":["../src/backoff.ts"],"names":[],"mappings":"AAAA,wBAAgB,gBAAgB,CAC9B,OAAO,EAAE,MAAM,EACf,cAAc,EAAE,MAAM,EACtB,UAAU,EAAE,MAAM,GACjB,MAAM,CAMR;AAED,wBAAgB,KAAK,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,
|
|
1
|
+
{"version":3,"file":"backoff.d.ts","sourceRoot":"","sources":["../src/backoff.ts"],"names":[],"mappings":"AAAA,wBAAgB,gBAAgB,CAC9B,OAAO,EAAE,MAAM,EACf,cAAc,EAAE,MAAM,EACtB,UAAU,EAAE,MAAM,GACjB,MAAM,CAMR;AAED,wBAAgB,KAAK,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAiBrE;AAED,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CA2DxD;AAED,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CA4B/D;AAED,wBAAgB,gBAAgB,IAAI,KAAK,CAIxC"}
|
package/dist/backoff.js
CHANGED
|
@@ -4,15 +4,27 @@ export function calculateBackoff(attempt, initialDelayMs, maxDelayMs) {
|
|
|
4
4
|
const delay = exponential + jitter;
|
|
5
5
|
return Math.min(Math.max(delay, initialDelayMs), maxDelayMs);
|
|
6
6
|
}
|
|
7
|
-
export function sleep(ms) {
|
|
8
|
-
|
|
7
|
+
export function sleep(ms, signal) {
|
|
8
|
+
if (signal?.aborted) {
|
|
9
|
+
return Promise.reject(createAbortError());
|
|
10
|
+
}
|
|
11
|
+
return new Promise((resolve, reject) => {
|
|
12
|
+
const timeout = setTimeout(resolve, ms);
|
|
13
|
+
signal?.addEventListener('abort', () => {
|
|
14
|
+
clearTimeout(timeout);
|
|
15
|
+
reject(createAbortError());
|
|
16
|
+
}, { once: true });
|
|
17
|
+
});
|
|
9
18
|
}
|
|
10
19
|
export function isRetryableError(error) {
|
|
11
20
|
if (!(error instanceof Error))
|
|
12
21
|
return false;
|
|
13
22
|
const err = error;
|
|
14
23
|
const status = toNumber(err.status ?? err.statusCode);
|
|
15
|
-
if (status ===
|
|
24
|
+
if (status === 408 ||
|
|
25
|
+
status === 409 ||
|
|
26
|
+
status === 429 ||
|
|
27
|
+
(status !== null && status >= 500 && status <= 599)) {
|
|
16
28
|
return true;
|
|
17
29
|
}
|
|
18
30
|
const code = typeof err.code === 'string' ? err.code.toLowerCase() : '';
|
|
@@ -23,6 +35,8 @@ export function isRetryableError(error) {
|
|
|
23
35
|
'enotfound',
|
|
24
36
|
'eai_again',
|
|
25
37
|
'rate_limit_exceeded',
|
|
38
|
+
'conflict',
|
|
39
|
+
'overloaded_error',
|
|
26
40
|
].includes(code)) {
|
|
27
41
|
return true;
|
|
28
42
|
}
|
|
@@ -32,6 +46,8 @@ export function isRetryableError(error) {
|
|
|
32
46
|
'rate_limit',
|
|
33
47
|
'too many requests',
|
|
34
48
|
'429',
|
|
49
|
+
'408',
|
|
50
|
+
'409',
|
|
35
51
|
'server error',
|
|
36
52
|
'500',
|
|
37
53
|
'502',
|
|
@@ -44,6 +60,7 @@ export function isRetryableError(error) {
|
|
|
44
60
|
'network',
|
|
45
61
|
'socket',
|
|
46
62
|
'overloaded',
|
|
63
|
+
'overloaded_error',
|
|
47
64
|
];
|
|
48
65
|
return retryablePatterns.some((pattern) => message.includes(pattern));
|
|
49
66
|
}
|
|
@@ -55,7 +72,9 @@ export function extractRetryAfter(error) {
|
|
|
55
72
|
if (retryAfter !== null) {
|
|
56
73
|
return retryAfter * 1000;
|
|
57
74
|
}
|
|
58
|
-
const
|
|
75
|
+
const response = err['response'];
|
|
76
|
+
const headerValue = getHeader(err['headers'], 'retry-after') ??
|
|
77
|
+
getHeader(response?.['headers'], 'retry-after');
|
|
59
78
|
if (!headerValue) {
|
|
60
79
|
return null;
|
|
61
80
|
}
|
|
@@ -69,6 +88,11 @@ export function extractRetryAfter(error) {
|
|
|
69
88
|
}
|
|
70
89
|
return null;
|
|
71
90
|
}
|
|
91
|
+
export function createAbortError() {
|
|
92
|
+
const error = new Error('The operation was aborted');
|
|
93
|
+
error.name = 'AbortError';
|
|
94
|
+
return error;
|
|
95
|
+
}
|
|
72
96
|
function toNumber(value) {
|
|
73
97
|
if (typeof value === 'number' && Number.isFinite(value)) {
|
|
74
98
|
return value;
|
package/dist/backoff.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"backoff.js","sourceRoot":"","sources":["../src/backoff.ts"],"names":[],"mappings":"AAAA,MAAM,UAAU,gBAAgB,CAC9B,OAAe,EACf,cAAsB,EACtB,UAAkB;IAElB,MAAM,WAAW,GAAG,cAAc,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,OAAO,CAAC,CAAA;IACzD,MAAM,MAAM,GAAG,WAAW,GAAG,IAAI,GAAG,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,CAAC,CAAC,CAAA;IAC3D,MAAM,KAAK,GAAG,WAAW,GAAG,MAAM,CAAA;IAElC,OAAO,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,cAAc,CAAC,EAAE,UAAU,CAAC,CAAA;AAC9D,CAAC;AAED,MAAM,UAAU,KAAK,CAAC,EAAU;
|
|
1
|
+
{"version":3,"file":"backoff.js","sourceRoot":"","sources":["../src/backoff.ts"],"names":[],"mappings":"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"}
|
package/dist/budget.d.ts
CHANGED
|
@@ -5,7 +5,8 @@ export declare class BudgetTracker {
|
|
|
5
5
|
private readonly costPer1kTokens;
|
|
6
6
|
private readonly maxCostUSD;
|
|
7
7
|
constructor(costPer1kTokens: number, maxCostUSD?: number);
|
|
8
|
-
add(usage: TokenUsage): void;
|
|
8
|
+
add(usage: TokenUsage, costUSD?: number): void;
|
|
9
|
+
estimate(usage: TokenUsage): number;
|
|
9
10
|
isExceeded(): boolean;
|
|
10
11
|
get spent(): number;
|
|
11
12
|
get tokens(): number;
|
package/dist/budget.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"budget.d.ts","sourceRoot":"","sources":["../src/budget.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAA;AAE5C,qBAAa,aAAa;IACxB,OAAO,CAAC,WAAW,CAAI;IACvB,OAAO,CAAC,YAAY,CAAI;IACxB,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAQ;IACxC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAe;gBAE9B,eAAe,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM;IAaxD,GAAG,CAAC,KAAK,EAAE,UAAU,GAAG,IAAI;
|
|
1
|
+
{"version":3,"file":"budget.d.ts","sourceRoot":"","sources":["../src/budget.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAA;AAE5C,qBAAa,aAAa;IACxB,OAAO,CAAC,WAAW,CAAI;IACvB,OAAO,CAAC,YAAY,CAAI;IACxB,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAQ;IACxC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAe;gBAE9B,eAAe,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM;IAaxD,GAAG,CAAC,KAAK,EAAE,UAAU,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI;IAK9C,QAAQ,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM;IAInC,UAAU,IAAI,OAAO;IAKrB,IAAI,KAAK,IAAI,MAAM,CAElB;IAED,IAAI,MAAM,IAAI,MAAM,CAEnB;IAED,IAAI,KAAK,IAAI,MAAM,GAAG,IAAI,CAEzB;IAED,OAAO,IAAI,MAAM;CAKlB"}
|
package/dist/budget.js
CHANGED
|
@@ -13,9 +13,12 @@ export class BudgetTracker {
|
|
|
13
13
|
this.costPer1kTokens = costPer1kTokens;
|
|
14
14
|
this.maxCostUSD = maxCostUSD ?? null;
|
|
15
15
|
}
|
|
16
|
-
add(usage) {
|
|
16
|
+
add(usage, costUSD) {
|
|
17
17
|
this.totalTokens += usage.totalTokens;
|
|
18
|
-
this.totalCostUSD +=
|
|
18
|
+
this.totalCostUSD += costUSD ?? this.estimate(usage);
|
|
19
|
+
}
|
|
20
|
+
estimate(usage) {
|
|
21
|
+
return (usage.totalTokens / 1000) * this.costPer1kTokens;
|
|
19
22
|
}
|
|
20
23
|
isExceeded() {
|
|
21
24
|
if (this.maxCostUSD === null)
|
package/dist/budget.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"budget.js","sourceRoot":"","sources":["../src/budget.ts"],"names":[],"mappings":"AAEA,MAAM,OAAO,aAAa;IAChB,WAAW,GAAG,CAAC,CAAA;IACf,YAAY,GAAG,CAAC,CAAA;IACP,eAAe,CAAQ;IACvB,UAAU,CAAe;IAE1C,YAAY,eAAuB,EAAE,UAAmB;QACtD,IAAI,eAAe,GAAG,CAAC,EAAE,CAAC;YACxB,MAAM,IAAI,KAAK,CAAC,oDAAoD,CAAC,CAAA;QACvE,CAAC;QAED,IAAI,UAAU,KAAK,SAAS,IAAI,UAAU,GAAG,CAAC,EAAE,CAAC;YAC/C,MAAM,IAAI,KAAK,CAAC,+CAA+C,CAAC,CAAA;QAClE,CAAC;QAED,IAAI,CAAC,eAAe,GAAG,eAAe,CAAA;QACtC,IAAI,CAAC,UAAU,GAAG,UAAU,IAAI,IAAI,CAAA;IACtC,CAAC;IAED,GAAG,CAAC,KAAiB;
|
|
1
|
+
{"version":3,"file":"budget.js","sourceRoot":"","sources":["../src/budget.ts"],"names":[],"mappings":"AAEA,MAAM,OAAO,aAAa;IAChB,WAAW,GAAG,CAAC,CAAA;IACf,YAAY,GAAG,CAAC,CAAA;IACP,eAAe,CAAQ;IACvB,UAAU,CAAe;IAE1C,YAAY,eAAuB,EAAE,UAAmB;QACtD,IAAI,eAAe,GAAG,CAAC,EAAE,CAAC;YACxB,MAAM,IAAI,KAAK,CAAC,oDAAoD,CAAC,CAAA;QACvE,CAAC;QAED,IAAI,UAAU,KAAK,SAAS,IAAI,UAAU,GAAG,CAAC,EAAE,CAAC;YAC/C,MAAM,IAAI,KAAK,CAAC,+CAA+C,CAAC,CAAA;QAClE,CAAC;QAED,IAAI,CAAC,eAAe,GAAG,eAAe,CAAA;QACtC,IAAI,CAAC,UAAU,GAAG,UAAU,IAAI,IAAI,CAAA;IACtC,CAAC;IAED,GAAG,CAAC,KAAiB,EAAE,OAAgB;QACrC,IAAI,CAAC,WAAW,IAAI,KAAK,CAAC,WAAW,CAAA;QACrC,IAAI,CAAC,YAAY,IAAI,OAAO,IAAI,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAA;IACtD,CAAC;IAED,QAAQ,CAAC,KAAiB;QACxB,OAAO,CAAC,KAAK,CAAC,WAAW,GAAG,IAAI,CAAC,GAAG,IAAI,CAAC,eAAe,CAAA;IAC1D,CAAC;IAED,UAAU;QACR,IAAI,IAAI,CAAC,UAAU,KAAK,IAAI;YAAE,OAAO,KAAK,CAAA;QAC1C,OAAO,IAAI,CAAC,YAAY,IAAI,IAAI,CAAC,UAAU,CAAA;IAC7C,CAAC;IAED,IAAI,KAAK;QACP,OAAO,IAAI,CAAC,YAAY,CAAA;IAC1B,CAAC;IAED,IAAI,MAAM;QACR,OAAO,IAAI,CAAC,WAAW,CAAA;IACzB,CAAC;IAED,IAAI,KAAK;QACP,OAAO,IAAI,CAAC,UAAU,CAAA;IACxB,CAAC;IAED,OAAO;QACL,MAAM,IAAI,GAAG,IAAI,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC,CAAC,CAAA;QACzC,MAAM,KAAK,GAAG,IAAI,CAAC,UAAU,KAAK,IAAI,CAAC,CAAC,CAAC,MAAM,IAAI,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC,EAAE,CAAA;QACrE,OAAO,IAAI,IAAI,GAAG,KAAK,KAAK,IAAI,CAAC,WAAW,CAAC,cAAc,EAAE,UAAU,CAAA;IACzE,CAAC;CACF"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { llmRetry, LLMRetryError } from './retry.js';
|
|
2
2
|
export { BudgetTracker } from './budget.js';
|
|
3
|
-
export { calculateBackoff, isRetryableError } from './backoff.js';
|
|
4
|
-
export type { RetryOptions,
|
|
3
|
+
export { calculateBackoff, extractRetryAfter, isRetryableError } from './backoff.js';
|
|
4
|
+
export type { CostCalculator, LLMCall, LLMResponse, RetryAttemptContext, RetryDecisionContext, RetryOptions, RetryProvider, RetryResult, RetrySuccessContext, RetryableError, ShouldRetry, TokenUsage, } from './types.js';
|
|
5
5
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AACpD,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAC3C,OAAO,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAA;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AACpD,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAC3C,OAAO,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAA;AACpF,YAAY,EACV,cAAc,EACd,OAAO,EACP,WAAW,EACX,mBAAmB,EACnB,oBAAoB,EACpB,YAAY,EACZ,aAAa,EACb,WAAW,EACX,mBAAmB,EACnB,cAAc,EACd,WAAW,EACX,UAAU,GACX,MAAM,YAAY,CAAA"}
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
export { llmRetry, LLMRetryError } from './retry.js';
|
|
2
2
|
export { BudgetTracker } from './budget.js';
|
|
3
|
-
export { calculateBackoff, isRetryableError } from './backoff.js';
|
|
3
|
+
export { calculateBackoff, extractRetryAfter, isRetryableError } from './backoff.js';
|
|
4
4
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AACpD,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAC3C,OAAO,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AACpD,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAC3C,OAAO,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAA"}
|
package/dist/retry.d.ts
CHANGED
|
@@ -5,6 +5,8 @@ export declare class LLMRetryError extends Error {
|
|
|
5
5
|
readonly fallbackError: Error | null;
|
|
6
6
|
readonly totalCostUSD: number;
|
|
7
7
|
readonly totalTokens: number;
|
|
8
|
-
|
|
8
|
+
readonly attempts: number;
|
|
9
|
+
readonly providers: string[];
|
|
10
|
+
constructor(message: string, primaryError: Error | null, fallbackError: Error | null, totalCostUSD: number, totalTokens: number, attempts?: number, providers?: string[]);
|
|
9
11
|
}
|
|
10
12
|
//# sourceMappingURL=retry.d.ts.map
|
package/dist/retry.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"retry.d.ts","sourceRoot":"","sources":["../src/retry.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"retry.d.ts","sourceRoot":"","sources":["../src/retry.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAKV,YAAY,EAEZ,WAAW,EAGZ,MAAM,YAAY,CAAA;AAEnB,wBAAsB,QAAQ,CAAC,CAAC,EAAE,OAAO,EAAE,YAAY,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,CAyJnF;AAgLD,qBAAa,aAAc,SAAQ,KAAK;aAGpB,YAAY,EAAE,KAAK,GAAG,IAAI;aAC1B,aAAa,EAAE,KAAK,GAAG,IAAI;aAC3B,YAAY,EAAE,MAAM;aACpB,WAAW,EAAE,MAAM;aACnB,QAAQ;aACR,SAAS,EAAE,MAAM,EAAE;gBANnC,OAAO,EAAE,MAAM,EACC,YAAY,EAAE,KAAK,GAAG,IAAI,EAC1B,aAAa,EAAE,KAAK,GAAG,IAAI,EAC3B,YAAY,EAAE,MAAM,EACpB,WAAW,EAAE,MAAM,EACnB,QAAQ,SAAI,EACZ,SAAS,GAAE,MAAM,EAAO;CAK3C"}
|
package/dist/retry.js
CHANGED
|
@@ -1,70 +1,215 @@
|
|
|
1
|
-
import { calculateBackoff, extractRetryAfter, isRetryableError, sleep } from './backoff.js';
|
|
1
|
+
import { calculateBackoff, createAbortError, extractRetryAfter, isRetryableError, sleep, } from './backoff.js';
|
|
2
2
|
import { BudgetTracker } from './budget.js';
|
|
3
3
|
export async function llmRetry(options) {
|
|
4
|
-
const {
|
|
5
|
-
validateOptions({ maxRetries, initialDelayMs, maxDelayMs });
|
|
4
|
+
const { maxRetries = 3, maxCostUSD, costPer1kTokens = 0.002, initialDelayMs = 1000, maxDelayMs = 30000, timeoutMs, signal, shouldRetry, onAttempt, onRetry, onSuccess, onFailure, onBudgetExceeded, } = options;
|
|
5
|
+
validateOptions({ maxRetries, initialDelayMs, maxDelayMs, timeoutMs });
|
|
6
|
+
const providers = normalizeProviders(options, maxRetries);
|
|
7
|
+
const startedAt = Date.now();
|
|
6
8
|
const budget = new BudgetTracker(costPer1kTokens, maxCostUSD);
|
|
7
|
-
|
|
9
|
+
const runtimeSignal = createRuntimeSignal(signal, timeoutMs);
|
|
8
10
|
let attempts = 0;
|
|
11
|
+
let lastError = null;
|
|
12
|
+
let primaryError = null;
|
|
13
|
+
let fallbackError = null;
|
|
9
14
|
let budgetExceededNotified = false;
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
15
|
+
try {
|
|
16
|
+
for (let providerIndex = 0; providerIndex < providers.length; providerIndex++) {
|
|
17
|
+
const provider = providers[providerIndex];
|
|
18
|
+
const providerMaxRetries = provider.maxRetries ?? maxRetries;
|
|
19
|
+
for (let retryAttempt = 0; retryAttempt <= providerMaxRetries; retryAttempt++) {
|
|
20
|
+
if (runtimeSignal.signal?.aborted) {
|
|
21
|
+
throw createAbortError();
|
|
22
|
+
}
|
|
23
|
+
if (budget.isExceeded()) {
|
|
24
|
+
budgetExceededNotified = notifyBudgetExceeded(budget.spent, budget.limit, onBudgetExceeded, budgetExceededNotified);
|
|
25
|
+
throw new Error('Budget exceeded');
|
|
26
|
+
}
|
|
27
|
+
attempts += 1;
|
|
28
|
+
const context = createAttemptContext({
|
|
29
|
+
attempt: attempts,
|
|
30
|
+
retryAttempt,
|
|
31
|
+
provider,
|
|
32
|
+
providerIndex,
|
|
33
|
+
startedAt,
|
|
34
|
+
signal: runtimeSignal.signal,
|
|
35
|
+
lastError,
|
|
36
|
+
});
|
|
37
|
+
onAttempt?.(context);
|
|
38
|
+
try {
|
|
39
|
+
const response = await runWithAbort(provider.fn(context), runtimeSignal.signal);
|
|
40
|
+
const costUSD = trackUsage({
|
|
41
|
+
response,
|
|
42
|
+
context,
|
|
43
|
+
provider,
|
|
44
|
+
defaultCostPer1kTokens: costPer1kTokens,
|
|
45
|
+
defaultCostCalculator: options.costCalculator,
|
|
46
|
+
budget,
|
|
47
|
+
});
|
|
48
|
+
onSuccess?.({
|
|
49
|
+
...context,
|
|
50
|
+
costUSD,
|
|
51
|
+
totalCostUSD: budget.spent,
|
|
52
|
+
totalTokens: budget.tokens,
|
|
53
|
+
});
|
|
54
|
+
return {
|
|
55
|
+
data: response.data,
|
|
56
|
+
attempts,
|
|
57
|
+
provider: provider.name,
|
|
58
|
+
usedFallback: providerIndex > 0,
|
|
59
|
+
totalCostUSD: budget.spent,
|
|
60
|
+
totalTokens: budget.tokens,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
catch (error) {
|
|
64
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
65
|
+
lastError = err;
|
|
66
|
+
if (providerIndex === 0) {
|
|
67
|
+
primaryError = err;
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
fallbackError = err;
|
|
71
|
+
}
|
|
72
|
+
const decisionContext = {
|
|
73
|
+
...context,
|
|
74
|
+
maxRetries: providerMaxRetries,
|
|
75
|
+
};
|
|
76
|
+
const retry = await shouldRetryAttempt({
|
|
77
|
+
error: err,
|
|
78
|
+
context: decisionContext,
|
|
79
|
+
shouldRetry,
|
|
80
|
+
retryAttempt,
|
|
81
|
+
maxRetries: providerMaxRetries,
|
|
82
|
+
});
|
|
83
|
+
if (!retry) {
|
|
84
|
+
break;
|
|
85
|
+
}
|
|
86
|
+
const serverDelay = extractRetryAfter(err);
|
|
87
|
+
const delay = serverDelay ?? calculateBackoff(retryAttempt, initialDelayMs, maxDelayMs);
|
|
88
|
+
onRetry?.(attempts, err, delay, decisionContext);
|
|
89
|
+
await sleep(delay, runtimeSignal.signal);
|
|
90
|
+
}
|
|
20
91
|
}
|
|
21
|
-
return {
|
|
22
|
-
data: response.data,
|
|
23
|
-
attempts,
|
|
24
|
-
usedFallback: false,
|
|
25
|
-
totalCostUSD: budget.spent,
|
|
26
|
-
totalTokens: budget.tokens,
|
|
27
|
-
};
|
|
28
92
|
}
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
lastError = err;
|
|
32
|
-
const isLastAttempt = retryIndex === maxRetries;
|
|
33
|
-
if (isLastAttempt || !isRetryableError(err)) {
|
|
34
|
-
break;
|
|
35
|
-
}
|
|
36
|
-
const serverDelay = extractRetryAfter(error);
|
|
37
|
-
const delay = serverDelay ?? calculateBackoff(retryIndex, initialDelayMs, maxDelayMs);
|
|
38
|
-
onRetry?.(attempts, err, delay);
|
|
39
|
-
await sleep(delay);
|
|
93
|
+
if (budget.isExceeded()) {
|
|
94
|
+
budgetExceededNotified = notifyBudgetExceeded(budget.spent, budget.limit, onBudgetExceeded, budgetExceededNotified);
|
|
40
95
|
}
|
|
96
|
+
throw new Error(lastError?.message ?? 'No provider returned a successful response');
|
|
41
97
|
}
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
}
|
|
57
|
-
catch (fallbackError) {
|
|
58
|
-
const err = fallbackError instanceof Error
|
|
59
|
-
? fallbackError
|
|
60
|
-
: new Error(String(fallbackError));
|
|
61
|
-
throw new LLMRetryError(`Both primary and fallback failed. Primary: ${lastError?.message ?? 'unknown error'}. Fallback: ${err.message}`, lastError, err, budget.spent, budget.tokens);
|
|
98
|
+
catch (error) {
|
|
99
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
100
|
+
const retryError = new LLMRetryError(`LLM call failed after ${attempts} attempt${attempts === 1 ? '' : 's'}: ${err.message}`, primaryError ?? lastError, fallbackError, budget.spent, budget.tokens, attempts, providers.map((provider) => provider.name));
|
|
101
|
+
onFailure?.(retryError);
|
|
102
|
+
throw retryError;
|
|
103
|
+
}
|
|
104
|
+
finally {
|
|
105
|
+
runtimeSignal.cleanup();
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
function normalizeProviders(options, defaultMaxRetries) {
|
|
109
|
+
if (options.providers && options.providers.length > 0) {
|
|
110
|
+
if (options.fn || options.fallback) {
|
|
111
|
+
throw new Error('Use either providers or fn/fallback, not both');
|
|
62
112
|
}
|
|
113
|
+
return options.providers.map((provider) => ({
|
|
114
|
+
...provider,
|
|
115
|
+
maxRetries: provider.maxRetries ?? defaultMaxRetries,
|
|
116
|
+
}));
|
|
117
|
+
}
|
|
118
|
+
if (!options.fn) {
|
|
119
|
+
throw new Error('llmRetry requires fn or at least one provider');
|
|
63
120
|
}
|
|
64
|
-
|
|
65
|
-
|
|
121
|
+
const providers = [
|
|
122
|
+
{
|
|
123
|
+
name: 'primary',
|
|
124
|
+
fn: options.fn,
|
|
125
|
+
maxRetries: defaultMaxRetries,
|
|
126
|
+
costCalculator: options.costCalculator,
|
|
127
|
+
costPer1kTokens: options.costPer1kTokens,
|
|
128
|
+
},
|
|
129
|
+
];
|
|
130
|
+
if (options.fallback) {
|
|
131
|
+
providers.push({
|
|
132
|
+
name: 'fallback',
|
|
133
|
+
fn: options.fallback,
|
|
134
|
+
maxRetries: 0,
|
|
135
|
+
costCalculator: options.costCalculator,
|
|
136
|
+
costPer1kTokens: options.costPer1kTokens,
|
|
137
|
+
});
|
|
66
138
|
}
|
|
67
|
-
|
|
139
|
+
return providers;
|
|
140
|
+
}
|
|
141
|
+
function createAttemptContext(options) {
|
|
142
|
+
return {
|
|
143
|
+
attempt: options.attempt,
|
|
144
|
+
retryAttempt: options.retryAttempt,
|
|
145
|
+
provider: options.provider.name,
|
|
146
|
+
providerIndex: options.providerIndex,
|
|
147
|
+
elapsedMs: Date.now() - options.startedAt,
|
|
148
|
+
signal: options.signal,
|
|
149
|
+
lastError: options.lastError ?? undefined,
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
async function shouldRetryAttempt(options) {
|
|
153
|
+
if (options.error.name === 'AbortError') {
|
|
154
|
+
return false;
|
|
155
|
+
}
|
|
156
|
+
if (options.retryAttempt >= options.maxRetries) {
|
|
157
|
+
return false;
|
|
158
|
+
}
|
|
159
|
+
if (options.shouldRetry) {
|
|
160
|
+
return options.shouldRetry(options.error, options.context);
|
|
161
|
+
}
|
|
162
|
+
return isRetryableError(options.error);
|
|
163
|
+
}
|
|
164
|
+
function trackUsage(options) {
|
|
165
|
+
const usage = options.response.usage;
|
|
166
|
+
if (!usage) {
|
|
167
|
+
return 0;
|
|
168
|
+
}
|
|
169
|
+
const calculator = options.provider.costCalculator ?? options.defaultCostCalculator;
|
|
170
|
+
const costUSD = calculator
|
|
171
|
+
? calculator(usage, options.context)
|
|
172
|
+
: calculateDefaultCost(usage, options.provider.costPer1kTokens ?? options.defaultCostPer1kTokens);
|
|
173
|
+
options.budget.add(usage, costUSD);
|
|
174
|
+
return costUSD;
|
|
175
|
+
}
|
|
176
|
+
function calculateDefaultCost(usage, costPer1kTokens) {
|
|
177
|
+
return (usage.totalTokens / 1000) * costPer1kTokens;
|
|
178
|
+
}
|
|
179
|
+
function runWithAbort(promise, signal) {
|
|
180
|
+
if (!signal) {
|
|
181
|
+
return promise;
|
|
182
|
+
}
|
|
183
|
+
if (signal.aborted) {
|
|
184
|
+
return Promise.reject(createAbortError());
|
|
185
|
+
}
|
|
186
|
+
return new Promise((resolve, reject) => {
|
|
187
|
+
const abort = () => reject(createAbortError());
|
|
188
|
+
signal.addEventListener('abort', abort, { once: true });
|
|
189
|
+
promise.then((value) => {
|
|
190
|
+
signal.removeEventListener('abort', abort);
|
|
191
|
+
resolve(value);
|
|
192
|
+
}, (error) => {
|
|
193
|
+
signal.removeEventListener('abort', abort);
|
|
194
|
+
reject(error);
|
|
195
|
+
});
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
function createRuntimeSignal(signal, timeoutMs) {
|
|
199
|
+
if (timeoutMs === undefined) {
|
|
200
|
+
return { signal, cleanup: () => undefined };
|
|
201
|
+
}
|
|
202
|
+
const controller = new AbortController();
|
|
203
|
+
const timeout = setTimeout(() => controller.abort(), timeoutMs);
|
|
204
|
+
const abortFromParent = () => controller.abort();
|
|
205
|
+
signal?.addEventListener('abort', abortFromParent, { once: true });
|
|
206
|
+
return {
|
|
207
|
+
signal: controller.signal,
|
|
208
|
+
cleanup: () => {
|
|
209
|
+
clearTimeout(timeout);
|
|
210
|
+
signal?.removeEventListener('abort', abortFromParent);
|
|
211
|
+
},
|
|
212
|
+
};
|
|
68
213
|
}
|
|
69
214
|
function notifyBudgetExceeded(spentUSD, limitUSD, callback, alreadyNotified) {
|
|
70
215
|
if (!alreadyNotified && limitUSD !== null) {
|
|
@@ -77,12 +222,16 @@ export class LLMRetryError extends Error {
|
|
|
77
222
|
fallbackError;
|
|
78
223
|
totalCostUSD;
|
|
79
224
|
totalTokens;
|
|
80
|
-
|
|
225
|
+
attempts;
|
|
226
|
+
providers;
|
|
227
|
+
constructor(message, primaryError, fallbackError, totalCostUSD, totalTokens, attempts = 0, providers = []) {
|
|
81
228
|
super(message);
|
|
82
229
|
this.primaryError = primaryError;
|
|
83
230
|
this.fallbackError = fallbackError;
|
|
84
231
|
this.totalCostUSD = totalCostUSD;
|
|
85
232
|
this.totalTokens = totalTokens;
|
|
233
|
+
this.attempts = attempts;
|
|
234
|
+
this.providers = providers;
|
|
86
235
|
this.name = 'LLMRetryError';
|
|
87
236
|
}
|
|
88
237
|
}
|
|
@@ -99,5 +248,8 @@ function validateOptions(options) {
|
|
|
99
248
|
if (options.maxDelayMs < options.initialDelayMs) {
|
|
100
249
|
throw new Error('maxDelayMs must be greater than or equal to initialDelayMs');
|
|
101
250
|
}
|
|
251
|
+
if (options.timeoutMs !== undefined && options.timeoutMs < 0) {
|
|
252
|
+
throw new Error('timeoutMs must be greater than or equal to 0');
|
|
253
|
+
}
|
|
102
254
|
}
|
|
103
255
|
//# sourceMappingURL=retry.js.map
|
package/dist/retry.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"retry.js","sourceRoot":"","sources":["../src/retry.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"retry.js","sourceRoot":"","sources":["../src/retry.ts"],"names":[],"mappings":"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"}
|
package/dist/types.d.ts
CHANGED
|
@@ -7,23 +7,59 @@ export interface LLMResponse<T = unknown> {
|
|
|
7
7
|
data: T;
|
|
8
8
|
usage?: TokenUsage;
|
|
9
9
|
}
|
|
10
|
+
export interface RetryAttemptContext {
|
|
11
|
+
attempt: number;
|
|
12
|
+
retryAttempt: number;
|
|
13
|
+
provider: string;
|
|
14
|
+
providerIndex: number;
|
|
15
|
+
elapsedMs: number;
|
|
16
|
+
signal?: AbortSignal;
|
|
17
|
+
lastError?: Error;
|
|
18
|
+
}
|
|
19
|
+
export interface RetryDecisionContext extends RetryAttemptContext {
|
|
20
|
+
maxRetries: number;
|
|
21
|
+
}
|
|
22
|
+
export interface RetrySuccessContext extends RetryAttemptContext {
|
|
23
|
+
costUSD: number;
|
|
24
|
+
totalCostUSD: number;
|
|
25
|
+
totalTokens: number;
|
|
26
|
+
}
|
|
27
|
+
export type LLMCall<T = unknown> = (context: RetryAttemptContext) => Promise<LLMResponse<T>>;
|
|
28
|
+
export type CostCalculator = (usage: TokenUsage, context: RetryAttemptContext) => number;
|
|
29
|
+
export type ShouldRetry = (error: Error, context: RetryDecisionContext) => boolean | Promise<boolean>;
|
|
30
|
+
export interface RetryProvider<T = unknown> {
|
|
31
|
+
name: string;
|
|
32
|
+
fn: LLMCall<T>;
|
|
33
|
+
maxRetries?: number;
|
|
34
|
+
costPer1kTokens?: number;
|
|
35
|
+
costCalculator?: CostCalculator;
|
|
36
|
+
}
|
|
10
37
|
export interface RetryOptions<T = unknown> {
|
|
11
|
-
fn
|
|
12
|
-
fallback?:
|
|
38
|
+
fn?: LLMCall<T>;
|
|
39
|
+
fallback?: LLMCall<T>;
|
|
40
|
+
providers?: Array<RetryProvider<T>>;
|
|
13
41
|
maxRetries?: number;
|
|
14
42
|
maxCostUSD?: number;
|
|
15
43
|
costPer1kTokens?: number;
|
|
44
|
+
costCalculator?: CostCalculator;
|
|
16
45
|
initialDelayMs?: number;
|
|
17
46
|
maxDelayMs?: number;
|
|
18
|
-
|
|
47
|
+
timeoutMs?: number;
|
|
48
|
+
signal?: AbortSignal;
|
|
49
|
+
shouldRetry?: ShouldRetry;
|
|
50
|
+
onAttempt?: (context: RetryAttemptContext) => void;
|
|
51
|
+
onRetry?: (attempt: number, error: Error, delayMs: number, context: RetryDecisionContext) => void;
|
|
52
|
+
onSuccess?: (context: RetrySuccessContext) => void;
|
|
53
|
+
onFailure?: (error: Error) => void;
|
|
19
54
|
onBudgetExceeded?: (spentUSD: number, limitUSD: number) => void;
|
|
20
55
|
}
|
|
21
56
|
export interface RetryResult<T = unknown> {
|
|
22
57
|
data: T;
|
|
23
58
|
attempts: number;
|
|
59
|
+
provider: string;
|
|
24
60
|
usedFallback: boolean;
|
|
25
61
|
totalCostUSD: number;
|
|
26
62
|
totalTokens: number;
|
|
27
63
|
}
|
|
28
|
-
export type RetryableError = 'rate_limit' | 'server_error' | 'timeout' | 'network_error';
|
|
64
|
+
export type RetryableError = 'rate_limit' | 'server_error' | 'timeout' | 'network_error' | 'conflict' | 'overloaded';
|
|
29
65
|
//# sourceMappingURL=types.d.ts.map
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,UAAU;IACzB,YAAY,EAAE,MAAM,CAAA;IACpB,gBAAgB,EAAE,MAAM,CAAA;IACxB,WAAW,EAAE,MAAM,CAAA;CACpB;AAED,MAAM,WAAW,WAAW,CAAC,CAAC,GAAG,OAAO;IACtC,IAAI,EAAE,CAAC,CAAA;IACP,KAAK,CAAC,EAAE,UAAU,CAAA;CACnB;AAED,MAAM,WAAW,YAAY,CAAC,CAAC,
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "llm-retry-kit",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Resilience toolkit for LLM APIs with retries, provider fallback, budget tracking, aborts, and observability.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
7
7
|
"types": "./dist/index.d.ts",
|