@painitehq/structured-llm 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +145 -59
- package/dist/index.cjs +280 -74
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +19 -5
- package/dist/index.d.ts +19 -5
- package/dist/index.js +279 -75
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -109,12 +109,6 @@ function repairJSON(raw) {
|
|
|
109
109
|
candidate = candidate.replace(/,\s*([}\]])/g, "$1");
|
|
110
110
|
candidate = candidate.replace(/'/g, '"');
|
|
111
111
|
candidate = candidate.replace(/(\w+)\s*:/g, '"$1":');
|
|
112
|
-
candidate = candidate.replace(/:\s*"([^"]*?)"/g, (match, content) => {
|
|
113
|
-
if (content.includes('"')) {
|
|
114
|
-
return match;
|
|
115
|
-
}
|
|
116
|
-
return match;
|
|
117
|
-
});
|
|
118
112
|
if (!candidate.startsWith("{") && !candidate.startsWith("[")) {
|
|
119
113
|
const firstBrace = candidate.indexOf("{");
|
|
120
114
|
const firstBracket = candidate.indexOf("[");
|
|
@@ -139,6 +133,128 @@ function repairJSON(raw) {
|
|
|
139
133
|
}
|
|
140
134
|
return null;
|
|
141
135
|
}
|
|
136
|
+
function coerceType(value, targetType) {
|
|
137
|
+
if (value === void 0 || value === null) {
|
|
138
|
+
return { value, coerced: false };
|
|
139
|
+
}
|
|
140
|
+
switch (targetType) {
|
|
141
|
+
case "string":
|
|
142
|
+
if (typeof value === "string") return { value, coerced: false };
|
|
143
|
+
return { value: String(value), coerced: true };
|
|
144
|
+
case "number":
|
|
145
|
+
if (typeof value === "number") return { value, coerced: false };
|
|
146
|
+
if (typeof value === "string") {
|
|
147
|
+
const num = Number(value);
|
|
148
|
+
if (!isNaN(num)) return { value: num, coerced: true };
|
|
149
|
+
}
|
|
150
|
+
return { value, coerced: false };
|
|
151
|
+
case "boolean":
|
|
152
|
+
if (typeof value === "boolean") return { value, coerced: false };
|
|
153
|
+
if (typeof value === "string") {
|
|
154
|
+
const lower = value.toLowerCase();
|
|
155
|
+
if (lower === "true" || lower === "yes" || lower === "1") return { value: true, coerced: true };
|
|
156
|
+
if (lower === "false" || lower === "no" || lower === "0") return { value: false, coerced: true };
|
|
157
|
+
}
|
|
158
|
+
if (typeof value === "number") {
|
|
159
|
+
return { value: value !== 0, coerced: true };
|
|
160
|
+
}
|
|
161
|
+
return { value, coerced: false };
|
|
162
|
+
case "array":
|
|
163
|
+
if (Array.isArray(value)) return { value, coerced: false };
|
|
164
|
+
return { value: [value], coerced: true };
|
|
165
|
+
default:
|
|
166
|
+
return { value, coerced: false };
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
function repairData(data, fields, schemaName) {
|
|
170
|
+
const actions = [];
|
|
171
|
+
if (typeof data !== "object" || data === null || Array.isArray(data)) {
|
|
172
|
+
return { data, actions };
|
|
173
|
+
}
|
|
174
|
+
let obj = data;
|
|
175
|
+
if (schemaName in obj && typeof obj[schemaName] === "object" && obj[schemaName] !== null) {
|
|
176
|
+
actions.push({ attempt: 0, type: "unwrap", detail: `Unwrapped "${schemaName}" wrapper` });
|
|
177
|
+
obj = obj[schemaName];
|
|
178
|
+
}
|
|
179
|
+
for (const [key, field] of Object.entries(fields)) {
|
|
180
|
+
if (obj[key] === void 0) {
|
|
181
|
+
if (field.default !== void 0) {
|
|
182
|
+
obj[key] = field.default;
|
|
183
|
+
actions.push({ attempt: 0, type: "default_fill", detail: `Set "${key}" to default: ${JSON.stringify(field.default)}` });
|
|
184
|
+
} else if (field.type === "string") {
|
|
185
|
+
obj[key] = "";
|
|
186
|
+
actions.push({ attempt: 0, type: "missing_field", detail: `Added empty string for "${key}"` });
|
|
187
|
+
} else if (field.type === "number") {
|
|
188
|
+
obj[key] = 0;
|
|
189
|
+
actions.push({ attempt: 0, type: "missing_field", detail: `Added 0 for "${key}"` });
|
|
190
|
+
} else if (field.type === "boolean") {
|
|
191
|
+
obj[key] = false;
|
|
192
|
+
actions.push({ attempt: 0, type: "missing_field", detail: `Added false for "${key}"` });
|
|
193
|
+
} else if (field.type === "array") {
|
|
194
|
+
obj[key] = [];
|
|
195
|
+
actions.push({ attempt: 0, type: "missing_field", detail: `Added empty array for "${key}"` });
|
|
196
|
+
} else if (field.type === "object") {
|
|
197
|
+
obj[key] = {};
|
|
198
|
+
actions.push({ attempt: 0, type: "missing_field", detail: `Added empty object for "${key}"` });
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
if (obj[key] !== void 0) {
|
|
202
|
+
const coerced = coerceType(obj[key], field.type);
|
|
203
|
+
if (coerced.coerced) {
|
|
204
|
+
obj[key] = coerced.value;
|
|
205
|
+
actions.push({ attempt: 0, type: "type_coercion", detail: `Coerced "${key}" to ${field.type}: ${JSON.stringify(coerced.value)}` });
|
|
206
|
+
}
|
|
207
|
+
if (field.type === "array" && Array.isArray(obj[key]) && field.items) {
|
|
208
|
+
obj[key] = obj[key].map((item, i) => {
|
|
209
|
+
const itemResult = repairItem(item, field.items);
|
|
210
|
+
if (itemResult.actions.length > 0) {
|
|
211
|
+
actions.push(...itemResult.actions.map((a) => ({ ...a, detail: `[${i}] ${a.detail}` })));
|
|
212
|
+
}
|
|
213
|
+
return itemResult.data;
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
if (field.type === "object" && typeof obj[key] === "object" && obj[key] !== null && field.properties) {
|
|
217
|
+
const nested = repairData(obj[key], field.properties, "");
|
|
218
|
+
if (nested.actions.length > 0) {
|
|
219
|
+
actions.push(...nested.actions);
|
|
220
|
+
}
|
|
221
|
+
obj[key] = nested.data;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
return { data: obj, actions };
|
|
226
|
+
}
|
|
227
|
+
function repairItem(item, field) {
|
|
228
|
+
const actions = [];
|
|
229
|
+
if (field.type === "object" && typeof item === "object" && item !== null && !Array.isArray(item) && field.properties) {
|
|
230
|
+
const obj = item;
|
|
231
|
+
for (const [key, prop] of Object.entries(field.properties)) {
|
|
232
|
+
if (obj[key] === void 0) {
|
|
233
|
+
if (prop.type === "string") {
|
|
234
|
+
obj[key] = "";
|
|
235
|
+
actions.push({ attempt: 0, type: "missing_field", detail: `Added empty string for "${key}"` });
|
|
236
|
+
} else if (prop.type === "number") {
|
|
237
|
+
obj[key] = 0;
|
|
238
|
+
actions.push({ attempt: 0, type: "missing_field", detail: `Added 0 for "${key}"` });
|
|
239
|
+
} else if (prop.type === "boolean") {
|
|
240
|
+
obj[key] = false;
|
|
241
|
+
actions.push({ attempt: 0, type: "missing_field", detail: `Added false for "${key}"` });
|
|
242
|
+
} else if (prop.type === "array") {
|
|
243
|
+
obj[key] = [];
|
|
244
|
+
actions.push({ attempt: 0, type: "missing_field", detail: `Added empty array for "${key}"` });
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
if (obj[key] !== void 0) {
|
|
248
|
+
const coerced = coerceType(obj[key], prop.type);
|
|
249
|
+
if (coerced.coerced) {
|
|
250
|
+
obj[key] = coerced.value;
|
|
251
|
+
actions.push({ attempt: 0, type: "type_coercion", detail: `Coerced "${key}" to ${prop.type}` });
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
return { data: item, actions };
|
|
257
|
+
}
|
|
142
258
|
function validateAgainstSchema(data, schema) {
|
|
143
259
|
if (typeof data !== "object" || data === null || Array.isArray(data)) {
|
|
144
260
|
return { valid: false, errors: ["Root value must be an object"] };
|
|
@@ -192,40 +308,120 @@ function validateAgainstSchema(data, schema) {
|
|
|
192
308
|
function defineSchema(name, fields) {
|
|
193
309
|
return { name, fields };
|
|
194
310
|
}
|
|
195
|
-
function
|
|
196
|
-
const
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
311
|
+
function schemaToJSONExample(fields) {
|
|
312
|
+
const result = {};
|
|
313
|
+
for (const [key, field] of Object.entries(fields)) {
|
|
314
|
+
switch (field.type) {
|
|
315
|
+
case "string":
|
|
316
|
+
result[key] = field.description || "...";
|
|
317
|
+
break;
|
|
318
|
+
case "number":
|
|
319
|
+
result[key] = 0;
|
|
320
|
+
break;
|
|
321
|
+
case "boolean":
|
|
322
|
+
result[key] = true;
|
|
323
|
+
break;
|
|
324
|
+
case "array":
|
|
325
|
+
if (field.items) {
|
|
326
|
+
result[key] = [schemaToJSONExample(field.items.properties || {})];
|
|
327
|
+
} else {
|
|
328
|
+
result[key] = [];
|
|
329
|
+
}
|
|
330
|
+
break;
|
|
331
|
+
case "object":
|
|
332
|
+
if (field.properties) {
|
|
333
|
+
result[key] = schemaToJSONExample(field.properties);
|
|
334
|
+
} else {
|
|
335
|
+
result[key] = {};
|
|
336
|
+
}
|
|
337
|
+
break;
|
|
200
338
|
}
|
|
201
|
-
return parts.join(" ");
|
|
202
|
-
}).join(",\n ");
|
|
203
|
-
return `{
|
|
204
|
-
"${schema.name}": {
|
|
205
|
-
${fieldDescriptions}
|
|
206
339
|
}
|
|
207
|
-
|
|
340
|
+
return result;
|
|
208
341
|
}
|
|
209
|
-
function
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
342
|
+
function buildForcedPrompt(input, schema) {
|
|
343
|
+
const example = schemaToJSONExample(schema.fields);
|
|
344
|
+
const exampleStr = JSON.stringify({ [schema.name]: example }, null, 2);
|
|
345
|
+
return `TASK: Extract structured data from the text below into EXACTLY this JSON format.
|
|
346
|
+
|
|
347
|
+
CRITICAL RULES:
|
|
348
|
+
- Output ONLY valid JSON. No text before or after.
|
|
349
|
+
- No markdown. No code blocks. No explanations.
|
|
350
|
+
- Every field MUST be present with the correct type.
|
|
351
|
+
- String fields: use "" if unknown.
|
|
352
|
+
- Number fields: use 0 if unknown.
|
|
353
|
+
- Boolean fields: use true or false, never null.
|
|
354
|
+
- Array fields: use [] if empty, never null.
|
|
355
|
+
- Object fields: use {} if empty, never null.
|
|
356
|
+
|
|
357
|
+
REQUIRED JSON STRUCTURE:
|
|
358
|
+
${exampleStr}
|
|
359
|
+
|
|
360
|
+
TEXT TO EXTRACT FROM:
|
|
361
|
+
"""
|
|
362
|
+
${input}
|
|
363
|
+
"""
|
|
364
|
+
|
|
365
|
+
OUTPUT ONLY THE JSON OBJECT. NOTHING ELSE.`;
|
|
366
|
+
}
|
|
367
|
+
function buildRetryPrompt(input, schema, previousResponse, error) {
|
|
368
|
+
const example = schemaToJSONExample(schema.fields);
|
|
369
|
+
const exampleStr = JSON.stringify({ [schema.name]: example }, null, 2);
|
|
370
|
+
return `YOUR PREVIOUS RESPONSE WAS INVALID. You MUST fix it.
|
|
371
|
+
|
|
372
|
+
ERROR: ${error}
|
|
373
|
+
|
|
374
|
+
YOUR PREVIOUS RESPONSE:
|
|
375
|
+
${previousResponse.slice(0, 1e3)}
|
|
376
|
+
|
|
377
|
+
WHAT YOU MUST DO NOW:
|
|
378
|
+
1. Output ONLY valid JSON matching this EXACT structure
|
|
379
|
+
2. No text before or after the JSON
|
|
380
|
+
3. No markdown, no code blocks, no explanations
|
|
381
|
+
4. Fix the errors listed above
|
|
382
|
+
5. Every field MUST be present
|
|
383
|
+
|
|
384
|
+
REQUIRED JSON STRUCTURE:
|
|
385
|
+
${exampleStr}
|
|
386
|
+
|
|
387
|
+
TEXT TO EXTRACT FROM:
|
|
388
|
+
"""
|
|
389
|
+
${input}
|
|
390
|
+
"""
|
|
391
|
+
|
|
392
|
+
OUTPUT ONLY THE JSON OBJECT. NOTHING ELSE.`;
|
|
393
|
+
}
|
|
394
|
+
function buildFinalPrompt(input, schema, previousResponse, error) {
|
|
395
|
+
const example = schemaToJSONExample(schema.fields);
|
|
396
|
+
const exampleStr = JSON.stringify({ [schema.name]: example }, null, 2);
|
|
397
|
+
return `FINAL ATTEMPT. THIS IS YOUR LAST CHANCE.
|
|
398
|
+
|
|
399
|
+
YOUR PREVIOUS RESPONSE FAILED VALIDATION:
|
|
400
|
+
${error}
|
|
401
|
+
|
|
402
|
+
YOUR PREVIOUS RESPONSE:
|
|
403
|
+
${previousResponse.slice(0, 800)}
|
|
404
|
+
|
|
405
|
+
YOU MUST OUTPUT EXACTLY THIS STRUCTURE. NOTHING MORE. NOTHING LESS.
|
|
406
|
+
DO NOT ADD FIELDS THAT ARE NOT IN THE SCHEMA.
|
|
407
|
+
DO NOT OMIT ANY FIELDS.
|
|
408
|
+
DO NOT WRAP IN markdown OR code blocks.
|
|
409
|
+
DO NOT ADD ANY TEXT BEFORE OR AFTER THE JSON.
|
|
410
|
+
|
|
411
|
+
STRUCTURE:
|
|
412
|
+
${exampleStr}
|
|
413
|
+
|
|
414
|
+
INPUT TEXT:
|
|
415
|
+
"""
|
|
416
|
+
${input}
|
|
417
|
+
"""
|
|
418
|
+
|
|
419
|
+
OUTPUT ONLY:
|
|
420
|
+
`;
|
|
421
|
+
}
|
|
422
|
+
function schemaToPrompt(schema) {
|
|
423
|
+
const example = schemaToJSONExample(schema.fields);
|
|
424
|
+
return JSON.stringify({ [schema.name]: example }, null, 2);
|
|
229
425
|
}
|
|
230
426
|
function schemaToZodishString(schema) {
|
|
231
427
|
const lines = [];
|
|
@@ -239,11 +435,16 @@ function schemaToZodishString(schema) {
|
|
|
239
435
|
|
|
240
436
|
// src/extractor.ts
|
|
241
437
|
var MAX_REPAIR_ATTEMPTS = 3;
|
|
242
|
-
function
|
|
243
|
-
|
|
244
|
-
|
|
438
|
+
function calculateConfidence(attempts, repairActions, jsonValidFirstTry) {
|
|
439
|
+
let score = 100;
|
|
440
|
+
if (!jsonValidFirstTry) {
|
|
441
|
+
score -= 15;
|
|
245
442
|
}
|
|
246
|
-
|
|
443
|
+
score -= (attempts - 1) * 10;
|
|
444
|
+
score -= repairActions.filter((a) => a.type === "type_coercion").length * 5;
|
|
445
|
+
score -= repairActions.filter((a) => a.type === "missing_field").length * 8;
|
|
446
|
+
score -= repairActions.filter((a) => a.type === "default_fill").length * 3;
|
|
447
|
+
return Math.max(0, Math.min(100, score));
|
|
247
448
|
}
|
|
248
449
|
async function extract(input, schema, options = {}) {
|
|
249
450
|
const config = {
|
|
@@ -257,59 +458,62 @@ async function extract(input, schema, options = {}) {
|
|
|
257
458
|
"OpenRouter API key required. Pass it in options or set OPENROUTER_API_KEY environment variable."
|
|
258
459
|
);
|
|
259
460
|
}
|
|
260
|
-
const
|
|
261
|
-
const exampleJSON = schemaToPrompt(schema);
|
|
262
|
-
const userPrompt = `Extract structured data from the following text.
|
|
263
|
-
|
|
264
|
-
${schemaDescription}
|
|
265
|
-
|
|
266
|
-
Return ONLY valid JSON matching this structure:
|
|
267
|
-
${exampleJSON}
|
|
268
|
-
|
|
269
|
-
Text to extract from:
|
|
270
|
-
"""
|
|
271
|
-
${input}
|
|
272
|
-
"""`;
|
|
273
|
-
const schemaFields = {};
|
|
274
|
-
for (const [key, field] of Object.entries(schema.fields)) {
|
|
275
|
-
schemaFields[key] = { type: field.type, required: field.required };
|
|
276
|
-
}
|
|
461
|
+
const allRepairActions = [];
|
|
277
462
|
let lastRaw = "";
|
|
278
463
|
let lastError = "";
|
|
464
|
+
let jsonValidFirstTry = false;
|
|
465
|
+
let successAttempt = 0;
|
|
279
466
|
for (let attempt = 0; attempt <= MAX_REPAIR_ATTEMPTS; attempt++) {
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
Fix the JSON and return ONLY valid JSON. No explanations, no markdown, just the raw JSON object.`;
|
|
467
|
+
let prompt;
|
|
468
|
+
if (attempt === 0) {
|
|
469
|
+
prompt = buildForcedPrompt(input, schema);
|
|
470
|
+
} else if (attempt === 1) {
|
|
471
|
+
prompt = buildRetryPrompt(input, schema, lastRaw, lastError);
|
|
472
|
+
} else {
|
|
473
|
+
prompt = buildFinalPrompt(input, schema, lastRaw, lastError);
|
|
474
|
+
}
|
|
289
475
|
const response = await callLLM(prompt, config);
|
|
290
476
|
lastRaw = response.content;
|
|
291
477
|
const extracted = extractJSON(response.content);
|
|
292
478
|
if (!extracted) {
|
|
293
|
-
lastError = "Could not extract JSON from response";
|
|
479
|
+
lastError = "Could not extract JSON from response. Your response contained no valid JSON.";
|
|
294
480
|
continue;
|
|
295
481
|
}
|
|
296
482
|
const parsed = tryParseJSON(extracted);
|
|
297
483
|
if (!parsed.success) {
|
|
298
|
-
lastError = parsed.error
|
|
484
|
+
lastError = `Invalid JSON: ${parsed.error}`;
|
|
299
485
|
continue;
|
|
300
486
|
}
|
|
487
|
+
if (attempt === 0) {
|
|
488
|
+
jsonValidFirstTry = true;
|
|
489
|
+
}
|
|
301
490
|
const repaired = repairJSON(extracted);
|
|
302
|
-
|
|
303
|
-
const
|
|
304
|
-
|
|
491
|
+
let finalData = repaired ? tryParseJSON(repaired).success ? JSON.parse(repaired) : parsed.data : parsed.data;
|
|
492
|
+
const repairResult = repairData(finalData, schema.fields, schema.name);
|
|
493
|
+
finalData = repairResult.data;
|
|
494
|
+
allRepairActions.push(...repairResult.actions);
|
|
495
|
+
const schemaFields = {};
|
|
496
|
+
for (const [key, field] of Object.entries(schema.fields)) {
|
|
497
|
+
schemaFields[key] = field;
|
|
498
|
+
}
|
|
499
|
+
const validation = validateAgainstSchema(finalData, schemaFields);
|
|
305
500
|
if (!validation.valid) {
|
|
306
|
-
lastError = `Schema validation failed: ${validation.errors.join("
|
|
501
|
+
lastError = `Schema validation failed: ${validation.errors.join("; ")}`;
|
|
502
|
+
allRepairActions.push({
|
|
503
|
+
attempt,
|
|
504
|
+
type: "type_coercion",
|
|
505
|
+
detail: `Validation failed: ${validation.errors.join("; ")}`
|
|
506
|
+
});
|
|
307
507
|
continue;
|
|
308
508
|
}
|
|
509
|
+
successAttempt = attempt + 1;
|
|
309
510
|
return {
|
|
310
|
-
data:
|
|
511
|
+
data: finalData,
|
|
311
512
|
raw: response.content,
|
|
312
513
|
model: response.model,
|
|
514
|
+
confidence: calculateConfidence(successAttempt, allRepairActions, jsonValidFirstTry),
|
|
515
|
+
repairLog: allRepairActions,
|
|
516
|
+
attempts: successAttempt,
|
|
313
517
|
usage: response.usage
|
|
314
518
|
};
|
|
315
519
|
}
|
|
@@ -318,6 +522,6 @@ Fix the JSON and return ONLY valid JSON. No explanations, no markdown, just the
|
|
|
318
522
|
);
|
|
319
523
|
}
|
|
320
524
|
|
|
321
|
-
export { defineSchema, extract, extractJSON, repairJSON, schemaToPrompt, schemaToZodishString, tryParseJSON, validateAgainstSchema };
|
|
525
|
+
export { coerceType, defineSchema, extract, extractJSON, repairData, repairJSON, schemaToPrompt, schemaToZodishString, tryParseJSON, validateAgainstSchema };
|
|
322
526
|
//# sourceMappingURL=index.js.map
|
|
323
527
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/llm-client.ts","../src/validator.ts","../src/schema.ts","../src/extractor.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["export interface LLMClientConfig {\n apiKey: string;\n model: string;\n temperature?: number;\n timeout?: number;\n}\n\nexport interface LLMResponse {\n content: string;\n model: string;\n usage?: {\n promptTokens: number;\n completionTokens: number;\n totalTokens: number;\n };\n}\n\ninterface OpenRouterChoice {\n message?: {\n content?: string;\n };\n}\n\ninterface OpenRouterResponse {\n choices?: OpenRouterChoice[];\n model?: string;\n usage?: {\n prompt_tokens: number;\n completion_tokens: number;\n total_tokens: number;\n };\n}\n\nconst DEFAULT_MODEL = \"openrouter/free\";\nconst DEFAULT_TEMPERATURE = 0;\nconst DEFAULT_TIMEOUT = 60000;\n\nexport async function callLLM(\n prompt: string,\n config: LLMClientConfig\n): Promise<LLMResponse> {\n const {\n apiKey,\n model = DEFAULT_MODEL,\n temperature = DEFAULT_TEMPERATURE,\n timeout = DEFAULT_TIMEOUT,\n } = config;\n\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), timeout);\n\n try {\n const response = await fetch(\"https://openrouter.ai/api/v1/chat/completions\", {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${apiKey}`,\n \"HTTP-Referer\": \"https://github.com/harshitduggal/structured-llm\",\n \"X-Title\": \"structured-llm\",\n },\n body: JSON.stringify({\n model,\n messages: [\n {\n role: \"system\",\n content: \"You are a precise data extraction assistant. You MUST respond with valid JSON only. No explanations, no markdown, no text outside the JSON. Just the raw JSON object.\",\n },\n {\n role: \"user\",\n content: prompt,\n },\n ],\n temperature,\n response_format: { type: \"json_object\" },\n }),\n signal: controller.signal,\n });\n\n if (!response.ok) {\n const errorBody = await response.text();\n throw new Error(\n `OpenRouter API error: ${response.status} - ${errorBody}`\n );\n }\n\n const data = (await response.json()) as OpenRouterResponse;\n\n if (!data.choices || data.choices.length === 0) {\n throw new Error(\"No response from LLM\");\n }\n\n const content = data.choices[0].message?.content;\n if (!content) {\n throw new Error(\"Empty response from LLM\");\n }\n\n return {\n content,\n model: data.model || model,\n usage: data.usage\n ? {\n promptTokens: data.usage.prompt_tokens,\n completionTokens: data.usage.completion_tokens,\n totalTokens: data.usage.total_tokens,\n }\n : undefined,\n };\n } finally {\n clearTimeout(timeoutId);\n }\n}\n","export function tryParseJSON(raw: string): { success: true; data: unknown } | { success: false; error: string } {\n try {\n const data = JSON.parse(raw);\n return { success: true, data };\n } catch (e) {\n return { success: false, error: e instanceof Error ? e.message : \"Unknown parse error\" };\n }\n}\n\nexport function extractJSON(raw: string): string | null {\n const trimmed = raw.trim();\n\n if (tryParseJSON(trimmed).success) {\n return trimmed;\n }\n\n const codeBlockMatch = trimmed.match(/```(?:json)?\\s*\\n?([\\s\\S]*?)\\n?\\s*```/);\n if (codeBlockMatch) {\n const inner = codeBlockMatch[1].trim();\n if (tryParseJSON(inner).success) {\n return inner;\n }\n }\n\n const firstBrace = trimmed.indexOf(\"{\");\n const lastBrace = trimmed.lastIndexOf(\"}\");\n if (firstBrace !== -1 && lastBrace > firstBrace) {\n const candidate = trimmed.slice(firstBrace, lastBrace + 1);\n if (tryParseJSON(candidate).success) {\n return candidate;\n }\n }\n\n const firstBracket = trimmed.indexOf(\"[\");\n const lastBracket = trimmed.lastIndexOf(\"]\");\n if (firstBracket !== -1 && lastBracket > firstBracket) {\n const candidate = trimmed.slice(firstBracket, lastBracket + 1);\n if (tryParseJSON(candidate).success) {\n return candidate;\n }\n }\n\n return null;\n}\n\nexport function repairJSON(raw: string): string | null {\n let candidate = raw.trim();\n\n candidate = candidate.replace(/,\\s*([}\\]])/g, \"$1\");\n\n candidate = candidate.replace(/'/g, '\"');\n\n candidate = candidate.replace(/(\\w+)\\s*:/g, '\"$1\":');\n\n candidate = candidate.replace(/:\\s*\"([^\"]*?)\"/g, (match, content) => {\n if (content.includes('\"')) {\n return match;\n }\n return match;\n });\n\n if (!candidate.startsWith(\"{\") && !candidate.startsWith(\"[\")) {\n const firstBrace = candidate.indexOf(\"{\");\n const firstBracket = candidate.indexOf(\"[\");\n if (firstBrace !== -1 && (firstBracket === -1 || firstBrace < firstBracket)) {\n candidate = candidate.slice(firstBrace);\n } else if (firstBracket !== -1) {\n candidate = candidate.slice(firstBracket);\n }\n }\n\n if (!candidate.endsWith(\"}\") && !candidate.endsWith(\"]\")) {\n const lastBrace = candidate.lastIndexOf(\"}\");\n const lastBracket = candidate.lastIndexOf(\"]\");\n if (lastBrace !== -1 && (lastBracket === -1 || lastBrace > lastBracket)) {\n candidate = candidate.slice(0, lastBrace + 1);\n } else if (lastBracket !== -1) {\n candidate = candidate.slice(0, lastBracket + 1);\n }\n }\n\n const result = tryParseJSON(candidate);\n if (result.success) {\n return candidate;\n }\n\n return null;\n}\n\nexport function validateAgainstSchema(data: unknown, schema: Record<string, { type: string; required?: boolean }>): { valid: true; data: unknown } | { valid: false; errors: string[] } {\n if (typeof data !== \"object\" || data === null || Array.isArray(data)) {\n return { valid: false, errors: [\"Root value must be an object\"] };\n }\n\n const errors: string[] = [];\n const obj = data as Record<string, unknown>;\n\n for (const [key, field] of Object.entries(schema)) {\n const value = obj[key];\n\n if (value === undefined && field.required !== false) {\n errors.push(`Missing required field: \"${key}\"`);\n continue;\n }\n\n if (value === undefined) {\n continue;\n }\n\n switch (field.type) {\n case \"string\":\n if (typeof value !== \"string\") {\n errors.push(`Field \"${key}\" must be string, got ${typeof value}`);\n }\n break;\n case \"number\":\n if (typeof value !== \"number\") {\n errors.push(`Field \"${key}\" must be number, got ${typeof value}`);\n }\n break;\n case \"boolean\":\n if (typeof value !== \"boolean\") {\n errors.push(`Field \"${key}\" must be boolean, got ${typeof value}`);\n }\n break;\n case \"array\":\n if (!Array.isArray(value)) {\n errors.push(`Field \"${key}\" must be array, got ${typeof value}`);\n }\n break;\n case \"object\":\n if (typeof value !== \"object\" || value === null || Array.isArray(value)) {\n errors.push(`Field \"${key}\" must be object, got ${typeof value}`);\n }\n break;\n }\n }\n\n if (errors.length > 0) {\n return { valid: false, errors };\n }\n\n return { valid: true, data };\n}\n","import { ExtractionSchema, SchemaField } from \"./types\";\n\nexport function defineSchema(name: string, fields: Record<string, SchemaField>): ExtractionSchema {\n return { name, fields };\n}\n\nexport function schemaToPrompt(schema: ExtractionSchema): string {\n const fieldDescriptions = Object.entries(schema.fields)\n .map(([key, field]) => {\n const parts = [`\"${key}\": ${fieldTypeToExample(field)}`];\n if (field.description) {\n parts.push(`// ${field.description}`);\n }\n return parts.join(\" \");\n })\n .join(\",\\n \");\n\n return `{\n \"${schema.name}\": {\n ${fieldDescriptions}\n }\n}`;\n}\n\nfunction fieldTypeToExample(field: SchemaField): string {\n switch (field.type) {\n case \"string\":\n return `\"...\"`;\n case \"number\":\n return \"0\";\n case \"boolean\":\n return \"true\";\n case \"array\":\n if (field.items) {\n return `[${fieldTypeToExample(field.items)}]`;\n }\n return \"[]\";\n case \"object\":\n if (field.properties) {\n const inner = Object.entries(field.properties)\n .map(([k, v]) => `\"${k}\": ${fieldTypeToExample(v)}`)\n .join(\", \");\n return `{ ${inner} }`;\n }\n return \"{}\";\n }\n}\n\nexport function schemaToZodishString(schema: ExtractionSchema): string {\n const lines: string[] = [];\n lines.push(`Expected JSON structure for \"${schema.name}\":`);\n\n for (const [key, field] of Object.entries(schema.fields)) {\n const required = field.required !== false ? \"(required)\" : \"(optional)\";\n lines.push(` - \"${key}\": ${field.type} ${required}${field.description ? ` - ${field.description}` : \"\"}`);\n }\n\n return lines.join(\"\\n\");\n}\n","import { ExtractionSchema, ExtractionOptions, ExtractionResult } from \"./types\";\nimport { callLLM, LLMClientConfig } from \"./llm-client\";\nimport { extractJSON, repairJSON, tryParseJSON, validateAgainstSchema } from \"./validator\";\nimport { schemaToPrompt, schemaToZodishString } from \"./schema\";\n\n\nconst MAX_REPAIR_ATTEMPTS = 3;\n\nfunction unwrapNamedResponse(data: unknown, schemaName: string): unknown {\n if (\n typeof data === \"object\" &&\n data !== null &&\n !Array.isArray(data) &&\n schemaName in data &&\n typeof (data as Record<string, unknown>)[schemaName] === \"object\"\n ) {\n return (data as Record<string, unknown>)[schemaName];\n }\n return data;\n}\n\nexport async function extract<T = Record<string, unknown>>(\n input: string,\n schema: ExtractionSchema,\n options: ExtractionOptions = {}\n): Promise<ExtractionResult<T>> {\n const config: LLMClientConfig = {\n apiKey: options.apiKey || process.env.OPENROUTER_API_KEY || \"\",\n model: options.model || \"openrouter/free\",\n temperature: options.temperature ?? 0,\n timeout: options.timeout ?? 30000,\n };\n\n if (!config.apiKey || config.apiKey.trim() === \"\") {\n throw new Error(\n \"OpenRouter API key required. Pass it in options or set OPENROUTER_API_KEY environment variable.\"\n );\n }\n\n const schemaDescription = schemaToZodishString(schema);\n const exampleJSON = schemaToPrompt(schema);\n\n const userPrompt = `Extract structured data from the following text.\n\n${schemaDescription}\n\nReturn ONLY valid JSON matching this structure:\n${exampleJSON}\n\nText to extract from:\n\"\"\"\n${input}\n\"\"\"`;\n\n const schemaFields: Record<string, { type: string; required?: boolean }> = {};\n for (const [key, field] of Object.entries(schema.fields)) {\n schemaFields[key] = { type: field.type, required: field.required };\n }\n\n let lastRaw = \"\";\n let lastError = \"\";\n\n for (let attempt = 0; attempt <= MAX_REPAIR_ATTEMPTS; attempt++) {\n const prompt =\n attempt === 0\n ? userPrompt\n : `${userPrompt}\n\nIMPORTANT: Your previous response was invalid JSON. Here was the error:\n${lastError}\n\nPrevious raw response:\n${lastRaw}\n\nFix the JSON and return ONLY valid JSON. No explanations, no markdown, just the raw JSON object.`;\n\n const response = await callLLM(prompt, config);\n lastRaw = response.content;\n\n const extracted = extractJSON(response.content);\n if (!extracted) {\n lastError = \"Could not extract JSON from response\";\n continue;\n }\n\n const parsed = tryParseJSON(extracted);\n if (!parsed.success) {\n lastError = parsed.error;\n continue;\n }\n\n const repaired = repairJSON(extracted);\n const finalData = repaired\n ? tryParseJSON(repaired).success\n ? JSON.parse(repaired)\n : parsed.data\n : parsed.data;\n\n const unwrapped = unwrapNamedResponse(finalData, schema.name);\n\n const validation = validateAgainstSchema(unwrapped, schemaFields);\n if (!validation.valid) {\n lastError = `Schema validation failed: ${validation.errors.join(\", \")}`;\n continue;\n }\n\n return {\n data: unwrapped as T,\n raw: response.content,\n model: response.model,\n usage: response.usage,\n };\n }\n\n throw new Error(\n `Failed to extract valid JSON after ${MAX_REPAIR_ATTEMPTS + 1} attempts. Last error: ${lastError}`\n );\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/llm-client.ts","../src/validator.ts","../src/schema.ts","../src/extractor.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["export interface LLMClientConfig {\n apiKey: string;\n model: string;\n temperature?: number;\n timeout?: number;\n}\n\nexport interface LLMResponse {\n content: string;\n model: string;\n usage?: {\n promptTokens: number;\n completionTokens: number;\n totalTokens: number;\n };\n}\n\ninterface OpenRouterChoice {\n message?: {\n content?: string;\n };\n}\n\ninterface OpenRouterResponse {\n choices?: OpenRouterChoice[];\n model?: string;\n usage?: {\n prompt_tokens: number;\n completion_tokens: number;\n total_tokens: number;\n };\n}\n\nconst DEFAULT_MODEL = \"openrouter/free\";\nconst DEFAULT_TEMPERATURE = 0;\nconst DEFAULT_TIMEOUT = 60000;\n\nexport async function callLLM(\n prompt: string,\n config: LLMClientConfig\n): Promise<LLMResponse> {\n const {\n apiKey,\n model = DEFAULT_MODEL,\n temperature = DEFAULT_TEMPERATURE,\n timeout = DEFAULT_TIMEOUT,\n } = config;\n\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), timeout);\n\n try {\n const response = await fetch(\"https://openrouter.ai/api/v1/chat/completions\", {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${apiKey}`,\n \"HTTP-Referer\": \"https://github.com/harshitduggal/structured-llm\",\n \"X-Title\": \"structured-llm\",\n },\n body: JSON.stringify({\n model,\n messages: [\n {\n role: \"system\",\n content: \"You are a precise data extraction assistant. You MUST respond with valid JSON only. No explanations, no markdown, no text outside the JSON. Just the raw JSON object.\",\n },\n {\n role: \"user\",\n content: prompt,\n },\n ],\n temperature,\n response_format: { type: \"json_object\" },\n }),\n signal: controller.signal,\n });\n\n if (!response.ok) {\n const errorBody = await response.text();\n throw new Error(\n `OpenRouter API error: ${response.status} - ${errorBody}`\n );\n }\n\n const data = (await response.json()) as OpenRouterResponse;\n\n if (!data.choices || data.choices.length === 0) {\n throw new Error(\"No response from LLM\");\n }\n\n const content = data.choices[0].message?.content;\n if (!content) {\n throw new Error(\"Empty response from LLM\");\n }\n\n return {\n content,\n model: data.model || model,\n usage: data.usage\n ? {\n promptTokens: data.usage.prompt_tokens,\n completionTokens: data.usage.completion_tokens,\n totalTokens: data.usage.total_tokens,\n }\n : undefined,\n };\n } finally {\n clearTimeout(timeoutId);\n }\n}\n","import { SchemaField, RepairAction } from \"./types\";\n\nexport function tryParseJSON(raw: string): { success: true; data: unknown } | { success: false; error: string } {\n try {\n const data = JSON.parse(raw);\n return { success: true, data };\n } catch (e) {\n return { success: false, error: e instanceof Error ? e.message : \"Unknown parse error\" };\n }\n}\n\nexport function extractJSON(raw: string): string | null {\n const trimmed = raw.trim();\n\n if (tryParseJSON(trimmed).success) {\n return trimmed;\n }\n\n const codeBlockMatch = trimmed.match(/```(?:json)?\\s*\\n?([\\s\\S]*?)\\n?\\s*```/);\n if (codeBlockMatch) {\n const inner = codeBlockMatch[1].trim();\n if (tryParseJSON(inner).success) {\n return inner;\n }\n }\n\n const firstBrace = trimmed.indexOf(\"{\");\n const lastBrace = trimmed.lastIndexOf(\"}\");\n if (firstBrace !== -1 && lastBrace > firstBrace) {\n const candidate = trimmed.slice(firstBrace, lastBrace + 1);\n if (tryParseJSON(candidate).success) {\n return candidate;\n }\n }\n\n const firstBracket = trimmed.indexOf(\"[\");\n const lastBracket = trimmed.lastIndexOf(\"]\");\n if (firstBracket !== -1 && lastBracket > firstBracket) {\n const candidate = trimmed.slice(firstBracket, lastBracket + 1);\n if (tryParseJSON(candidate).success) {\n return candidate;\n }\n }\n\n return null;\n}\n\nexport function repairJSON(raw: string): string | null {\n let candidate = raw.trim();\n\n candidate = candidate.replace(/,\\s*([}\\]])/g, \"$1\");\n candidate = candidate.replace(/'/g, '\"');\n candidate = candidate.replace(/(\\w+)\\s*:/g, '\"$1\":');\n\n if (!candidate.startsWith(\"{\") && !candidate.startsWith(\"[\")) {\n const firstBrace = candidate.indexOf(\"{\");\n const firstBracket = candidate.indexOf(\"[\");\n if (firstBrace !== -1 && (firstBracket === -1 || firstBrace < firstBracket)) {\n candidate = candidate.slice(firstBrace);\n } else if (firstBracket !== -1) {\n candidate = candidate.slice(firstBracket);\n }\n }\n\n if (!candidate.endsWith(\"}\") && !candidate.endsWith(\"]\")) {\n const lastBrace = candidate.lastIndexOf(\"}\");\n const lastBracket = candidate.lastIndexOf(\"]\");\n if (lastBrace !== -1 && (lastBracket === -1 || lastBrace > lastBracket)) {\n candidate = candidate.slice(0, lastBrace + 1);\n } else if (lastBracket !== -1) {\n candidate = candidate.slice(0, lastBracket + 1);\n }\n }\n\n const result = tryParseJSON(candidate);\n if (result.success) {\n return candidate;\n }\n\n return null;\n}\n\nexport function coerceType(value: unknown, targetType: string): { value: unknown; coerced: boolean } {\n if (value === undefined || value === null) {\n return { value, coerced: false };\n }\n\n switch (targetType) {\n case \"string\":\n if (typeof value === \"string\") return { value, coerced: false };\n return { value: String(value), coerced: true };\n\n case \"number\":\n if (typeof value === \"number\") return { value, coerced: false };\n if (typeof value === \"string\") {\n const num = Number(value);\n if (!isNaN(num)) return { value: num, coerced: true };\n }\n return { value, coerced: false };\n\n case \"boolean\":\n if (typeof value === \"boolean\") return { value, coerced: false };\n if (typeof value === \"string\") {\n const lower = value.toLowerCase();\n if (lower === \"true\" || lower === \"yes\" || lower === \"1\") return { value: true, coerced: true };\n if (lower === \"false\" || lower === \"no\" || lower === \"0\") return { value: false, coerced: true };\n }\n if (typeof value === \"number\") {\n return { value: value !== 0, coerced: true };\n }\n return { value, coerced: false };\n\n case \"array\":\n if (Array.isArray(value)) return { value, coerced: false };\n return { value: [value], coerced: true };\n\n default:\n return { value, coerced: false };\n }\n}\n\nexport function repairData(data: unknown, fields: Record<string, SchemaField>, schemaName: string): { data: unknown; actions: RepairAction[] } {\n const actions: RepairAction[] = [];\n\n if (typeof data !== \"object\" || data === null || Array.isArray(data)) {\n return { data, actions };\n }\n\n let obj = data as Record<string, unknown>;\n\n if (schemaName in obj && typeof obj[schemaName] === \"object\" && obj[schemaName] !== null) {\n actions.push({ attempt: 0, type: \"unwrap\", detail: `Unwrapped \"${schemaName}\" wrapper` });\n obj = obj[schemaName] as Record<string, unknown>;\n }\n\n for (const [key, field] of Object.entries(fields)) {\n if (obj[key] === undefined) {\n if (field.default !== undefined) {\n obj[key] = field.default;\n actions.push({ attempt: 0, type: \"default_fill\", detail: `Set \"${key}\" to default: ${JSON.stringify(field.default)}` });\n } else if (field.type === \"string\") {\n obj[key] = \"\";\n actions.push({ attempt: 0, type: \"missing_field\", detail: `Added empty string for \"${key}\"` });\n } else if (field.type === \"number\") {\n obj[key] = 0;\n actions.push({ attempt: 0, type: \"missing_field\", detail: `Added 0 for \"${key}\"` });\n } else if (field.type === \"boolean\") {\n obj[key] = false;\n actions.push({ attempt: 0, type: \"missing_field\", detail: `Added false for \"${key}\"` });\n } else if (field.type === \"array\") {\n obj[key] = [];\n actions.push({ attempt: 0, type: \"missing_field\", detail: `Added empty array for \"${key}\"` });\n } else if (field.type === \"object\") {\n obj[key] = {};\n actions.push({ attempt: 0, type: \"missing_field\", detail: `Added empty object for \"${key}\"` });\n }\n }\n\n if (obj[key] !== undefined) {\n const coerced = coerceType(obj[key], field.type);\n if (coerced.coerced) {\n obj[key] = coerced.value;\n actions.push({ attempt: 0, type: \"type_coercion\", detail: `Coerced \"${key}\" to ${field.type}: ${JSON.stringify(coerced.value)}` });\n }\n\n if (field.type === \"array\" && Array.isArray(obj[key]) && field.items) {\n obj[key] = (obj[key] as unknown[]).map((item, i) => {\n const itemResult = repairItem(item, field.items!);\n if (itemResult.actions.length > 0) {\n actions.push(...itemResult.actions.map(a => ({ ...a, detail: `[${i}] ${a.detail}` })));\n }\n return itemResult.data;\n });\n }\n\n if (field.type === \"object\" && typeof obj[key] === \"object\" && obj[key] !== null && field.properties) {\n const nested = repairData(obj[key], field.properties, \"\");\n if (nested.actions.length > 0) {\n actions.push(...nested.actions);\n }\n obj[key] = nested.data;\n }\n }\n }\n\n return { data: obj, actions };\n}\n\nfunction repairItem(item: unknown, field: SchemaField): { data: unknown; actions: RepairAction[] } {\n const actions: RepairAction[] = [];\n\n if (field.type === \"object\" && typeof item === \"object\" && item !== null && !Array.isArray(item) && field.properties) {\n const obj = item as Record<string, unknown>;\n for (const [key, prop] of Object.entries(field.properties)) {\n if (obj[key] === undefined) {\n if (prop.type === \"string\") {\n obj[key] = \"\";\n actions.push({ attempt: 0, type: \"missing_field\", detail: `Added empty string for \"${key}\"` });\n } else if (prop.type === \"number\") {\n obj[key] = 0;\n actions.push({ attempt: 0, type: \"missing_field\", detail: `Added 0 for \"${key}\"` });\n } else if (prop.type === \"boolean\") {\n obj[key] = false;\n actions.push({ attempt: 0, type: \"missing_field\", detail: `Added false for \"${key}\"` });\n } else if (prop.type === \"array\") {\n obj[key] = [];\n actions.push({ attempt: 0, type: \"missing_field\", detail: `Added empty array for \"${key}\"` });\n }\n }\n\n if (obj[key] !== undefined) {\n const coerced = coerceType(obj[key], prop.type);\n if (coerced.coerced) {\n obj[key] = coerced.value;\n actions.push({ attempt: 0, type: \"type_coercion\", detail: `Coerced \"${key}\" to ${prop.type}` });\n }\n }\n }\n }\n\n return { data: item, actions };\n}\n\nexport function validateAgainstSchema(data: unknown, schema: Record<string, SchemaField>): { valid: true; data: unknown } | { valid: false; errors: string[] } {\n if (typeof data !== \"object\" || data === null || Array.isArray(data)) {\n return { valid: false, errors: [\"Root value must be an object\"] };\n }\n\n const errors: string[] = [];\n const obj = data as Record<string, unknown>;\n\n for (const [key, field] of Object.entries(schema)) {\n const value = obj[key];\n\n if (value === undefined && field.required !== false) {\n errors.push(`Missing required field: \"${key}\"`);\n continue;\n }\n\n if (value === undefined) {\n continue;\n }\n\n switch (field.type) {\n case \"string\":\n if (typeof value !== \"string\") {\n errors.push(`Field \"${key}\" must be string, got ${typeof value}`);\n }\n break;\n case \"number\":\n if (typeof value !== \"number\") {\n errors.push(`Field \"${key}\" must be number, got ${typeof value}`);\n }\n break;\n case \"boolean\":\n if (typeof value !== \"boolean\") {\n errors.push(`Field \"${key}\" must be boolean, got ${typeof value}`);\n }\n break;\n case \"array\":\n if (!Array.isArray(value)) {\n errors.push(`Field \"${key}\" must be array, got ${typeof value}`);\n }\n break;\n case \"object\":\n if (typeof value !== \"object\" || value === null || Array.isArray(value)) {\n errors.push(`Field \"${key}\" must be object, got ${typeof value}`);\n }\n break;\n }\n }\n\n if (errors.length > 0) {\n return { valid: false, errors };\n }\n\n return { valid: true, data };\n}\n","import { ExtractionSchema, SchemaField } from \"./types\";\n\nexport function defineSchema(name: string, fields: Record<string, SchemaField>): ExtractionSchema {\n return { name, fields };\n}\n\nfunction schemaToJSONExample(fields: Record<string, SchemaField>): Record<string, unknown> {\n const result: Record<string, unknown> = {};\n for (const [key, field] of Object.entries(fields)) {\n switch (field.type) {\n case \"string\":\n result[key] = field.description || \"...\";\n break;\n case \"number\":\n result[key] = 0;\n break;\n case \"boolean\":\n result[key] = true;\n break;\n case \"array\":\n if (field.items) {\n result[key] = [schemaToJSONExample(field.items.properties || {})];\n } else {\n result[key] = [];\n }\n break;\n case \"object\":\n if (field.properties) {\n result[key] = schemaToJSONExample(field.properties);\n } else {\n result[key] = {};\n }\n break;\n }\n }\n return result;\n}\n\nexport function buildForcedPrompt(input: string, schema: ExtractionSchema): string {\n const example = schemaToJSONExample(schema.fields);\n const exampleStr = JSON.stringify({ [schema.name]: example }, null, 2);\n\n return `TASK: Extract structured data from the text below into EXACTLY this JSON format.\n\nCRITICAL RULES:\n- Output ONLY valid JSON. No text before or after.\n- No markdown. No code blocks. No explanations.\n- Every field MUST be present with the correct type.\n- String fields: use \"\" if unknown.\n- Number fields: use 0 if unknown.\n- Boolean fields: use true or false, never null.\n- Array fields: use [] if empty, never null.\n- Object fields: use {} if empty, never null.\n\nREQUIRED JSON STRUCTURE:\n${exampleStr}\n\nTEXT TO EXTRACT FROM:\n\"\"\"\n${input}\n\"\"\"\n\nOUTPUT ONLY THE JSON OBJECT. NOTHING ELSE.`;\n}\n\nexport function buildRetryPrompt(\n input: string,\n schema: ExtractionSchema,\n previousResponse: string,\n error: string\n): string {\n const example = schemaToJSONExample(schema.fields);\n const exampleStr = JSON.stringify({ [schema.name]: example }, null, 2);\n\n return `YOUR PREVIOUS RESPONSE WAS INVALID. You MUST fix it.\n\nERROR: ${error}\n\nYOUR PREVIOUS RESPONSE:\n${previousResponse.slice(0, 1000)}\n\nWHAT YOU MUST DO NOW:\n1. Output ONLY valid JSON matching this EXACT structure\n2. No text before or after the JSON\n3. No markdown, no code blocks, no explanations\n4. Fix the errors listed above\n5. Every field MUST be present\n\nREQUIRED JSON STRUCTURE:\n${exampleStr}\n\nTEXT TO EXTRACT FROM:\n\"\"\"\n${input}\n\"\"\"\n\nOUTPUT ONLY THE JSON OBJECT. NOTHING ELSE.`;\n}\n\nexport function buildFinalPrompt(\n input: string,\n schema: ExtractionSchema,\n previousResponse: string,\n error: string\n): string {\n const example = schemaToJSONExample(schema.fields);\n const exampleStr = JSON.stringify({ [schema.name]: example }, null, 2);\n\n return `FINAL ATTEMPT. THIS IS YOUR LAST CHANCE.\n\nYOUR PREVIOUS RESPONSE FAILED VALIDATION:\n${error}\n\nYOUR PREVIOUS RESPONSE:\n${previousResponse.slice(0, 800)}\n\nYOU MUST OUTPUT EXACTLY THIS STRUCTURE. NOTHING MORE. NOTHING LESS.\nDO NOT ADD FIELDS THAT ARE NOT IN THE SCHEMA.\nDO NOT OMIT ANY FIELDS.\nDO NOT WRAP IN markdown OR code blocks.\nDO NOT ADD ANY TEXT BEFORE OR AFTER THE JSON.\n\nSTRUCTURE:\n${exampleStr}\n\nINPUT TEXT:\n\"\"\"\n${input}\n\"\"\"\n\nOUTPUT ONLY:\n`;\n}\n\nexport function schemaToPrompt(schema: ExtractionSchema): string {\n const example = schemaToJSONExample(schema.fields);\n return JSON.stringify({ [schema.name]: example }, null, 2);\n}\n\nexport function schemaToZodishString(schema: ExtractionSchema): string {\n const lines: string[] = [];\n lines.push(`Expected JSON structure for \"${schema.name}\":`);\n\n for (const [key, field] of Object.entries(schema.fields)) {\n const required = field.required !== false ? \"(required)\" : \"(optional)\";\n lines.push(` - \"${key}\": ${field.type} ${required}${field.description ? ` - ${field.description}` : \"\"}`);\n }\n\n return lines.join(\"\\n\");\n}\n","import { ExtractionSchema, ExtractionOptions, ExtractionResult, RepairAction, SchemaField } from \"./types\";\nimport { callLLM, LLMClientConfig } from \"./llm-client\";\nimport { extractJSON, repairJSON, tryParseJSON, validateAgainstSchema, repairData } from \"./validator\";\nimport { buildForcedPrompt, buildRetryPrompt, buildFinalPrompt } from \"./schema\";\n\nconst MAX_REPAIR_ATTEMPTS = 3;\n\nfunction calculateConfidence(\n attempts: number,\n repairActions: RepairAction[],\n jsonValidFirstTry: boolean\n): number {\n let score = 100;\n\n if (!jsonValidFirstTry) {\n score -= 15;\n }\n\n score -= (attempts - 1) * 10;\n\n score -= repairActions.filter(a => a.type === \"type_coercion\").length * 5;\n score -= repairActions.filter(a => a.type === \"missing_field\").length * 8;\n score -= repairActions.filter(a => a.type === \"default_fill\").length * 3;\n\n return Math.max(0, Math.min(100, score));\n}\n\nexport async function extract<T = Record<string, unknown>>(\n input: string,\n schema: ExtractionSchema,\n options: ExtractionOptions = {}\n): Promise<ExtractionResult<T>> {\n const config: LLMClientConfig = {\n apiKey: options.apiKey || process.env.OPENROUTER_API_KEY || \"\",\n model: options.model || \"openrouter/free\",\n temperature: options.temperature ?? 0,\n timeout: options.timeout ?? 30000,\n };\n\n if (!config.apiKey || config.apiKey.trim() === \"\") {\n throw new Error(\n \"OpenRouter API key required. Pass it in options or set OPENROUTER_API_KEY environment variable.\"\n );\n }\n\n const allRepairActions: RepairAction[] = [];\n let lastRaw = \"\";\n let lastError = \"\";\n let jsonValidFirstTry = false;\n let successAttempt = 0;\n\n for (let attempt = 0; attempt <= MAX_REPAIR_ATTEMPTS; attempt++) {\n let prompt: string;\n\n if (attempt === 0) {\n prompt = buildForcedPrompt(input, schema);\n } else if (attempt === 1) {\n prompt = buildRetryPrompt(input, schema, lastRaw, lastError);\n } else {\n prompt = buildFinalPrompt(input, schema, lastRaw, lastError);\n }\n\n const response = await callLLM(prompt, config);\n lastRaw = response.content;\n\n const extracted = extractJSON(response.content);\n if (!extracted) {\n lastError = \"Could not extract JSON from response. Your response contained no valid JSON.\";\n continue;\n }\n\n const parsed = tryParseJSON(extracted);\n if (!parsed.success) {\n lastError = `Invalid JSON: ${parsed.error}`;\n continue;\n }\n\n if (attempt === 0) {\n jsonValidFirstTry = true;\n }\n\n const repaired = repairJSON(extracted);\n let finalData = repaired\n ? tryParseJSON(repaired).success\n ? JSON.parse(repaired)\n : parsed.data\n : parsed.data;\n\n const repairResult = repairData(finalData, schema.fields, schema.name);\n finalData = repairResult.data;\n allRepairActions.push(...repairResult.actions);\n\n const schemaFields: Record<string, SchemaField> = {};\n for (const [key, field] of Object.entries(schema.fields)) {\n schemaFields[key] = field;\n }\n\n const validation = validateAgainstSchema(finalData, schemaFields);\n if (!validation.valid) {\n lastError = `Schema validation failed: ${validation.errors.join(\"; \")}`;\n allRepairActions.push({\n attempt,\n type: \"type_coercion\",\n detail: `Validation failed: ${validation.errors.join(\"; \")}`,\n });\n continue;\n }\n\n successAttempt = attempt + 1;\n\n return {\n data: finalData as T,\n raw: response.content,\n model: response.model,\n confidence: calculateConfidence(successAttempt, allRepairActions, jsonValidFirstTry),\n repairLog: allRepairActions,\n attempts: successAttempt,\n usage: response.usage,\n };\n }\n\n throw new Error(\n `Failed to extract valid JSON after ${MAX_REPAIR_ATTEMPTS + 1} attempts. Last error: ${lastError}`\n );\n}\n"]}
|
package/package.json
CHANGED