evalsense 0.2.0 → 0.3.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.
@@ -1,6 +1,7 @@
1
1
  'use strict';
2
2
 
3
- var chunkY23VHTD3_cjs = require('../chunk-Y23VHTD3.cjs');
3
+ var chunkRZFLCWTW_cjs = require('../chunk-RZFLCWTW.cjs');
4
+ var chunkJEQ2X3Z6_cjs = require('../chunk-JEQ2X3Z6.cjs');
4
5
 
5
6
  // src/metrics/custom/index.ts
6
7
  var customMetrics = /* @__PURE__ */ new Map();
@@ -188,78 +189,317 @@ function createSpyMockClient(response) {
188
189
  return { client, prompts };
189
190
  }
190
191
 
192
+ // src/metrics/llm/adapters/openai.ts
193
+ function createOpenAIAdapter(apiKey, options = {}) {
194
+ const {
195
+ model = "gpt-4-turbo-preview",
196
+ temperature = 0,
197
+ maxTokens = 4096,
198
+ baseURL,
199
+ organization,
200
+ timeout = 3e4
201
+ } = options;
202
+ if (!apiKey) {
203
+ throw new Error(
204
+ "OpenAI API key is required. Get one at https://platform.openai.com/api-keys"
205
+ );
206
+ }
207
+ let OpenAI;
208
+ let openaiClient;
209
+ function ensureClient() {
210
+ if (openaiClient) return openaiClient;
211
+ try {
212
+ OpenAI = chunkJEQ2X3Z6_cjs.__require("openai").default || chunkJEQ2X3Z6_cjs.__require("openai");
213
+ } catch (error) {
214
+ throw new Error(
215
+ "OpenAI SDK not found. Install it with: npm install openai\nVisit https://github.com/openai/openai-node for documentation."
216
+ );
217
+ }
218
+ openaiClient = new OpenAI({
219
+ apiKey,
220
+ baseURL,
221
+ organization,
222
+ timeout
223
+ });
224
+ return openaiClient;
225
+ }
226
+ return {
227
+ async complete(prompt) {
228
+ const client = ensureClient();
229
+ try {
230
+ const response = await client.chat.completions.create({
231
+ model,
232
+ messages: [{ role: "user", content: prompt }],
233
+ temperature,
234
+ max_tokens: maxTokens
235
+ });
236
+ return response.choices[0]?.message?.content ?? "";
237
+ } catch (error) {
238
+ const errorMessage = error?.message || error?.error?.message || String(error);
239
+ throw new Error(
240
+ `OpenAI API error (model: ${model}): ${errorMessage}
241
+ Check your API key and quota at https://platform.openai.com/account/usage`
242
+ );
243
+ }
244
+ },
245
+ async completeStructured(prompt, _schema) {
246
+ const client = ensureClient();
247
+ try {
248
+ const response = await client.chat.completions.create({
249
+ model,
250
+ messages: [{ role: "user", content: prompt }],
251
+ response_format: { type: "json_object" },
252
+ temperature,
253
+ max_tokens: maxTokens
254
+ });
255
+ const text = response.choices[0]?.message?.content ?? "{}";
256
+ return JSON.parse(text);
257
+ } catch (error) {
258
+ const errorMessage = error?.message || error?.error?.message || String(error);
259
+ throw new Error(
260
+ `OpenAI API error (model: ${model}): ${errorMessage}
261
+ Check your API key and quota at https://platform.openai.com/account/usage`
262
+ );
263
+ }
264
+ }
265
+ };
266
+ }
267
+
268
+ // src/metrics/llm/adapters/anthropic.ts
269
+ function createAnthropicAdapter(apiKey, options = {}) {
270
+ const {
271
+ model = "claude-3-5-sonnet-20241022",
272
+ maxTokens = 4096,
273
+ temperature = 0,
274
+ timeout = 3e4
275
+ } = options;
276
+ if (!apiKey) {
277
+ throw new Error(
278
+ "Anthropic API key is required. Get one at https://console.anthropic.com/"
279
+ );
280
+ }
281
+ if (temperature < 0 || temperature > 1) {
282
+ throw new Error(`Anthropic temperature must be between 0 and 1, got ${temperature}`);
283
+ }
284
+ let Anthropic;
285
+ let anthropicClient;
286
+ function ensureClient() {
287
+ if (anthropicClient) return anthropicClient;
288
+ try {
289
+ Anthropic = chunkJEQ2X3Z6_cjs.__require("@anthropic-ai/sdk").default || chunkJEQ2X3Z6_cjs.__require("@anthropic-ai/sdk");
290
+ } catch (error) {
291
+ throw new Error(
292
+ "Anthropic SDK not found. Install it with: npm install @anthropic-ai/sdk\nVisit https://github.com/anthropics/anthropic-sdk-typescript for documentation."
293
+ );
294
+ }
295
+ anthropicClient = new Anthropic({
296
+ apiKey,
297
+ timeout
298
+ });
299
+ return anthropicClient;
300
+ }
301
+ return {
302
+ async complete(prompt) {
303
+ const client = ensureClient();
304
+ try {
305
+ const message = await client.messages.create({
306
+ model,
307
+ max_tokens: maxTokens,
308
+ temperature,
309
+ messages: [{ role: "user", content: prompt }]
310
+ });
311
+ const firstBlock = message.content[0];
312
+ return firstBlock?.type === "text" ? firstBlock.text : "";
313
+ } catch (error) {
314
+ const errorMessage = error?.message || error?.error?.message || String(error);
315
+ throw new Error(
316
+ `Anthropic API error (model: ${model}): ${errorMessage}
317
+ Check your API key and usage at https://console.anthropic.com/`
318
+ );
319
+ }
320
+ },
321
+ async completeStructured(prompt, schema) {
322
+ const jsonPrompt = prompt + `
323
+
324
+ IMPORTANT: Respond with valid JSON only. No markdown, no explanation. The JSON must match this schema: ${JSON.stringify(schema)}`;
325
+ const response = await this.complete(jsonPrompt);
326
+ try {
327
+ return chunkRZFLCWTW_cjs.parseJSONResponse(response);
328
+ } catch (error) {
329
+ throw new Error(
330
+ `Failed to parse Anthropic response as JSON: ${error.message}
331
+ Response preview: ${response.substring(0, 200)}...`
332
+ );
333
+ }
334
+ }
335
+ };
336
+ }
337
+
338
+ // src/metrics/llm/adapters/openrouter.ts
339
+ function createOpenRouterAdapter(apiKey, options = {}) {
340
+ const {
341
+ model = "anthropic/claude-3.5-sonnet",
342
+ temperature = 0,
343
+ maxTokens = 4096,
344
+ appName = "evalsense",
345
+ siteUrl,
346
+ timeout = 3e4
347
+ } = options;
348
+ if (!apiKey) {
349
+ throw new Error("OpenRouter API key is required. Get one at https://openrouter.ai/keys");
350
+ }
351
+ const baseURL = "https://openrouter.ai/api/v1";
352
+ async function callAPI(messages, jsonMode = false) {
353
+ const headers = {
354
+ Authorization: `Bearer ${apiKey}`,
355
+ "Content-Type": "application/json",
356
+ "HTTP-Referer": siteUrl || "https://github.com/evalsense/evalsense",
357
+ "X-Title": appName
358
+ };
359
+ const body = {
360
+ model,
361
+ messages,
362
+ temperature,
363
+ max_tokens: maxTokens
364
+ };
365
+ if (jsonMode) {
366
+ body.response_format = { type: "json_object" };
367
+ }
368
+ const controller = new AbortController();
369
+ const timeoutId = setTimeout(() => controller.abort(), timeout);
370
+ try {
371
+ const response = await fetch(`${baseURL}/chat/completions`, {
372
+ method: "POST",
373
+ headers,
374
+ body: JSON.stringify(body),
375
+ signal: controller.signal
376
+ });
377
+ clearTimeout(timeoutId);
378
+ if (!response.ok) {
379
+ const errorData = await response.json().catch(() => ({}));
380
+ const errorMessage = errorData.error?.message || response.statusText || "Unknown error";
381
+ throw new Error(`OpenRouter API error (${response.status}): ${errorMessage}`);
382
+ }
383
+ const data = await response.json();
384
+ return data.choices?.[0]?.message?.content ?? "";
385
+ } catch (error) {
386
+ clearTimeout(timeoutId);
387
+ if (error.name === "AbortError") {
388
+ throw new Error(`OpenRouter request timed out after ${timeout}ms (model: ${model})`);
389
+ }
390
+ const errorMessage = error?.message || String(error);
391
+ throw new Error(
392
+ `OpenRouter API error (model: ${model}): ${errorMessage}
393
+ Check your API key and credits at https://openrouter.ai/activity`
394
+ );
395
+ }
396
+ }
397
+ return {
398
+ async complete(prompt) {
399
+ return callAPI([{ role: "user", content: prompt }], false);
400
+ },
401
+ async completeStructured(prompt, schema) {
402
+ let response;
403
+ try {
404
+ response = await callAPI(
405
+ [{ role: "user", content: prompt }],
406
+ true
407
+ // Enable JSON mode
408
+ );
409
+ } catch (error) {
410
+ const jsonPrompt = prompt + `
411
+
412
+ IMPORTANT: Respond with valid JSON only. No markdown, no explanation. The JSON must match this schema: ${JSON.stringify(schema)}`;
413
+ response = await callAPI([{ role: "user", content: jsonPrompt }], false);
414
+ }
415
+ try {
416
+ return chunkRZFLCWTW_cjs.parseJSONResponse(response);
417
+ } catch (error) {
418
+ throw new Error(
419
+ `Failed to parse OpenRouter response as JSON: ${error.message}
420
+ Model: ${model}
421
+ Response preview: ${response.substring(0, 200)}...`
422
+ );
423
+ }
424
+ }
425
+ };
426
+ }
427
+
191
428
  Object.defineProperty(exports, "batchItems", {
192
429
  enumerable: true,
193
- get: function () { return chunkY23VHTD3_cjs.batchItems; }
430
+ get: function () { return chunkRZFLCWTW_cjs.batchItems; }
194
431
  });
