File size: 2,668 Bytes
e706de2 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 |
import { BaseOutputParser, OutputParserException } from './base-parser.js';
/**
* Parser that extracts JSON from LLM output
* Handles markdown code blocks and extra text
*
* Example:
* const parser = new JsonOutputParser();
* const result = await parser.parse('```json\n{"name": "Alice"}\n```');
* // { name: "Alice" }
*/
export class JsonOutputParser extends BaseOutputParser {
constructor(options = {}) {
super();
this.schema = options.schema;
}
/**
* Parse JSON from text
*/
async parse(text) {
try {
// Try to extract JSON from the text
const jsonText = this._extractJson(text);
const parsed = JSON.parse(jsonText);
// Validate against schema if provided
if (this.schema) {
this._validateSchema(parsed);
}
return parsed;
} catch (error) {
throw new OutputParserException(
`Failed to parse JSON: ${error.message}`,
text,
error
);
}
}
/**
* Extract JSON from text (handles markdown, extra text)
*/
_extractJson(text) {
// Try direct parse first
try {
JSON.parse(text.trim());
return text.trim();
} catch {
// Not direct JSON, try to find it
}
// Look for JSON in markdown code blocks
const markdownMatch = text.match(/```(?:json)?\s*\n?([\s\S]*?)\n?```/);
if (markdownMatch) {
return markdownMatch[1].trim();
}
// Look for JSON object/array patterns
const jsonObjectMatch = text.match(/\{[\s\S]*\}/);
if (jsonObjectMatch) {
return jsonObjectMatch[0];
}
const jsonArrayMatch = text.match(/\[[\s\S]*\]/);
if (jsonArrayMatch) {
return jsonArrayMatch[0];
}
// Give up, return original
return text.trim();
}
/**
* Validate parsed JSON against schema
*/
_validateSchema(parsed) {
if (!this.schema) return;
for (const [key, type] of Object.entries(this.schema)) {
if (!(key in parsed)) {
throw new Error(`Missing required field: ${key}`);
}
const actualType = typeof parsed[key];
if (actualType !== type) {
throw new Error(
`Field ${key} should be ${type}, got ${actualType}`
);
}
}
}
getFormatInstructions() {
let instructions = 'Respond with valid JSON.';
if (this.schema) {
const schemaDesc = Object.entries(this.schema)
.map(([key, type]) => `"${key}": ${type}`)
.join(', ');
instructions += ` Schema: { ${schemaDesc} }`;
}
return instructions;
}
} |