llm-fns 1.0.7 → 1.0.9
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.
|
@@ -23,7 +23,7 @@ export interface CreateJsonSchemaLlmClientParams {
|
|
|
23
23
|
disableJsonFixer?: boolean;
|
|
24
24
|
}
|
|
25
25
|
export declare function createJsonSchemaLlmClient(params: CreateJsonSchemaLlmClientParams): {
|
|
26
|
-
promptJson: <T>(messages: OpenAI.Chat.Completions.ChatCompletionMessageParam[], schema: Record<string, any>,
|
|
26
|
+
promptJson: <T>(messages: OpenAI.Chat.Completions.ChatCompletionMessageParam[], schema: Record<string, any>, options?: JsonSchemaLlmClientOptions) => Promise<T>;
|
|
27
27
|
isPromptJsonCached: (messages: OpenAI.Chat.Completions.ChatCompletionMessageParam[], schema: Record<string, any>, options?: JsonSchemaLlmClientOptions) => Promise<boolean>;
|
|
28
28
|
};
|
|
29
29
|
export type JsonSchemaClient = ReturnType<typeof createJsonSchemaLlmClient>;
|
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
2
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
6
|
exports.createJsonSchemaLlmClient = createJsonSchemaLlmClient;
|
|
7
|
+
const ajv_1 = __importDefault(require("ajv"));
|
|
4
8
|
const createLlmRetryClient_js_1 = require("./createLlmRetryClient.js");
|
|
5
9
|
function createJsonSchemaLlmClient(params) {
|
|
6
10
|
const { prompt, isPromptCached, fallbackPrompt, disableJsonFixer = false } = params;
|
|
7
11
|
const llmRetryClient = (0, createLlmRetryClient_js_1.createLlmRetryClient)({ prompt, fallbackPrompt });
|
|
12
|
+
const ajv = new ajv_1.default({ strict: false }); // Initialize AJV
|
|
8
13
|
async function _tryToFixJson(brokenResponse, schemaJsonString, errorDetails, options) {
|
|
9
14
|
const fixupPrompt = `
|
|
10
15
|
An attempt to generate a JSON object resulted in the following output, which is either not valid JSON or does not conform to the required schema.
|
|
@@ -140,7 +145,17 @@ ${schemaJsonString}`;
|
|
|
140
145
|
: undefined;
|
|
141
146
|
return { finalMessages, schemaJsonString, response_format };
|
|
142
147
|
}
|
|
143
|
-
async function promptJson(messages, schema,
|
|
148
|
+
async function promptJson(messages, schema, options) {
|
|
149
|
+
// Always validate against the schema using AJV
|
|
150
|
+
const ajvValidator = (data) => {
|
|
151
|
+
const validate = ajv.compile(schema);
|
|
152
|
+
const valid = validate(data);
|
|
153
|
+
if (!valid) {
|
|
154
|
+
const errors = validate.errors?.map(e => `${e.instancePath} ${e.message}`).join(', ');
|
|
155
|
+
throw new Error(`AJV Validation Error: ${errors}`);
|
|
156
|
+
}
|
|
157
|
+
return data;
|
|
158
|
+
};
|
|
144
159
|
const { finalMessages, schemaJsonString, response_format } = _getJsonPromptConfig(messages, schema, options);
|
|
145
160
|
const processResponse = async (llmResponseString) => {
|
|
146
161
|
let jsonData;
|
|
@@ -155,7 +170,7 @@ The response provided was not valid JSON. Please correct it.`;
|
|
|
155
170
|
throw new createLlmRetryClient_js_1.LlmRetryError(errorMessage, 'JSON_PARSE_ERROR', undefined, llmResponseString);
|
|
156
171
|
}
|
|
157
172
|
try {
|
|
158
|
-
const validatedData = await _validateOrFix(jsonData,
|
|
173
|
+
const validatedData = await _validateOrFix(jsonData, ajvValidator, schemaJsonString, options);
|
|
159
174
|
return validatedData;
|
|
160
175
|
}
|
|
161
176
|
catch (validationError) {
|
|
@@ -36,7 +36,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
36
36
|
exports.normalizeZodArgs = normalizeZodArgs;
|
|
37
37
|
exports.createZodLlmClient = createZodLlmClient;
|
|
38
38
|
const z = __importStar(require("zod"));
|
|
39
|
-
const zod_1 = require("zod");
|
|
40
39
|
function isZodSchema(obj) {
|
|
41
40
|
return (typeof obj === 'object' &&
|
|
42
41
|
obj !== null &&
|
|
@@ -94,19 +93,11 @@ function createZodLlmClient(params) {
|
|
|
94
93
|
const schema = z.toJSONSchema(dataExtractionSchema, {
|
|
95
94
|
unrepresentable: 'any'
|
|
96
95
|
});
|
|
97
|
-
const
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
if (error instanceof zod_1.ZodError) {
|
|
103
|
-
// Format the error nicely for the LLM
|
|
104
|
-
throw new Error(JSON.stringify(error.format(), null, 2));
|
|
105
|
-
}
|
|
106
|
-
throw error;
|
|
107
|
-
}
|
|
108
|
-
};
|
|
109
|
-
return jsonSchemaClient.promptJson(messages, schema, validator, options);
|
|
96
|
+
const result = await jsonSchemaClient.promptJson(messages, schema, options);
|
|
97
|
+
// We still parse with Zod to ensure the types are correct and to apply any transformations/refinements
|
|
98
|
+
// that JSON Schema might not capture perfectly, or just to get the typed return.
|
|
99
|
+
// Note: If this fails, it won't trigger a retry in promptJson anymore, because promptJson only retries on AJV errors.
|
|
100
|
+
return dataExtractionSchema.parse(result);
|
|
110
101
|
}
|
|
111
102
|
async function isPromptZodCached(arg1, arg2, arg3, arg4) {
|
|
112
103
|
const { messages, dataExtractionSchema, options } = normalizeZodArgs(arg1, arg2, arg3, arg4);
|
package/dist/llmFactory.d.ts
CHANGED
|
@@ -14,7 +14,7 @@ export declare function createLlm(params: CreateLlmFactoryParams): {
|
|
|
14
14
|
<T extends import("zod").ZodType>(prompt: string, schema: T, options?: import("./createZodLlmClient.js").ZodLlmClientOptions): Promise<boolean>;
|
|
15
15
|
<T extends import("zod").ZodType>(mainInstruction: string, userMessagePayload: string | import("openai/resources/index.js").ChatCompletionContentPart[], dataExtractionSchema: T, options?: import("./createZodLlmClient.js").ZodLlmClientOptions): Promise<boolean>;
|
|
16
16
|
};
|
|
17
|
-
promptJson: <T>(messages: import("openai/resources/index.js").ChatCompletionMessageParam[], schema: Record<string, any>,
|
|
17
|
+
promptJson: <T>(messages: import("openai/resources/index.js").ChatCompletionMessageParam[], schema: Record<string, any>, options?: import("./createJsonSchemaLlmClient.js").JsonSchemaLlmClientOptions) => Promise<T>;
|
|
18
18
|
isPromptJsonCached: (messages: import("openai/resources/index.js").ChatCompletionMessageParam[], schema: Record<string, any>, options?: import("./createJsonSchemaLlmClient.js").JsonSchemaLlmClientOptions) => Promise<boolean>;
|
|
19
19
|
promptRetry: {
|
|
20
20
|
<T = import("openai/resources/index.js").ChatCompletion>(content: string, options?: Omit<import("./createLlmRetryClient.js").LlmRetryOptions<T>, "messages">): Promise<T>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "llm-fns",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.9",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
"author": "",
|
|
12
12
|
"license": "MIT",
|
|
13
13
|
"dependencies": {
|
|
14
|
+
"ajv": "^8.17.1",
|
|
14
15
|
"openai": "^6.9.1",
|
|
15
16
|
"zod": "^4.1.13"
|
|
16
17
|
},
|
package/readme.md
CHANGED
|
@@ -7,7 +7,7 @@ Designed for power users who need to switch between simple string prompts and co
|
|
|
7
7
|
## Installation
|
|
8
8
|
|
|
9
9
|
```bash
|
|
10
|
-
npm install openai zod cache-manager p-queue
|
|
10
|
+
npm install openai zod cache-manager p-queue ajv
|
|
11
11
|
```
|
|
12
12
|
|
|
13
13
|
## Quick Start (Factory)
|
|
@@ -115,47 +115,49 @@ const buffer2 = await llm.promptImage({
|
|
|
115
115
|
|
|
116
116
|
---
|
|
117
117
|
|
|
118
|
-
# Use Case 3: Structured Data (`llm.promptZod`)
|
|
118
|
+
# Use Case 3: Structured Data (`llm.promptJson` & `llm.promptZod`)
|
|
119
119
|
|
|
120
|
-
This is a high-level wrapper that employs a **Re-asking Loop**. If the LLM outputs invalid JSON or data that fails the
|
|
120
|
+
This is a high-level wrapper that employs a **Re-asking Loop**. If the LLM outputs invalid JSON or data that fails the schema validation, the client automatically feeds the error back to the LLM and asks it to fix it (up to `maxRetries`).
|
|
121
121
|
|
|
122
|
-
**Return Type:** `Promise<
|
|
122
|
+
**Return Type:** `Promise<T>`
|
|
123
123
|
|
|
124
|
-
### Level 1:
|
|
125
|
-
|
|
124
|
+
### Level 1: Raw JSON Schema (`promptJson`)
|
|
125
|
+
Use this if you have a standard JSON Schema object (e.g. from another library or API) and don't want to use Zod. It uses **AJV** internally to validate the response against the schema.
|
|
126
126
|
|
|
127
127
|
```typescript
|
|
128
|
-
|
|
129
|
-
|
|
128
|
+
const MySchema = {
|
|
129
|
+
type: "object",
|
|
130
|
+
properties: {
|
|
131
|
+
sentiment: { type: "string", enum: ["positive", "negative"] },
|
|
132
|
+
score: { type: "number" }
|
|
133
|
+
},
|
|
134
|
+
required: ["sentiment", "score"],
|
|
135
|
+
additionalProperties: false
|
|
136
|
+
};
|
|
130
137
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
138
|
+
const result = await llm.promptJson(
|
|
139
|
+
[{ role: "user", content: "I love this!" }],
|
|
140
|
+
MySchema
|
|
141
|
+
);
|
|
134
142
|
```
|
|
135
143
|
|
|
136
|
-
### Level 2:
|
|
137
|
-
|
|
144
|
+
### Level 2: Zod Wrapper (`promptZod`)
|
|
145
|
+
This is syntactic sugar over `promptJson`. It converts your Zod schema to JSON Schema and automatically sets up the validator to throw formatted Zod errors for the retry loop.
|
|
138
146
|
|
|
139
|
-
|
|
140
|
-
// 1. Extract from String
|
|
141
|
-
const email = "Meeting at 2 PM with Bob.";
|
|
142
|
-
const event = await llm.promptZod(email, z.object({ time: z.string(), who: z.string() }));
|
|
147
|
+
**Return Type:** `Promise<z.infer<typeof Schema>>`
|
|
143
148
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
const
|
|
147
|
-
"You are a security auditor.", // Arg 1: System
|
|
148
|
-
"function dangerous() {}", // Arg 2: User Data
|
|
149
|
-
SecuritySchema // Arg 3: Schema
|
|
150
|
-
);
|
|
151
|
-
```
|
|
149
|
+
```typescript
|
|
150
|
+
import { z } from 'zod';
|
|
151
|
+
const UserSchema = z.object({ name: z.string(), age: z.number() });
|
|
152
152
|
|
|
153
|
-
|
|
154
|
-
|
|
153
|
+
// 1. Schema Only (Hallucinate data)
|
|
154
|
+
const user = await llm.promptZod(UserSchema);
|
|
155
155
|
|
|
156
|
-
|
|
156
|
+
// 2. Extraction (Context + Schema)
|
|
157
|
+
const email = "Meeting at 2 PM with Bob.";
|
|
158
|
+
const event = await llm.promptZod(email, z.object({ time: z.string(), who: z.string() }));
|
|
157
159
|
|
|
158
|
-
|
|
160
|
+
// 3. Full Control (History + Schema + Options)
|
|
159
161
|
const history = [
|
|
160
162
|
{ role: "user", content: "I cast Fireball." },
|
|
161
163
|
{ role: "assistant", content: "It misses." }
|
|
@@ -173,12 +175,12 @@ const gameState = await llm.promptZod(
|
|
|
173
175
|
);
|
|
174
176
|
```
|
|
175
177
|
|
|
176
|
-
### Level
|
|
177
|
-
Sometimes LLMs output data that is *almost* correct (e.g., strings for numbers). You can sanitize data before
|
|
178
|
+
### Level 3: Hooks & Pre-processing
|
|
179
|
+
Sometimes LLMs output data that is *almost* correct (e.g., strings for numbers). You can sanitize data before validation runs.
|
|
178
180
|
|
|
179
181
|
```typescript
|
|
180
182
|
const result = await llm.promptZod(MySchema, {
|
|
181
|
-
// Transform JSON before
|
|
183
|
+
// Transform JSON before validation runs
|
|
182
184
|
beforeValidation: (data) => {
|
|
183
185
|
if (data.price && typeof data.price === 'string') {
|
|
184
186
|
return { ...data, price: parseFloat(data.price) };
|
|
@@ -187,7 +189,6 @@ const result = await llm.promptZod(MySchema, {
|
|
|
187
189
|
},
|
|
188
190
|
|
|
189
191
|
// Toggle usage of 'response_format: { type: "json_object" }'
|
|
190
|
-
// Sometimes strict JSON mode is too restrictive for creative tasks
|
|
191
192
|
useResponseFormat: false
|
|
192
193
|
});
|
|
193
194
|
```
|