195
432
  Object.defineProperty(exports, "createJSONSchema", {
196
433
  enumerable: true,
197
- get: function () { return chunkY23VHTD3_cjs.createJSONSchema; }
434
+ get: function () { return chunkRZFLCWTW_cjs.createJSONSchema; }
198
435
  });
199
436
  Object.defineProperty(exports, "createLLMError", {
200
437
  enumerable: true,
201
- get: function () { return chunkY23VHTD3_cjs.createLLMError; }
438
+ get: function () { return chunkRZFLCWTW_cjs.createLLMError; }
202
439
  });
203
440
  Object.defineProperty(exports, "extractScore", {
204
441
  enumerable: true,
205
- get: function () { return chunkY23VHTD3_cjs.extractScore; }
442
+ get: function () { return chunkRZFLCWTW_cjs.extractScore; }
206
443
  });
207
444
  Object.defineProperty(exports, "faithfulness", {
208
445
  enumerable: true,
209
- get: function () { return chunkY23VHTD3_cjs.faithfulness; }
446
+ get: function () { return chunkRZFLCWTW_cjs.faithfulness; }
210
447
  });
211
448
  Object.defineProperty(exports, "fillPrompt", {
212
449
  enumerable: true,
213
- get: function () { return chunkY23VHTD3_cjs.fillPrompt; }
450
+ get: function () { return chunkRZFLCWTW_cjs.fillPrompt; }
214
451
  });
215
452
  Object.defineProperty(exports, "getLLMClient", {
216
453
  enumerable: true,
217
- get: function () { return chunkY23VHTD3_cjs.getLLMClient; }
454
+ get: function () { return chunkRZFLCWTW_cjs.getLLMClient; }
218
455
  });
219
456
  Object.defineProperty(exports, "hallucination", {
220
457
  enumerable: true,
221
- get: function () { return chunkY23VHTD3_cjs.hallucination; }
458
+ get: function () { return chunkRZFLCWTW_cjs.hallucination; }
222
459
  });
223
460
  Object.defineProperty(exports, "parseJSONResponse", {
224
461
  enumerable: true,
225
- get: function () { return chunkY23VHTD3_cjs.parseJSONResponse; }
462
+ get: function () { return chunkRZFLCWTW_cjs.parseJSONResponse; }
226
463
  });
227
464
  Object.defineProperty(exports, "relevance", {
228
465
  enumerable: true,
229
- get: function () { return chunkY23VHTD3_cjs.relevance; }
466
+ get: function () { return chunkRZFLCWTW_cjs.relevance; }
230
467
  });
231
468
  Object.defineProperty(exports, "requireLLMClient", {
232
469
  enumerable: true,
233
- get: function () { return chunkY23VHTD3_cjs.requireLLMClient; }
470
+ get: function () { return chunkRZFLCWTW_cjs.requireLLMClient; }
234
471
  });
235
472
  Object.defineProperty(exports, "resetLLMClient", {
236
473
  enumerable: true,
237
- get: function () { return chunkY23VHTD3_cjs.resetLLMClient; }
474
+ get: function () { return chunkRZFLCWTW_cjs.resetLLMClient; }
238
475
  });
