@anvia/graph 0.0.0-bootstrap.0 → 1.0.4
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/LICENSE +21 -0
- package/README.md +120 -1
- package/dist/chunk-N3CKMOPE.js +85 -0
- package/dist/chunk-N3CKMOPE.js.map +1 -0
- package/dist/explore-9jm2c1mG.d.ts +277 -0
- package/dist/explore.d.ts +5 -0
- package/dist/explore.js +7 -0
- package/dist/explore.js.map +1 -0
- package/dist/index.d.ts +69 -0
- package/dist/index.js +672 -0
- package/dist/index.js.map +1 -0
- package/package.json +40 -5
- package/index.js +0 -1
package/dist/index.js
ADDED
|
@@ -0,0 +1,672 @@
|
|
|
1
|
+
import {
|
|
2
|
+
resolveGraphExploreOptions
|
|
3
|
+
} from "./chunk-N3CKMOPE.js";
|
|
4
|
+
|
|
5
|
+
// src/extract.ts
|
|
6
|
+
import { Usage } from "@anvia/core";
|
|
7
|
+
import { extract } from "@anvia/core/extractor";
|
|
8
|
+
import { z as z2 } from "zod";
|
|
9
|
+
|
|
10
|
+
// src/schema.ts
|
|
11
|
+
import { z } from "zod";
|
|
12
|
+
var graphReservedPropertyPrefix = "__anvia_";
|
|
13
|
+
function defineGraphSchema(options) {
|
|
14
|
+
validateGraphSchemaOptions(options);
|
|
15
|
+
return freezeGraphSchema(options, "graph-schema");
|
|
16
|
+
}
|
|
17
|
+
function validateGraphSchemaOptions(options) {
|
|
18
|
+
if (Object.keys(options.nodes).length === 0) {
|
|
19
|
+
throw new TypeError("A graph schema requires at least one node type.");
|
|
20
|
+
}
|
|
21
|
+
for (const [type, definition] of Object.entries(options.nodes)) {
|
|
22
|
+
assertGraphName(type, "node type");
|
|
23
|
+
assertDescription(definition.description, `node ${type}`);
|
|
24
|
+
if (!(definition.properties instanceof z.ZodObject)) {
|
|
25
|
+
throw new TypeError(`Properties for node ${type} must be a Zod object schema.`);
|
|
26
|
+
}
|
|
27
|
+
assertExactPropertySchema(definition.properties, `node ${type}`);
|
|
28
|
+
if (definition.identity.length === 0 || new Set(definition.identity).size !== definition.identity.length) {
|
|
29
|
+
throw new TypeError(`Node ${type} must declare unique identity properties.`);
|
|
30
|
+
}
|
|
31
|
+
for (const property of definition.identity) {
|
|
32
|
+
assertGraphPropertyName(property);
|
|
33
|
+
if (!(property in definition.properties.shape)) {
|
|
34
|
+
throw new TypeError(`Identity property ${property} is not declared by node ${type}.`);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
for (const property of Object.keys(definition.properties.shape)) {
|
|
38
|
+
assertGraphPropertyName(property);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
for (const [type, definition] of Object.entries(options.relationships)) {
|
|
42
|
+
assertGraphName(type, "relationship type");
|
|
43
|
+
assertDescription(definition.description, `relationship ${type}`);
|
|
44
|
+
if (!(definition.from in options.nodes) || !(definition.to in options.nodes)) {
|
|
45
|
+
throw new TypeError(`Relationship ${type} references an unknown endpoint type.`);
|
|
46
|
+
}
|
|
47
|
+
if (!(definition.properties instanceof z.ZodObject)) {
|
|
48
|
+
throw new TypeError(`Properties for relationship ${type} must be a Zod object schema.`);
|
|
49
|
+
}
|
|
50
|
+
assertExactPropertySchema(definition.properties, `relationship ${type}`);
|
|
51
|
+
const identity = definition.identity ?? [];
|
|
52
|
+
if (new Set(identity).size !== identity.length) {
|
|
53
|
+
throw new TypeError(`Relationship ${type} contains duplicate identity properties.`);
|
|
54
|
+
}
|
|
55
|
+
for (const property of Object.keys(definition.properties.shape)) {
|
|
56
|
+
assertGraphPropertyName(property);
|
|
57
|
+
}
|
|
58
|
+
for (const property of identity) {
|
|
59
|
+
if (!(property in definition.properties.shape)) {
|
|
60
|
+
throw new TypeError(
|
|
61
|
+
`Identity property ${property} is not declared by relationship ${type}.`
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
function freezeGraphSchema(options, kind) {
|
|
68
|
+
const nodes = Object.fromEntries(
|
|
69
|
+
Object.entries(options.nodes).map(([type, definition]) => [
|
|
70
|
+
type,
|
|
71
|
+
Object.freeze({ ...definition, identity: Object.freeze([...definition.identity]) })
|
|
72
|
+
])
|
|
73
|
+
);
|
|
74
|
+
const relationships = Object.fromEntries(
|
|
75
|
+
Object.entries(options.relationships).map(([type, definition]) => {
|
|
76
|
+
const copy = { ...definition };
|
|
77
|
+
if (definition.identity !== void 0) {
|
|
78
|
+
Object.assign(copy, { identity: Object.freeze([...definition.identity]) });
|
|
79
|
+
}
|
|
80
|
+
return [type, Object.freeze(copy)];
|
|
81
|
+
})
|
|
82
|
+
);
|
|
83
|
+
return Object.freeze({
|
|
84
|
+
nodes: Object.freeze(nodes),
|
|
85
|
+
relationships: Object.freeze(relationships),
|
|
86
|
+
kind
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
function parseGraphProperties(value, label) {
|
|
90
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
91
|
+
throw new TypeError(`${label} must be an object of graph property values.`);
|
|
92
|
+
}
|
|
93
|
+
const output = {};
|
|
94
|
+
for (const [key, item] of Object.entries(value)) {
|
|
95
|
+
assertGraphPropertyName(key);
|
|
96
|
+
output[key] = parseGraphPropertyValue(item, `${label}.${key}`);
|
|
97
|
+
}
|
|
98
|
+
return output;
|
|
99
|
+
}
|
|
100
|
+
function parseGraphPropertyValue(value, label) {
|
|
101
|
+
if (typeof value === "string" || typeof value === "boolean") return value;
|
|
102
|
+
if (typeof value === "number") {
|
|
103
|
+
if (!Number.isFinite(value)) throw new TypeError(`${label} must be finite.`);
|
|
104
|
+
if (Number.isInteger(value) && !Number.isSafeInteger(value)) {
|
|
105
|
+
throw new TypeError(`${label} must be a safe integer.`);
|
|
106
|
+
}
|
|
107
|
+
return value;
|
|
108
|
+
}
|
|
109
|
+
if (Array.isArray(value)) {
|
|
110
|
+
if (value.length === 0) return [];
|
|
111
|
+
const parsed = value.map((item, index) => parsePrimitive(item, `${label}[${index}]`));
|
|
112
|
+
const type = typeof parsed[0];
|
|
113
|
+
if (!parsed.every((item) => typeof item === type)) {
|
|
114
|
+
throw new TypeError(`${label} must be a homogeneous array.`);
|
|
115
|
+
}
|
|
116
|
+
return parsed;
|
|
117
|
+
}
|
|
118
|
+
throw new TypeError(`${label} is not a supported graph property value.`);
|
|
119
|
+
}
|
|
120
|
+
function assertGraphName(value, label) {
|
|
121
|
+
if (typeof value !== "string" || value.length === 0) {
|
|
122
|
+
throw new TypeError(`${label} must be a non-empty string.`);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
function assertGraphPropertyName(value) {
|
|
126
|
+
assertGraphName(value, "graph property name");
|
|
127
|
+
if (value.startsWith(graphReservedPropertyPrefix)) {
|
|
128
|
+
throw new TypeError(
|
|
129
|
+
`Graph property names beginning with ${graphReservedPropertyPrefix} are reserved.`
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
function parsePrimitive(value, label) {
|
|
134
|
+
const parsed = parseGraphPropertyValue(value, label);
|
|
135
|
+
if (typeof parsed === "string" || typeof parsed === "number" || typeof parsed === "boolean") {
|
|
136
|
+
return parsed;
|
|
137
|
+
}
|
|
138
|
+
throw new TypeError(`${label} must be a primitive property value.`);
|
|
139
|
+
}
|
|
140
|
+
function assertDescription(value, label) {
|
|
141
|
+
if (typeof value !== "string" || value.trim().length === 0) {
|
|
142
|
+
throw new TypeError(`${label} requires a non-empty description.`);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
function assertExactPropertySchema(schema, label) {
|
|
146
|
+
const definition = schema._zod.def;
|
|
147
|
+
if (!(definition.catchall instanceof z.ZodNever)) {
|
|
148
|
+
throw new TypeError(`${label} properties must use a strict Zod object schema.`);
|
|
149
|
+
}
|
|
150
|
+
for (const [property, propertySchema] of Object.entries(schema.shape)) {
|
|
151
|
+
assertNonEffectfulSchema(propertySchema, `${label} property ${property}`, /* @__PURE__ */ new Set());
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
function assertNonEffectfulSchema(value, label, visited) {
|
|
155
|
+
if (!(value instanceof z.ZodType)) throw new TypeError(`${label} must be a Zod schema.`);
|
|
156
|
+
if (visited.has(value)) return;
|
|
157
|
+
visited.add(value);
|
|
158
|
+
const definition = value._zod.def;
|
|
159
|
+
const type = definition.type;
|
|
160
|
+
if (definition.coerce === true || type === "default" || type === "prefault" || type === "catch" || type === "transform" || type === "pipe") {
|
|
161
|
+
throw new TypeError(
|
|
162
|
+
`${label} must not coerce, default, transform, preprocess, or catch values.`
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
const checks = definition.checks;
|
|
166
|
+
if (Array.isArray(checks) && checks.some((check) => {
|
|
167
|
+
if (typeof check !== "object" || check === null) return false;
|
|
168
|
+
return check._zod?.def?.check === "overwrite";
|
|
169
|
+
})) {
|
|
170
|
+
throw new TypeError(`${label} must not normalize or overwrite values.`);
|
|
171
|
+
}
|
|
172
|
+
for (const child of schemaChildren(definition)) assertNonEffectfulSchema(child, label, visited);
|
|
173
|
+
}
|
|
174
|
+
function schemaChildren(definition) {
|
|
175
|
+
const children = [];
|
|
176
|
+
for (const key of ["innerType", "element", "left", "right", "rest", "keyType", "valueType"]) {
|
|
177
|
+
const value = definition[key];
|
|
178
|
+
if (value instanceof z.ZodType) children.push(value);
|
|
179
|
+
}
|
|
180
|
+
for (const key of ["options", "items"]) {
|
|
181
|
+
const value = definition[key];
|
|
182
|
+
if (Array.isArray(value)) {
|
|
183
|
+
for (const item of value) if (item instanceof z.ZodType) children.push(item);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
return children;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// src/extract.ts
|
|
190
|
+
async function extractGraphFacts(options) {
|
|
191
|
+
assertConcurrency(options.concurrency);
|
|
192
|
+
throwIfAborted(options.abortSignal);
|
|
193
|
+
const ids = /* @__PURE__ */ new Set();
|
|
194
|
+
for (const chunk of options.chunks) {
|
|
195
|
+
if (ids.has(chunk.id)) throw new TypeError(`Duplicate graph chunk id: ${chunk.id}`);
|
|
196
|
+
ids.add(chunk.id);
|
|
197
|
+
if (typeof chunk.text !== "string") {
|
|
198
|
+
throw new TypeError(`Graph chunk ${chunk.id} text must be a string.`);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
const outputSchema = extractionSchema(options.schema);
|
|
202
|
+
const instructions = graphInstructions(options.schema, options.instructions);
|
|
203
|
+
const extractions = await mapConcurrent(
|
|
204
|
+
options.chunks,
|
|
205
|
+
options.concurrency ?? 1,
|
|
206
|
+
async (chunk) => {
|
|
207
|
+
throwIfAborted(options.abortSignal);
|
|
208
|
+
const result = await extract({
|
|
209
|
+
model: options.model,
|
|
210
|
+
text: chunk.text,
|
|
211
|
+
outputSchema,
|
|
212
|
+
instructions,
|
|
213
|
+
retries: options.retries,
|
|
214
|
+
abortSignal: options.abortSignal
|
|
215
|
+
});
|
|
216
|
+
return { chunkId: chunk.id, output: result.output, usage: result.usage };
|
|
217
|
+
}
|
|
218
|
+
);
|
|
219
|
+
return {
|
|
220
|
+
output: normalizeFacts(options.schema, extractions),
|
|
221
|
+
usage: extractions.reduce((usage, item) => Usage.add(usage, item.usage), Usage.empty())
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
function extractionSchema(schema) {
|
|
225
|
+
const entities = Object.entries(schema.nodes).map(
|
|
226
|
+
([type, definition]) => z2.object({ ref: z2.string().min(1), type: z2.literal(type), properties: definition.properties })
|
|
227
|
+
);
|
|
228
|
+
const relationships = Object.entries(schema.relationships).map(
|
|
229
|
+
([type, definition]) => z2.object({
|
|
230
|
+
type: z2.literal(type),
|
|
231
|
+
from: z2.string().min(1),
|
|
232
|
+
to: z2.string().min(1),
|
|
233
|
+
properties: definition.properties
|
|
234
|
+
})
|
|
235
|
+
);
|
|
236
|
+
return z2.object({
|
|
237
|
+
entities: z2.array(union(entities, z2.never())),
|
|
238
|
+
relationships: z2.array(union(relationships, z2.never()))
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
function union(items, empty) {
|
|
242
|
+
if (items.length === 0) return empty;
|
|
243
|
+
if (items.length === 1) return items[0];
|
|
244
|
+
return z2.union(items);
|
|
245
|
+
}
|
|
246
|
+
function graphInstructions(schema, extra) {
|
|
247
|
+
const nodes = Object.entries(schema.nodes).map(([type, definition]) => `- ${type}: ${definition.description}`).join("\n");
|
|
248
|
+
const relationships = Object.entries(schema.relationships).map(
|
|
249
|
+
([type, definition]) => `- ${type}: ${definition.from} -> ${definition.to}. ${definition.description}`
|
|
250
|
+
).join("\n");
|
|
251
|
+
return [
|
|
252
|
+
"Extract only facts directly supported by the source text.",
|
|
253
|
+
"Give every extracted entity a unique local ref and use those refs as relationship endpoints.",
|
|
254
|
+
"Do not invent entity or relationship types outside the declared schema.",
|
|
255
|
+
`Node types:
|
|
256
|
+
${nodes}`,
|
|
257
|
+
relationships.length === 0 ? "No relationship types are allowed." : `Relationship types:
|
|
258
|
+
${relationships}`,
|
|
259
|
+
extra
|
|
260
|
+
].filter((part) => part !== void 0 && part.length > 0).join("\n\n");
|
|
261
|
+
}
|
|
262
|
+
function normalizeFacts(schema, extractions) {
|
|
263
|
+
const entities = /* @__PURE__ */ new Map();
|
|
264
|
+
const relationships = /* @__PURE__ */ new Map();
|
|
265
|
+
const mentions = /* @__PURE__ */ new Map();
|
|
266
|
+
for (const extraction of extractions) {
|
|
267
|
+
const refs = /* @__PURE__ */ new Map();
|
|
268
|
+
for (const raw of extraction.output.entities) {
|
|
269
|
+
if (refs.has(raw.ref)) {
|
|
270
|
+
throw new TypeError(`Duplicate entity ref ${raw.ref} in chunk ${extraction.chunkId}.`);
|
|
271
|
+
}
|
|
272
|
+
const definition = schema.nodes[raw.type];
|
|
273
|
+
if (definition === void 0) throw new TypeError(`Unknown graph entity type: ${raw.type}`);
|
|
274
|
+
const properties2 = parseGraphProperties(
|
|
275
|
+
definition.properties.parse(raw.properties),
|
|
276
|
+
`entity ${raw.type}`
|
|
277
|
+
);
|
|
278
|
+
const identity = identityFromProperties(definition.identity, properties2, raw.type);
|
|
279
|
+
const key = `${raw.type}:${stableObject(identity)}`;
|
|
280
|
+
const current = entities.get(key);
|
|
281
|
+
const next = {
|
|
282
|
+
key,
|
|
283
|
+
type: raw.type,
|
|
284
|
+
identity,
|
|
285
|
+
properties: properties2,
|
|
286
|
+
sourceChunkIds: [extraction.chunkId]
|
|
287
|
+
};
|
|
288
|
+
if (current === void 0) {
|
|
289
|
+
entities.set(key, next);
|
|
290
|
+
refs.set(raw.ref, next);
|
|
291
|
+
} else {
|
|
292
|
+
assertSameProperties(
|
|
293
|
+
current.properties,
|
|
294
|
+
properties2,
|
|
295
|
+
key,
|
|
296
|
+
current.sourceChunkIds,
|
|
297
|
+
extraction.chunkId
|
|
298
|
+
);
|
|
299
|
+
const merged = {
|
|
300
|
+
...current,
|
|
301
|
+
sourceChunkIds: [...current.sourceChunkIds, extraction.chunkId]
|
|
302
|
+
};
|
|
303
|
+
entities.set(key, merged);
|
|
304
|
+
refs.set(raw.ref, merged);
|
|
305
|
+
}
|
|
306
|
+
mentions.set(`${extraction.chunkId}\0${key}`, {
|
|
307
|
+
chunkId: extraction.chunkId,
|
|
308
|
+
entityKey: key
|
|
309
|
+
});
|
|
310
|
+
}
|
|
311
|
+
for (const raw of extraction.output.relationships) {
|
|
312
|
+
const definition = schema.relationships[raw.type];
|
|
313
|
+
if (definition === void 0) {
|
|
314
|
+
throw new TypeError(`Unknown graph relationship type: ${raw.type}`);
|
|
315
|
+
}
|
|
316
|
+
const from = refs.get(raw.from);
|
|
317
|
+
const to = refs.get(raw.to);
|
|
318
|
+
if (from === void 0 || to === void 0) {
|
|
319
|
+
throw new TypeError(
|
|
320
|
+
`Relationship ${raw.type} in chunk ${extraction.chunkId} references an unknown entity ref.`
|
|
321
|
+
);
|
|
322
|
+
}
|
|
323
|
+
if (from.type !== definition.from || to.type !== definition.to) {
|
|
324
|
+
throw new TypeError(
|
|
325
|
+
`Relationship ${raw.type} has invalid endpoint types ${from.type} -> ${to.type}.`
|
|
326
|
+
);
|
|
327
|
+
}
|
|
328
|
+
const properties2 = parseGraphProperties(
|
|
329
|
+
definition.properties.parse(raw.properties),
|
|
330
|
+
`relationship ${raw.type}`
|
|
331
|
+
);
|
|
332
|
+
const identity = identityFromProperties(definition.identity ?? [], properties2, raw.type);
|
|
333
|
+
const key = `${raw.type}:${from.key}->${to.key}:${stableObject(identity)}`;
|
|
334
|
+
const current = relationships.get(key);
|
|
335
|
+
const next = {
|
|
336
|
+
key,
|
|
337
|
+
type: raw.type,
|
|
338
|
+
from: from.key,
|
|
339
|
+
to: to.key,
|
|
340
|
+
properties: properties2,
|
|
341
|
+
sourceChunkIds: [extraction.chunkId]
|
|
342
|
+
};
|
|
343
|
+
if (current === void 0) relationships.set(key, next);
|
|
344
|
+
else {
|
|
345
|
+
assertSameProperties(
|
|
346
|
+
current.properties,
|
|
347
|
+
properties2,
|
|
348
|
+
key,
|
|
349
|
+
current.sourceChunkIds,
|
|
350
|
+
extraction.chunkId
|
|
351
|
+
);
|
|
352
|
+
relationships.set(key, {
|
|
353
|
+
...current,
|
|
354
|
+
sourceChunkIds: [...current.sourceChunkIds, extraction.chunkId]
|
|
355
|
+
});
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
return {
|
|
360
|
+
entities: [...entities.values()],
|
|
361
|
+
relationships: [...relationships.values()],
|
|
362
|
+
mentions: [...mentions.values()]
|
|
363
|
+
};
|
|
364
|
+
}
|
|
365
|
+
function identityFromProperties(keys, properties2, type) {
|
|
366
|
+
const identity = {};
|
|
367
|
+
for (const key of keys) {
|
|
368
|
+
const value = properties2[key];
|
|
369
|
+
if (value === void 0 || !(typeof value === "string" || typeof value === "number" || typeof value === "boolean")) {
|
|
370
|
+
throw new TypeError(`Identity property ${key} for ${type} must be a primitive value.`);
|
|
371
|
+
}
|
|
372
|
+
identity[key] = value;
|
|
373
|
+
}
|
|
374
|
+
return identity;
|
|
375
|
+
}
|
|
376
|
+
function stableObject(value) {
|
|
377
|
+
return JSON.stringify(
|
|
378
|
+
Object.fromEntries(Object.entries(value).sort(([left], [right]) => left.localeCompare(right)))
|
|
379
|
+
);
|
|
380
|
+
}
|
|
381
|
+
function assertSameProperties(left, right, key, sourceChunkIds, nextChunkId) {
|
|
382
|
+
if (stableObject(left) !== stableObject(right)) {
|
|
383
|
+
throw new GraphFactConflictError(key, [...sourceChunkIds, nextChunkId]);
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
var GraphFactConflictError = class extends Error {
|
|
387
|
+
constructor(factKey, sourceChunkIds) {
|
|
388
|
+
super(
|
|
389
|
+
`Conflicting graph fact ${factKey} was extracted from chunks ${sourceChunkIds.join(", ")}.`
|
|
390
|
+
);
|
|
391
|
+
this.factKey = factKey;
|
|
392
|
+
this.sourceChunkIds = sourceChunkIds;
|
|
393
|
+
this.name = "GraphFactConflictError";
|
|
394
|
+
}
|
|
395
|
+
factKey;
|
|
396
|
+
sourceChunkIds;
|
|
397
|
+
};
|
|
398
|
+
function assertConcurrency(value) {
|
|
399
|
+
if (value !== void 0 && (!Number.isSafeInteger(value) || value < 1)) {
|
|
400
|
+
throw new RangeError("Graph extraction concurrency must be a positive safe integer.");
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
function throwIfAborted(signal) {
|
|
404
|
+
if (signal?.aborted !== true) return;
|
|
405
|
+
if (signal.reason instanceof Error) throw signal.reason;
|
|
406
|
+
throw new DOMException("The operation was aborted.", "AbortError");
|
|
407
|
+
}
|
|
408
|
+
async function mapConcurrent(values, concurrency, run) {
|
|
409
|
+
const output = Array(values.length);
|
|
410
|
+
let cursor = 0;
|
|
411
|
+
await Promise.all(
|
|
412
|
+
Array.from({ length: Math.min(concurrency, values.length) }, async () => {
|
|
413
|
+
for (; ; ) {
|
|
414
|
+
const index = cursor;
|
|
415
|
+
cursor += 1;
|
|
416
|
+
if (index >= values.length) return;
|
|
417
|
+
output[index] = await run(values[index]);
|
|
418
|
+
}
|
|
419
|
+
})
|
|
420
|
+
);
|
|
421
|
+
return output;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
// src/ingest.ts
|
|
425
|
+
import {
|
|
426
|
+
chunkTextDocuments
|
|
427
|
+
} from "@anvia/core/documents";
|
|
428
|
+
import { embedDocuments } from "@anvia/core/embeddings";
|
|
429
|
+
async function ingestGraphText(options) {
|
|
430
|
+
return ingestGraphDocuments({
|
|
431
|
+
graph: options.graph,
|
|
432
|
+
documents: [options.document],
|
|
433
|
+
extractionModel: options.extractionModel,
|
|
434
|
+
embeddingModel: options.embeddingModel,
|
|
435
|
+
chunking: options.chunking,
|
|
436
|
+
entityText: options.entityText,
|
|
437
|
+
instructions: options.instructions,
|
|
438
|
+
retries: options.retries,
|
|
439
|
+
concurrency: options.concurrency,
|
|
440
|
+
abortSignal: options.abortSignal,
|
|
441
|
+
conflict: options.conflict,
|
|
442
|
+
orphanEntities: options.orphanEntities
|
|
443
|
+
});
|
|
444
|
+
}
|
|
445
|
+
async function ingestGraphDocuments(options) {
|
|
446
|
+
const prepared = await prepareGraphDocuments(options);
|
|
447
|
+
const write = await options.graph.replaceDocuments({
|
|
448
|
+
documents: prepared.documents,
|
|
449
|
+
chunks: prepared.chunks,
|
|
450
|
+
entities: prepared.entities,
|
|
451
|
+
relationships: prepared.relationships,
|
|
452
|
+
mentions: prepared.mentions,
|
|
453
|
+
conflict: options.conflict,
|
|
454
|
+
orphanEntities: options.orphanEntities,
|
|
455
|
+
abortSignal: options.abortSignal
|
|
456
|
+
});
|
|
457
|
+
return { ...prepared, write };
|
|
458
|
+
}
|
|
459
|
+
async function prepareGraphDocuments(options) {
|
|
460
|
+
validatePortableMetadata(options.documents);
|
|
461
|
+
const chunks = chunkTextDocuments({
|
|
462
|
+
documents: options.documents,
|
|
463
|
+
chunking: options.chunking
|
|
464
|
+
});
|
|
465
|
+
const graphChunks = chunks.map((chunk) => graphChunk(chunk));
|
|
466
|
+
const [facts, embeddedChunks] = await Promise.all([
|
|
467
|
+
extractGraphFacts({
|
|
468
|
+
model: options.extractionModel,
|
|
469
|
+
schema: options.graph.schema,
|
|
470
|
+
chunks: graphChunks,
|
|
471
|
+
instructions: options.instructions,
|
|
472
|
+
retries: options.retries,
|
|
473
|
+
concurrency: options.concurrency,
|
|
474
|
+
abortSignal: options.abortSignal
|
|
475
|
+
}),
|
|
476
|
+
embedDocuments({
|
|
477
|
+
model: options.embeddingModel,
|
|
478
|
+
documents: graphChunks,
|
|
479
|
+
id: (chunk) => chunk.id,
|
|
480
|
+
content: (chunk) => chunk.text,
|
|
481
|
+
metadata: (chunk) => chunk.metadata,
|
|
482
|
+
retries: options.retries,
|
|
483
|
+
concurrency: options.concurrency,
|
|
484
|
+
abortSignal: options.abortSignal
|
|
485
|
+
})
|
|
486
|
+
]);
|
|
487
|
+
const embeddedEntities = await embedDocuments({
|
|
488
|
+
model: options.embeddingModel,
|
|
489
|
+
documents: [...facts.output.entities],
|
|
490
|
+
id: (entity) => entity.key,
|
|
491
|
+
content: options.entityText ?? defaultGraphEntityText,
|
|
492
|
+
retries: options.retries,
|
|
493
|
+
concurrency: options.concurrency,
|
|
494
|
+
abortSignal: options.abortSignal
|
|
495
|
+
});
|
|
496
|
+
const vectorDocuments = groupVectorDocuments(options.documents, embeddedChunks.documents);
|
|
497
|
+
return {
|
|
498
|
+
documents: options.documents.map((document) => graphDocument(document)),
|
|
499
|
+
chunks: embeddedChunks.documents,
|
|
500
|
+
vectorDocuments,
|
|
501
|
+
entities: embeddedEntities.documents,
|
|
502
|
+
relationships: facts.output.relationships,
|
|
503
|
+
mentions: facts.output.mentions,
|
|
504
|
+
usage: facts.usage
|
|
505
|
+
};
|
|
506
|
+
}
|
|
507
|
+
function validatePortableMetadata(documents) {
|
|
508
|
+
for (const document of documents) {
|
|
509
|
+
if (document.metadata === void 0) {
|
|
510
|
+
continue;
|
|
511
|
+
}
|
|
512
|
+
const metadata = parseGraphProperties(
|
|
513
|
+
document.metadata,
|
|
514
|
+
`Text document ${document.id} metadata`
|
|
515
|
+
);
|
|
516
|
+
for (const [key, value] of Object.entries(metadata)) {
|
|
517
|
+
if (Array.isArray(value)) {
|
|
518
|
+
throw new TypeError(
|
|
519
|
+
`Text document ${document.id} metadata.${key} must be a portable primitive value`
|
|
520
|
+
);
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
function groupVectorDocuments(documents, chunks) {
|
|
526
|
+
const chunksByDocument = /* @__PURE__ */ new Map();
|
|
527
|
+
for (const chunk of chunks) {
|
|
528
|
+
const documentChunks = chunksByDocument.get(chunk.document.documentId);
|
|
529
|
+
if (documentChunks === void 0) {
|
|
530
|
+
chunksByDocument.set(chunk.document.documentId, [chunk]);
|
|
531
|
+
} else {
|
|
532
|
+
documentChunks.push(chunk);
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
return documents.map((document) => {
|
|
536
|
+
const documentChunks = chunksByDocument.get(document.id);
|
|
537
|
+
if (documentChunks === void 0) {
|
|
538
|
+
throw new TypeError(`Text document ${document.id} has no embedded graph chunks`);
|
|
539
|
+
}
|
|
540
|
+
const embeddedDocument = {
|
|
541
|
+
id: document.id,
|
|
542
|
+
document,
|
|
543
|
+
embeddings: documentChunks.flatMap((chunk) => chunk.embeddings)
|
|
544
|
+
};
|
|
545
|
+
if (document.metadata !== void 0) {
|
|
546
|
+
embeddedDocument.metadata = document.metadata;
|
|
547
|
+
}
|
|
548
|
+
return embeddedDocument;
|
|
549
|
+
});
|
|
550
|
+
}
|
|
551
|
+
function graphDocument(document) {
|
|
552
|
+
if (document.metadata === void 0) {
|
|
553
|
+
return { id: document.id };
|
|
554
|
+
}
|
|
555
|
+
return { id: document.id, properties: document.metadata };
|
|
556
|
+
}
|
|
557
|
+
function graphChunk(chunk) {
|
|
558
|
+
if (chunk.metadata === void 0) {
|
|
559
|
+
return {
|
|
560
|
+
id: chunk.id,
|
|
561
|
+
documentId: chunk.documentId,
|
|
562
|
+
index: chunk.index,
|
|
563
|
+
text: chunk.text
|
|
564
|
+
};
|
|
565
|
+
}
|
|
566
|
+
return {
|
|
567
|
+
id: chunk.id,
|
|
568
|
+
documentId: chunk.documentId,
|
|
569
|
+
index: chunk.index,
|
|
570
|
+
text: chunk.text,
|
|
571
|
+
metadata: chunk.metadata
|
|
572
|
+
};
|
|
573
|
+
}
|
|
574
|
+
function defaultGraphEntityText(entity) {
|
|
575
|
+
const properties2 = Object.entries(entity.properties).sort(([left], [right]) => left.localeCompare(right)).map(([key, value]) => `${key}: ${formatGraphProperty(value)}`).join("\n");
|
|
576
|
+
return properties2.length === 0 ? entity.type : `${entity.type}
|
|
577
|
+
${properties2}`;
|
|
578
|
+
}
|
|
579
|
+
function formatGraphProperty(value) {
|
|
580
|
+
if (Array.isArray(value)) {
|
|
581
|
+
return value.join(", ");
|
|
582
|
+
}
|
|
583
|
+
return String(value);
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
// src/tool.ts
|
|
587
|
+
import { createTool } from "@anvia/core/tool";
|
|
588
|
+
import { z as z3 } from "zod";
|
|
589
|
+
var primitive = z3.union([z3.string(), z3.number().finite(), z3.boolean()]);
|
|
590
|
+
var propertyValue = z3.union([
|
|
591
|
+
primitive,
|
|
592
|
+
z3.array(z3.string()),
|
|
593
|
+
z3.array(z3.number().finite()),
|
|
594
|
+
z3.array(z3.boolean())
|
|
595
|
+
]);
|
|
596
|
+
var properties = z3.record(z3.string(), propertyValue);
|
|
597
|
+
var contextSchema = z3.object({
|
|
598
|
+
seeds: z3.array(
|
|
599
|
+
z3.object({
|
|
600
|
+
key: z3.string(),
|
|
601
|
+
type: z3.string(),
|
|
602
|
+
score: z3.number().finite(),
|
|
603
|
+
properties,
|
|
604
|
+
sourceChunkIds: z3.array(z3.string())
|
|
605
|
+
})
|
|
606
|
+
),
|
|
607
|
+
nodes: z3.array(
|
|
608
|
+
z3.object({
|
|
609
|
+
key: z3.string(),
|
|
610
|
+
type: z3.string(),
|
|
611
|
+
identity: z3.record(z3.string(), primitive),
|
|
612
|
+
properties,
|
|
613
|
+
sourceChunkIds: z3.array(z3.string())
|
|
614
|
+
})
|
|
615
|
+
),
|
|
616
|
+
relationships: z3.array(
|
|
617
|
+
z3.object({
|
|
618
|
+
key: z3.string(),
|
|
619
|
+
type: z3.string(),
|
|
620
|
+
from: z3.string(),
|
|
621
|
+
to: z3.string(),
|
|
622
|
+
properties,
|
|
623
|
+
sourceChunkIds: z3.array(z3.string())
|
|
624
|
+
})
|
|
625
|
+
),
|
|
626
|
+
evidence: z3.array(
|
|
627
|
+
z3.object({
|
|
628
|
+
chunkId: z3.string(),
|
|
629
|
+
documentId: z3.string(),
|
|
630
|
+
index: z3.number().int(),
|
|
631
|
+
text: z3.string(),
|
|
632
|
+
metadata: properties
|
|
633
|
+
})
|
|
634
|
+
)
|
|
635
|
+
});
|
|
636
|
+
function createGraphSearchTool(options) {
|
|
637
|
+
return createTool({
|
|
638
|
+
name: options.name,
|
|
639
|
+
description: options.description,
|
|
640
|
+
inputSchema: z3.object({
|
|
641
|
+
query: z3.string().min(1).describe("The question or concept to search for in the graph.")
|
|
642
|
+
}),
|
|
643
|
+
outputSchema: contextSchema,
|
|
644
|
+
execute: async ({ query }, context) => await options.graph.retrieve({
|
|
645
|
+
model: options.model,
|
|
646
|
+
query,
|
|
647
|
+
search: options.search,
|
|
648
|
+
traversal: options.traversal,
|
|
649
|
+
evidence: options.evidence,
|
|
650
|
+
retries: options.retries,
|
|
651
|
+
abortSignal: context.abortSignal
|
|
652
|
+
})
|
|
653
|
+
});
|
|
654
|
+
}
|
|
655
|
+
export {
|
|
656
|
+
GraphFactConflictError,
|
|
657
|
+
assertGraphName,
|
|
658
|
+
assertGraphPropertyName,
|
|
659
|
+
createGraphSearchTool,
|
|
660
|
+
defineGraphSchema,
|
|
661
|
+
extractGraphFacts,
|
|
662
|
+
freezeGraphSchema,
|
|
663
|
+
graphReservedPropertyPrefix,
|
|
664
|
+
ingestGraphDocuments,
|
|
665
|
+
ingestGraphText,
|
|
666
|
+
parseGraphProperties,
|
|
667
|
+
parseGraphPropertyValue,
|
|
668
|
+
prepareGraphDocuments,
|
|
669
|
+
resolveGraphExploreOptions,
|
|
670
|
+
validateGraphSchemaOptions
|
|
671
|
+
};
|
|
672
|
+
//# sourceMappingURL=index.js.map
|