structured-json-agent 1.0.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 +125 -0
- package/dist/agent/index.d.ts +15 -0
- package/dist/agent/index.js +150 -0
- package/dist/agent/index.js.map +1 -0
- package/dist/errors/index.d.ts +23 -0
- package/dist/errors/index.js +43 -0
- package/dist/errors/index.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -0
- package/dist/llm/index.d.ts +2 -0
- package/dist/llm/index.js +3 -0
- package/dist/llm/index.js.map +1 -0
- package/dist/llm/openai.d.ts +7 -0
- package/dist/llm/openai.js +33 -0
- package/dist/llm/openai.js.map +1 -0
- package/dist/llm/types.d.ts +8 -0
- package/dist/llm/types.js +2 -0
- package/dist/llm/types.js.map +1 -0
- package/dist/schemas/validator.d.ts +20 -0
- package/dist/schemas/validator.js +39 -0
- package/dist/schemas/validator.js.map +1 -0
- package/dist/types/index.d.ts +26 -0
- package/dist/types/index.js +2 -0
- package/dist/types/index.js.map +1 -0
- package/package.json +41 -0
package/README.md
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
# Structured JSON Agent
|
|
2
|
+
|
|
3
|
+
A typed and extensible TypeScript library for creating and running Iterative AI Agents that guarantee structured JSON output.
|
|
4
|
+
|
|
5
|
+
This library orchestrates a **Generator ↔ Reviewer** cycle to ensure that the output from Large Language Models (LLMs) strictly adheres to a defined JSON Schema.
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
* **Guaranteed JSON Output**: Enforces strict adherence to JSON Schemas (Draft-07+).
|
|
10
|
+
* **Iterative Self-Correction**: Automatically detects validation errors and feeds them back to a "Reviewer" model to fix the output.
|
|
11
|
+
* **Type-Safe**: Built with TypeScript for full type inference and safety.
|
|
12
|
+
* **Model Agnostic**: Compatible with OpenAI by default, but extensible for other providers.
|
|
13
|
+
* **Production Ready**: Includes typed errors, extensive validation, and a clean API.
|
|
14
|
+
|
|
15
|
+
## Installation
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npm install structured-json-agent
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Usage
|
|
22
|
+
|
|
23
|
+
### 1. Import and Configure
|
|
24
|
+
|
|
25
|
+
```typescript
|
|
26
|
+
import { StructuredAgent } from "structured-json-agent";
|
|
27
|
+
|
|
28
|
+
// Define your Schemas
|
|
29
|
+
const inputSchema = {
|
|
30
|
+
type: "object",
|
|
31
|
+
properties: {
|
|
32
|
+
topic: { type: "string" },
|
|
33
|
+
depth: { type: "string", enum: ["basic", "advanced"] }
|
|
34
|
+
},
|
|
35
|
+
required: ["topic", "depth"]
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const outputSchema = {
|
|
39
|
+
type: "object",
|
|
40
|
+
properties: {
|
|
41
|
+
title: { type: "string" },
|
|
42
|
+
keyPoints: { type: "array", items: { type: "string" } },
|
|
43
|
+
summary: { type: "string" }
|
|
44
|
+
},
|
|
45
|
+
required: ["title", "keyPoints", "summary"]
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
// Initialize the Agent
|
|
49
|
+
const agent = new StructuredAgent({
|
|
50
|
+
openAiApiKey: process.env.OPENAI_API_KEY!,
|
|
51
|
+
generatorModel: "gpt-4-turbo",
|
|
52
|
+
reviewerModel: "gpt-3.5-turbo", // Can be a faster/cheaper model for simple fixes
|
|
53
|
+
inputSchema,
|
|
54
|
+
outputSchema,
|
|
55
|
+
systemPrompt: "You are an expert summarizer. Create a structured summary based on the topic.",
|
|
56
|
+
maxIterations: 3 // Optional: Max correction attempts (default: 5)
|
|
57
|
+
});
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### 2. Run the Agent
|
|
61
|
+
|
|
62
|
+
```typescript
|
|
63
|
+
async function main() {
|
|
64
|
+
try {
|
|
65
|
+
const result = await agent.run({
|
|
66
|
+
topic: "Clean Architecture",
|
|
67
|
+
depth: "advanced"
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
console.log("Result:", result);
|
|
71
|
+
// Output is guaranteed to match outputSchema
|
|
72
|
+
} catch (error) {
|
|
73
|
+
console.error("Agent failed:", error);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
main();
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## How It Works
|
|
81
|
+
|
|
82
|
+
1. **Validation**: The input JSON is validated against the `inputSchema`.
|
|
83
|
+
2. **Generation**: The `generatorModel` creates an initial response based on the system prompt and input.
|
|
84
|
+
3. **Verification Loop**:
|
|
85
|
+
* The response is parsed and validated against `outputSchema`.
|
|
86
|
+
* **If Valid**: The result is returned immediately.
|
|
87
|
+
* **If Invalid**: The `reviewerModel` is invoked with the invalid JSON, the specific validation errors, and the expected schema. It attempts to fix the JSON.
|
|
88
|
+
4. **Convergence**: This cycle repeats until a valid JSON is produced or `maxIterations` is reached.
|
|
89
|
+
|
|
90
|
+
## API Reference
|
|
91
|
+
|
|
92
|
+
### `StructuredAgent` Config
|
|
93
|
+
|
|
94
|
+
| Property | Type | Description |
|
|
95
|
+
|Col |Col |Col |
|
|
96
|
+
| `openAiApiKey` | `string` | Your OpenAI API Key. |
|
|
97
|
+
| `generatorModel` | `string` | Model ID for the initial generation (e.g., `gpt-4`). |
|
|
98
|
+
| `reviewerModel` | `string` | Model ID for the review/correction phase. |
|
|
99
|
+
| `inputSchema` | `object` | JSON Schema for validating the input. |
|
|
100
|
+
| `outputSchema` | `object` | JSON Schema for the expected output. |
|
|
101
|
+
| `systemPrompt` | `string` | Core instructions for the agent. |
|
|
102
|
+
| `maxIterations` | `number?` | Max retries for correction. Default: 5. |
|
|
103
|
+
| `modelConfig` | `ModelConfig?` | Optional parameters (temperature, etc.). |
|
|
104
|
+
| `llmService` | `ILLMService?` | Optional custom LLM service implementation. |
|
|
105
|
+
|
|
106
|
+
### Error Handling
|
|
107
|
+
|
|
108
|
+
The library exports specific error classes for handling failures:
|
|
109
|
+
|
|
110
|
+
* `InvalidInputSchemaError`: Input schema is invalid.
|
|
111
|
+
* `InvalidOutputSchemaError`: Output schema is invalid.
|
|
112
|
+
* `SchemaValidationError`: Input data does not match the schema.
|
|
113
|
+
* `MaxIterationsExceededError`: The agent could not produce valid JSON within the limit.
|
|
114
|
+
* `LLMExecutionError`: Failure in communicating with the LLM provider.
|
|
115
|
+
|
|
116
|
+
## Architecture
|
|
117
|
+
|
|
118
|
+
The project is structured by domain:
|
|
119
|
+
|
|
120
|
+
* `src/agent`: Core orchestration logic.
|
|
121
|
+
* `src/schemas`: Validation logic using AJV.
|
|
122
|
+
* `src/llm`: Interface and implementation for LLM providers.
|
|
123
|
+
* `src/errors`: Custom error definitions.
|
|
124
|
+
* `src/types`: Shared interfaces.
|
|
125
|
+
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { AgentConfig } from "../types/index.js";
|
|
2
|
+
export declare class StructuredAgent {
|
|
3
|
+
private schemaValidator;
|
|
4
|
+
private llmService;
|
|
5
|
+
private inputValidator;
|
|
6
|
+
private outputValidator;
|
|
7
|
+
private config;
|
|
8
|
+
constructor(config: AgentConfig);
|
|
9
|
+
run(inputJson: unknown): Promise<unknown>;
|
|
10
|
+
private generateInitialResponse;
|
|
11
|
+
private reviewResponse;
|
|
12
|
+
private validateOutput;
|
|
13
|
+
private parseJson;
|
|
14
|
+
private buildSystemPrompt;
|
|
15
|
+
}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { SchemaValidator } from "../schemas/validator.js";
|
|
2
|
+
import { OpenAILLMService } from "../llm/index.js";
|
|
3
|
+
import { InvalidInputSchemaError, InvalidOutputSchemaError, SchemaValidationError, MaxIterationsExceededError, LLMExecutionError, } from "../errors/index.js";
|
|
4
|
+
export class StructuredAgent {
|
|
5
|
+
schemaValidator;
|
|
6
|
+
llmService;
|
|
7
|
+
inputValidator;
|
|
8
|
+
outputValidator;
|
|
9
|
+
config;
|
|
10
|
+
constructor(config) {
|
|
11
|
+
this.config = config;
|
|
12
|
+
this.schemaValidator = new SchemaValidator();
|
|
13
|
+
// Use provided LLM service or default to OpenAI
|
|
14
|
+
this.llmService = config.llmService || new OpenAILLMService(config.openAiApiKey);
|
|
15
|
+
try {
|
|
16
|
+
this.inputValidator = this.schemaValidator.compile(config.inputSchema);
|
|
17
|
+
}
|
|
18
|
+
catch (e) {
|
|
19
|
+
throw new InvalidInputSchemaError("Failed to compile input schema", e);
|
|
20
|
+
}
|
|
21
|
+
try {
|
|
22
|
+
this.outputValidator = this.schemaValidator.compile(config.outputSchema);
|
|
23
|
+
}
|
|
24
|
+
catch (e) {
|
|
25
|
+
throw new InvalidOutputSchemaError("Failed to compile output schema", e);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
async run(inputJson) {
|
|
29
|
+
// 1. Validate Input
|
|
30
|
+
try {
|
|
31
|
+
this.schemaValidator.validate(this.inputValidator, inputJson);
|
|
32
|
+
}
|
|
33
|
+
catch (error) {
|
|
34
|
+
if (error instanceof SchemaValidationError) {
|
|
35
|
+
// Enhance error message if needed, but the original is good
|
|
36
|
+
throw error;
|
|
37
|
+
}
|
|
38
|
+
throw error;
|
|
39
|
+
}
|
|
40
|
+
const maxIterations = this.config.maxIterations ?? 5;
|
|
41
|
+
const history = [];
|
|
42
|
+
// 2. Initial Generation
|
|
43
|
+
let currentJson = await this.generateInitialResponse(inputJson);
|
|
44
|
+
history.push({ step: "generation", result: currentJson });
|
|
45
|
+
let validationResult = this.validateOutput(currentJson);
|
|
46
|
+
if (validationResult.valid) {
|
|
47
|
+
return currentJson;
|
|
48
|
+
}
|
|
49
|
+
// 3. Review Loop
|
|
50
|
+
for (let i = 0; i < maxIterations; i++) {
|
|
51
|
+
try {
|
|
52
|
+
currentJson = await this.reviewResponse(currentJson, validationResult.errors || [], inputJson // Context might be needed
|
|
53
|
+
);
|
|
54
|
+
history.push({ step: `review-${i + 1}`, result: currentJson });
|
|
55
|
+
validationResult = this.validateOutput(currentJson);
|
|
56
|
+
if (validationResult.valid) {
|
|
57
|
+
return currentJson;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
catch (error) {
|
|
61
|
+
// If LLM fails or parsing fails during review, we record it and continue?
|
|
62
|
+
// Or throw? The requirement says "return exclusively a valid JSON".
|
|
63
|
+
// If we can't continue, we should probably throw or let the loop hit max iterations.
|
|
64
|
+
// For now, let's treat execution errors as fatal or part of the attempt?
|
|
65
|
+
// If it's a parsing error, it's a validation error.
|
|
66
|
+
// If it's an API error, it's an LLMExecutionError.
|
|
67
|
+
if (error instanceof LLMExecutionError) {
|
|
68
|
+
throw error;
|
|
69
|
+
}
|
|
70
|
+
// If JSON parse error in reviewResponse, it might be caught there.
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
throw new MaxIterationsExceededError(`Failed to generate valid JSON after ${maxIterations} review iterations`, history);
|
|
74
|
+
}
|
|
75
|
+
async generateInitialResponse(input) {
|
|
76
|
+
const messages = [
|
|
77
|
+
{
|
|
78
|
+
role: "system",
|
|
79
|
+
content: this.buildSystemPrompt(),
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
role: "user",
|
|
83
|
+
content: JSON.stringify(input),
|
|
84
|
+
},
|
|
85
|
+
];
|
|
86
|
+
const responseText = await this.llmService.complete(messages, this.config.generatorModel, this.config.modelConfig);
|
|
87
|
+
return this.parseJson(responseText);
|
|
88
|
+
}
|
|
89
|
+
async reviewResponse(invalidJson, errors, originalInput) {
|
|
90
|
+
const messages = [
|
|
91
|
+
{
|
|
92
|
+
role: "system",
|
|
93
|
+
content: `You are a strict JSON reviewer.
|
|
94
|
+
Your task is to fix the provided JSON so it adheres to the Schema.
|
|
95
|
+
Output ONLY the corrected JSON.`,
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
role: "user",
|
|
99
|
+
content: `Original Input Context: ${JSON.stringify(originalInput)}
|
|
100
|
+
|
|
101
|
+
The following JSON is INVALID based on the output schema:
|
|
102
|
+
${JSON.stringify(invalidJson)}
|
|
103
|
+
|
|
104
|
+
Validation Errors:
|
|
105
|
+
${errors.join("\n")}
|
|
106
|
+
|
|
107
|
+
Expected Output Schema:
|
|
108
|
+
${JSON.stringify(this.config.outputSchema)}
|
|
109
|
+
|
|
110
|
+
Please correct the JSON.`,
|
|
111
|
+
},
|
|
112
|
+
];
|
|
113
|
+
const responseText = await this.llmService.complete(messages, this.config.reviewerModel, this.config.modelConfig);
|
|
114
|
+
return this.parseJson(responseText);
|
|
115
|
+
}
|
|
116
|
+
validateOutput(data) {
|
|
117
|
+
try {
|
|
118
|
+
this.schemaValidator.validate(this.outputValidator, data);
|
|
119
|
+
return { valid: true };
|
|
120
|
+
}
|
|
121
|
+
catch (error) {
|
|
122
|
+
if (error instanceof SchemaValidationError) {
|
|
123
|
+
const formattedErrors = this.schemaValidator.formatErrors(error.errors);
|
|
124
|
+
return { valid: false, errors: [formattedErrors] };
|
|
125
|
+
}
|
|
126
|
+
return { valid: false, errors: ["Unknown validation error"] };
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
parseJson(text) {
|
|
130
|
+
try {
|
|
131
|
+
return JSON.parse(text);
|
|
132
|
+
}
|
|
133
|
+
catch (e) {
|
|
134
|
+
// If parsing fails, we return the text (as string) or null?
|
|
135
|
+
// But the flow expects unknown (object).
|
|
136
|
+
// If we return the text, the schema validation will likely fail (unless schema allows string).
|
|
137
|
+
// This allows the reviewer to see the malformed JSON text.
|
|
138
|
+
return text;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
buildSystemPrompt() {
|
|
142
|
+
return `${this.config.systemPrompt}
|
|
143
|
+
|
|
144
|
+
IMPORTANT: You must output strict JSON only.
|
|
145
|
+
The output must adhere to the following JSON Schema:
|
|
146
|
+
${JSON.stringify(this.config.outputSchema)}
|
|
147
|
+
`;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/agent/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export declare class StructuredAgentError extends Error {
|
|
2
|
+
constructor(message: string);
|
|
3
|
+
}
|
|
4
|
+
export declare class InvalidInputSchemaError extends StructuredAgentError {
|
|
5
|
+
context?: unknown | undefined;
|
|
6
|
+
constructor(message: string, context?: unknown | undefined);
|
|
7
|
+
}
|
|
8
|
+
export declare class InvalidOutputSchemaError extends StructuredAgentError {
|
|
9
|
+
context?: unknown | undefined;
|
|
10
|
+
constructor(message: string, context?: unknown | undefined);
|
|
11
|
+
}
|
|
12
|
+
export declare class SchemaValidationError extends StructuredAgentError {
|
|
13
|
+
errors: unknown[];
|
|
14
|
+
constructor(message: string, errors: unknown[]);
|
|
15
|
+
}
|
|
16
|
+
export declare class MaxIterationsExceededError extends StructuredAgentError {
|
|
17
|
+
attempts: unknown[];
|
|
18
|
+
constructor(message: string, attempts: unknown[]);
|
|
19
|
+
}
|
|
20
|
+
export declare class LLMExecutionError extends StructuredAgentError {
|
|
21
|
+
originalError?: unknown | undefined;
|
|
22
|
+
constructor(message: string, originalError?: unknown | undefined);
|
|
23
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
export class StructuredAgentError extends Error {
|
|
2
|
+
constructor(message) {
|
|
3
|
+
super(message);
|
|
4
|
+
this.name = this.constructor.name;
|
|
5
|
+
Error.captureStackTrace(this, this.constructor);
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
export class InvalidInputSchemaError extends StructuredAgentError {
|
|
9
|
+
context;
|
|
10
|
+
constructor(message, context) {
|
|
11
|
+
super(message);
|
|
12
|
+
this.context = context;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
export class InvalidOutputSchemaError extends StructuredAgentError {
|
|
16
|
+
context;
|
|
17
|
+
constructor(message, context) {
|
|
18
|
+
super(message);
|
|
19
|
+
this.context = context;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
export class SchemaValidationError extends StructuredAgentError {
|
|
23
|
+
errors;
|
|
24
|
+
constructor(message, errors) {
|
|
25
|
+
super(message);
|
|
26
|
+
this.errors = errors;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
export class MaxIterationsExceededError extends StructuredAgentError {
|
|
30
|
+
attempts;
|
|
31
|
+
constructor(message, attempts) {
|
|
32
|
+
super(message);
|
|
33
|
+
this.attempts = attempts;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
export class LLMExecutionError extends StructuredAgentError {
|
|
37
|
+
originalError;
|
|
38
|
+
constructor(message, originalError) {
|
|
39
|
+
super(message);
|
|
40
|
+
this.originalError = originalError;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/errors/index.ts"],"names":[],"mappings":"AAAA,MAAM,OAAO,oBAAqB,SAAQ,KAAK;IAC7C,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC;QAClC,KAAK,CAAC,iBAAiB,CAAC,IAAI,EAAE,IAAI,CAAC,WAAW,CAAC,CAAC;IAClD,CAAC;CACF;AAED,MAAM,OAAO,uBAAwB,SAAQ,oBAAoB;IAC3B;IAApC,YAAY,OAAe,EAAS,OAAiB;QACnD,KAAK,CAAC,OAAO,CAAC,CAAC;QADmB,YAAO,GAAP,OAAO,CAAU;IAErD,CAAC;CACF;AAED,MAAM,OAAO,wBAAyB,SAAQ,oBAAoB;IAC5B;IAApC,YAAY,OAAe,EAAS,OAAiB;QACnD,KAAK,CAAC,OAAO,CAAC,CAAC;QADmB,YAAO,GAAP,OAAO,CAAU;IAErD,CAAC;CACF;AAED,MAAM,OAAO,qBAAsB,SAAQ,oBAAoB;IACzB;IAApC,YAAY,OAAe,EAAS,MAAiB;QACnD,KAAK,CAAC,OAAO,CAAC,CAAC;QADmB,WAAM,GAAN,MAAM,CAAW;IAErD,CAAC;CACF;AAED,MAAM,OAAO,0BAA2B,SAAQ,oBAAoB;IAC9B;IAApC,YAAY,OAAe,EAAS,QAAmB;QACrD,KAAK,CAAC,OAAO,CAAC,CAAC;QADmB,aAAQ,GAAR,QAAQ,CAAW;IAEvD,CAAC;CACF;AAED,MAAM,OAAO,iBAAkB,SAAQ,oBAAoB;IACrB;IAApC,YAAY,OAAe,EAAS,aAAuB;QACzD,KAAK,CAAC,OAAO,CAAC,CAAC;QADmB,kBAAa,GAAb,aAAa,CAAU;IAE3D,CAAC;CACF"}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,kBAAkB,CAAC;AACjC,cAAc,mBAAmB,CAAC;AAClC,cAAc,kBAAkB,CAAC;AACjC,6EAA6E;AAC7E,cAAc,wBAAwB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/llm/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,aAAa,CAAC"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { ILLMService, ChatMessage } from "./types.js";
|
|
2
|
+
import { ModelConfig } from "../types/index.js";
|
|
3
|
+
export declare class OpenAILLMService implements ILLMService {
|
|
4
|
+
private client;
|
|
5
|
+
constructor(apiKey: string);
|
|
6
|
+
complete(messages: ChatMessage[], model: string, config?: ModelConfig): Promise<string>;
|
|
7
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import OpenAI from "openai";
|
|
2
|
+
import { LLMExecutionError } from "../errors/index.js";
|
|
3
|
+
export class OpenAILLMService {
|
|
4
|
+
client;
|
|
5
|
+
constructor(apiKey) {
|
|
6
|
+
this.client = new OpenAI({
|
|
7
|
+
apiKey: apiKey,
|
|
8
|
+
});
|
|
9
|
+
}
|
|
10
|
+
async complete(messages, model, config) {
|
|
11
|
+
try {
|
|
12
|
+
const response = await this.client.chat.completions.create({
|
|
13
|
+
messages: messages,
|
|
14
|
+
model: model,
|
|
15
|
+
response_format: { type: "json_object" }, // Force JSON mode
|
|
16
|
+
temperature: config?.temperature ?? 0.7,
|
|
17
|
+
top_p: config?.top_p,
|
|
18
|
+
max_tokens: config?.max_tokens,
|
|
19
|
+
presence_penalty: config?.presence_penalty,
|
|
20
|
+
frequency_penalty: config?.frequency_penalty,
|
|
21
|
+
});
|
|
22
|
+
const content = response.choices[0]?.message?.content;
|
|
23
|
+
if (!content) {
|
|
24
|
+
throw new LLMExecutionError("Received empty response from LLM");
|
|
25
|
+
}
|
|
26
|
+
return content;
|
|
27
|
+
}
|
|
28
|
+
catch (error) {
|
|
29
|
+
throw new LLMExecutionError("Failed to execute LLM request", error);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=openai.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"openai.js","sourceRoot":"","sources":["../../src/llm/openai.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,QAAQ,CAAC;AAG5B,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAEvD,MAAM,OAAO,gBAAgB;IACnB,MAAM,CAAS;IAEvB,YAAY,MAAc;QACxB,IAAI,CAAC,MAAM,GAAG,IAAI,MAAM,CAAC;YACvB,MAAM,EAAE,MAAM;SACf,CAAC,CAAC;IACL,CAAC;IAEM,KAAK,CAAC,QAAQ,CACnB,QAAuB,EACvB,KAAa,EACb,MAAoB;QAEpB,IAAI,CAAC;YACH,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC;gBACzD,QAAQ,EAAE,QAAQ;gBAClB,KAAK,EAAE,KAAK;gBACZ,eAAe,EAAE,EAAE,IAAI,EAAE,aAAa,EAAE,EAAE,kBAAkB;gBAC5D,WAAW,EAAE,MAAM,EAAE,WAAW,IAAI,GAAG;gBACvC,KAAK,EAAE,MAAM,EAAE,KAAK;gBACpB,UAAU,EAAE,MAAM,EAAE,UAAU;gBAC9B,gBAAgB,EAAE,MAAM,EAAE,gBAAgB;gBAC1C,iBAAiB,EAAE,MAAM,EAAE,iBAAiB;aAC7C,CAAC,CAAC;YAEH,MAAM,OAAO,GAAG,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,OAAO,CAAC;YACtD,IAAI,CAAC,OAAO,EAAE,CAAC;gBACb,MAAM,IAAI,iBAAiB,CAAC,kCAAkC,CAAC,CAAC;YAClE,CAAC;YAED,OAAO,OAAO,CAAC;QACjB,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,IAAI,iBAAiB,CAAC,+BAA+B,EAAE,KAAK,CAAC,CAAC;QACtE,CAAC;IACH,CAAC;CACF"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { ModelConfig } from "../types/index.js";
|
|
2
|
+
export interface ChatMessage {
|
|
3
|
+
role: "system" | "user" | "assistant";
|
|
4
|
+
content: string;
|
|
5
|
+
}
|
|
6
|
+
export interface ILLMService {
|
|
7
|
+
complete(messages: ChatMessage[], model: string, config?: ModelConfig): Promise<string>;
|
|
8
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/llm/types.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { ValidateFunction } from "ajv";
|
|
2
|
+
export declare class SchemaValidator {
|
|
3
|
+
private ajv;
|
|
4
|
+
constructor();
|
|
5
|
+
/**
|
|
6
|
+
* Compiles a schema and returns a validation function.
|
|
7
|
+
* Throws InvalidInputSchemaError if the schema is invalid.
|
|
8
|
+
*/
|
|
9
|
+
compile(schema: object): ValidateFunction;
|
|
10
|
+
/**
|
|
11
|
+
* Validates data against a compiled schema validator.
|
|
12
|
+
* Returns true if valid.
|
|
13
|
+
* Throws SchemaValidationError if invalid.
|
|
14
|
+
*/
|
|
15
|
+
validate(validator: ValidateFunction, data: unknown): void;
|
|
16
|
+
/**
|
|
17
|
+
* Helper to format AJV errors into a readable string
|
|
18
|
+
*/
|
|
19
|
+
formatErrors(errors: any[]): string;
|
|
20
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import Ajv from "ajv";
|
|
2
|
+
import { SchemaValidationError, InvalidInputSchemaError } from "../errors/index.js";
|
|
3
|
+
export class SchemaValidator {
|
|
4
|
+
ajv;
|
|
5
|
+
constructor() {
|
|
6
|
+
// @ts-ignore: Ajv import structure compatibility
|
|
7
|
+
this.ajv = new Ajv({ allErrors: true, strict: false });
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Compiles a schema and returns a validation function.
|
|
11
|
+
* Throws InvalidInputSchemaError if the schema is invalid.
|
|
12
|
+
*/
|
|
13
|
+
compile(schema) {
|
|
14
|
+
try {
|
|
15
|
+
return this.ajv.compile(schema);
|
|
16
|
+
}
|
|
17
|
+
catch (error) {
|
|
18
|
+
throw new InvalidInputSchemaError("Invalid JSON Schema provided", error);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Validates data against a compiled schema validator.
|
|
23
|
+
* Returns true if valid.
|
|
24
|
+
* Throws SchemaValidationError if invalid.
|
|
25
|
+
*/
|
|
26
|
+
validate(validator, data) {
|
|
27
|
+
const valid = validator(data);
|
|
28
|
+
if (!valid) {
|
|
29
|
+
throw new SchemaValidationError("Data validation failed", validator.errors || []);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Helper to format AJV errors into a readable string
|
|
34
|
+
*/
|
|
35
|
+
formatErrors(errors) {
|
|
36
|
+
return this.ajv.errorsText(errors);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
//# sourceMappingURL=validator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validator.js","sourceRoot":"","sources":["../../src/schemas/validator.ts"],"names":[],"mappings":"AAAA,OAAO,GAAG,MAAM,KAAK,CAAC;AAEtB,OAAO,EAAE,qBAAqB,EAAE,uBAAuB,EAAE,MAAM,oBAAoB,CAAC;AAEpF,MAAM,OAAO,eAAe;IAClB,GAAG,CAAM;IAEjB;QACE,iDAAiD;QACjD,IAAI,CAAC,GAAG,GAAG,IAAI,GAAG,CAAC,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,CAAC;IACzD,CAAC;IAED;;;OAGG;IACI,OAAO,CAAC,MAAc;QAC3B,IAAI,CAAC;YACH,OAAO,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;QAClC,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,IAAI,uBAAuB,CAAC,8BAA8B,EAAE,KAAK,CAAC,CAAC;QAC3E,CAAC;IACH,CAAC;IAED;;;;OAIG;IACI,QAAQ,CAAC,SAA2B,EAAE,IAAa;QACxD,MAAM,KAAK,GAAG,SAAS,CAAC,IAAI,CAAC,CAAC;QAC9B,IAAI,CAAC,KAAK,EAAE,CAAC;YACX,MAAM,IAAI,qBAAqB,CAC7B,wBAAwB,EACxB,SAAS,CAAC,MAAM,IAAI,EAAE,CACvB,CAAC;QACJ,CAAC;IACH,CAAC;IAED;;OAEG;IACI,YAAY,CAAC,MAAa;QAC/B,OAAO,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC;IACrC,CAAC;CACF"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { ILLMService } from "../llm/types.js";
|
|
2
|
+
export interface ModelConfig {
|
|
3
|
+
temperature?: number;
|
|
4
|
+
top_p?: number;
|
|
5
|
+
max_tokens?: number;
|
|
6
|
+
presence_penalty?: number;
|
|
7
|
+
frequency_penalty?: number;
|
|
8
|
+
}
|
|
9
|
+
export interface AgentConfig {
|
|
10
|
+
openAiApiKey: string;
|
|
11
|
+
generatorModel: string;
|
|
12
|
+
reviewerModel: string;
|
|
13
|
+
inputSchema: object;
|
|
14
|
+
outputSchema: object;
|
|
15
|
+
systemPrompt: string;
|
|
16
|
+
modelConfig?: ModelConfig;
|
|
17
|
+
maxIterations?: number;
|
|
18
|
+
llmService?: ILLMService;
|
|
19
|
+
}
|
|
20
|
+
export interface AgentRunOptions {
|
|
21
|
+
input: unknown;
|
|
22
|
+
}
|
|
23
|
+
export interface ValidationResult {
|
|
24
|
+
valid: boolean;
|
|
25
|
+
errors?: string[];
|
|
26
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":""}
|
package/package.json
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "structured-json-agent",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "A typed and extensible library for creating and running Iterative AI Agents that generate structured JSON output.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"files": [
|
|
9
|
+
"dist"
|
|
10
|
+
],
|
|
11
|
+
"scripts": {
|
|
12
|
+
"build": "rm -rf dist && tsc",
|
|
13
|
+
"prepublishOnly": "npm run build",
|
|
14
|
+
"test": "NODE_OPTIONS='--experimental-vm-modules' jest"
|
|
15
|
+
},
|
|
16
|
+
"keywords": [
|
|
17
|
+
"ai",
|
|
18
|
+
"agent",
|
|
19
|
+
"json",
|
|
20
|
+
"schema",
|
|
21
|
+
"openai",
|
|
22
|
+
"structured-output",
|
|
23
|
+
"llm"
|
|
24
|
+
],
|
|
25
|
+
"author": "",
|
|
26
|
+
"license": "ISC",
|
|
27
|
+
"devDependencies": {
|
|
28
|
+
"@types/jest": "^30.0.0",
|
|
29
|
+
"@types/node": "^20.0.0",
|
|
30
|
+
"jest": "^30.2.0",
|
|
31
|
+
"ts-jest": "^29.4.6",
|
|
32
|
+
"typescript": "^5.0.0"
|
|
33
|
+
},
|
|
34
|
+
"dependencies": {
|
|
35
|
+
"ajv": "^8.12.0",
|
|
36
|
+
"openai": "^4.0.0"
|
|
37
|
+
},
|
|
38
|
+
"engines": {
|
|
39
|
+
"node": ">=18.0.0"
|
|
40
|
+
}
|
|
41
|
+
}
|