239
476
  Object.defineProperty(exports, "setLLMClient", {
240
477
  enumerable: true,
241
- get: function () { return chunkY23VHTD3_cjs.setLLMClient; }
478
+ get: function () { return chunkRZFLCWTW_cjs.setLLMClient; }
242
479
  });
243
480
  Object.defineProperty(exports, "toxicity", {
244
481
  enumerable: true,
245
- get: function () { return chunkY23VHTD3_cjs.toxicity; }
482
+ get: function () { return chunkRZFLCWTW_cjs.toxicity; }
246
483
  });
247
484
  Object.defineProperty(exports, "validateResponse", {
248
485
  enumerable: true,
249
- get: function () { return chunkY23VHTD3_cjs.validateResponse; }
486
+ get: function () { return chunkRZFLCWTW_cjs.validateResponse; }
250
487
  });
251
488
  Object.defineProperty(exports, "withTimeout", {
252
489
  enumerable: true,
253
- get: function () { return chunkY23VHTD3_cjs.withTimeout; }
490
+ get: function () { return chunkRZFLCWTW_cjs.withTimeout; }
254
491
  });
255
492
  exports.BINARY_THRESHOLDS = BINARY_THRESHOLDS;
256
493
  exports.SEVERITY_THRESHOLDS = SEVERITY_THRESHOLDS;
257
494
  exports.batch = batch;
258
495
  exports.clearMetrics = clearMetrics;
496
+ exports.createAnthropicAdapter = createAnthropicAdapter;
259
497
  exports.createErrorMockClient = createErrorMockClient;
260
498
  exports.createKeywordMetric = createKeywordMetric;
261
499
  exports.createMetricOutput = createMetricOutput;
262
500
  exports.createMockLLMClient = createMockLLMClient;
501
+ exports.createOpenAIAdapter = createOpenAIAdapter;
502
+ exports.createOpenRouterAdapter = createOpenRouterAdapter;
263
503
  exports.createPatternMetric = createPatternMetric;
264
504
  exports.createSequentialMockClient = createSequentialMockClient;
