@zenera/rag 1.1.3 → 1.1.5
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 +68 -9
- package/dist/command.js +290 -32
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/schema/build.d.ts +2 -0
- package/dist/schema/build.js +62 -34
- package/dist/schema/entities.d.ts +2 -0
- package/dist/schema/entities.js +2 -1
- package/dist/schema/files.d.ts +17 -2
- package/dist/schema/files.js +36 -7
- package/dist/schema/lookup.d.ts +44 -0
- package/dist/schema/lookup.js +83 -0
- package/dist/schema/match.d.ts +36 -0
- package/dist/schema/match.js +85 -0
- package/dist/schema/progress.d.ts +26 -0
- package/dist/schema/progress.js +316 -0
- package/dist/schema/spec.d.ts +5 -0
- package/dist/schema/spec.js +34 -10
- package/dist/schema/subgraph.d.ts +6 -0
- package/dist/schema/subgraph.js +27 -0
- package/dist/schema/tools.js +140 -28
- package/package.json +3 -3
package/dist/schema/tools.js
CHANGED
|
@@ -2,22 +2,32 @@ import { tool } from '@zenera/neo';
|
|
|
2
2
|
import { FORMATS, isFormat, present } from "../present.js";
|
|
3
3
|
import { isEmpty, parseQuery, QueryError } from "../query.js";
|
|
4
4
|
import { toTypeScript } from "./hydrate.js";
|
|
5
|
+
import { fields, grepNodes, listNodes, propertyCount } from "./lookup.js";
|
|
6
|
+
import { loose, matcher, PatternError } from "./match.js";
|
|
5
7
|
import { stitch } from "./subgraph.js";
|
|
6
8
|
// ---------------------------------------------------------------------------
|
|
7
9
|
// The same index, given to an agent
|
|
8
10
|
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
//
|
|
13
|
-
//
|
|
14
|
-
//
|
|
11
|
+
// Five tools over one engine, and only one of them ranks anything. `search_api`
|
|
12
|
+
// is the way in when the question is vague; the other four are exact, because
|
|
13
|
+
// a model that has been told "no results" by a vector search has learned
|
|
14
|
+
// nothing — a ranking returns the top of a list, so an empty answer and an
|
|
15
|
+
// absent thing look identical.
|
|
16
|
+
//
|
|
17
|
+
// `find_types_with_property` is for the moment after the compiler says
|
|
18
|
+
// `'password' does not exist in type 'PublicUserProfile'`. At that point the
|
|
19
|
+
// model does not need to be reminded what a password is — it needs the list of
|
|
20
|
+
// types that have one. `grep_api` is the same instinct widened: every literal
|
|
21
|
+
// occurrence, counted in full, so "it is not there" can actually be concluded.
|
|
15
22
|
// ---------------------------------------------------------------------------
|
|
16
23
|
const GROUP = 'schema';
|
|
17
24
|
/** Kept small on purpose: a tool result is prompt, and the model asked for one thing. */
|
|
18
25
|
const DEFAULT_LIMIT = 4;
|
|
19
26
|
const DEFAULT_MAX_NODES = 60;
|
|
20
27
|
const MAX_CANDIDATES = 25;
|
|
28
|
+
/** A listing is lines rather than subgraphs, so it can afford more of them. */
|
|
29
|
+
const DEFAULT_ROWS = 50;
|
|
30
|
+
const MAX_ROWS = 200;
|
|
21
31
|
export function schemaTools(index, options = {}) {
|
|
22
32
|
const fallback = options.format ?? 'text';
|
|
23
33
|
const docs = options.docs ?? true;
|
|
@@ -178,43 +188,145 @@ export function schemaTools(index, options = {}) {
|
|
|
178
188
|
: { found: candidates.length, candidates: candidates.slice(0, MAX_CANDIDATES) };
|
|
179
189
|
},
|
|
180
190
|
});
|
|
181
|
-
const
|
|
182
|
-
name: '
|
|
191
|
+
const listApi = tool({
|
|
192
|
+
name: 'list_api',
|
|
183
193
|
group: GROUP,
|
|
184
|
-
description: 'Lists operations by
|
|
185
|
-
'
|
|
194
|
+
description: 'Lists operations, schemas or fields by name, with no searching and no ranking. ' +
|
|
195
|
+
'The answer is complete: every match is counted, so `found` tells you how many ' +
|
|
196
|
+
'exist even when the list was shortened. Use it to see the shape of the API ' +
|
|
197
|
+
'before deciding what to ask for, and to settle whether something exists at all — ' +
|
|
198
|
+
'search can only ever return its best guesses, so it cannot answer that.',
|
|
186
199
|
parameters: {
|
|
187
200
|
type: 'object',
|
|
188
201
|
properties: {
|
|
189
|
-
|
|
202
|
+
kind: {
|
|
203
|
+
type: 'string',
|
|
204
|
+
enum: ['methods', 'types', 'properties'],
|
|
205
|
+
description: 'What to list. Default methods.',
|
|
206
|
+
},
|
|
207
|
+
name: {
|
|
208
|
+
type: 'string',
|
|
209
|
+
description: 'Match the name. A plain word matches anywhere in it; use * and ? ' +
|
|
210
|
+
'to match the whole name, e.g. "*Password*".',
|
|
211
|
+
},
|
|
212
|
+
path: { type: 'string', description: 'Match the route, e.g. "/users*".' },
|
|
190
213
|
method_type: {
|
|
191
214
|
type: 'string',
|
|
192
215
|
enum: ['read_only', 'read_write', 'any'],
|
|
193
216
|
},
|
|
217
|
+
direction: { type: 'string', enum: ['input', 'output', 'any'] },
|
|
218
|
+
limit: {
|
|
219
|
+
type: 'integer',
|
|
220
|
+
description: `Rows to return. Default ${DEFAULT_ROWS}.`,
|
|
221
|
+
},
|
|
194
222
|
},
|
|
195
223
|
additionalProperties: false,
|
|
196
224
|
},
|
|
197
|
-
execute: async ({
|
|
198
|
-
const
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
225
|
+
execute: async ({ kind, name, path, method_type, direction, limit }) => {
|
|
226
|
+
const subject = SUBJECTS[kind ?? 'methods'];
|
|
227
|
+
if (!subject) {
|
|
228
|
+
return { error: `cannot list "${kind}"`, hint: 'kind is methods, types or fields' };
|
|
229
|
+
}
|
|
230
|
+
let result;
|
|
231
|
+
try {
|
|
232
|
+
result = listNodes(index.graph, {
|
|
233
|
+
kind: subject,
|
|
234
|
+
name: name ? [loose(name)] : undefined,
|
|
235
|
+
path: path ? [loose(path)] : undefined,
|
|
236
|
+
methodType: enumerated(method_type),
|
|
237
|
+
direction: enumerated(direction),
|
|
238
|
+
limit: Math.min(limit ?? DEFAULT_ROWS, MAX_ROWS),
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
catch (err) {
|
|
242
|
+
return { error: err instanceof PatternError ? err.message : String(err) };
|
|
243
|
+
}
|
|
244
|
+
return {
|
|
245
|
+
found: result.found,
|
|
246
|
+
truncated: result.truncated,
|
|
247
|
+
[PLURALS[subject]]: result.rows.map((r) => line(index.graph, subject, r)),
|
|
248
|
+
};
|
|
213
249
|
},
|
|
214
250
|
});
|
|
215
|
-
|
|
251
|
+
const grepApi = tool({
|
|
252
|
+
name: 'grep_api',
|
|
253
|
+
group: GROUP,
|
|
254
|
+
description: 'Finds every literal occurrence of a string across the whole API description — ' +
|
|
255
|
+
'operations, schemas and fields alike. No embeddings and no ranking, so ' +
|
|
256
|
+
'nothing is missed for being an unusual word or an odd spelling. This is the ' +
|
|
257
|
+
'tool for "does X exist anywhere", and for checking that a search which ' +
|
|
258
|
+
'returned nothing really means there is nothing.',
|
|
259
|
+
parameters: {
|
|
260
|
+
type: 'object',
|
|
261
|
+
properties: {
|
|
262
|
+
pattern: {
|
|
263
|
+
type: 'string',
|
|
264
|
+
description: 'The text to find. Matched anywhere, ignoring case.',
|
|
265
|
+
},
|
|
266
|
+
regex: {
|
|
267
|
+
type: 'boolean',
|
|
268
|
+
description: 'Read the pattern as a regular expression instead.',
|
|
269
|
+
},
|
|
270
|
+
kind: { type: 'string', enum: ['method', 'type', 'property'] },
|
|
271
|
+
limit: {
|
|
272
|
+
type: 'integer',
|
|
273
|
+
description: `Matches to return. Default ${DEFAULT_ROWS}. \`found\` always counts them all.`,
|
|
274
|
+
},
|
|
275
|
+
},
|
|
276
|
+
required: ['pattern'],
|
|
277
|
+
additionalProperties: false,
|
|
278
|
+
},
|
|
279
|
+
execute: async ({ pattern, regex, kind, limit }) => {
|
|
280
|
+
let result;
|
|
281
|
+
try {
|
|
282
|
+
result = grepNodes(index.graph, matcher(pattern, { regex }), {
|
|
283
|
+
kinds: kind ? [kind] : undefined,
|
|
284
|
+
limit: Math.min(limit ?? DEFAULT_ROWS, MAX_ROWS),
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
catch (err) {
|
|
288
|
+
return { error: err instanceof PatternError ? err.message : String(err) };
|
|
289
|
+
}
|
|
290
|
+
if (result.found === 0) {
|
|
291
|
+
return {
|
|
292
|
+
found: 0,
|
|
293
|
+
hint: 'nothing in the description contains it — it is not there under this name',
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
return {
|
|
297
|
+
found: result.found,
|
|
298
|
+
truncated: result.truncated,
|
|
299
|
+
matches: result.matches.map((m) => ({ id: m.id, text: m.text })),
|
|
300
|
+
};
|
|
301
|
+
},
|
|
302
|
+
});
|
|
303
|
+
return [searchApi, describeTypes, findTypesWithProperty, listApi, grepApi];
|
|
216
304
|
}
|
|
217
305
|
// ---------------------------------------------------------------------------
|
|
306
|
+
const SUBJECTS = {
|
|
307
|
+
methods: 'method',
|
|
308
|
+
types: 'type',
|
|
309
|
+
properties: 'property',
|
|
310
|
+
fields: 'property',
|
|
311
|
+
};
|
|
312
|
+
const PLURALS = {
|
|
313
|
+
method: 'methods',
|
|
314
|
+
type: 'types',
|
|
315
|
+
property: 'properties',
|
|
316
|
+
};
|
|
317
|
+
/** One row, as the line a model reads rather than an object it has to walk. */
|
|
318
|
+
function line(graph, kind, row) {
|
|
319
|
+
if (kind === 'method') {
|
|
320
|
+
return `${row.httpMethod} ${row.path} ${row.name}${row.doc ? ` — ${row.doc}` : ''}`;
|
|
321
|
+
}
|
|
322
|
+
if (kind === 'type') {
|
|
323
|
+
const side = row.direction === 'none' ? '' : ` (${row.direction})`;
|
|
324
|
+
return `${row.name}${side} ${fields(propertyCount(graph, row.id))}${row.doc ? ` — ${row.doc}` : ''}`;
|
|
325
|
+
}
|
|
326
|
+
const owner = row.parent ? `${row.parent}.` : '';
|
|
327
|
+
return `${owner}${row.name}${row.required ? '' : '?'}: ${row.signature || 'unknown'}`;
|
|
328
|
+
}
|
|
329
|
+
const enumerated = (value) => value && value !== 'any' ? value : undefined;
|
|
218
330
|
function list(description) {
|
|
219
331
|
return { type: 'array', items: { type: 'string' }, description };
|
|
220
332
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zenera/rag",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.5",
|
|
4
4
|
"description": "Retrieval over API descriptions: openapi/swagger documents as a searchable graph.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"agents",
|
|
@@ -53,7 +53,7 @@
|
|
|
53
53
|
"@apidevtools/swagger-parser": "^12.0.0",
|
|
54
54
|
"@lancedb/lancedb": "^0.38.0",
|
|
55
55
|
"graphology": "^0.26.0",
|
|
56
|
-
"@zenera/cli": "^1.1.
|
|
57
|
-
"@zenera/neo": "^1.1.
|
|
56
|
+
"@zenera/cli": "^1.1.5",
|
|
57
|
+
"@zenera/neo": "^1.1.5"
|
|
58
58
|
}
|
|
59
59
|
}
|