recursive-llm-ts 4.0.0 → 4.1.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/bin/rlm-go +0 -0
- package/dist/rlm.d.ts +1 -0
- package/dist/rlm.js +317 -30
- package/go/internal/rlm/structured.go +123 -22
- package/package.json +1 -1
package/bin/rlm-go
CHANGED
|
Binary file
|
package/dist/rlm.d.ts
CHANGED
package/dist/rlm.js
CHANGED
|
@@ -52,62 +52,349 @@ class RLM {
|
|
|
52
52
|
});
|
|
53
53
|
}
|
|
54
54
|
zodToJsonSchema(schema) {
|
|
55
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
|
|
55
56
|
const def = schema._def;
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
57
|
+
const defType = def.type;
|
|
58
|
+
// Handle wrapped types (optional, nullable, default, catch)
|
|
59
|
+
if (defType === 'optional' || defType === 'nullable' || defType === 'default' || defType === 'catch') {
|
|
60
|
+
const inner = this.zodToJsonSchema(def.innerType);
|
|
61
|
+
if (defType === 'nullable') {
|
|
62
|
+
return Object.assign(Object.assign({}, inner), { nullable: true });
|
|
63
|
+
}
|
|
64
|
+
return inner; // Optional/Default/Catch don't change the schema, just validation
|
|
65
|
+
}
|
|
66
|
+
// Handle effects (refine, transform, preprocess) - unwrap to inner type
|
|
67
|
+
if (defType === 'effects') {
|
|
68
|
+
return this.zodToJsonSchema(def.schema);
|
|
69
|
+
}
|
|
70
|
+
// Handle pipeline (pipe) - use the output schema
|
|
71
|
+
if (defType === 'pipeline') {
|
|
72
|
+
return this.zodToJsonSchema(def.out);
|
|
73
|
+
}
|
|
74
|
+
// Handle lazy schemas - unwrap the getter
|
|
75
|
+
if (defType === 'lazy') {
|
|
76
|
+
// For lazy schemas, we need to call the getter to get the actual schema
|
|
77
|
+
try {
|
|
78
|
+
const actualSchema = def.getter();
|
|
79
|
+
return this.zodToJsonSchema(actualSchema);
|
|
80
|
+
}
|
|
81
|
+
catch (e) {
|
|
82
|
+
// If lazy getter fails, fall back to generic object
|
|
83
|
+
return { type: 'object' };
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
// Handle branded types - unwrap to base type
|
|
87
|
+
if (defType === 'branded') {
|
|
88
|
+
return this.zodToJsonSchema(def.type);
|
|
89
|
+
}
|
|
90
|
+
// Handle readonly - pass through
|
|
91
|
+
if (defType === 'readonly') {
|
|
92
|
+
return this.zodToJsonSchema(def.innerType);
|
|
93
|
+
}
|
|
94
|
+
// Handle literals
|
|
95
|
+
if (defType === 'literal') {
|
|
96
|
+
// Literals in this Zod version use 'values' array
|
|
97
|
+
if (def.values && def.values.length > 0) {
|
|
98
|
+
const value = def.values[0];
|
|
99
|
+
const valueType = typeof value;
|
|
100
|
+
return {
|
|
101
|
+
type: valueType === 'object' ? 'string' : valueType,
|
|
102
|
+
enum: [value]
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
// Fallback for other literal formats
|
|
106
|
+
const value = def.value;
|
|
107
|
+
if (value !== undefined) {
|
|
108
|
+
const valueType = typeof value;
|
|
109
|
+
return {
|
|
110
|
+
type: valueType === 'object' ? 'string' : valueType,
|
|
111
|
+
enum: [value]
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
// Handle unions
|
|
116
|
+
if (defType === 'union' || defType === 'discriminatedUnion') {
|
|
117
|
+
const options = def.options || Array.from(((_a = def.optionsMap) === null || _a === void 0 ? void 0 : _a.values()) || []);
|
|
118
|
+
if (options.length > 0) {
|
|
119
|
+
return {
|
|
120
|
+
anyOf: options.map((opt) => this.zodToJsonSchema(opt))
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
// Handle intersections
|
|
125
|
+
if (defType === 'intersection') {
|
|
126
|
+
return {
|
|
127
|
+
allOf: [
|
|
128
|
+
this.zodToJsonSchema(def.left),
|
|
129
|
+
this.zodToJsonSchema(def.right)
|
|
130
|
+
]
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
// Handle object type
|
|
134
|
+
if (def.shape || defType === 'object') {
|
|
135
|
+
const shape = def.shape || {};
|
|
59
136
|
const properties = {};
|
|
60
137
|
const required = [];
|
|
61
138
|
for (const [key, value] of Object.entries(shape)) {
|
|
62
139
|
properties[key] = this.zodToJsonSchema(value);
|
|
63
|
-
if
|
|
140
|
+
// A field is required if it's not optional and doesn't have a default
|
|
141
|
+
const valueDef = value._def;
|
|
142
|
+
const isOptional = (_d = (_c = (_b = value).isOptional) === null || _c === void 0 ? void 0 : _c.call(_b)) !== null && _d !== void 0 ? _d : false;
|
|
143
|
+
const hasDefault = (valueDef === null || valueDef === void 0 ? void 0 : valueDef.type) === 'default';
|
|
144
|
+
if (!isOptional && !hasDefault) {
|
|
64
145
|
required.push(key);
|
|
65
146
|
}
|
|
66
147
|
}
|
|
67
|
-
|
|
148
|
+
const result = {
|
|
68
149
|
type: 'object',
|
|
69
|
-
properties
|
|
70
|
-
required: required.length > 0 ? required : undefined
|
|
150
|
+
properties
|
|
71
151
|
};
|
|
152
|
+
if (required.length > 0) {
|
|
153
|
+
result.required = required;
|
|
154
|
+
}
|
|
155
|
+
// Handle unknown keys via catchall
|
|
156
|
+
if (def.catchall) {
|
|
157
|
+
const catchallType = (_e = def.catchall._def) === null || _e === void 0 ? void 0 : _e.type;
|
|
158
|
+
if (catchallType === 'unknown') {
|
|
159
|
+
result.additionalProperties = true;
|
|
160
|
+
}
|
|
161
|
+
else if (catchallType === 'never') {
|
|
162
|
+
result.additionalProperties = false;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
// Also check legacy unknownKeys
|
|
166
|
+
if (def.unknownKeys === 'passthrough') {
|
|
167
|
+
result.additionalProperties = true;
|
|
168
|
+
}
|
|
169
|
+
else if (def.unknownKeys === 'strict') {
|
|
170
|
+
result.additionalProperties = false;
|
|
171
|
+
}
|
|
172
|
+
return result;
|
|
72
173
|
}
|
|
73
|
-
//
|
|
74
|
-
if (
|
|
75
|
-
const itemSchema = def.element
|
|
76
|
-
|
|
174
|
+
// Handle array type
|
|
175
|
+
if (defType === 'array') {
|
|
176
|
+
const itemSchema = def.element;
|
|
177
|
+
const result = {
|
|
77
178
|
type: 'array',
|
|
78
179
|
items: this.zodToJsonSchema(itemSchema)
|
|
79
180
|
};
|
|
181
|
+
// Handle array length constraints from checks
|
|
182
|
+
if (def.checks && Array.isArray(def.checks)) {
|
|
183
|
+
for (const check of def.checks) {
|
|
184
|
+
const checkDef = ((_f = check._zod) === null || _f === void 0 ? void 0 : _f.def) || check.def || check;
|
|
185
|
+
switch (checkDef.check) {
|
|
186
|
+
case 'min_length':
|
|
187
|
+
result.minItems = checkDef.minimum || checkDef.value;
|
|
188
|
+
break;
|
|
189
|
+
case 'max_length':
|
|
190
|
+
result.maxItems = checkDef.maximum || checkDef.value;
|
|
191
|
+
break;
|
|
192
|
+
case 'exact_length':
|
|
193
|
+
result.minItems = checkDef.value;
|
|
194
|
+
result.maxItems = checkDef.value;
|
|
195
|
+
break;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
// Also check direct properties (legacy)
|
|
200
|
+
if (def.minLength)
|
|
201
|
+
result.minItems = def.minLength.value || def.minLength;
|
|
202
|
+
if (def.maxLength)
|
|
203
|
+
result.maxItems = def.maxLength.value || def.maxLength;
|
|
204
|
+
if (def.exactLength) {
|
|
205
|
+
const exact = def.exactLength.value || def.exactLength;
|
|
206
|
+
result.minItems = exact;
|
|
207
|
+
result.maxItems = exact;
|
|
208
|
+
}
|
|
209
|
+
return result;
|
|
80
210
|
}
|
|
81
|
-
//
|
|
82
|
-
if (
|
|
211
|
+
// Handle tuple
|
|
212
|
+
if (defType === 'tuple') {
|
|
213
|
+
const items = ((_g = def.items) === null || _g === void 0 ? void 0 : _g.map((item) => this.zodToJsonSchema(item))) || [];
|
|
214
|
+
const result = {
|
|
215
|
+
type: 'array',
|
|
216
|
+
prefixItems: items,
|
|
217
|
+
minItems: items.length,
|
|
218
|
+
maxItems: def.rest ? undefined : items.length
|
|
219
|
+
};
|
|
220
|
+
// Handle rest element
|
|
221
|
+
if (def.rest) {
|
|
222
|
+
result.items = this.zodToJsonSchema(def.rest);
|
|
223
|
+
}
|
|
224
|
+
else {
|
|
225
|
+
result.items = false; // No additional items allowed
|
|
226
|
+
}
|
|
227
|
+
return result;
|
|
228
|
+
}
|
|
229
|
+
// Handle set - convert to array
|
|
230
|
+
if (defType === 'set') {
|
|
83
231
|
return {
|
|
84
|
-
type: '
|
|
85
|
-
|
|
232
|
+
type: 'array',
|
|
233
|
+
uniqueItems: true,
|
|
234
|
+
items: def.valueType ? this.zodToJsonSchema(def.valueType) : {}
|
|
86
235
|
};
|
|
87
236
|
}
|
|
88
|
-
//
|
|
89
|
-
if (
|
|
237
|
+
// Handle map - convert to object
|
|
238
|
+
if (defType === 'map') {
|
|
90
239
|
return {
|
|
91
|
-
type: '
|
|
92
|
-
|
|
240
|
+
type: 'object',
|
|
241
|
+
additionalProperties: def.valueType ? this.zodToJsonSchema(def.valueType) : true
|
|
93
242
|
};
|
|
94
243
|
}
|
|
95
|
-
//
|
|
96
|
-
if (
|
|
97
|
-
|
|
98
|
-
|
|
244
|
+
// Handle record
|
|
245
|
+
if (defType === 'record') {
|
|
246
|
+
return {
|
|
247
|
+
type: 'object',
|
|
248
|
+
additionalProperties: def.valueType ? this.zodToJsonSchema(def.valueType) : true
|
|
249
|
+
};
|
|
99
250
|
}
|
|
100
|
-
//
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
251
|
+
// Handle enum
|
|
252
|
+
if (defType === 'enum' || defType === 'nativeEnum') {
|
|
253
|
+
if (def.values && Array.isArray(def.values)) {
|
|
254
|
+
return { type: 'string', enum: def.values };
|
|
255
|
+
}
|
|
256
|
+
if (def.entries) {
|
|
257
|
+
return { type: 'string', enum: Object.keys(def.entries) };
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
// Handle string with constraints
|
|
261
|
+
if (defType === 'string') {
|
|
262
|
+
const result = { type: 'string' };
|
|
263
|
+
if (def.checks && Array.isArray(def.checks)) {
|
|
264
|
+
for (const check of def.checks) {
|
|
265
|
+
// Access the actual check data via _zod.def
|
|
266
|
+
const checkDef = ((_h = check._zod) === null || _h === void 0 ? void 0 : _h.def) || check.def || check;
|
|
267
|
+
switch (checkDef.check) {
|
|
268
|
+
case 'min_length':
|
|
269
|
+
result.minLength = checkDef.minimum || checkDef.value;
|
|
270
|
+
break;
|
|
271
|
+
case 'max_length':
|
|
272
|
+
result.maxLength = checkDef.maximum || checkDef.value;
|
|
273
|
+
break;
|
|
274
|
+
case 'length_equals':
|
|
275
|
+
result.minLength = checkDef.length;
|
|
276
|
+
result.maxLength = checkDef.length;
|
|
277
|
+
break;
|
|
278
|
+
case 'string_format':
|
|
279
|
+
switch (checkDef.format) {
|
|
280
|
+
case 'email':
|
|
281
|
+
result.format = 'email';
|
|
282
|
+
break;
|
|
283
|
+
case 'url':
|
|
284
|
+
result.format = 'uri';
|
|
285
|
+
break;
|
|
286
|
+
case 'uuid':
|
|
287
|
+
result.format = 'uuid';
|
|
288
|
+
break;
|
|
289
|
+
case 'regex':
|
|
290
|
+
if (checkDef.pattern) {
|
|
291
|
+
result.pattern = checkDef.pattern.source || checkDef.pattern;
|
|
292
|
+
}
|
|
293
|
+
break;
|
|
294
|
+
}
|
|
295
|
+
break;
|
|
296
|
+
case 'regex':
|
|
297
|
+
result.pattern = ((_j = checkDef.pattern) === null || _j === void 0 ? void 0 : _j.source) || checkDef.pattern;
|
|
298
|
+
break;
|
|
299
|
+
}
|
|
300
|
+
// Also check nested def for formats
|
|
301
|
+
if (check.def && check.def.format) {
|
|
302
|
+
switch (check.def.format) {
|
|
303
|
+
case 'email':
|
|
304
|
+
result.format = 'email';
|
|
305
|
+
break;
|
|
306
|
+
case 'url':
|
|
307
|
+
result.format = 'uri';
|
|
308
|
+
break;
|
|
309
|
+
case 'uuid':
|
|
310
|
+
result.format = 'uuid';
|
|
311
|
+
break;
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
return result;
|
|
317
|
+
}
|
|
318
|
+
// Handle number/bigint with constraints
|
|
319
|
+
if (defType === 'number' || defType === 'bigint') {
|
|
320
|
+
const result = { type: defType === 'bigint' ? 'integer' : 'number' };
|
|
321
|
+
if (def.checks && Array.isArray(def.checks)) {
|
|
322
|
+
for (const check of def.checks) {
|
|
323
|
+
// Access the actual check data via _zod.def
|
|
324
|
+
const checkDef = ((_k = check._zod) === null || _k === void 0 ? void 0 : _k.def) || check.def || check;
|
|
325
|
+
switch (checkDef.check) {
|
|
326
|
+
case 'number_format':
|
|
327
|
+
if (checkDef.format === 'safeint') {
|
|
328
|
+
result.type = 'integer';
|
|
329
|
+
}
|
|
330
|
+
break;
|
|
331
|
+
case 'greater_than':
|
|
332
|
+
result.minimum = checkDef.value;
|
|
333
|
+
if (!checkDef.inclusive) {
|
|
334
|
+
result.exclusiveMinimum = true;
|
|
335
|
+
}
|
|
336
|
+
break;
|
|
337
|
+
case 'less_than':
|
|
338
|
+
result.maximum = checkDef.value;
|
|
339
|
+
if (!checkDef.inclusive) {
|
|
340
|
+
result.exclusiveMaximum = true;
|
|
341
|
+
}
|
|
342
|
+
break;
|
|
343
|
+
case 'multiple_of':
|
|
344
|
+
result.multipleOf = checkDef.value;
|
|
345
|
+
break;
|
|
346
|
+
}
|
|
347
|
+
// Also check direct properties (legacy support)
|
|
348
|
+
if (check.isInt === true) {
|
|
349
|
+
result.type = 'integer';
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
return result;
|
|
354
|
+
}
|
|
355
|
+
// Handle boolean
|
|
356
|
+
if (defType === 'boolean') {
|
|
107
357
|
return { type: 'boolean' };
|
|
358
|
+
}
|
|
359
|
+
// Handle date
|
|
360
|
+
if (defType === 'date') {
|
|
361
|
+
return { type: 'string', format: 'date-time' };
|
|
362
|
+
}
|
|
363
|
+
// Handle null
|
|
364
|
+
if (defType === 'null') {
|
|
365
|
+
return { type: 'null' };
|
|
366
|
+
}
|
|
367
|
+
// Handle undefined (not really JSON-serializable, but treat as null)
|
|
368
|
+
if (defType === 'undefined') {
|
|
369
|
+
return { type: 'null' };
|
|
370
|
+
}
|
|
371
|
+
// Handle void (same as undefined)
|
|
372
|
+
if (defType === 'void') {
|
|
373
|
+
return { type: 'null' };
|
|
374
|
+
}
|
|
375
|
+
// Handle any/unknown - no constraints
|
|
376
|
+
if (defType === 'any' || defType === 'unknown') {
|
|
377
|
+
return {}; // Empty schema accepts anything
|
|
378
|
+
}
|
|
379
|
+
// Handle never - impossible to satisfy
|
|
380
|
+
if (defType === 'never') {
|
|
381
|
+
return { not: {} }; // Schema that matches nothing
|
|
382
|
+
}
|
|
383
|
+
// Handle promise - unwrap to inner type
|
|
384
|
+
if (defType === 'promise') {
|
|
385
|
+
return this.zodToJsonSchema(def.innerType || def.type);
|
|
386
|
+
}
|
|
387
|
+
// Handle function - not JSON-serializable
|
|
388
|
+
if (defType === 'function') {
|
|
389
|
+
return { type: 'string', description: 'Function (not serializable)' };
|
|
390
|
+
}
|
|
108
391
|
// Default fallback
|
|
392
|
+
console.warn(`Unknown Zod type: ${defType}, falling back to string`);
|
|
109
393
|
return { type: 'string' };
|
|
110
394
|
}
|
|
395
|
+
escapeRegex(str) {
|
|
396
|
+
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
397
|
+
}
|
|
111
398
|
cleanup() {
|
|
112
399
|
return __awaiter(this, void 0, void 0, function* () {
|
|
113
400
|
if (this.bridge) {
|
|
@@ -37,22 +37,28 @@ func (r *RLM) structuredCompletionDirect(query string, context string, config *S
|
|
|
37
37
|
|
|
38
38
|
// Build comprehensive prompt with context and schema
|
|
39
39
|
constraints := generateSchemaConstraints(config.Schema)
|
|
40
|
+
requiredFieldsHint := ""
|
|
41
|
+
if config.Schema.Type == "object" && len(config.Schema.Required) > 0 {
|
|
42
|
+
requiredFieldsHint = fmt.Sprintf("\nREQUIRED FIELDS (you MUST include these): %s\n", strings.Join(config.Schema.Required, ", "))
|
|
43
|
+
}
|
|
44
|
+
|
|
40
45
|
prompt := fmt.Sprintf(
|
|
41
46
|
"You are a data extraction assistant. Extract information from the context and return it as JSON.\n\n"+
|
|
42
47
|
"Context:\n%s\n\n"+
|
|
43
48
|
"Task: %s\n\n"+
|
|
44
|
-
"Required JSON Schema:\n%s\n\n"+
|
|
49
|
+
"Required JSON Schema:\n%s%s\n\n"+
|
|
45
50
|
"%s"+
|
|
46
51
|
"CRITICAL INSTRUCTIONS:\n"+
|
|
47
52
|
"1. Return ONLY valid JSON - no explanations, no markdown, no code blocks\n"+
|
|
48
53
|
"2. The JSON must match the schema EXACTLY\n"+
|
|
49
|
-
"3. Include ALL required fields\n"+
|
|
54
|
+
"3. Include ALL required fields (see list above)\n"+
|
|
50
55
|
"4. Use correct data types (strings in quotes, numbers without quotes, arrays in [], objects in {})\n"+
|
|
51
56
|
"5. For arrays, return actual JSON arrays [] not objects\n"+
|
|
52
57
|
"6. For enum fields, use ONLY the EXACT values listed - do not paraphrase or substitute\n"+
|
|
53
|
-
"7.
|
|
58
|
+
"7. For nested objects, ensure ALL required fields within those objects are included\n"+
|
|
59
|
+
"8. Start your response directly with { or [ depending on the schema\n\n"+
|
|
54
60
|
"JSON Response:",
|
|
55
|
-
context, query, string(schemaJSON), constraints,
|
|
61
|
+
context, query, string(schemaJSON), requiredFieldsHint, constraints,
|
|
56
62
|
)
|
|
57
63
|
|
|
58
64
|
var lastErr error
|
|
@@ -192,7 +198,7 @@ func generateSchemaConstraints(schema *JSONSchema) string {
|
|
|
192
198
|
if schema.Type == "object" && schema.Properties != nil {
|
|
193
199
|
for fieldName, fieldSchema := range schema.Properties {
|
|
194
200
|
if fieldSchema.Type == "number" {
|
|
195
|
-
if strings.Contains(strings.ToLower(fieldName), "sentiment") {
|
|
201
|
+
if strings.Contains(strings.ToLower(fieldName), "sentiment") || strings.Contains(strings.ToLower(fieldName), "score") {
|
|
196
202
|
constraints = append(constraints, fmt.Sprintf("- %s must be a number between 1 and 5 (inclusive)", fieldName))
|
|
197
203
|
}
|
|
198
204
|
}
|
|
@@ -202,6 +208,10 @@ func generateSchemaConstraints(schema *JSONSchema) string {
|
|
|
202
208
|
if fieldSchema.Type == "array" {
|
|
203
209
|
constraints = append(constraints, fmt.Sprintf("- %s must be a JSON array []", fieldName))
|
|
204
210
|
}
|
|
211
|
+
// Add constraint for nested objects with required fields
|
|
212
|
+
if fieldSchema.Type == "object" && len(fieldSchema.Required) > 0 {
|
|
213
|
+
constraints = append(constraints, fmt.Sprintf("- %s must be an object with these REQUIRED fields: %s", fieldName, strings.Join(fieldSchema.Required, ", ")))
|
|
214
|
+
}
|
|
205
215
|
}
|
|
206
216
|
}
|
|
207
217
|
|
|
@@ -225,21 +235,75 @@ func generateSchemaConstraints(schema *JSONSchema) string {
|
|
|
225
235
|
return ""
|
|
226
236
|
}
|
|
227
237
|
|
|
228
|
-
// generateFieldQuery creates a focused query for a specific field
|
|
238
|
+
// generateFieldQuery creates a focused query for a specific field based on its schema
|
|
229
239
|
func generateFieldQuery(fieldName string, schema *JSONSchema) string {
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
+
var queryParts []string
|
|
241
|
+
|
|
242
|
+
// Start with field name
|
|
243
|
+
queryParts = append(queryParts, fmt.Sprintf("Extract the %s from the conversation.", fieldName))
|
|
244
|
+
|
|
245
|
+
// Add type-specific instructions
|
|
246
|
+
switch schema.Type {
|
|
247
|
+
case "object":
|
|
248
|
+
if len(schema.Required) > 0 {
|
|
249
|
+
fieldDetails := make([]string, 0, len(schema.Required))
|
|
250
|
+
for _, reqField := range schema.Required {
|
|
251
|
+
if propSchema, exists := schema.Properties[reqField]; exists {
|
|
252
|
+
fieldDetails = append(fieldDetails, fmt.Sprintf("'%s' (%s)", reqField, propSchema.Type))
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
queryParts = append(queryParts, fmt.Sprintf("Return a JSON object with these REQUIRED fields: %s.", strings.Join(fieldDetails, ", ")))
|
|
256
|
+
} else {
|
|
257
|
+
queryParts = append(queryParts, "Return a JSON object.")
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
case "array":
|
|
261
|
+
if schema.Items != nil {
|
|
262
|
+
if schema.Items.Type == "object" && schema.Items.Properties != nil {
|
|
263
|
+
// Build detailed description of array item structure
|
|
264
|
+
requiredFields := make([]string, 0)
|
|
265
|
+
optionalFields := make([]string, 0)
|
|
266
|
+
|
|
267
|
+
for propName, propSchema := range schema.Items.Properties {
|
|
268
|
+
fieldDesc := fmt.Sprintf("'%s' (%s)", propName, propSchema.Type)
|
|
269
|
+
if contains(schema.Items.Required, propName) {
|
|
270
|
+
requiredFields = append(requiredFields, fieldDesc)
|
|
271
|
+
} else {
|
|
272
|
+
optionalFields = append(optionalFields, fieldDesc)
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
var itemDesc []string
|
|
277
|
+
if len(requiredFields) > 0 {
|
|
278
|
+
itemDesc = append(itemDesc, fmt.Sprintf("REQUIRED fields: %s", strings.Join(requiredFields, ", ")))
|
|
279
|
+
}
|
|
280
|
+
if len(optionalFields) > 0 {
|
|
281
|
+
itemDesc = append(itemDesc, fmt.Sprintf("Optional fields: %s", strings.Join(optionalFields, ", ")))
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
queryParts = append(queryParts, fmt.Sprintf("Return a JSON array where each item is an object with %s.", strings.Join(itemDesc, ". ")))
|
|
285
|
+
} else {
|
|
286
|
+
queryParts = append(queryParts, fmt.Sprintf("Return a JSON array of %s values.", schema.Items.Type))
|
|
287
|
+
}
|
|
288
|
+
} else {
|
|
289
|
+
queryParts = append(queryParts, "Return a JSON array.")
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
case "string":
|
|
293
|
+
if schema.Enum != nil && len(schema.Enum) > 0 {
|
|
294
|
+
queryParts = append(queryParts, fmt.Sprintf("Return EXACTLY one of these values: %s (use exact strings).", strings.Join(schema.Enum, ", ")))
|
|
295
|
+
} else {
|
|
296
|
+
queryParts = append(queryParts, "Return a string value.")
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
case "number":
|
|
300
|
+
queryParts = append(queryParts, "Return a numeric value.")
|
|
301
|
+
|
|
302
|
+
case "boolean":
|
|
303
|
+
queryParts = append(queryParts, "Return a boolean value (true or false).")
|
|
240
304
|
}
|
|
241
|
-
|
|
242
|
-
return
|
|
305
|
+
|
|
306
|
+
return strings.Join(queryParts, " ")
|
|
243
307
|
}
|
|
244
308
|
|
|
245
309
|
// parseAndValidateJSON extracts JSON from response and validates against schema
|
|
@@ -264,11 +328,48 @@ func parseAndValidateJSON(result string, schema *JSONSchema) (map[string]interfa
|
|
|
264
328
|
if parseErr == nil {
|
|
265
329
|
// Check if it's a map (LLM wrapped the value in an object)
|
|
266
330
|
if valueMap, ok := value.(map[string]interface{}); ok {
|
|
267
|
-
//
|
|
268
|
-
|
|
331
|
+
// Try to unwrap based on expected type
|
|
332
|
+
switch schema.Type {
|
|
333
|
+
case "array":
|
|
334
|
+
// Look for any array value in the map
|
|
269
335
|
for _, v := range valueMap {
|
|
270
|
-
|
|
271
|
-
|
|
336
|
+
if arr, ok := v.([]interface{}); ok {
|
|
337
|
+
value = arr
|
|
338
|
+
break
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
case "string":
|
|
342
|
+
// Look for any string value in the map
|
|
343
|
+
for _, v := range valueMap {
|
|
344
|
+
if str, ok := v.(string); ok {
|
|
345
|
+
value = str
|
|
346
|
+
break
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
case "number":
|
|
350
|
+
// Look for any number value in the map
|
|
351
|
+
for _, v := range valueMap {
|
|
352
|
+
switch v.(type) {
|
|
353
|
+
case float64, float32, int, int32, int64:
|
|
354
|
+
value = v
|
|
355
|
+
break
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
case "boolean":
|
|
359
|
+
// Look for any boolean value in the map
|
|
360
|
+
for _, v := range valueMap {
|
|
361
|
+
if b, ok := v.(bool); ok {
|
|
362
|
+
value = b
|
|
363
|
+
break
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
default:
|
|
367
|
+
// For other types, extract single-key value
|
|
368
|
+
if len(valueMap) == 1 {
|
|
369
|
+
for _, v := range valueMap {
|
|
370
|
+
value = v
|
|
371
|
+
break
|
|
372
|
+
}
|
|
272
373
|
}
|
|
273
374
|
}
|
|
274
375
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "recursive-llm-ts",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.1.0",
|
|
4
4
|
"description": "TypeScript bridge for recursive-llm: Recursive Language Models for unbounded context processing with structured outputs",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|