265
505
  exports.createSpyMockClient = createSpyMockClient;
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/metrics/custom/index.ts","../../src/metrics/utils/index.ts","../../src/metrics/llm/adapters/mock.ts"],"names":["delay"],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * Custom metric registration\n */\n\nimport type { MetricFn, MetricOutput, MetricConfig } from \"../../core/types.js\";\n\n/**\n * Registry of custom metrics\n */\nconst customMetrics = new Map<string, MetricFn>();\n\n/**\n * Registers a custom metric\n *\n * @example\n * ```ts\n * registerMetric(\"custom-relevance\", async ({ outputs, query }) => {\n * // Custom evaluation logic\n * return outputs.map(o => ({\n * id: o.id,\n * metric: \"custom-relevance\",\n * score: evaluateRelevance(o.output, query),\n * }));\n * });\n * ```\n */\nexport function registerMetric(name: string, fn: MetricFn): void {\n if (customMetrics.has(name)) {\n throw new Error(`Metric \"${name}\" is already registered`);\n }\n customMetrics.set(name, fn);\n}\n\n/**\n * Gets a registered custom metric\n */\nexport function getMetric(name: string): MetricFn | undefined {\n return customMetrics.get(name);\n}\n\n/**\n * Runs a registered metric\n */\nexport async function runMetric(name: string, config: MetricConfig): Promise<MetricOutput[]> {\n const fn = customMetrics.get(name);\n if (!fn) {\n throw new Error(`Metric \"${name}\" is not registered`);\n }\n return fn(config);\n}\n\n/**\n * Lists all registered custom metrics\n */\nexport function listMetrics(): string[] {\n return Array.from(customMetrics.keys());\n}\n\n/**\n * Unregisters a metric (mainly for testing)\n */\nexport function unregisterMetric(name: string): boolean {\n return customMetrics.delete(name);\n}\n\n/**\n * Clears all registered metrics (mainly for testing)\n */\nexport function clearMetrics(): void {\n customMetrics.clear();\n}\n\n/**\n * Creates a simple string-matching metric\n */\nexport function createPatternMetric(\n name: string,\n patterns: RegExp[],\n options: { matchScore?: number; noMatchScore?: number } = {}\n): MetricFn {\n const { matchScore = 1, noMatchScore = 0 } = options;\n\n return async (config: MetricConfig): Promise<MetricOutput[]> => {\n return config.outputs.map((o) => {\n const hasMatch = patterns.some((p) => p.test(o.output));\n return {\n id: o.id,\n metric: name,\n score: hasMatch ? matchScore : noMatchScore,\n label: hasMatch ? \"detected\" : \"not_detected\",\n };\n });\n };\n}\n\n/**\n * Creates a keyword-based metric\n */\nexport function createKeywordMetric(\n name: string,\n keywords: string[],\n options: { caseSensitive?: boolean; threshold?: number } = {}\n): MetricFn {\n const { caseSensitive = false, threshold = 0.5 } = options;\n\n const normalizedKeywords = caseSensitive\n ? keywords\n : keywords.map((k) => k.toLowerCase());\n\n return async (config: MetricConfig): Promise<MetricOutput[]> => {\n return config.outputs.map((o) => {\n const text = caseSensitive ? o.output : o.output.toLowerCase();\n const matches = normalizedKeywords.filter((k) => text.includes(k));\n const score = matches.length / keywords.length;\n\n return {\n id: o.id,\n metric: name,\n score,\n label: score >= threshold ? \"detected\" : \"not_detected\",\n };\n });\n };\n}\n","/**\n * Metric utilities\n */\n\nimport type { MetricOutput } from \"../../core/types.js\";\n\n/**\n * Normalizes a score to 0-1 range\n */\nexport function normalizeScore(score: number, min = 0, max = 1): number {\n const range = max - min;\n if (range === 0) return 0;\n return Math.max(0, Math.min(1, (score - min) / range));\n}\n\n/**\n * Converts a numeric score to a label based on thresholds\n */\nexport function scoreToLabel(\n score: number,\n thresholds: { label: string; min: number }[]\n): string {\n // Sort thresholds by min descending\n const sorted = [...thresholds].sort((a, b) => b.min - a.min);\n\n for (const { label, min } of sorted) {\n if (score >= min) {\n return label;\n }\n }\n\n return thresholds[thresholds.length - 1]?.label ?? \"unknown\";\n}\n\n/**\n * Creates a metric output from a score\n */\nexport function createMetricOutput(\n id: string,\n metric: string,\n score: number,\n labelThresholds?: { label: string; min: number }[]\n): MetricOutput {\n const normalizedScore = normalizeScore(score);\n const label = labelThresholds\n ? scoreToLabel(normalizedScore, labelThresholds)\n : normalizedScore >= 0.5\n ? \"high\"\n : \"low\";\n\n return {\n id,\n metric,\n score: normalizedScore,\n label,\n };\n}\n\n/**\n * Default thresholds for binary metrics\n */\nexport const BINARY_THRESHOLDS = [\n { label: \"true\", min: 0.5 },\n { label: \"false\", min: 0 },\n];\n\n/**\n * Default thresholds for severity metrics\n */\nexport const SEVERITY_THRESHOLDS = [\n { label: \"high\", min: 0.7 },\n { label: \"medium\", min: 0.4 },\n { label: \"low\", min: 0 },\n];\n\n/**\n * Batches items for parallel processing\n */\nexport function batch<T>(items: T[], size: number): T[][] {\n const batches: T[][] = [];\n for (let i = 0; i < items.length; i += size) {\n batches.push(items.slice(i, i + size));\n }\n return batches;\n}\n\n/**\n * Delays execution\n */\nexport function delay(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n","/**\n * Mock LLM client for testing\n *\n * Provides a configurable mock implementation of LLMClient for unit tests.\n */\n\nimport type { LLMClient, JSONSchema } from \"../../../core/types.js\";\n\n/**\n * Configuration for mock LLM client\n */\nexport interface MockLLMConfig {\n /** Fixed response to return (can be string or object for JSON mode) */\n response?: string | Record<string, unknown>;\n\n /** Multiple responses for sequential calls */\n responses?: Array<string | Record<string, unknown>>;\n\n /** Delay in milliseconds before responding */\n delay?: number;\n\n /** Whether to throw an error */\n shouldError?: boolean;\n\n /** Error message to throw */\n errorMessage?: string;\n\n /** Function to validate prompts */\n onPrompt?: (prompt: string) => void;\n}\n\n/**\n * Creates a mock LLM client for testing\n *\n * @example\n * ```ts\n * const mock = createMockLLMClient({\n * response: JSON.stringify({ score: 0.8, reasoning: \"test\" }),\n * delay: 100\n * });\n *\n * setLLMClient(mock);\n * ```\n */\nexport function createMockLLMClient(config: MockLLMConfig = {}): LLMClient {\n const {\n response,\n responses,\n delay = 0,\n shouldError = false,\n errorMessage = \"Mock LLM error\",\n onPrompt,\n } = config;\n\n let callCount = 0;\n\n const getResponse = (): string | Record<string, unknown> => {\n if (responses && responses.length > 0) {\n const resp = responses[Math.min(callCount, responses.length - 1)];\n if (!resp) {\n return JSON.stringify({ score: 0.5, reasoning: \"Mock response\" });\n }\n callCount++;\n return resp;\n }\n\n if (response !== undefined) {\n return response;\n }\n\n // Default response\n return JSON.stringify({ score: 0.5, reasoning: \"Mock response\" });\n };\n\n return {\n async complete(prompt: string): Promise<string> {\n // Call validation hook if provided\n if (onPrompt) {\n onPrompt(prompt);\n }\n\n // Simulate delay\n if (delay > 0) {\n await new Promise((resolve) => setTimeout(resolve, delay));\n }\n\n // Simulate error\n if (shouldError) {\n throw new Error(errorMessage);\n }\n\n // Return response\n const resp = getResponse();\n return typeof resp === \"string\" ? resp : JSON.stringify(resp);\n },\n\n async completeStructured<T>(prompt: string, _schema: JSONSchema): Promise<T> {\n // Call validation hook if provided\n if (onPrompt) {\n onPrompt(prompt);\n }\n\n // Simulate delay\n if (delay > 0) {\n await new Promise((resolve) => setTimeout(resolve, delay));\n }\n\n // Simulate error\n if (shouldError) {\n throw new Error(errorMessage);\n }\n\n // Return response as object\n const resp = getResponse();\n if (typeof resp === \"string\") {\n try {\n return JSON.parse(resp) as T;\n } catch {\n throw new Error(`Mock response is not valid JSON: ${resp}`);\n }\n }\n\n return resp as T;\n },\n };\n}\n\n/**\n * Creates a mock client that returns sequential responses\n *\n * Useful for testing multiple calls with different responses.\n *\n * @example\n * ```ts\n * const mock = createSequentialMockClient([\n * { score: 0.2, reasoning: \"First call\" },\n * { score: 0.8, reasoning: \"Second call\" }\n * ]);\n * ```\n */\nexport function createSequentialMockClient(\n responses: Array<string | Record<string, unknown>>,\n options: { delay?: number } = {}\n): LLMClient {\n return createMockLLMClient({\n responses,\n delay: options.delay,\n });\n}\n\n/**\n * Creates a mock client that always errors\n *\n * Useful for testing error handling.\n */\nexport function createErrorMockClient(errorMessage = \"Mock LLM error\"): LLMClient {\n return createMockLLMClient({\n shouldError: true,\n errorMessage,\n });\n}\n\n/**\n * Creates a spy mock client that records all prompts\n *\n * Useful for testing what prompts are being sent to the LLM.\n *\n * @example\n * ```ts\n * const { client, prompts } = createSpyMockClient({ score: 0.5 });\n * await metric({ outputs, context, llmClient: client });\n * console.log(prompts); // See all prompts that were sent\n * ```\n */\nexport function createSpyMockClient(\n response: string | Record<string, unknown>\n): { client: LLMClient; prompts: string[] } {\n const prompts: string[] = [];\n\n const client = createMockLLMClient({\n response,\n onPrompt: (prompt) => prompts.push(prompt),\n });\n\n return { client, prompts };\n}\n"]}
1
+ {"version":3,"sources":["../../src/metrics/custom/index.ts","../../src/metrics/utils/index.ts","../../src/metrics/llm/adapters/mock.ts","../../src/metrics/llm/adapters/openai.ts","../../src/metrics/llm/adapters/anthropic.ts","../../src/metrics/llm/adapters/openrouter.ts"],"names":["delay","__require","parseJSONResponse"],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * Custom metric registration\n */\n\nimport type { MetricFn, MetricOutput, MetricConfig } from \"../../core/types.js\";\n\n/**\n * Registry of custom metrics\n */\nconst customMetrics = new Map<string, MetricFn>();\n\n/**\n * Registers a custom metric\n *\n * @example\n * ```ts\n * registerMetric(\"custom-relevance\", async ({ outputs, query }) => {\n * // Custom evaluation logic\n * return outputs.map(o => ({\n * id: o.id,\n * metric: \"custom-relevance\",\n * score: evaluateRelevance(o.output, query),\n * }));\n * });\n * ```\n */\nexport function registerMetric(name: string, fn: MetricFn): void {\n if (customMetrics.has(name)) {\n throw new Error(`Metric \"${name}\" is already registered`);\n }\n customMetrics.set(name, fn);\n}\n\n/**\n * Gets a registered custom metric\n */\nexport function getMetric(name: string): MetricFn | undefined {\n return customMetrics.get(name);\n}\n\n/**\n * Runs a registered metric\n */\nexport async function runMetric(name: string, config: MetricConfig): Promise<MetricOutput[]> {\n const fn = customMetrics.get(name);\n if (!fn) {\n throw new Error(`Metric \"${name}\" is not registered`);\n }\n return fn(config);\n}\n\n/**\n * Lists all registered custom metrics\n */\nexport function listMetrics(): string[] {\n return Array.from(customMetrics.keys());\n}\n\n/**\n * Unregisters a metric (mainly for testing)\n */\nexport function unregisterMetric(name: string): boolean {\n return customMetrics.delete(name);\n}\n\n/**\n * Clears all registered metrics (mainly for testing)\n */\nexport function clearMetrics(): void {\n customMetrics.clear();\n}\n\n/**\n * Creates a simple string-matching metric\n */\nexport function createPatternMetric(\n name: string,\n patterns: RegExp[],\n options: { matchScore?: number; noMatchScore?: number } = {}\n): MetricFn {\n const { matchScore = 1, noMatchScore = 0 } = options;\n\n return async (config: MetricConfig): Promise<MetricOutput[]> => {\n return config.outputs.map((o) => {\n const hasMatch = patterns.some((p) => p.test(o.output));\n return {\n id: o.id,\n metric: name,\n score: hasMatch ? matchScore : noMatchScore,\n label: hasMatch ? \"detected\" : \"not_detected\",\n };\n });\n };\n}\n\n/**\n * Creates a keyword-based metric\n */\nexport function createKeywordMetric(\n name: string,\n keywords: string[],\n options: { caseSensitive?: boolean; threshold?: number } = {}\n): MetricFn {\n const { caseSensitive = false, threshold = 0.5 } = options;\n\n const normalizedKeywords = caseSensitive ? keywords : keywords.map((k) => k.toLowerCase());\n\n return async (config: MetricConfig): Promise<MetricOutput[]> => {\n return config.outputs.map((o) => {\n const text = caseSensitive ? o.output : o.output.toLowerCase();\n const matches = normalizedKeywords.filter((k) => text.includes(k));\n const score = matches.length / keywords.length;\n\n return {\n id: o.id,\n metric: name,\n score,\n label: score >= threshold ? \"detected\" : \"not_detected\",\n };\n });\n };\n}\n","/**\n * Metric utilities\n */\n\nimport type { MetricOutput } from \"../../core/types.js\";\n\n/**\n * Normalizes a score to 0-1 range\n */\nexport function normalizeScore(score: number, min = 0, max = 1): number {\n const range = max - min;\n if (range === 0) return 0;\n return Math.max(0, Math.min(1, (score - min) / range));\n}\n\n/**\n * Converts a numeric score to a label based on thresholds\n */\nexport function scoreToLabel(score: number, thresholds: { label: string; min: number }[]): string {\n // Sort thresholds by min descending\n const sorted = [...thresholds].sort((a, b) => b.min - a.min);\n\n for (const { label, min } of sorted) {\n if (score >= min) {\n return label;\n }\n }\n\n return thresholds[thresholds.length - 1]?.label ?? \"unknown\";\n}\n\n/**\n * Creates a metric output from a score\n */\nexport function createMetricOutput(\n id: string,\n metric: string,\n score: number,\n labelThresholds?: { label: string; min: number }[]\n): MetricOutput {\n const normalizedScore = normalizeScore(score);\n const label = labelThresholds\n ? scoreToLabel(normalizedScore, labelThresholds)\n : normalizedScore >= 0.5\n ? \"high\"\n : \"low\";\n\n return {\n id,\n metric,\n score: normalizedScore,\n label,\n };\n}\n\n/**\n * Default thresholds for binary metrics\n */\nexport const BINARY_THRESHOLDS = [\n { label: \"true\", min: 0.5 },\n { label: \"false\", min: 0 },\n];\n\n/**\n * Default thresholds for severity metrics\n */\nexport const SEVERITY_THRESHOLDS = [\n { label: \"high\", min: 0.7 },\n { label: \"medium\", min: 0.4 },\n { label: \"low\", min: 0 },\n];\n\n/**\n * Batches items for parallel processing\n */\nexport function batch<T>(items: T[], size: number): T[][] {\n const batches: T[][] = [];\n for (let i = 0; i < items.length; i += size) {\n batches.push(items.slice(i, i + size));\n }\n return batches;\n}\n\n/**\n * Delays execution\n */\nexport function delay(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n","/**\n * Mock LLM client for testing\n *\n * Provides a configurable mock implementation of LLMClient for unit tests.\n */\n\nimport type { LLMClient, JSONSchema } from \"../../../core/types.js\";\n\n/**\n * Configuration for mock LLM client\n */\nexport interface MockLLMConfig {\n /** Fixed response to return (can be string or object for JSON mode) */\n response?: string | Record<string, unknown>;\n\n /** Multiple responses for sequential calls */\n responses?: Array<string | Record<string, unknown>>;\n\n /** Delay in milliseconds before responding */\n delay?: number;\n\n /** Whether to throw an error */\n shouldError?: boolean;\n\n /** Error message to throw */\n errorMessage?: string;\n\n /** Function to validate prompts */\n onPrompt?: (prompt: string) => void;\n}\n\n/**\n * Creates a mock LLM client for testing\n *\n * @example\n * ```ts\n * const mock = createMockLLMClient({\n * response: JSON.stringify({ score: 0.8, reasoning: \"test\" }),\n * delay: 100\n * });\n *\n * setLLMClient(mock);\n * ```\n */\nexport function createMockLLMClient(config: MockLLMConfig = {}): LLMClient {\n const {\n response,\n responses,\n delay = 0,\n shouldError = false,\n errorMessage = \"Mock LLM error\",\n onPrompt,\n } = config;\n\n let callCount = 0;\n\n const getResponse = (): string | Record<string, unknown> => {\n if (responses && responses.length > 0) {\n const resp = responses[Math.min(callCount, responses.length - 1)];\n if (!resp) {\n return JSON.stringify({ score: 0.5, reasoning: \"Mock response\" });\n }\n callCount++;\n return resp;\n }\n\n if (response !== undefined) {\n return response;\n }\n\n // Default response\n return JSON.stringify({ score: 0.5, reasoning: \"Mock response\" });\n };\n\n return {\n async complete(prompt: string): Promise<string> {\n // Call validation hook if provided\n if (onPrompt) {\n onPrompt(prompt);\n }\n\n // Simulate delay\n if (delay > 0) {\n await new Promise((resolve) => setTimeout(resolve, delay));\n }\n\n // Simulate error\n if (shouldError) {\n throw new Error(errorMessage);\n }\n\n // Return response\n const resp = getResponse();\n return typeof resp === \"string\" ? resp : JSON.stringify(resp);\n },\n\n async completeStructured<T>(prompt: string, _schema: JSONSchema): Promise<T> {\n // Call validation hook if provided\n if (onPrompt) {\n onPrompt(prompt);\n }\n\n // Simulate delay\n if (delay > 0) {\n await new Promise((resolve) => setTimeout(resolve, delay));\n }\n\n // Simulate error\n if (shouldError) {\n throw new Error(errorMessage);\n }\n\n // Return response as object\n const resp = getResponse();\n if (typeof resp === \"string\") {\n try {\n return JSON.parse(resp) as T;\n } catch {\n throw new Error(`Mock response is not valid JSON: ${resp}`);\n }\n }\n\n return resp as T;\n },\n };\n}\n\n/**\n * Creates a mock client that returns sequential responses\n *\n * Useful for testing multiple calls with different responses.\n *\n * @example\n * ```ts\n * const mock = createSequentialMockClient([\n * { score: 0.2, reasoning: \"First call\" },\n * { score: 0.8, reasoning: \"Second call\" }\n * ]);\n * ```\n */\nexport function createSequentialMockClient(\n responses: Array<string | Record<string, unknown>>,\n options: { delay?: number } = {}\n): LLMClient {\n return createMockLLMClient({\n responses,\n delay: options.delay,\n });\n}\n\n/**\n * Creates a mock client that always errors\n *\n * Useful for testing error handling.\n */\nexport function createErrorMockClient(errorMessage = \"Mock LLM error\"): LLMClient {\n return createMockLLMClient({\n shouldError: true,\n errorMessage,\n });\n}\n\n/**\n * Creates a spy mock client that records all prompts\n *\n * Useful for testing what prompts are being sent to the LLM.\n *\n * @example\n * ```ts\n * const { client, prompts } = createSpyMockClient({ score: 0.5 });\n * await metric({ outputs, context, llmClient: client });\n * console.log(prompts); // See all prompts that were sent\n * ```\n */\nexport function createSpyMockClient(response: string | Record<string, unknown>): {\n client: LLMClient;\n prompts: string[];\n} {\n const prompts: string[] = [];\n\n const client = createMockLLMClient({\n response,\n onPrompt: (prompt) => prompts.push(prompt),\n });\n\n return { client, prompts };\n}\n","/**\n * Built-in OpenAI adapter for evalsense\n *\n * Provides a simple way to use OpenAI models without writing adapter code.\n *\n * @example\n * ```javascript\n * import { setLLMClient, createOpenAIAdapter } from 'evalsense/metrics';\n *\n * setLLMClient(createOpenAIAdapter(process.env.OPENAI_API_KEY, {\n * model: 'gpt-4-turbo-preview',\n * temperature: 0\n * }));\n * ```\n */\n\nimport type { LLMClient, JSONSchema } from \"../../../core/types.js\";\n\nexport interface OpenAIAdapterOptions {\n /**\n * OpenAI model to use\n * @default \"gpt-4-turbo-preview\"\n */\n model?: string;\n\n /**\n * Temperature for generation (0-2)\n * @default 0\n */\n temperature?: number;\n\n /**\n * Maximum tokens per completion\n * @default 4096\n */\n maxTokens?: number;\n\n /**\n * API base URL (for Azure OpenAI or proxies)\n * @default undefined (uses default OpenAI endpoint)\n */\n baseURL?: string;\n\n /**\n * Organization ID (optional)\n */\n organization?: string;\n\n /**\n * Request timeout in milliseconds\n * @default 30000\n */\n timeout?: number;\n}\n\n/**\n * Creates an LLM client adapter for OpenAI.\n *\n * **Setup:**\n * 1. Install OpenAI SDK: `npm install openai`\n * 2. Get API key from https://platform.openai.com/api-keys\n * 3. Set environment variable: `export OPENAI_API_KEY=\"sk-...\"`\n *\n * **Model Options:**\n * - `gpt-4-turbo-preview` - Most capable, expensive\n * - `gpt-4` - High quality, expensive\n * - `gpt-3.5-turbo` - Fast and cheap (20x cheaper than GPT-4)\n *\n * @param apiKey - Your OpenAI API key\n * @param options - Configuration options\n * @returns LLM client for use with evalsense metrics\n *\n * @example\n * ```javascript\n * // Basic usage\n * const client = createOpenAIAdapter(process.env.OPENAI_API_KEY);\n * setLLMClient(client);\n *\n * // With custom model\n * const client = createOpenAIAdapter(process.env.OPENAI_API_KEY, {\n * model: 'gpt-3.5-turbo', // Cheaper model\n * temperature: 0.3\n * });\n *\n * // With Azure OpenAI\n * const client = createOpenAIAdapter(process.env.AZURE_OPENAI_KEY, {\n * baseURL: 'https://your-resource.openai.azure.com',\n * model: 'gpt-4'\n * });\n * ```\n */\nexport function createOpenAIAdapter(apiKey: string, options: OpenAIAdapterOptions = {}): LLMClient {\n const {\n model = \"gpt-4-turbo-preview\",\n temperature = 0,\n maxTokens = 4096,\n baseURL,\n organization,\n timeout = 30000,\n } = options;\n\n // Validate API key\n if (!apiKey) {\n throw new Error(\n \"OpenAI API key is required. \" + \"Get one at https://platform.openai.com/api-keys\"\n );\n }\n\n // Lazy-load OpenAI SDK (peer dependency)\n let OpenAI: any;\n let openaiClient: any;\n\n function ensureClient() {\n if (openaiClient) return openaiClient;\n\n try {\n // Try ESM import first\n OpenAI = require(\"openai\").default || require(\"openai\");\n } catch (error) {\n throw new Error(\n \"OpenAI SDK not found. Install it with: npm install openai\\n\" +\n \"Visit https://github.com/openai/openai-node for documentation.\"\n );\n }\n\n openaiClient = new OpenAI({\n apiKey,\n baseURL,\n organization,\n timeout,\n });\n\n return openaiClient;\n }\n\n return {\n async complete(prompt: string): Promise<string> {\n const client = ensureClient();\n\n try {\n const response = await client.chat.completions.create({\n model,\n messages: [{ role: \"user\", content: prompt }],\n temperature,\n max_tokens: maxTokens,\n });\n\n return response.choices[0]?.message?.content ?? \"\";\n } catch (error: any) {\n // Enhance error message with context\n const errorMessage = error?.message || error?.error?.message || String(error);\n throw new Error(\n `OpenAI API error (model: ${model}): ${errorMessage}\\n` +\n `Check your API key and quota at https://platform.openai.com/account/usage`\n );\n }\n },\n\n async completeStructured<T>(prompt: string, _schema: JSONSchema): Promise<T> {\n const client = ensureClient();\n\n try {\n const response = await client.chat.completions.create({\n model,\n messages: [{ role: \"user\", content: prompt }],\n response_format: { type: \"json_object\" },\n temperature,\n max_tokens: maxTokens,\n });\n\n const text = response.choices[0]?.message?.content ?? \"{}\";\n return JSON.parse(text) as T;\n } catch (error: any) {\n const errorMessage = error?.message || error?.error?.message || String(error);\n throw new Error(\n `OpenAI API error (model: ${model}): ${errorMessage}\\n` +\n `Check your API key and quota at https://platform.openai.com/account/usage`\n );\n }\n },\n };\n}\n","/**\n * Built-in Anthropic (Claude) adapter for evalsense\n *\n * Provides a simple way to use Claude models without writing adapter code.\n *\n * @example\n * ```javascript\n * import { setLLMClient, createAnthropicAdapter } from 'evalsense/metrics';\n *\n * setLLMClient(createAnthropicAdapter(process.env.ANTHROPIC_API_KEY, {\n * model: 'claude-3-5-sonnet-20241022'\n * }));\n * ```\n */\n\nimport type { LLMClient, JSONSchema } from \"../../../core/types.js\";\nimport { parseJSONResponse } from \"../utils.js\";\n\nexport interface AnthropicAdapterOptions {\n /**\n * Anthropic model to use\n * @default \"claude-3-5-sonnet-20241022\"\n */\n model?: string;\n\n /**\n * Maximum tokens per completion\n * @default 4096\n */\n maxTokens?: number;\n\n /**\n * Temperature for generation (0-1)\n * Note: Anthropic doesn't support temperature > 1\n * @default 0\n */\n temperature?: number;\n\n /**\n * Request timeout in milliseconds\n * @default 30000\n */\n timeout?: number;\n}\n\n/**\n * Creates an LLM client adapter for Anthropic Claude.\n *\n * **Setup:**\n * 1. Install Anthropic SDK: `npm install @anthropic-ai/sdk`\n * 2. Get API key from https://console.anthropic.com/\n * 3. Set environment variable: `export ANTHROPIC_API_KEY=\"sk-ant-...\"`\n *\n * **Model Options:**\n * - `claude-3-5-sonnet-20241022` - Latest, most capable (recommended)\n * - `claude-3-opus-20240229` - Most capable, expensive\n * - `claude-3-sonnet-20240229` - Balanced performance\n * - `claude-3-haiku-20240307` - Fast and affordable\n *\n * @param apiKey - Your Anthropic API key\n * @param options - Configuration options\n * @returns LLM client for use with evalsense metrics\n *\n * @example\n * ```javascript\n * // Basic usage\n * const client = createAnthropicAdapter(process.env.ANTHROPIC_API_KEY);\n * setLLMClient(client);\n *\n * // With custom model\n * const client = createAnthropicAdapter(process.env.ANTHROPIC_API_KEY, {\n * model: 'claude-3-haiku-20240307', // Cheaper, faster model\n * maxTokens: 2048\n * });\n * ```\n */\nexport function createAnthropicAdapter(\n apiKey: string,\n options: AnthropicAdapterOptions = {}\n): LLMClient {\n const {\n model = \"claude-3-5-sonnet-20241022\",\n maxTokens = 4096,\n temperature = 0,\n timeout = 30000,\n } = options;\n\n // Validate API key\n if (!apiKey) {\n throw new Error(\n \"Anthropic API key is required. \" + \"Get one at https://console.anthropic.com/\"\n );\n }\n\n // Validate temperature (Anthropic only supports 0-1)\n if (temperature < 0 || temperature > 1) {\n throw new Error(`Anthropic temperature must be between 0 and 1, got ${temperature}`);\n }\n\n // Lazy-load Anthropic SDK (peer dependency)\n let Anthropic: any;\n let anthropicClient: any;\n\n function ensureClient() {\n if (anthropicClient) return anthropicClient;\n\n try {\n // Try ESM import first\n Anthropic = require(\"@anthropic-ai/sdk\").default || require(\"@anthropic-ai/sdk\");\n } catch (error) {\n throw new Error(\n \"Anthropic SDK not found. Install it with: npm install @anthropic-ai/sdk\\n\" +\n \"Visit https://github.com/anthropics/anthropic-sdk-typescript for documentation.\"\n );\n }\n\n anthropicClient = new Anthropic({\n apiKey,\n timeout,\n });\n\n return anthropicClient;\n }\n\n return {\n async complete(prompt: string): Promise<string> {\n const client = ensureClient();\n\n try {\n const message = await client.messages.create({\n model,\n max_tokens: maxTokens,\n temperature,\n messages: [{ role: \"user\", content: prompt }],\n });\n\n // Extract text from first content block\n const firstBlock = message.content[0];\n return firstBlock?.type === \"text\" ? firstBlock.text : \"\";\n } catch (error: any) {\n const errorMessage = error?.message || error?.error?.message || String(error);\n throw new Error(\n `Anthropic API error (model: ${model}): ${errorMessage}\\n` +\n `Check your API key and usage at https://console.anthropic.com/`\n );\n }\n },\n\n async completeStructured<T>(prompt: string, schema: JSONSchema): Promise<T> {\n // Note: Anthropic doesn't have built-in JSON mode yet\n // We parse JSON from text response\n const jsonPrompt =\n prompt +\n \"\\n\\nIMPORTANT: Respond with valid JSON only. No markdown, no explanation. \" +\n `The JSON must match this schema: ${JSON.stringify(schema)}`;\n\n const response = await this.complete(jsonPrompt);\n\n try {\n return parseJSONResponse<T>(response);\n } catch (error: any) {\n throw new Error(\n `Failed to parse Anthropic response as JSON: ${error.message}\\n` +\n `Response preview: ${response.substring(0, 200)}...`\n );\n }\n },\n };\n}\n","/**\n * Built-in OpenRouter adapter for evalsense\n *\n * OpenRouter provides access to multiple LLM providers (OpenAI, Anthropic, Google, Meta, etc.)\n * through a single unified API. Great for comparing models or avoiding vendor lock-in.\n *\n * @example\n * ```javascript\n * import { setLLMClient, createOpenRouterAdapter } from 'evalsense/metrics';\n *\n * setLLMClient(createOpenRouterAdapter(process.env.OPENROUTER_API_KEY, {\n * model: 'anthropic/claude-3.5-sonnet'\n * }));\n * ```\n */\n\nimport type { LLMClient, JSONSchema } from \"../../../core/types.js\";\nimport { parseJSONResponse } from \"../utils.js\";\n\nexport interface OpenRouterAdapterOptions {\n /**\n * Model to use (in format: provider/model-name)\n *\n * Popular options:\n * - `anthropic/claude-3.5-sonnet` - Latest Claude\n * - `openai/gpt-4-turbo` - GPT-4 Turbo\n * - `openai/gpt-3.5-turbo` - Cheap and fast\n * - `google/gemini-pro` - Google Gemini\n * - `meta-llama/llama-3-70b-instruct` - Open source\n *\n * See full list: https://openrouter.ai/models\n *\n * @default \"anthropic/claude-3.5-sonnet\"\n */\n model?: string;\n\n /**\n * Temperature for generation (0-2)\n * @default 0\n */\n temperature?: number;\n\n /**\n * Maximum tokens per completion\n * @default 4096\n */\n maxTokens?: number;\n\n /**\n * Your app name (for OpenRouter analytics)\n * @default \"evalsense\"\n */\n appName?: string;\n\n /**\n * Your app URL (for OpenRouter analytics)\n */\n siteUrl?: string;\n\n /**\n * Request timeout in milliseconds\n * @default 30000\n */\n timeout?: number;\n}\n\n/**\n * Creates an LLM client adapter for OpenRouter.\n *\n * **Setup:**\n * 1. Get API key from https://openrouter.ai/keys\n * 2. Set environment variable: `export OPENROUTER_API_KEY=\"sk-or-...\"`\n * 3. No SDK needed - uses fetch API\n *\n * **Benefits:**\n * - Access 100+ models from one API\n * - Compare different providers easily\n * - Automatic fallbacks and retries\n * - Transparent pricing\n *\n * @param apiKey - Your OpenRouter API key\n * @param options - Configuration options\n * @returns LLM client for use with evalsense metrics\n *\n * @example\n * ```javascript\n * // Basic usage\n * const client = createOpenRouterAdapter(process.env.OPENROUTER_API_KEY);\n * setLLMClient(client);\n *\n * // Use different model\n * const client = createOpenRouterAdapter(process.env.OPENROUTER_API_KEY, {\n * model: 'openai/gpt-3.5-turbo', // Cheaper option\n * appName: 'my-eval-system'\n * });\n *\n * // Use free models for testing\n * const client = createOpenRouterAdapter(process.env.OPENROUTER_API_KEY, {\n * model: 'meta-llama/llama-3-8b-instruct:free'\n * });\n * ```\n */\nexport function createOpenRouterAdapter(\n apiKey: string,\n options: OpenRouterAdapterOptions = {}\n): LLMClient {\n const {\n model = \"anthropic/claude-3.5-sonnet\",\n temperature = 0,\n maxTokens = 4096,\n appName = \"evalsense\",\n siteUrl,\n timeout = 30000,\n } = options;\n\n // Validate API key\n if (!apiKey) {\n throw new Error(\"OpenRouter API key is required. \" + \"Get one at https://openrouter.ai/keys\");\n }\n\n const baseURL = \"https://openrouter.ai/api/v1\";\n\n async function callAPI(\n messages: Array<{ role: string; content: string }>,\n jsonMode: boolean = false\n ): Promise<string> {\n const headers: Record<string, string> = {\n Authorization: `Bearer ${apiKey}`,\n \"Content-Type\": \"application/json\",\n \"HTTP-Referer\": siteUrl || \"https://github.com/evalsense/evalsense\",\n \"X-Title\": appName,\n };\n\n const body: any = {\n model,\n messages,\n temperature,\n max_tokens: maxTokens,\n };\n\n // Enable JSON mode if supported by model\n if (jsonMode) {\n body.response_format = { type: \"json_object\" };\n }\n\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), timeout);\n\n try {\n const response = await fetch(`${baseURL}/chat/completions`, {\n method: \"POST\",\n headers,\n body: JSON.stringify(body),\n signal: controller.signal,\n });\n\n clearTimeout(timeoutId);\n\n if (!response.ok) {\n const errorData: any = await response.json().catch(() => ({}));\n const errorMessage = errorData.error?.message || response.statusText || \"Unknown error\";\n throw new Error(`OpenRouter API error (${response.status}): ${errorMessage}`);\n }\n\n const data: any = await response.json();\n return data.choices?.[0]?.message?.content ?? \"\";\n } catch (error: any) {\n clearTimeout(timeoutId);\n\n if (error.name === \"AbortError\") {\n throw new Error(`OpenRouter request timed out after ${timeout}ms (model: ${model})`);\n }\n\n const errorMessage = error?.message || String(error);\n throw new Error(\n `OpenRouter API error (model: ${model}): ${errorMessage}\\n` +\n `Check your API key and credits at https://openrouter.ai/activity`\n );\n }\n }\n\n return {\n async complete(prompt: string): Promise<string> {\n return callAPI([{ role: \"user\", content: prompt }], false);\n },\n\n async completeStructured<T>(prompt: string, schema: JSONSchema): Promise<T> {\n // Try JSON mode first (works for OpenAI models via OpenRouter)\n let response: string;\n try {\n response = await callAPI(\n [{ role: \"user\", content: prompt }],\n true // Enable JSON mode\n );\n } catch (error) {\n // If JSON mode not supported, fall back to prompt engineering\n const jsonPrompt =\n prompt +\n \"\\n\\nIMPORTANT: Respond with valid JSON only. No markdown, no explanation. \" +\n `The JSON must match this schema: ${JSON.stringify(schema)}`;\n response = await callAPI([{ role: \"user\", content: jsonPrompt }], false);\n }\n\n try {\n return parseJSONResponse<T>(response);\n } catch (error: any) {\n throw new Error(\n `Failed to parse OpenRouter response as JSON: ${error.message}\\n` +\n `Model: ${model}\\n` +\n `Response preview: ${response.substring(0, 200)}...`\n );\n }\n },\n };\n}\n"]}