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>, validator: (data: any) => T, options?: JsonSchemaLlmClientOptions) => Promise<T>;
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, validator, options) {
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, validator, schemaJsonString, options);
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 validator = (data) => {
98
- try {
99
- return dataExtractionSchema.parse(data);
100
- }
101
- catch (error) {
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);
@@ -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>, validator: (data: any) => T, options?: import("./createJsonSchemaLlmClient.js").JsonSchemaLlmClientOptions) => Promise<T>;
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.7",
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 Zod schema validation, the client automatically feeds the error back to the LLM and asks it to fix it (up to `maxRetries`).
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<z.infer<typeof Schema>>`
122
+ **Return Type:** `Promise<T>`
123
123
 
124
- ### Level 1: Generation (Schema Only)
125
- The client "hallucinates" data matching the shape.
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
- import { z } from 'zod';
129
- const UserSchema = z.object({ name: z.string(), age: z.number() });
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
- // Input: Schema only
132
- const user = await llm.promptZod(UserSchema);
133
- // Output: { name: "Alice", age: 32 }
138
+ const result = await llm.promptJson(
139
+ [{ role: "user", content: "I love this!" }],
140
+ MySchema
141
+ );
134
142
  ```
135
143
 
136
- ### Level 2: Extraction (Injection Shortcuts)
137
- Pass context alongside the schema. This automates the "System Prompt JSON Injection".
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
- ```typescript
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
- // 2. Strict Separation (System, User, Schema)
145
- // Useful for auditing code or translations where instructions must not bleed into data.
146
- const analysis = await llm.promptZod(
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
- ### Level 3: State & Options (History + Config)
154
- Process full chat history into state, and use the **Options Object (4th Argument)** to control the internals (Models, Retries, Caching).
153
+ // 1. Schema Only (Hallucinate data)
154
+ const user = await llm.promptZod(UserSchema);
155
155
 
156
- **Input Type:** `ZodLlmClientOptions`
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
- ```typescript
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 4: Hooks & Pre-processing
177
- Sometimes LLMs output data that is *almost* correct (e.g., strings for numbers). You can sanitize data before Zod validation runs.
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 Zod validation runs
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
  ```