llm-retry-kit 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 +21 -0
- package/README.md +152 -0
- package/dist/backoff.d.ts +5 -0
- package/dist/backoff.d.ts.map +1 -0
- package/dist/backoff.js +96 -0
- package/dist/backoff.js.map +1 -0
- package/dist/budget.d.ts +15 -0
- package/dist/budget.d.ts.map +1 -0
- package/dist/budget.js +40 -0
- package/dist/budget.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -0
- package/dist/retry.d.ts +10 -0
- package/dist/retry.d.ts.map +1 -0
- package/dist/retry.js +103 -0
- package/dist/retry.js.map +1 -0
- package/dist/types.d.ts +29 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +51 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 llm-retry contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
# llm-retry-kit
|
|
2
|
+
|
|
3
|
+
Smart retry wrapper for LLM APIs. It handles transient failures, rate limits,
|
|
4
|
+
fallback calls, exponential backoff, and simple token-cost tracking.
|
|
5
|
+
|
|
6
|
+
## Install
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
npm install llm-retry-kit
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## Quick Start
|
|
13
|
+
|
|
14
|
+
```ts
|
|
15
|
+
import { llmRetry } from 'llm-retry-kit'
|
|
16
|
+
import OpenAI from 'openai'
|
|
17
|
+
|
|
18
|
+
const openai = new OpenAI()
|
|
19
|
+
|
|
20
|
+
const result = await llmRetry({
|
|
21
|
+
fn: async () => {
|
|
22
|
+
const response = await openai.chat.completions.create({
|
|
23
|
+
model: 'gpt-4o-mini',
|
|
24
|
+
messages: [{ role: 'user', content: 'Hello!' }],
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
return {
|
|
28
|
+
data: response.choices[0]?.message.content ?? '',
|
|
29
|
+
usage: response.usage
|
|
30
|
+
? {
|
|
31
|
+
promptTokens: response.usage.prompt_tokens,
|
|
32
|
+
completionTokens: response.usage.completion_tokens,
|
|
33
|
+
totalTokens: response.usage.total_tokens,
|
|
34
|
+
}
|
|
35
|
+
: undefined,
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
console.log(result.data)
|
|
41
|
+
console.log(result.attempts)
|
|
42
|
+
console.log(result.totalCostUSD)
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Fallback Example
|
|
46
|
+
|
|
47
|
+
```ts
|
|
48
|
+
import { llmRetry } from 'llm-retry-kit'
|
|
49
|
+
|
|
50
|
+
const result = await llmRetry({
|
|
51
|
+
fn: async () => callPrimaryModel(),
|
|
52
|
+
fallback: async () => callFallbackModel(),
|
|
53
|
+
maxRetries: 3,
|
|
54
|
+
initialDelayMs: 1000,
|
|
55
|
+
maxDelayMs: 30000,
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
if (result.usedFallback) {
|
|
59
|
+
console.log('Fallback model was used')
|
|
60
|
+
}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Budget Tracking
|
|
64
|
+
|
|
65
|
+
`llm-retry-kit` tracks cost from the `usage.totalTokens` value returned by your
|
|
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.
|
|
69
|
+
|
|
70
|
+
```ts
|
|
71
|
+
const result = await llmRetry({
|
|
72
|
+
fn: myLLMCall,
|
|
73
|
+
maxCostUSD: 0.5,
|
|
74
|
+
costPer1kTokens: 0.002,
|
|
75
|
+
onBudgetExceeded: (spent, limit) => {
|
|
76
|
+
console.warn(`Budget exceeded: $${spent.toFixed(4)} / $${limit}`)
|
|
77
|
+
},
|
|
78
|
+
})
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Retry Logging
|
|
82
|
+
|
|
83
|
+
```ts
|
|
84
|
+
const result = await llmRetry({
|
|
85
|
+
fn: myLLMCall,
|
|
86
|
+
maxRetries: 4,
|
|
87
|
+
initialDelayMs: 1000,
|
|
88
|
+
maxDelayMs: 60000,
|
|
89
|
+
onRetry: (attempt, error, delayMs) => {
|
|
90
|
+
console.log(`Attempt ${attempt} failed: ${error.message}`)
|
|
91
|
+
console.log(`Waiting ${(delayMs / 1000).toFixed(1)}s before retrying`)
|
|
92
|
+
},
|
|
93
|
+
})
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## API
|
|
97
|
+
|
|
98
|
+
### `llmRetry(options)`
|
|
99
|
+
|
|
100
|
+
| Option | Type | Default | Description |
|
|
101
|
+
| --- | --- | --- | --- |
|
|
102
|
+
| `fn` | `() => Promise<LLMResponse<T>>` | required | Primary async LLM call. |
|
|
103
|
+
| `fallback` | `() => Promise<LLMResponse<T>>` | optional | Backup async LLM call. |
|
|
104
|
+
| `maxRetries` | `number` | `3` | Number of retries after the first attempt. |
|
|
105
|
+
| `maxCostUSD` | `number` | optional | Maximum tracked cost in USD. |
|
|
106
|
+
| `costPer1kTokens` | `number` | `0.002` | Estimated cost per 1,000 tokens. |
|
|
107
|
+
| `initialDelayMs` | `number` | `1000` | Initial retry delay. |
|
|
108
|
+
| `maxDelayMs` | `number` | `30000` | Maximum retry delay. |
|
|
109
|
+
| `onRetry` | `(attempt, error, delayMs) => void` | optional | Called before each retry wait. |
|
|
110
|
+
| `onBudgetExceeded` | `(spentUSD, limitUSD) => void` | optional | Called when tracked budget is exhausted. |
|
|
111
|
+
|
|
112
|
+
### `LLMResponse<T>`
|
|
113
|
+
|
|
114
|
+
```ts
|
|
115
|
+
{
|
|
116
|
+
data: T
|
|
117
|
+
usage?: {
|
|
118
|
+
promptTokens: number
|
|
119
|
+
completionTokens: number
|
|
120
|
+
totalTokens: number
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### `RetryResult<T>`
|
|
126
|
+
|
|
127
|
+
```ts
|
|
128
|
+
{
|
|
129
|
+
data: T
|
|
130
|
+
attempts: number
|
|
131
|
+
usedFallback: boolean
|
|
132
|
+
totalCostUSD: number
|
|
133
|
+
totalTokens: number
|
|
134
|
+
}
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
## Retryable Errors
|
|
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
|
|
146
|
+
|
|
147
|
+
Authentication errors, invalid requests, and other non-transient failures are
|
|
148
|
+
not retried.
|
|
149
|
+
|
|
150
|
+
## License
|
|
151
|
+
|
|
152
|
+
MIT
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export declare function calculateBackoff(attempt: number, initialDelayMs: number, maxDelayMs: number): number;
|
|
2
|
+
export declare function sleep(ms: number): Promise<void>;
|
|
3
|
+
export declare function isRetryableError(error: unknown): boolean;
|
|
4
|
+
export declare function extractRetryAfter(error: unknown): number | null;
|
|
5
|
+
//# sourceMappingURL=backoff.d.ts.map
|
|
@@ -0,0 +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,CAE/C;AAED,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAiDxD;AAED,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAyB/D"}
|
package/dist/backoff.js
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
export function calculateBackoff(attempt, initialDelayMs, maxDelayMs) {
|
|
2
|
+
const exponential = initialDelayMs * Math.pow(2, attempt);
|
|
3
|
+
const jitter = exponential * 0.25 * (Math.random() * 2 - 1);
|
|
4
|
+
const delay = exponential + jitter;
|
|
5
|
+
return Math.min(Math.max(delay, initialDelayMs), maxDelayMs);
|
|
6
|
+
}
|
|
7
|
+
export function sleep(ms) {
|
|
8
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
9
|
+
}
|
|
10
|
+
export function isRetryableError(error) {
|
|
11
|
+
if (!(error instanceof Error))
|
|
12
|
+
return false;
|
|
13
|
+
const err = error;
|
|
14
|
+
const status = toNumber(err.status ?? err.statusCode);
|
|
15
|
+
if (status === 429 || (status !== null && status >= 500 && status <= 599)) {
|
|
16
|
+
return true;
|
|
17
|
+
}
|
|
18
|
+
const code = typeof err.code === 'string' ? err.code.toLowerCase() : '';
|
|
19
|
+
if ([
|
|
20
|
+
'etimedout',
|
|
21
|
+
'econnreset',
|
|
22
|
+
'econnrefused',
|
|
23
|
+
'enotfound',
|
|
24
|
+
'eai_again',
|
|
25
|
+
'rate_limit_exceeded',
|
|
26
|
+
].includes(code)) {
|
|
27
|
+
return true;
|
|
28
|
+
}
|
|
29
|
+
const message = error.message.toLowerCase();
|
|
30
|
+
const retryablePatterns = [
|
|
31
|
+
'rate limit',
|
|
32
|
+
'rate_limit',
|
|
33
|
+
'too many requests',
|
|
34
|
+
'429',
|
|
35
|
+
'server error',
|
|
36
|
+
'500',
|
|
37
|
+
'502',
|
|
38
|
+
'503',
|
|
39
|
+
'504',
|
|
40
|
+
'timeout',
|
|
41
|
+
'timed out',
|
|
42
|
+
'econnreset',
|
|
43
|
+
'econnrefused',
|
|
44
|
+
'network',
|
|
45
|
+
'socket',
|
|
46
|
+
'overloaded',
|
|
47
|
+
];
|
|
48
|
+
return retryablePatterns.some((pattern) => message.includes(pattern));
|
|
49
|
+
}
|
|
50
|
+
export function extractRetryAfter(error) {
|
|
51
|
+
if (!error || typeof error !== 'object')
|
|
52
|
+
return null;
|
|
53
|
+
const err = error;
|
|
54
|
+
const retryAfter = toNumber(err['retryAfter'] ?? err['retry-after']);
|
|
55
|
+
if (retryAfter !== null) {
|
|
56
|
+
return retryAfter * 1000;
|
|
57
|
+
}
|
|
58
|
+
const headerValue = getHeader(err['headers'], 'retry-after');
|
|
59
|
+
if (!headerValue) {
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
const seconds = Number(headerValue);
|
|
63
|
+
if (Number.isFinite(seconds)) {
|
|
64
|
+
return seconds * 1000;
|
|
65
|
+
}
|
|
66
|
+
const dateMs = Date.parse(headerValue);
|
|
67
|
+
if (Number.isFinite(dateMs)) {
|
|
68
|
+
return Math.max(dateMs - Date.now(), 0);
|
|
69
|
+
}
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
function toNumber(value) {
|
|
73
|
+
if (typeof value === 'number' && Number.isFinite(value)) {
|
|
74
|
+
return value;
|
|
75
|
+
}
|
|
76
|
+
if (typeof value === 'string' && value.trim() !== '') {
|
|
77
|
+
const parsed = Number(value);
|
|
78
|
+
return Number.isFinite(parsed) ? parsed : null;
|
|
79
|
+
}
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
function getHeader(headers, name) {
|
|
83
|
+
if (!headers || typeof headers !== 'object')
|
|
84
|
+
return null;
|
|
85
|
+
if ('get' in headers && typeof headers.get === 'function') {
|
|
86
|
+
const value = headers.get(name);
|
|
87
|
+
return typeof value === 'string' ? value : null;
|
|
88
|
+
}
|
|
89
|
+
const record = headers;
|
|
90
|
+
const matchedKey = Object.keys(record).find((key) => key.toLowerCase() === name.toLowerCase());
|
|
91
|
+
if (!matchedKey)
|
|
92
|
+
return null;
|
|
93
|
+
const value = record[matchedKey];
|
|
94
|
+
return typeof value === 'string' ? value : null;
|
|
95
|
+
}
|
|
96
|
+
//# sourceMappingURL=backoff.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"backoff.js","sourceRoot":"","sources":["../src/backoff.ts"],"names":[],"mappings":"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"}
|
package/dist/budget.d.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { TokenUsage } from './types.js';
|
|
2
|
+
export declare class BudgetTracker {
|
|
3
|
+
private totalTokens;
|
|
4
|
+
private totalCostUSD;
|
|
5
|
+
private readonly costPer1kTokens;
|
|
6
|
+
private readonly maxCostUSD;
|
|
7
|
+
constructor(costPer1kTokens: number, maxCostUSD?: number);
|
|
8
|
+
add(usage: TokenUsage): void;
|
|
9
|
+
isExceeded(): boolean;
|
|
10
|
+
get spent(): number;
|
|
11
|
+
get tokens(): number;
|
|
12
|
+
get limit(): number | null;
|
|
13
|
+
summary(): string;
|
|
14
|
+
}
|
|
15
|
+
//# sourceMappingURL=budget.d.ts.map
|
|
@@ -0,0 +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;IAK5B,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
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
export class BudgetTracker {
|
|
2
|
+
totalTokens = 0;
|
|
3
|
+
totalCostUSD = 0;
|
|
4
|
+
costPer1kTokens;
|
|
5
|
+
maxCostUSD;
|
|
6
|
+
constructor(costPer1kTokens, maxCostUSD) {
|
|
7
|
+
if (costPer1kTokens < 0) {
|
|
8
|
+
throw new Error('costPer1kTokens must be greater than or equal to 0');
|
|
9
|
+
}
|
|
10
|
+
if (maxCostUSD !== undefined && maxCostUSD < 0) {
|
|
11
|
+
throw new Error('maxCostUSD must be greater than or equal to 0');
|
|
12
|
+
}
|
|
13
|
+
this.costPer1kTokens = costPer1kTokens;
|
|
14
|
+
this.maxCostUSD = maxCostUSD ?? null;
|
|
15
|
+
}
|
|
16
|
+
add(usage) {
|
|
17
|
+
this.totalTokens += usage.totalTokens;
|
|
18
|
+
this.totalCostUSD += (usage.totalTokens / 1000) * this.costPer1kTokens;
|
|
19
|
+
}
|
|
20
|
+
isExceeded() {
|
|
21
|
+
if (this.maxCostUSD === null)
|
|
22
|
+
return false;
|
|
23
|
+
return this.totalCostUSD >= this.maxCostUSD;
|
|
24
|
+
}
|
|
25
|
+
get spent() {
|
|
26
|
+
return this.totalCostUSD;
|
|
27
|
+
}
|
|
28
|
+
get tokens() {
|
|
29
|
+
return this.totalTokens;
|
|
30
|
+
}
|
|
31
|
+
get limit() {
|
|
32
|
+
return this.maxCostUSD;
|
|
33
|
+
}
|
|
34
|
+
summary() {
|
|
35
|
+
const cost = this.totalCostUSD.toFixed(4);
|
|
36
|
+
const limit = this.maxCostUSD !== null ? `/ $${this.maxCostUSD}` : '';
|
|
37
|
+
return `$${cost}${limit} (${this.totalTokens.toLocaleString()} tokens)`;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
//# sourceMappingURL=budget.js.map
|
|
@@ -0,0 +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;QACnB,IAAI,CAAC,WAAW,IAAI,KAAK,CAAC,WAAW,CAAA;QACrC,IAAI,CAAC,YAAY,IAAI,CAAC,KAAK,CAAC,WAAW,GAAG,IAAI,CAAC,GAAG,IAAI,CAAC,eAAe,CAAA;IACxE,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
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { llmRetry, LLMRetryError } from './retry.js';
|
|
2
|
+
export { BudgetTracker } from './budget.js';
|
|
3
|
+
export { calculateBackoff, isRetryableError } from './backoff.js';
|
|
4
|
+
export type { RetryOptions, RetryResult, TokenUsage, LLMResponse, RetryableError, } from './types.js';
|
|
5
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +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;AACjE,YAAY,EACV,YAAY,EACZ,WAAW,EACX,UAAU,EACV,WAAW,EACX,cAAc,GACf,MAAM,YAAY,CAAA"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +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"}
|
package/dist/retry.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { RetryOptions, RetryResult } from './types.js';
|
|
2
|
+
export declare function llmRetry<T>(options: RetryOptions<T>): Promise<RetryResult<T>>;
|
|
3
|
+
export declare class LLMRetryError extends Error {
|
|
4
|
+
readonly primaryError: Error | null;
|
|
5
|
+
readonly fallbackError: Error | null;
|
|
6
|
+
readonly totalCostUSD: number;
|
|
7
|
+
readonly totalTokens: number;
|
|
8
|
+
constructor(message: string, primaryError: Error | null, fallbackError: Error | null, totalCostUSD: number, totalTokens: number);
|
|
9
|
+
}
|
|
10
|
+
//# sourceMappingURL=retry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"retry.d.ts","sourceRoot":"","sources":["../src/retry.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,YAAY,CAAA;AAE3D,wBAAsB,QAAQ,CAAC,CAAC,EAAE,OAAO,EAAE,YAAY,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,CAgHnF;AAeD,qBAAa,aAAc,SAAQ,KAAK;aAGpB,YAAY,EAAE,KAAK,GAAG,IAAI;aAC1B,aAAa,EAAE,KAAK,GAAG,IAAI;aAC3B,YAAY,EAAE,MAAM;aACpB,WAAW,EAAE,MAAM;gBAJnC,OAAO,EAAE,MAAM,EACC,YAAY,EAAE,KAAK,GAAG,IAAI,EAC1B,aAAa,EAAE,KAAK,GAAG,IAAI,EAC3B,YAAY,EAAE,MAAM,EACpB,WAAW,EAAE,MAAM;CAKtC"}
|
package/dist/retry.js
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { calculateBackoff, extractRetryAfter, isRetryableError, sleep } from './backoff.js';
|
|
2
|
+
import { BudgetTracker } from './budget.js';
|
|
3
|
+
export async function llmRetry(options) {
|
|
4
|
+
const { fn, fallback, maxRetries = 3, maxCostUSD, costPer1kTokens = 0.002, initialDelayMs = 1000, maxDelayMs = 30000, onRetry, onBudgetExceeded, } = options;
|
|
5
|
+
validateOptions({ maxRetries, initialDelayMs, maxDelayMs });
|
|
6
|
+
const budget = new BudgetTracker(costPer1kTokens, maxCostUSD);
|
|
7
|
+
let lastError = null;
|
|
8
|
+
let attempts = 0;
|
|
9
|
+
let budgetExceededNotified = false;
|
|
10
|
+
for (let retryIndex = 0; retryIndex <= maxRetries; retryIndex++) {
|
|
11
|
+
if (budget.isExceeded()) {
|
|
12
|
+
budgetExceededNotified = notifyBudgetExceeded(budget.spent, budget.limit, onBudgetExceeded, budgetExceededNotified);
|
|
13
|
+
break;
|
|
14
|
+
}
|
|
15
|
+
attempts += 1;
|
|
16
|
+
try {
|
|
17
|
+
const response = await fn();
|
|
18
|
+
if (response.usage) {
|
|
19
|
+
budget.add(response.usage);
|
|
20
|
+
}
|
|
21
|
+
return {
|
|
22
|
+
data: response.data,
|
|
23
|
+
attempts,
|
|
24
|
+
usedFallback: false,
|
|
25
|
+
totalCostUSD: budget.spent,
|
|
26
|
+
totalTokens: budget.tokens,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
catch (error) {
|
|
30
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
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);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
if (fallback && !budget.isExceeded()) {
|
|
43
|
+
attempts += 1;
|
|
44
|
+
try {
|
|
45
|
+
const response = await fallback();
|
|
46
|
+
if (response.usage) {
|
|
47
|
+
budget.add(response.usage);
|
|
48
|
+
}
|
|
49
|
+
return {
|
|
50
|
+
data: response.data,
|
|
51
|
+
attempts,
|
|
52
|
+
usedFallback: true,
|
|
53
|
+
totalCostUSD: budget.spent,
|
|
54
|
+
totalTokens: budget.tokens,
|
|
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);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
if (budget.isExceeded()) {
|
|
65
|
+
notifyBudgetExceeded(budget.spent, budget.limit, onBudgetExceeded, budgetExceededNotified);
|
|
66
|
+
}
|
|
67
|
+
throw new LLMRetryError(`LLM call failed after ${attempts} attempt${attempts === 1 ? '' : 's'}: ${lastError?.message ?? 'budget exceeded'}`, lastError, null, budget.spent, budget.tokens);
|
|
68
|
+
}
|
|
69
|
+
function notifyBudgetExceeded(spentUSD, limitUSD, callback, alreadyNotified) {
|
|
70
|
+
if (!alreadyNotified && limitUSD !== null) {
|
|
71
|
+
callback?.(spentUSD, limitUSD);
|
|
72
|
+
}
|
|
73
|
+
return true;
|
|
74
|
+
}
|
|
75
|
+
export class LLMRetryError extends Error {
|
|
76
|
+
primaryError;
|
|
77
|
+
fallbackError;
|
|
78
|
+
totalCostUSD;
|
|
79
|
+
totalTokens;
|
|
80
|
+
constructor(message, primaryError, fallbackError, totalCostUSD, totalTokens) {
|
|
81
|
+
super(message);
|
|
82
|
+
this.primaryError = primaryError;
|
|
83
|
+
this.fallbackError = fallbackError;
|
|
84
|
+
this.totalCostUSD = totalCostUSD;
|
|
85
|
+
this.totalTokens = totalTokens;
|
|
86
|
+
this.name = 'LLMRetryError';
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
function validateOptions(options) {
|
|
90
|
+
if (!Number.isInteger(options.maxRetries) || options.maxRetries < 0) {
|
|
91
|
+
throw new Error('maxRetries must be a non-negative integer');
|
|
92
|
+
}
|
|
93
|
+
if (options.initialDelayMs < 0) {
|
|
94
|
+
throw new Error('initialDelayMs must be greater than or equal to 0');
|
|
95
|
+
}
|
|
96
|
+
if (options.maxDelayMs < 0) {
|
|
97
|
+
throw new Error('maxDelayMs must be greater than or equal to 0');
|
|
98
|
+
}
|
|
99
|
+
if (options.maxDelayMs < options.initialDelayMs) {
|
|
100
|
+
throw new Error('maxDelayMs must be greater than or equal to initialDelayMs');
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
//# sourceMappingURL=retry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"retry.js","sourceRoot":"","sources":["../src/retry.ts"],"names":[],"mappings":"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"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export interface TokenUsage {
|
|
2
|
+
promptTokens: number;
|
|
3
|
+
completionTokens: number;
|
|
4
|
+
totalTokens: number;
|
|
5
|
+
}
|
|
6
|
+
export interface LLMResponse<T = unknown> {
|
|
7
|
+
data: T;
|
|
8
|
+
usage?: TokenUsage;
|
|
9
|
+
}
|
|
10
|
+
export interface RetryOptions<T = unknown> {
|
|
11
|
+
fn: () => Promise<LLMResponse<T>>;
|
|
12
|
+
fallback?: () => Promise<LLMResponse<T>>;
|
|
13
|
+
maxRetries?: number;
|
|
14
|
+
maxCostUSD?: number;
|
|
15
|
+
costPer1kTokens?: number;
|
|
16
|
+
initialDelayMs?: number;
|
|
17
|
+
maxDelayMs?: number;
|
|
18
|
+
onRetry?: (attempt: number, error: Error, delayMs: number) => void;
|
|
19
|
+
onBudgetExceeded?: (spentUSD: number, limitUSD: number) => void;
|
|
20
|
+
}
|
|
21
|
+
export interface RetryResult<T = unknown> {
|
|
22
|
+
data: T;
|
|
23
|
+
attempts: number;
|
|
24
|
+
usedFallback: boolean;
|
|
25
|
+
totalCostUSD: number;
|
|
26
|
+
totalTokens: number;
|
|
27
|
+
}
|
|
28
|
+
export type RetryableError = 'rate_limit' | 'server_error' | 'timeout' | 'network_error';
|
|
29
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +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,GAAG,OAAO;IACvC,EAAE,EAAE,MAAM,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,CAAA;IACjC,QAAQ,CAAC,EAAE,MAAM,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,CAAA;IACxC,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,KAAK,IAAI,CAAA;IAClE,gBAAgB,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAA;CAChE;AAED,MAAM,WAAW,WAAW,CAAC,CAAC,GAAG,OAAO;IACtC,IAAI,EAAE,CAAC,CAAA;IACP,QAAQ,EAAE,MAAM,CAAA;IAChB,YAAY,EAAE,OAAO,CAAA;IACrB,YAAY,EAAE,MAAM,CAAA;IACpB,WAAW,EAAE,MAAM,CAAA;CACpB;AAED,MAAM,MAAM,cAAc,GACtB,YAAY,GACZ,cAAc,GACd,SAAS,GACT,eAAe,CAAA"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
|
package/package.json
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "llm-retry-kit",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Smart retry wrapper for LLM APIs with fallback, budget tracking, and exponential backoff.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.js"
|
|
12
|
+
},
|
|
13
|
+
"./package.json": "./package.json"
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist",
|
|
17
|
+
"README.md",
|
|
18
|
+
"LICENSE"
|
|
19
|
+
],
|
|
20
|
+
"sideEffects": false,
|
|
21
|
+
"engines": {
|
|
22
|
+
"node": ">=18"
|
|
23
|
+
},
|
|
24
|
+
"scripts": {
|
|
25
|
+
"clean": "node -e \"fs.rmSync('dist', { recursive: true, force: true })\"",
|
|
26
|
+
"build": "npm run clean && tsc -p tsconfig.json",
|
|
27
|
+
"test": "vitest run",
|
|
28
|
+
"test:watch": "vitest",
|
|
29
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
30
|
+
"prepack": "npm run build",
|
|
31
|
+
"prepublishOnly": "npm test && npm run typecheck"
|
|
32
|
+
},
|
|
33
|
+
"keywords": [
|
|
34
|
+
"llm",
|
|
35
|
+
"openai",
|
|
36
|
+
"anthropic",
|
|
37
|
+
"retry",
|
|
38
|
+
"rate-limit",
|
|
39
|
+
"fallback",
|
|
40
|
+
"backoff",
|
|
41
|
+
"ai",
|
|
42
|
+
"gpt",
|
|
43
|
+
"claude"
|
|
44
|
+
],
|
|
45
|
+
"author": "llm-retry contributors",
|
|
46
|
+
"license": "MIT",
|
|
47
|
+
"devDependencies": {
|
|
48
|
+
"typescript": "^5.4.0",
|
|
49
|
+
"vitest": "^4.1.8"
|
|
50
|
+
}
|
|
51
|
+
}
|