@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/src/pricing.ts ADDED
@@ -0,0 +1,36 @@
1
+ import pricingData from './pricing-data.json';
2
+ import type { LlmUsageCost } from './types/client-interface';
3
+
4
+ export interface ModelPricing {
5
+ inputPerMillion: number;
6
+ outputPerMillion: number;
7
+ cachedInputPerMillion?: number;
8
+ }
9
+
10
+ export const MODEL_PRICING: Record<string, ModelPricing> = pricingData.models;
11
+
12
+ export function calculateUsageCost(
13
+ model: string,
14
+ promptTokens: number,
15
+ completionTokens: number,
16
+ cachedTokens?: number,
17
+ ): LlmUsageCost | null {
18
+ const pricing = MODEL_PRICING[model];
19
+ if (!pricing) return null;
20
+
21
+ const cached = cachedTokens ?? 0;
22
+ const nonCachedInput = promptTokens - cached;
23
+
24
+ const inputCost = (nonCachedInput / 1_000_000) * pricing.inputPerMillion;
25
+ const outputCost = (completionTokens / 1_000_000) * pricing.outputPerMillion;
26
+ const cachedInputCost = pricing.cachedInputPerMillion
27
+ ? (cached / 1_000_000) * pricing.cachedInputPerMillion
28
+ : 0;
29
+
30
+ return {
31
+ input: inputCost,
32
+ output: outputCost,
33
+ cachedInput: cachedInputCost,
34
+ total: inputCost + cachedInputCost + outputCost,
35
+ };
36
+ }
@@ -0,0 +1,222 @@
1
+ import { z } from 'zod/v4';
2
+
3
+ /**
4
+ * Normalized reasoning effort levels shared across providers.
5
+ *
6
+ * `'none'` means "turn reasoning off" — providers that support it receive an
7
+ * explicit off signal; providers whose reasoning is opt-in simply stay off.
8
+ * Leaving the value `undefined` means "don't send any reasoning preference"
9
+ * and preserves each provider's own default behaviour.
10
+ */
11
+ export type ReasoningEffortLevel = 'none' | 'low' | 'medium' | 'high';
12
+
13
+ /**
14
+ * Base configuration for all LLM clients
15
+ */
16
+ export interface BaseLlmClientConfig {
17
+ /**
18
+ * API key for the provider
19
+ */
20
+ apiKey: string;
21
+
22
+ /**
23
+ * The model to use
24
+ */
25
+ model: string;
26
+
27
+ /**
28
+ * Debug logging configuration
29
+ * - If true: always log debug messages
30
+ * - If false: never log debug messages
31
+ * - If undefined: auto-detect based on NODE_ENV (enabled in development)
32
+ */
33
+ debug?: boolean;
34
+
35
+ /**
36
+ * Request timeout in milliseconds
37
+ * - Default: 120000 (120 seconds / 2 minutes)
38
+ * - Maximum recommended: 600000 (10 minutes)
39
+ * - Set to 0 to disable timeout (not recommended)
40
+ */
41
+ timeout?: number;
42
+
43
+ /**
44
+ * Maximum number of retries for failed requests
45
+ * - Default: 2
46
+ * - Set to 0 to disable retries
47
+ */
48
+ maxRetries?: number;
49
+ }
50
+
51
+ /**
52
+ * Options for batch processing
53
+ */
54
+ export interface BatchProcessOptions<T, R> {
55
+ /**
56
+ * The items to process
57
+ */
58
+ items: T[];
59
+
60
+ /**
61
+ * The function to process each batch
62
+ */
63
+ processFn: (batch: T[]) => Promise<R[]>;
64
+
65
+ /**
66
+ * The size of each batch (default: 5)
67
+ */
68
+ batchSize?: number;
69
+ }
70
+
71
+ /**
72
+ * Streaming chunk from an LLM response
73
+ */
74
+ export interface StreamChunk {
75
+ /**
76
+ * The text content of this chunk
77
+ */
78
+ text: string;
79
+
80
+ /**
81
+ * Whether this is the final chunk
82
+ */
83
+ isComplete: boolean;
84
+
85
+ /**
86
+ * Accumulated text so far (including this chunk)
87
+ */
88
+ accumulatedText?: string;
89
+
90
+ /**
91
+ * Optional usage information (only present in final chunk for some providers)
92
+ */
93
+ usage?: {
94
+ promptTokens?: number;
95
+ completionTokens?: number;
96
+ totalTokens?: number;
97
+ };
98
+
99
+ /**
100
+ * The finish reason from the API (only present in final chunk)
101
+ * Common values: 'stop' (normal), 'length' (truncated), 'content_filter'
102
+ */
103
+ finishReason?: string;
104
+ }
105
+
106
+ /**
107
+ * Cost breakdown for an LLM API call.
108
+ */
109
+ export interface LlmUsageCost {
110
+ /** Cost for non-cached input tokens */
111
+ input: number;
112
+ /** Cost for output/completion tokens */
113
+ output: number;
114
+ /** Cost for cached input tokens (reduced rate) */
115
+ cachedInput: number;
116
+ /** Total cost: input + cachedInput + output */
117
+ total: number;
118
+ }
119
+
120
+ /**
121
+ * Token usage data from an LLM API call.
122
+ */
123
+ export interface LlmTokenUsage {
124
+ promptTokens: number;
125
+ completionTokens: number;
126
+ totalTokens: number;
127
+ cachedTokens?: number;
128
+ /** The model ID used for this call */
129
+ model: string;
130
+ /** Calculated cost breakdown, or null if model not in pricing table */
131
+ cost: LlmUsageCost | null;
132
+ }
133
+
134
+ /**
135
+ * Unified interface that all LLM clients must implement
136
+ * This ensures consistent API across different providers
137
+ */
138
+ export interface LlmClientInterface {
139
+ /**
140
+ * Token usage from the most recent createStructuredResponse call.
141
+ * Reset to null before each call, populated after successful completion.
142
+ */
143
+ readonly lastUsage: LlmTokenUsage | null;
144
+
145
+ /**
146
+ * Validates that the client is properly configured
147
+ * @returns true if valid, throws an error with details if not
148
+ */
149
+ validateConfiguration(): boolean;
150
+
151
+ /**
152
+ * Creates a raw response from the provider's API and returns the text content
153
+ * This is a simple, stateless method that extracts text automatically
154
+ *
155
+ * @param prompt The prompt to send to the model
156
+ * @param options Optional configuration
157
+ * @param options.timeout Request timeout in milliseconds (overrides client default)
158
+ * @returns The text content as a string
159
+ */
160
+ createResponse(prompt: string, options?: { timeout?: number }): Promise<string>;
161
+
162
+ /**
163
+ * Creates a raw response from the provider's API and returns the full response object
164
+ * Use this when you need access to metadata like usage stats, finish reason, etc.
165
+ *
166
+ * @param prompt The prompt to send to the model
167
+ * @param options Optional configuration
168
+ * @param options.timeout Request timeout in milliseconds (overrides client default)
169
+ * @returns The raw response object from the provider
170
+ */
171
+ createRawResponse(prompt: string, options?: { timeout?: number }): Promise<unknown>;
172
+
173
+ /**
174
+ * Creates a streaming response from the provider's API
175
+ * Returns an async iterator that yields chunks of text as they arrive
176
+ *
177
+ * @param prompt The prompt to send to the model
178
+ * @param options Optional configuration
179
+ * @param options.timeout Request timeout in milliseconds (overrides client default)
180
+ * @returns An async iterable of stream chunks
181
+ */
182
+ createStreamingResponse(prompt: string, options?: { timeout?: number }): AsyncIterable<StreamChunk>;
183
+
184
+ /**
185
+ * Creates a structured response using the provider's API with Zod schema validation
186
+ * Includes built-in retry logic, execution time tracking, and error handling
187
+ *
188
+ * @param options.prompt The prompt to send to the model
189
+ * @param options.schema The Zod schema to validate the response against
190
+ * @param options.formatGuidance Optional guidance for formatting the response
191
+ * @param options.reasoningEffort Normalized reasoning effort level ('none' | 'low' | 'medium' | 'high').
192
+ * Omit to leave the provider default untouched; pass 'none' to turn reasoning off.
193
+ * @param options.maxAttempts Maximum number of retry attempts (default: 3)
194
+ * @param options.logExecutionTime Whether to log execution time warnings (default: false)
195
+ * @param options.responseInstructions Additional instructions to append to the prompt (deprecated, use formatGuidance)
196
+ * @param options.useWebSearch Whether to enable web search for this request (default: false)
197
+ * @param options.stream Whether to use streaming for the generation phase (default: true)
198
+ * @param options.timeout Request timeout in milliseconds (overrides client default)
199
+ */
200
+ createStructuredResponse<T extends z.ZodType>(options: {
201
+ prompt: string;
202
+ schema: T;
203
+ formatGuidance?: string;
204
+ reasoningEffort?: ReasoningEffortLevel;
205
+ maxAttempts?: number;
206
+ logExecutionTime?: boolean;
207
+ responseInstructions?: string;
208
+ useWebSearch?: boolean;
209
+ stream?: boolean;
210
+ timeout?: number;
211
+ }): Promise<z.infer<T>>;
212
+
213
+ /**
214
+ * Process a batch of items with parallel processing
215
+ */
216
+ processBatchWithLLM<T, R>(options: BatchProcessOptions<T, R>): Promise<R[]>;
217
+
218
+ /**
219
+ * Generates an embedding vector for the provided text
220
+ */
221
+ generateEmbedding(value: string): Promise<number[]>;
222
+ }
@@ -0,0 +1,120 @@
1
+ /**
2
+ * Debug logging utility for LLM clients
3
+ */
4
+
5
+ export interface DebugConfig {
6
+ /**
7
+ * Enable debug logging
8
+ * - If true: always log debug messages
9
+ * - If false: never log debug messages
10
+ * - If undefined: auto-detect based on NODE_ENV (enabled in development)
11
+ */
12
+ enabled?: boolean;
13
+ }
14
+
15
+ export class DebugLogger {
16
+ private enabled: boolean;
17
+ private clientName: string;
18
+
19
+ constructor(clientName: string, config?: DebugConfig) {
20
+ this.clientName = clientName;
21
+
22
+ // Auto-detect debug mode based on NODE_ENV if not explicitly set
23
+ if (config?.enabled !== undefined) {
24
+ this.enabled = config.enabled;
25
+ } else {
26
+ this.enabled = process.env.NODE_ENV === 'development';
27
+ }
28
+ }
29
+
30
+ /**
31
+ * Log a debug message (only in debug mode)
32
+ */
33
+ log(message: string, data?: Record<string, any>): void {
34
+ if (!this.enabled) return;
35
+
36
+ const timestamp = new Date().toISOString();
37
+ console.log(`[${timestamp}] [${this.clientName}] ${message}`);
38
+
39
+ if (data) {
40
+ console.log(JSON.stringify(data, null, 2));
41
+ }
42
+ }
43
+
44
+ /**
45
+ * Log an error message (always logs, even in production)
46
+ */
47
+ logError(message: string, error?: unknown, data?: Record<string, any>): void {
48
+ const timestamp = new Date().toISOString();
49
+ console.error(`[${timestamp}] [${this.clientName}] ERROR: ${message}`);
50
+
51
+ if (error) {
52
+ if (error instanceof Error) {
53
+ console.error('Error details:', {
54
+ name: error.name,
55
+ message: error.message,
56
+ stack: error.stack,
57
+ });
58
+ } else {
59
+ console.error('Error details:', error);
60
+ }
61
+ }
62
+
63
+ if (data) {
64
+ console.error('Additional context:', JSON.stringify(data, null, 2));
65
+ }
66
+ }
67
+
68
+ /**
69
+ * Log a warning message (always logs, even in production)
70
+ */
71
+ logWarning(message: string, data?: Record<string, any>): void {
72
+ const timestamp = new Date().toISOString();
73
+ console.warn(`[${timestamp}] [${this.clientName}] WARNING: ${message}`);
74
+
75
+ if (data) {
76
+ console.warn(JSON.stringify(data, null, 2));
77
+ }
78
+ }
79
+
80
+ /**
81
+ * Log execution time if it exceeds the threshold
82
+ */
83
+ logExecutionTime(operation: string, timeMs: number, threshold: number = 20000): void {
84
+ if (!this.enabled) return;
85
+
86
+ if (timeMs > threshold) {
87
+ this.log(`⚠️ Long execution time for ${operation}: ${timeMs}ms`);
88
+ } else {
89
+ this.log(`✓ ${operation} completed in ${timeMs}ms`);
90
+ }
91
+ }
92
+
93
+ /**
94
+ * Log API usage information
95
+ */
96
+ logUsage(usage: Record<string, any>): void {
97
+ if (!this.enabled) return;
98
+
99
+ this.log('API Usage', usage);
100
+ }
101
+
102
+ /**
103
+ * Log response preview
104
+ */
105
+ logResponsePreview(response: string, maxLength: number = 500): void {
106
+ if (!this.enabled) return;
107
+
108
+ this.log('Response Preview', {
109
+ length: response.length,
110
+ preview: response.substring(0, maxLength) + (response.length > maxLength ? '...' : ''),
111
+ });
112
+ }
113
+
114
+ /**
115
+ * Check if debug logging is enabled
116
+ */
117
+ isEnabled(): boolean {
118
+ return this.enabled;
119
+ }
120
+ }