@sprqvntrs/llm 3.13.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 +595 -0
- package/index.ts +53 -0
- package/package.json +56 -0
- package/src/clients/anthropic-client.ts +636 -0
- package/src/clients/openai-client.ts +548 -0
- package/src/clients/openrouter-client.ts +802 -0
- package/src/helpers.ts +185 -0
- package/src/llm.ts +107 -0
- package/src/model-types.ts +61 -0
- package/src/models.ts +98 -0
- package/src/pricing-data.json +1651 -0
- package/src/pricing.ts +36 -0
- package/src/types/client-interface.ts +222 -0
- package/src/utils/debug.ts +120 -0
- package/src/utils/errors.ts +508 -0
- package/src/utils/normalize-null-strings.ts +103 -0
- package/src/utils/resolve-refs.ts +91 -0
- package/src/utils/strip-json-artifacts.ts +98 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025-2026 SPRQVNTRS
|
|
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,595 @@
|
|
|
1
|
+
# @sprqvntrs/llm
|
|
2
|
+
|
|
3
|
+
A unified LLM client library for seamless integration with multiple AI providers (OpenAI and Anthropic). This package provides a consistent API across different LLM providers with built-in support for structured outputs, batch processing, embeddings, and web search.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Unified Interface**: Single consistent API for both OpenAI and Anthropic
|
|
8
|
+
- **Structured Outputs**: Generate responses that match Zod schemas with automatic validation
|
|
9
|
+
- **Multi-Provider Support**: Seamlessly switch between OpenAI and Anthropic
|
|
10
|
+
- **Web Search Integration**: Enable web search capabilities for real-time information
|
|
11
|
+
- **Batch Processing**: Process multiple items in parallel with configurable batch sizes
|
|
12
|
+
- **Embeddings**: Generate embeddings for semantic search and similarity tasks
|
|
13
|
+
- **Language Validation**: Check and validate content language
|
|
14
|
+
- **Retry Logic**: Built-in retry mechanisms with configurable attempts
|
|
15
|
+
- **Type Safety**: Full TypeScript support with provider-specific model type inference
|
|
16
|
+
|
|
17
|
+
## Installation
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npm install @sprqvntrs/llm
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Quick Start
|
|
24
|
+
|
|
25
|
+
### Basic Usage
|
|
26
|
+
|
|
27
|
+
```typescript
|
|
28
|
+
import { LLM } from '@sprqvntrs/llm';
|
|
29
|
+
import { z } from 'zod';
|
|
30
|
+
|
|
31
|
+
// Get a client for your chosen provider
|
|
32
|
+
const client = LLM.getClient('openai', 'gpt-4o');
|
|
33
|
+
|
|
34
|
+
// Validate configuration
|
|
35
|
+
client.validateConfiguration(); // throws if not configured
|
|
36
|
+
|
|
37
|
+
// Create a structured response
|
|
38
|
+
const schema = z.object({
|
|
39
|
+
summary: z.string(),
|
|
40
|
+
sentiment: z.enum(['positive', 'negative', 'neutral']),
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
const result = await client.createStructuredResponse({
|
|
44
|
+
prompt: 'Analyze this text and provide a summary with sentiment.',
|
|
45
|
+
schema,
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
console.log(result.summary); // Structured output
|
|
49
|
+
console.log(result.sentiment); // Type-safe enum
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### Environment Setup
|
|
53
|
+
|
|
54
|
+
Set up your API keys as environment variables:
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
# For OpenAI
|
|
58
|
+
export OPENAI_API_KEY="sk-..."
|
|
59
|
+
|
|
60
|
+
# For Anthropic
|
|
61
|
+
export ANTHROPIC_API_KEY="sk-ant-..."
|
|
62
|
+
|
|
63
|
+
# For Anthropic with structured output support (optional)
|
|
64
|
+
# Anthropic will use OpenAI for formatting if available
|
|
65
|
+
export OPENAI_API_KEY="sk-..."
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Alternatively, pass API keys directly:
|
|
69
|
+
|
|
70
|
+
```typescript
|
|
71
|
+
const client = LLM.getClient('openai', 'gpt-4o', {
|
|
72
|
+
apiKey: 'sk-your-key-here',
|
|
73
|
+
});
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Core API
|
|
77
|
+
|
|
78
|
+
### LLM.getClient()
|
|
79
|
+
|
|
80
|
+
Factory method to create a client for a specific provider.
|
|
81
|
+
|
|
82
|
+
```typescript
|
|
83
|
+
const client = LLM.getClient(provider, model, options?);
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
**Parameters:**
|
|
87
|
+
- `provider: 'openai' | 'anthropic'` - The LLM provider
|
|
88
|
+
- `model: string` - The model identifier
|
|
89
|
+
- `options?: LlmClientOptions` - Optional configuration
|
|
90
|
+
|
|
91
|
+
**Options:**
|
|
92
|
+
```typescript
|
|
93
|
+
{
|
|
94
|
+
apiKey?: string; // Override environment API key
|
|
95
|
+
debug?: boolean; // Enable debug logging
|
|
96
|
+
useReasoningMode?: boolean; // Enable reasoning endpoints (OpenAI only)
|
|
97
|
+
openaiApiKey?: string; // OpenAI key for Anthropic formatting
|
|
98
|
+
}
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
**Returns:** `LlmClientInterface` - A client implementing the unified interface
|
|
102
|
+
|
|
103
|
+
**Example:**
|
|
104
|
+
```typescript
|
|
105
|
+
// Using environment variables
|
|
106
|
+
const client1 = LLM.getClient('openai', 'gpt-4o');
|
|
107
|
+
|
|
108
|
+
// Override with explicit keys
|
|
109
|
+
const client2 = LLM.getClient('anthropic', 'claude-sonnet-4-5-20250929', {
|
|
110
|
+
apiKey: process.env.ANTHROPIC_KEY,
|
|
111
|
+
openaiApiKey: process.env.OPENAI_KEY,
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
// Enable debug mode
|
|
115
|
+
const client3 = LLM.getClient('openai', 'gpt-4o', { debug: true });
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### createStructuredResponse()
|
|
119
|
+
|
|
120
|
+
Generate a response that conforms to a Zod schema with automatic validation and retry logic.
|
|
121
|
+
|
|
122
|
+
```typescript
|
|
123
|
+
const result = await client.createStructuredResponse({
|
|
124
|
+
prompt: string;
|
|
125
|
+
schema: ZodSchema;
|
|
126
|
+
formatGuidance?: string;
|
|
127
|
+
reasoningEffort?: 'low' | 'medium' | 'high';
|
|
128
|
+
maxAttempts?: number;
|
|
129
|
+
logExecutionTime?: boolean;
|
|
130
|
+
useWebSearch?: boolean;
|
|
131
|
+
});
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
**Parameters:**
|
|
135
|
+
- `prompt` - The prompt to send to the model
|
|
136
|
+
- `schema` - A Zod schema defining the expected response structure
|
|
137
|
+
- `formatGuidance` - Optional guidance for formatting the response
|
|
138
|
+
- `reasoningEffort` - Level of reasoning (OpenAI o1 models only)
|
|
139
|
+
- `'low'` - Minimal reasoning
|
|
140
|
+
- `'medium'` - Standard reasoning
|
|
141
|
+
- `'high'` - Extended reasoning
|
|
142
|
+
- `maxAttempts` - Retry attempts if validation fails (default: 1)
|
|
143
|
+
- `logExecutionTime` - Log warnings for slow executions
|
|
144
|
+
- `useWebSearch` - Enable web search for this request
|
|
145
|
+
|
|
146
|
+
**Returns:** Promise resolving to typed object matching the schema
|
|
147
|
+
|
|
148
|
+
**Example:**
|
|
149
|
+
```typescript
|
|
150
|
+
const schema = z.object({
|
|
151
|
+
title: z.string(),
|
|
152
|
+
sections: z.array(z.object({
|
|
153
|
+
heading: z.string(),
|
|
154
|
+
content: z.string(),
|
|
155
|
+
})),
|
|
156
|
+
metadata: z.object({
|
|
157
|
+
wordCount: z.number(),
|
|
158
|
+
estimatedReadTime: z.number(),
|
|
159
|
+
}),
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
const article = await client.createStructuredResponse({
|
|
163
|
+
prompt: 'Create a blog post about TypeScript best practices',
|
|
164
|
+
schema,
|
|
165
|
+
formatGuidance: 'Ensure all sections are well-structured and informative',
|
|
166
|
+
maxAttempts: 3, // Retry up to 3 times if validation fails
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
console.log(article.title);
|
|
170
|
+
console.log(article.sections[0].heading);
|
|
171
|
+
console.log(article.metadata.wordCount);
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
### processBatchWithLLM()
|
|
175
|
+
|
|
176
|
+
Process a batch of items in parallel, automatically chunking into smaller batches.
|
|
177
|
+
|
|
178
|
+
```typescript
|
|
179
|
+
const results = await client.processBatchWithLLM(
|
|
180
|
+
items,
|
|
181
|
+
processFn,
|
|
182
|
+
batchSize?
|
|
183
|
+
);
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
**Parameters:**
|
|
187
|
+
- `items: T[]` - Array of items to process
|
|
188
|
+
- `processFn: (batch: T[]) => Promise<R[]>` - Function to process each batch
|
|
189
|
+
- `batchSize?: number` - Items per batch (default: provider-specific)
|
|
190
|
+
|
|
191
|
+
**Returns:** Promise resolving to flattened array of results
|
|
192
|
+
|
|
193
|
+
**Example:**
|
|
194
|
+
```typescript
|
|
195
|
+
const articles = ['article1', 'article2', 'article3', 'article4'];
|
|
196
|
+
|
|
197
|
+
const summaries = await client.processBatchWithLLM(
|
|
198
|
+
articles,
|
|
199
|
+
async (batch) => {
|
|
200
|
+
// This function is called with [2 items] at a time
|
|
201
|
+
return Promise.all(batch.map(article =>
|
|
202
|
+
client.createStructuredResponse({
|
|
203
|
+
prompt: `Summarize: ${article}`,
|
|
204
|
+
schema: z.object({ summary: z.string() }),
|
|
205
|
+
})
|
|
206
|
+
));
|
|
207
|
+
},
|
|
208
|
+
2, // Process 2 items at a time
|
|
209
|
+
);
|
|
210
|
+
|
|
211
|
+
console.log(summaries); // All summaries processed efficiently
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
### generateEmbedding()
|
|
215
|
+
|
|
216
|
+
Generate a vector embedding for semantic search and similarity tasks.
|
|
217
|
+
|
|
218
|
+
```typescript
|
|
219
|
+
const embedding = await client.generateEmbedding(text);
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
**Parameters:**
|
|
223
|
+
- `text: string` - The text to embed
|
|
224
|
+
|
|
225
|
+
**Returns:** Promise resolving to numeric array (vector)
|
|
226
|
+
|
|
227
|
+
**Example:**
|
|
228
|
+
```typescript
|
|
229
|
+
const embedding = await client.generateEmbedding(
|
|
230
|
+
'The quick brown fox jumps over the lazy dog'
|
|
231
|
+
);
|
|
232
|
+
|
|
233
|
+
console.log(embedding.length); // 3072 for text-embedding-3-large
|
|
234
|
+
// Store in vector database for similarity search
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
### validateConfiguration()
|
|
238
|
+
|
|
239
|
+
Check if the client is properly configured with valid credentials.
|
|
240
|
+
|
|
241
|
+
```typescript
|
|
242
|
+
try {
|
|
243
|
+
client.validateConfiguration();
|
|
244
|
+
console.log('Client is ready to use');
|
|
245
|
+
} catch (error) {
|
|
246
|
+
console.error('Configuration error:', error.message);
|
|
247
|
+
}
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
## Helper Functions
|
|
251
|
+
|
|
252
|
+
### isContentInLanguage()
|
|
253
|
+
|
|
254
|
+
Validate that content is written in the expected language.
|
|
255
|
+
|
|
256
|
+
```typescript
|
|
257
|
+
import { isContentInLanguage } from '@sprqvntrs/llm';
|
|
258
|
+
|
|
259
|
+
const isEnglish = await isContentInLanguage({
|
|
260
|
+
llm: client,
|
|
261
|
+
content: 'Hello, how are you?',
|
|
262
|
+
expectedLanguage: 'English',
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
console.log(isEnglish); // true
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
### formatContentToStructure()
|
|
269
|
+
|
|
270
|
+
Convert unstructured content (from an LLM or other source) into a structured format.
|
|
271
|
+
|
|
272
|
+
```typescript
|
|
273
|
+
import { formatContentToStructure } from '@sprqvntrs/llm';
|
|
274
|
+
|
|
275
|
+
const schema = z.object({
|
|
276
|
+
name: z.string(),
|
|
277
|
+
age: z.number(),
|
|
278
|
+
email: z.string().email(),
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
const structured = await formatContentToStructure({
|
|
282
|
+
llm: client,
|
|
283
|
+
unstructuredContent: 'John is 30 years old and his email is john@example.com',
|
|
284
|
+
schema,
|
|
285
|
+
additionalInstructions: 'Ensure email format is valid',
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
console.log(structured.name); // "John"
|
|
289
|
+
console.log(structured.age); // 30
|
|
290
|
+
console.log(structured.email); // "john@example.com"
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
### generateAndFormatWithLanguageCheck()
|
|
294
|
+
|
|
295
|
+
Orchestrate content generation with language validation and formatting in a single call.
|
|
296
|
+
|
|
297
|
+
```typescript
|
|
298
|
+
import { generateAndFormatWithLanguageCheck } from '@sprqvntrs/llm';
|
|
299
|
+
|
|
300
|
+
const schema = z.object({
|
|
301
|
+
response: z.string(),
|
|
302
|
+
isValid: z.boolean(),
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
const result = await generateAndFormatWithLanguageCheck({
|
|
306
|
+
generateContent: async () => {
|
|
307
|
+
// Function that generates the initial content
|
|
308
|
+
const completion = await client.createStructuredResponse({
|
|
309
|
+
prompt: 'Generate a story about adventure',
|
|
310
|
+
schema: z.object({ text: z.string() }),
|
|
311
|
+
});
|
|
312
|
+
return completion.text;
|
|
313
|
+
},
|
|
314
|
+
languageCheckLlm: client,
|
|
315
|
+
formatterLlm: client,
|
|
316
|
+
expectedLanguage: 'English',
|
|
317
|
+
schema,
|
|
318
|
+
maxAttempts: 3,
|
|
319
|
+
additionalInstructions: 'Ensure proper formatting',
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
console.log(result.response); // Formatted content
|
|
323
|
+
console.log(result.languageCorrect); // Language validation result
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
## Model Types and Configuration
|
|
327
|
+
|
|
328
|
+
### Available Models
|
|
329
|
+
|
|
330
|
+
The package includes pre-configured default models for common tasks:
|
|
331
|
+
|
|
332
|
+
```typescript
|
|
333
|
+
import { DEFAULT_MODELS } from '@sprqvntrs/llm';
|
|
334
|
+
|
|
335
|
+
// Access default configurations
|
|
336
|
+
DEFAULT_MODELS.OPENAI_DEFAULT; // gpt-5-mini-2025-08-07
|
|
337
|
+
DEFAULT_MODELS.ANTHROPIC_DEFAULT; // claude-sonnet-4-5-20250929
|
|
338
|
+
DEFAULT_MODELS.STRUCTURED_FORMATTER; // gpt-5-mini-2025-08-07 (for Anthropic formatting)
|
|
339
|
+
DEFAULT_MODELS.LANGUAGE_DETECTOR; // gpt-5-nano-2025-08-07
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
### Type-Safe Model Selection
|
|
343
|
+
|
|
344
|
+
The package provides TypeScript types that enable IDE autocomplete for provider-specific models:
|
|
345
|
+
|
|
346
|
+
```typescript
|
|
347
|
+
import type { ModelConfig, OpenAIModel, AnthropicModel } from '@sprqvntrs/llm';
|
|
348
|
+
|
|
349
|
+
// Type-safe configuration with autocomplete
|
|
350
|
+
const openaiConfig: ModelConfig<'openai'> = {
|
|
351
|
+
provider: 'openai',
|
|
352
|
+
model: 'gpt-4o', // Autocomplete shows only OpenAI models
|
|
353
|
+
};
|
|
354
|
+
|
|
355
|
+
const anthropicConfig: ModelConfig<'anthropic'> = {
|
|
356
|
+
provider: 'anthropic',
|
|
357
|
+
model: 'claude-sonnet-4-5-20250929', // Autocomplete shows only Anthropic models
|
|
358
|
+
};
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
### Web Search Configuration
|
|
362
|
+
|
|
363
|
+
Enable real-time web search for responses:
|
|
364
|
+
|
|
365
|
+
```typescript
|
|
366
|
+
import { WEB_SEARCH_TOOLS } from '@sprqvntrs/llm';
|
|
367
|
+
|
|
368
|
+
// Use web search in structured responses
|
|
369
|
+
const result = await client.createStructuredResponse({
|
|
370
|
+
prompt: 'What are the latest developments in AI?',
|
|
371
|
+
schema: z.object({ summary: z.string() }),
|
|
372
|
+
useWebSearch: true,
|
|
373
|
+
});
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
## Advanced Examples
|
|
377
|
+
|
|
378
|
+
### Multi-Step Processing Pipeline
|
|
379
|
+
|
|
380
|
+
```typescript
|
|
381
|
+
import { LLM, formatContentToStructure } from '@sprqvntrs/llm';
|
|
382
|
+
import { z } from 'zod';
|
|
383
|
+
|
|
384
|
+
const client = LLM.getClient('anthropic', 'claude-sonnet-4-5-20250929');
|
|
385
|
+
|
|
386
|
+
// Step 1: Generate unstructured content
|
|
387
|
+
const generationResult = await client.createStructuredResponse({
|
|
388
|
+
prompt: 'Write a technical analysis',
|
|
389
|
+
schema: z.object({ analysis: z.string() }),
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
// Step 2: Structure and validate the content
|
|
393
|
+
const schema = z.object({
|
|
394
|
+
title: z.string(),
|
|
395
|
+
keyPoints: z.array(z.string()),
|
|
396
|
+
conclusion: z.string(),
|
|
397
|
+
});
|
|
398
|
+
|
|
399
|
+
const structured = await formatContentToStructure({
|
|
400
|
+
llm: client,
|
|
401
|
+
unstructuredContent: generationResult.analysis,
|
|
402
|
+
schema,
|
|
403
|
+
});
|
|
404
|
+
|
|
405
|
+
console.log(structured.keyPoints);
|
|
406
|
+
```
|
|
407
|
+
|
|
408
|
+
### Handling Provider-Specific Features
|
|
409
|
+
|
|
410
|
+
```typescript
|
|
411
|
+
// Use reasoning effort (OpenAI o1 models only)
|
|
412
|
+
const result = await client.createStructuredResponse({
|
|
413
|
+
prompt: 'Solve this complex problem...',
|
|
414
|
+
schema: z.object({ solution: z.string() }),
|
|
415
|
+
reasoningEffort: 'high', // Only works with OpenAI
|
|
416
|
+
});
|
|
417
|
+
|
|
418
|
+
// Switch to Anthropic with web search
|
|
419
|
+
const anthropic = LLM.getClient('anthropic', 'claude-opus-4-1-20250805');
|
|
420
|
+
const webResult = await anthropic.createStructuredResponse({
|
|
421
|
+
prompt: 'What is the current stock price of...?',
|
|
422
|
+
schema: z.object({ price: z.number() }),
|
|
423
|
+
useWebSearch: true,
|
|
424
|
+
});
|
|
425
|
+
```
|
|
426
|
+
|
|
427
|
+
### Error Handling
|
|
428
|
+
|
|
429
|
+
```typescript
|
|
430
|
+
try {
|
|
431
|
+
const client = LLM.getClient('openai', 'gpt-4o');
|
|
432
|
+
|
|
433
|
+
const result = await client.createStructuredResponse({
|
|
434
|
+
prompt: 'Generate data',
|
|
435
|
+
schema: z.object({ data: z.string() }),
|
|
436
|
+
maxAttempts: 3, // Will retry if validation fails
|
|
437
|
+
});
|
|
438
|
+
|
|
439
|
+
console.log('Success:', result);
|
|
440
|
+
} catch (error) {
|
|
441
|
+
if (error.message.includes('API key')) {
|
|
442
|
+
console.error('Authentication failed');
|
|
443
|
+
} else if (error instanceof z.ZodError) {
|
|
444
|
+
console.error('Validation error:', error.issues);
|
|
445
|
+
} else {
|
|
446
|
+
console.error('Request failed:', error.message);
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
```
|
|
450
|
+
|
|
451
|
+
## Reasoning Effort Levels
|
|
452
|
+
|
|
453
|
+
The `reasoningEffort` parameter controls how much computational effort the model uses. This is currently supported by OpenAI's reasoning models:
|
|
454
|
+
|
|
455
|
+
| Level | Use Case | Cost | Latency |
|
|
456
|
+
|-------|----------|------|---------|
|
|
457
|
+
| `'low'` | Simple tasks, quick responses | Lower | Fast |
|
|
458
|
+
| `'medium'` | Balanced reasoning for complex problems | Medium | Medium |
|
|
459
|
+
| `'high'` | Complex problem-solving requiring deep reasoning | Higher | Slower |
|
|
460
|
+
|
|
461
|
+
**Note:** Anthropic models ignore this parameter as they don't support explicit reasoning effort control.
|
|
462
|
+
|
|
463
|
+
## Anthropic-Specific Behavior
|
|
464
|
+
|
|
465
|
+
When using an Anthropic client, the package can automatically format structured outputs using OpenAI if an OpenAI API key is available:
|
|
466
|
+
|
|
467
|
+
1. **With OpenAI Key Available** (Recommended):
|
|
468
|
+
- Anthropic generates the response
|
|
469
|
+
- OpenAI validates and formats it according to the schema
|
|
470
|
+
- Highest reliability for structured outputs
|
|
471
|
+
|
|
472
|
+
2. **Without OpenAI Key**:
|
|
473
|
+
- Anthropic attempts direct JSON generation
|
|
474
|
+
- Less reliable but still functional
|
|
475
|
+
- Falls back gracefully with warnings
|
|
476
|
+
|
|
477
|
+
```typescript
|
|
478
|
+
// Best practice: Provide both keys for optimal formatting
|
|
479
|
+
const client = LLM.getClient('anthropic', 'claude-sonnet-4-5-20250929', {
|
|
480
|
+
apiKey: process.env.ANTHROPIC_API_KEY,
|
|
481
|
+
openaiApiKey: process.env.OPENAI_API_KEY,
|
|
482
|
+
});
|
|
483
|
+
```
|
|
484
|
+
|
|
485
|
+
## Batch Processing Strategy
|
|
486
|
+
|
|
487
|
+
The `processBatchWithLLM()` method is optimized for:
|
|
488
|
+
- Parallel processing of independent items
|
|
489
|
+
- Automatic batching to respect API rate limits
|
|
490
|
+
- Memory-efficient chunking of large datasets
|
|
491
|
+
|
|
492
|
+
```typescript
|
|
493
|
+
// Process 1000 items with batch size of 10
|
|
494
|
+
const results = await client.processBatchWithLLM(
|
|
495
|
+
largeArray,
|
|
496
|
+
async (batch) => {
|
|
497
|
+
return Promise.all(
|
|
498
|
+
batch.map(item =>
|
|
499
|
+
client.createStructuredResponse({ /* ... */ })
|
|
500
|
+
)
|
|
501
|
+
);
|
|
502
|
+
},
|
|
503
|
+
10 // Process 10 items at a time
|
|
504
|
+
);
|
|
505
|
+
```
|
|
506
|
+
|
|
507
|
+
## Caching and Memoization
|
|
508
|
+
|
|
509
|
+
For repeated requests with identical prompts, consider caching:
|
|
510
|
+
|
|
511
|
+
```typescript
|
|
512
|
+
const cache = new Map<string, any>();
|
|
513
|
+
|
|
514
|
+
async function cachedRequest(prompt: string) {
|
|
515
|
+
if (cache.has(prompt)) {
|
|
516
|
+
return cache.get(prompt);
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
const result = await client.createStructuredResponse({
|
|
520
|
+
prompt,
|
|
521
|
+
schema: z.object({ /* ... */ }),
|
|
522
|
+
});
|
|
523
|
+
|
|
524
|
+
cache.set(prompt, result);
|
|
525
|
+
return result;
|
|
526
|
+
}
|
|
527
|
+
```
|
|
528
|
+
|
|
529
|
+
## Troubleshooting
|
|
530
|
+
|
|
531
|
+
### "API key is not set" Error
|
|
532
|
+
|
|
533
|
+
```typescript
|
|
534
|
+
// Solution 1: Set environment variables
|
|
535
|
+
export OPENAI_API_KEY="sk-..."
|
|
536
|
+
export ANTHROPIC_API_KEY="sk-ant-..."
|
|
537
|
+
|
|
538
|
+
// Solution 2: Pass keys explicitly
|
|
539
|
+
const client = LLM.getClient('openai', 'gpt-4o', {
|
|
540
|
+
apiKey: 'sk-your-key',
|
|
541
|
+
});
|
|
542
|
+
```
|
|
543
|
+
|
|
544
|
+
### Schema Validation Errors
|
|
545
|
+
|
|
546
|
+
```typescript
|
|
547
|
+
// Enable retries to handle temporary validation issues
|
|
548
|
+
const result = await client.createStructuredResponse({
|
|
549
|
+
prompt: 'Your prompt',
|
|
550
|
+
schema: yourSchema,
|
|
551
|
+
maxAttempts: 3, // Retry up to 3 times
|
|
552
|
+
});
|
|
553
|
+
|
|
554
|
+
// Or use formatContentToStructure for manual control
|
|
555
|
+
const formatted = await formatContentToStructure({
|
|
556
|
+
llm: client,
|
|
557
|
+
unstructuredContent: rawContent,
|
|
558
|
+
schema: yourSchema,
|
|
559
|
+
});
|
|
560
|
+
```
|
|
561
|
+
|
|
562
|
+
### Slow Responses
|
|
563
|
+
|
|
564
|
+
```typescript
|
|
565
|
+
// Monitor execution time
|
|
566
|
+
const result = await client.createStructuredResponse({
|
|
567
|
+
prompt: 'Your prompt',
|
|
568
|
+
schema: yourSchema,
|
|
569
|
+
logExecutionTime: true, // Log warnings for slow requests
|
|
570
|
+
reasoningEffort: 'low', // Use lower reasoning effort if possible
|
|
571
|
+
});
|
|
572
|
+
```
|
|
573
|
+
|
|
574
|
+
## Performance Tips
|
|
575
|
+
|
|
576
|
+
1. **Batch similar requests** using `processBatchWithLLM()`
|
|
577
|
+
2. **Use appropriate reasoning effort** - higher levels are slower but solve harder problems
|
|
578
|
+
3. **Enable web search sparingly** - it adds latency for real-time information
|
|
579
|
+
4. **Cache responses** for repeated identical prompts
|
|
580
|
+
5. **Use smaller models** (gpt-4o-mini, claude-haiku) for simple tasks
|
|
581
|
+
6. **Set reasonable maxAttempts** - balance reliability with cost
|
|
582
|
+
|
|
583
|
+
## API Reference
|
|
584
|
+
|
|
585
|
+
See [LlmClientInterface](src/types/client-interface.ts) for complete interface documentation.
|
|
586
|
+
|
|
587
|
+
## Raw TypeScript
|
|
588
|
+
|
|
589
|
+
This package ships raw TypeScript (`main` and `types` point at `index.ts`), so a Vite
|
|
590
|
+
consumer (Vite, React Router, Remix) must add the scope to `ssr.noExternal`:
|
|
591
|
+
`ssr: { noExternal: [/^@sprqvntrs\//] }`.
|
|
592
|
+
|
|
593
|
+
## License
|
|
594
|
+
|
|
595
|
+
MIT
|
package/index.ts
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
// Factory class
|
|
2
|
+
export { LLM } from './src/llm';
|
|
3
|
+
export type { LlmProvider, LlmClientOptions } from './src/llm';
|
|
4
|
+
|
|
5
|
+
// Client classes
|
|
6
|
+
export { OpenAIClient } from './src/clients/openai-client';
|
|
7
|
+
export { AnthropicClient } from './src/clients/anthropic-client';
|
|
8
|
+
export { OpenRouterClient } from './src/clients/openrouter-client';
|
|
9
|
+
|
|
10
|
+
// Client configuration types
|
|
11
|
+
export type { OpenAIClientConfig } from './src/clients/openai-client';
|
|
12
|
+
export type { AnthropicClientConfig } from './src/clients/anthropic-client';
|
|
13
|
+
export type { OpenRouterClientConfig } from './src/clients/openrouter-client';
|
|
14
|
+
|
|
15
|
+
// Interface and base types
|
|
16
|
+
export type { LlmClientInterface, BaseLlmClientConfig, BatchProcessOptions, StreamChunk, LlmTokenUsage, LlmUsageCost, ReasoningEffortLevel } from './src/types/client-interface';
|
|
17
|
+
|
|
18
|
+
// Pricing utilities
|
|
19
|
+
export { MODEL_PRICING, calculateUsageCost } from './src/pricing';
|
|
20
|
+
export type { ModelPricing } from './src/pricing';
|
|
21
|
+
|
|
22
|
+
// Model types
|
|
23
|
+
export type { ModelConfig, ProviderModelMap, OpenAIModel, AnthropicModel, OpenRouterModel } from './src/model-types';
|
|
24
|
+
|
|
25
|
+
// Model configurations
|
|
26
|
+
export { DEFAULT_MODELS, REASONING_EFFORT_MAP, ANTHROPIC_MAX_TOKENS, WEB_SEARCH_TOOLS } from './src/models';
|
|
27
|
+
|
|
28
|
+
// Error types and utilities
|
|
29
|
+
export {
|
|
30
|
+
LlmError,
|
|
31
|
+
LlmTimeoutError,
|
|
32
|
+
LlmValidationError,
|
|
33
|
+
LlmApiError,
|
|
34
|
+
LlmConfigurationError,
|
|
35
|
+
LlmOutputTruncatedError,
|
|
36
|
+
LlmJsonParseError,
|
|
37
|
+
generateRequestId,
|
|
38
|
+
isTimeoutError,
|
|
39
|
+
isRetryableError,
|
|
40
|
+
wrapSdkError,
|
|
41
|
+
} from './src/utils/errors';
|
|
42
|
+
export type { LlmErrorContext } from './src/utils/errors';
|
|
43
|
+
|
|
44
|
+
// Helper functions
|
|
45
|
+
export {
|
|
46
|
+
isContentInLanguage,
|
|
47
|
+
formatContentToStructure,
|
|
48
|
+
generateAndFormatWithLanguageCheck,
|
|
49
|
+
} from './src/helpers';
|
|
50
|
+
|
|
51
|
+
// Response normalization utilities
|
|
52
|
+
export { stripJsonArtifacts } from './src/utils/strip-json-artifacts';
|
|
53
|
+
export type { SanitizationResult } from './src/utils/strip-json-artifacts';
|