agentic-json-repair 1.0.0 → 1.0.1
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 +105 -0
- package/package.json +5 -1
- package/.vscode/extensions.json +0 -3
- package/src/index.test.ts +0 -35
- package/src/index.ts +0 -128
- package/tsconfig.json +0 -14
package/README.md
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# agentic-json-repair
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/agentic-json-repair)
|
|
4
|
+
|
|
5
|
+
Repair and validate AI-generated JSON with auto-generated retry prompts. Perfect for building reliable AI agents and LLM pipelines.
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- 🔧 **Auto-repair** malformed JSON (missing quotes, trailing commas, etc.)
|
|
10
|
+
- 📝 **Extract JSON** from markdown code blocks or mixed prose
|
|
11
|
+
- ✅ **Validate** against Zod schemas with full type inference
|
|
12
|
+
- 🔄 **Auto-generate retry prompts** when validation fails
|
|
13
|
+
|
|
14
|
+
## Installation
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
npm install agentic-json-repair zod
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Usage
|
|
21
|
+
|
|
22
|
+
```typescript
|
|
23
|
+
import { safeParseAI } from 'agentic-json-repair';
|
|
24
|
+
import { z } from 'zod';
|
|
25
|
+
|
|
26
|
+
// Define your expected schema
|
|
27
|
+
const UserSchema = z.object({
|
|
28
|
+
name: z.string(),
|
|
29
|
+
age: z.number(),
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
// Parse messy AI output
|
|
33
|
+
const aiOutput = `
|
|
34
|
+
Sure! Here is the user data:
|
|
35
|
+
\`\`\`json
|
|
36
|
+
{
|
|
37
|
+
name: "Alice",
|
|
38
|
+
age: 30,
|
|
39
|
+
}
|
|
40
|
+
\`\`\`
|
|
41
|
+
`;
|
|
42
|
+
|
|
43
|
+
const result = safeParseAI(aiOutput, UserSchema);
|
|
44
|
+
|
|
45
|
+
if (result.success) {
|
|
46
|
+
console.log(result.data); // { name: "Alice", age: 30 } - fully typed!
|
|
47
|
+
} else {
|
|
48
|
+
// Use retry prompt to ask the AI to fix the output
|
|
49
|
+
console.log(result.retryPrompt);
|
|
50
|
+
}
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## API
|
|
54
|
+
|
|
55
|
+
### `safeParseAI<T>(aiOutput: string, schema: ZodType<T>): AgenticParseResult<T>`
|
|
56
|
+
|
|
57
|
+
Attempts to extract, repair, and validate JSON from an AI's text output.
|
|
58
|
+
|
|
59
|
+
**Returns:**
|
|
60
|
+
|
|
61
|
+
```typescript
|
|
62
|
+
// On success
|
|
63
|
+
{ success: true; data: T }
|
|
64
|
+
|
|
65
|
+
// On failure
|
|
66
|
+
{ success: false; error: string; retryPrompt: string }
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## What it handles
|
|
70
|
+
|
|
71
|
+
| Problem | Example | Fixed |
|
|
72
|
+
|---------|---------|-------|
|
|
73
|
+
| Markdown code blocks | `` ```json {...} ``` `` | ✅ |
|
|
74
|
+
| Surrounding prose | "Here's the JSON: {...}" | ✅ |
|
|
75
|
+
| Unquoted keys | `{ name: "Alice" }` | ✅ |
|
|
76
|
+
| Trailing commas | `{ "a": 1, }` | ✅ |
|
|
77
|
+
| Schema mismatches | Wrong types, missing fields | ✅ (retry prompt) |
|
|
78
|
+
|
|
79
|
+
## Agentic Loop Example
|
|
80
|
+
|
|
81
|
+
```typescript
|
|
82
|
+
async function getStructuredData(prompt: string, schema: z.ZodType) {
|
|
83
|
+
let attempts = 0;
|
|
84
|
+
let currentPrompt = prompt;
|
|
85
|
+
|
|
86
|
+
while (attempts < 3) {
|
|
87
|
+
const aiOutput = await callLLM(currentPrompt);
|
|
88
|
+
const result = safeParseAI(aiOutput, schema);
|
|
89
|
+
|
|
90
|
+
if (result.success) {
|
|
91
|
+
return result.data;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Use the auto-generated retry prompt
|
|
95
|
+
currentPrompt = result.retryPrompt;
|
|
96
|
+
attempts++;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
throw new Error('Failed to get valid structured data');
|
|
100
|
+
}
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## License
|
|
104
|
+
|
|
105
|
+
MIT
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agentic-json-repair",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.1",
|
|
4
4
|
"description": "Repair and validate AI-generated JSON with auto-generated retry prompts.",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"module": "./dist/index.mjs",
|
|
@@ -12,6 +12,10 @@
|
|
|
12
12
|
"require": "./dist/index.js"
|
|
13
13
|
}
|
|
14
14
|
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist",
|
|
17
|
+
"README.md"
|
|
18
|
+
],
|
|
15
19
|
"scripts": {
|
|
16
20
|
"build": "tsup src/index.ts --format cjs,esm --dts --clean",
|
|
17
21
|
"test": "vitest run",
|
package/.vscode/extensions.json
DELETED
package/src/index.test.ts
DELETED
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
import { expect, test } from 'vitest';
|
|
2
|
-
import { safeParseAI } from './index';
|
|
3
|
-
import { z } from 'zod';
|
|
4
|
-
|
|
5
|
-
test('fixes broken AI output', () => {
|
|
6
|
-
const brokenAiOutput = `
|
|
7
|
-
Sure! Here is the user:
|
|
8
|
-
\`\`\`json
|
|
9
|
-
{
|
|
10
|
-
name: "Alice",
|
|
11
|
-
age: 30,
|
|
12
|
-
}
|
|
13
|
-
\`\`\`
|
|
14
|
-
`;
|
|
15
|
-
|
|
16
|
-
const UserSchema = z.object({ name: z.string(), age: z.number() });
|
|
17
|
-
const result = safeParseAI(brokenAiOutput, UserSchema);
|
|
18
|
-
|
|
19
|
-
expect(result.success).toBe(true);
|
|
20
|
-
if (result.success) {
|
|
21
|
-
expect(result.data.name).toBe("Alice");
|
|
22
|
-
}
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
test('generates retry prompt on wrong schema', () => {
|
|
26
|
-
const wrongTypeOutput = `{"age": "thirty"}`; // string instead of number
|
|
27
|
-
const Schema = z.object({ age: z.number() });
|
|
28
|
-
|
|
29
|
-
const result = safeParseAI(wrongTypeOutput, Schema);
|
|
30
|
-
|
|
31
|
-
expect(result.success).toBe(false);
|
|
32
|
-
if (!result.success) {
|
|
33
|
-
expect(result.retryPrompt).toContain("expected number, received string");
|
|
34
|
-
}
|
|
35
|
-
});
|
package/src/index.ts
DELETED
|
@@ -1,128 +0,0 @@
|
|
|
1
|
-
import { jsonrepair } from 'jsonrepair';
|
|
2
|
-
import { z } from 'zod';
|
|
3
|
-
|
|
4
|
-
export type AgenticParseResult<T> =
|
|
5
|
-
| { success: true; data: T }
|
|
6
|
-
| { success: false; error: string; retryPrompt: string };
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Attempts to extract, repair, and validate JSON from an AI's text output.
|
|
10
|
-
* * @param aiOutput The raw string from the LLM (e.g., "Here is the data: ```json ...")
|
|
11
|
-
* @param schema The Zod schema to validate against
|
|
12
|
-
*/
|
|
13
|
-
/**
|
|
14
|
-
* Extracts JSON content from AI output that may contain markdown code blocks or prose.
|
|
15
|
-
*/
|
|
16
|
-
function extractJsonContent(aiOutput: string): string {
|
|
17
|
-
// First, try to extract from markdown code blocks
|
|
18
|
-
const codeBlockMatch = aiOutput.match(/```(?:json)?\s*([\s\S]*?)```/);
|
|
19
|
-
if (codeBlockMatch) {
|
|
20
|
-
return codeBlockMatch[1].trim();
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
// Otherwise, try to find the first JSON object or array
|
|
24
|
-
const firstBrace = aiOutput.indexOf('{');
|
|
25
|
-
const firstBracket = aiOutput.indexOf('[');
|
|
26
|
-
|
|
27
|
-
let startChar: '{' | '[';
|
|
28
|
-
let endChar: '}' | ']';
|
|
29
|
-
let startIndex: number;
|
|
30
|
-
|
|
31
|
-
if (firstBrace === -1 && firstBracket === -1) {
|
|
32
|
-
// No JSON structure found, return as-is for jsonrepair to handle
|
|
33
|
-
return aiOutput.trim();
|
|
34
|
-
} else if (firstBrace === -1) {
|
|
35
|
-
startChar = '[';
|
|
36
|
-
endChar = ']';
|
|
37
|
-
startIndex = firstBracket;
|
|
38
|
-
} else if (firstBracket === -1) {
|
|
39
|
-
startChar = '{';
|
|
40
|
-
endChar = '}';
|
|
41
|
-
startIndex = firstBrace;
|
|
42
|
-
} else {
|
|
43
|
-
// Both found, use whichever comes first
|
|
44
|
-
if (firstBrace < firstBracket) {
|
|
45
|
-
startChar = '{';
|
|
46
|
-
endChar = '}';
|
|
47
|
-
startIndex = firstBrace;
|
|
48
|
-
} else {
|
|
49
|
-
startChar = '[';
|
|
50
|
-
endChar = ']';
|
|
51
|
-
startIndex = firstBracket;
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
// Find the matching closing bracket/brace
|
|
56
|
-
let depth = 0;
|
|
57
|
-
let inString = false;
|
|
58
|
-
let escapeNext = false;
|
|
59
|
-
|
|
60
|
-
for (let i = startIndex; i < aiOutput.length; i++) {
|
|
61
|
-
const char = aiOutput[i];
|
|
62
|
-
|
|
63
|
-
if (escapeNext) {
|
|
64
|
-
escapeNext = false;
|
|
65
|
-
continue;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
if (char === '\\' && inString) {
|
|
69
|
-
escapeNext = true;
|
|
70
|
-
continue;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
if (char === '"') {
|
|
74
|
-
inString = !inString;
|
|
75
|
-
continue;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
if (!inString) {
|
|
79
|
-
if (char === startChar) {
|
|
80
|
-
depth++;
|
|
81
|
-
} else if (char === endChar) {
|
|
82
|
-
depth--;
|
|
83
|
-
if (depth === 0) {
|
|
84
|
-
return aiOutput.slice(startIndex, i + 1);
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
// If we couldn't find matching brackets, return from startIndex to end
|
|
91
|
-
return aiOutput.slice(startIndex).trim();
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
export function safeParseAI<T>(aiOutput: string, schema: z.ZodType<T>): AgenticParseResult<T> {
|
|
95
|
-
try {
|
|
96
|
-
// 1. Extract JSON: Find the JSON content within the AI output
|
|
97
|
-
const cleanOutput = extractJsonContent(aiOutput);
|
|
98
|
-
|
|
99
|
-
// 2. Repair JSON: Fixes missing quotes, trailing commas, etc.
|
|
100
|
-
const repairedJson = jsonrepair(cleanOutput);
|
|
101
|
-
|
|
102
|
-
// 3. Parse JSON
|
|
103
|
-
const parsedObject = JSON.parse(repairedJson);
|
|
104
|
-
|
|
105
|
-
// 4. Validate Schema
|
|
106
|
-
const validation = schema.safeParse(parsedObject);
|
|
107
|
-
|
|
108
|
-
if (validation.success) {
|
|
109
|
-
return { success: true, data: validation.data };
|
|
110
|
-
} else {
|
|
111
|
-
// 5. Generate a "Self-Correction" prompt for the AI
|
|
112
|
-
const issues = validation.error.issues.map(i => `${i.path.join('.')}: ${i.message}`).join(', ');
|
|
113
|
-
return {
|
|
114
|
-
success: false,
|
|
115
|
-
error: "Validation Failed",
|
|
116
|
-
retryPrompt: `The JSON was parsed but failed validation. Fix these issues: [${issues}] and return ONLY the JSON.`
|
|
117
|
-
};
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
} catch (error) {
|
|
121
|
-
// 6. Handle Syntax Errors that jsonrepair couldn't fix
|
|
122
|
-
return {
|
|
123
|
-
success: false,
|
|
124
|
-
error: "JSON Syntax Error",
|
|
125
|
-
retryPrompt: `The output was not valid JSON. Error: ${(error as Error).message}. Return ONLY valid JSON.`
|
|
126
|
-
};
|
|
127
|
-
}
|
|
128
|
-
}
|
package/tsconfig.json
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"compilerOptions": {
|
|
3
|
-
"target": "ES2020",
|
|
4
|
-
"module": "ESNext",
|
|
5
|
-
"moduleResolution": "bundler",
|
|
6
|
-
"esModuleInterop": true,
|
|
7
|
-
"strict": true,
|
|
8
|
-
"declaration": true,
|
|
9
|
-
"outDir": "dist",
|
|
10
|
-
"skipLibCheck": true
|
|
11
|
-
},
|
|
12
|
-
"include": ["src"],
|
|
13
|
-
"exclude": ["node_modules", "dist"]
|
|
14
|
-
}
|