@opengeni/documents 0.8.28-canary.1 → 0.8.28-canary.2
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/dist/index.d.ts +3 -9
- package/dist/index.js +76 -189
- package/dist/index.js.map +1 -1
- package/dist/knowledge-index.d.ts +11 -0
- package/package.json +5 -5
- package/src/index.ts +52 -245
- package/src/knowledge-index.ts +44 -0
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
export { knowledgeIndexChunks } from "./knowledge-index.js";
|
|
2
|
+
export type { KnowledgeIndexChunk } from "./knowledge-index.js";
|
|
1
3
|
import type { Settings } from "@opengeni/config";
|
|
2
4
|
import { type AddDocumentRequest, CreateDocumentBaseRequest, Document, DocumentAuthorityKind, DocumentBase, DocumentCurationStatus, DocumentSearchMode, DocumentSearchResult, DocumentStatus, DocumentVisibility, FileAsset, KnowledgeBrowseResponse, KnowledgeRecord, KnowledgeSearchResponse, KnowledgeSourceKind, ListIndexedDocumentsResponse, type ReclassifyDocumentAuthorityRequest, type RunDocumentDefaultCollectionBackfillRequest } from "@opengeni/contracts";
|
|
3
5
|
import { type Database } from "@opengeni/db";
|
|
@@ -201,14 +203,6 @@ export type EffectiveKnowledgeBrowseInput = {
|
|
|
201
203
|
limit?: number | undefined;
|
|
202
204
|
agentAuthority?: AgentDocumentAuthorityContext | undefined;
|
|
203
205
|
};
|
|
204
|
-
export type DocumentIndexHooks = {
|
|
205
|
-
beforeEmbed?: (input: {
|
|
206
|
-
accountId: string;
|
|
207
|
-
workspaceId: string;
|
|
208
|
-
documentId: string;
|
|
209
|
-
chunkCount: number;
|
|
210
|
-
}) => Promise<void>;
|
|
211
|
-
};
|
|
212
206
|
export declare class LiteParseDocumentParser implements DocumentParser {
|
|
213
207
|
readonly name = "liteparse";
|
|
214
208
|
private parseQueue;
|
|
@@ -536,7 +530,7 @@ export declare function getDocumentOriginalFile(db: Database, input: {
|
|
|
536
530
|
*/
|
|
537
531
|
export declare function getDocumentForIndexing(db: Database, workspaceId: string, documentId: string): Promise<Document | null>;
|
|
538
532
|
export declare function queueDocumentForReindex(db: Database, workspaceId: string, documentId: string, access?: DocumentAccessFilter, organizationAuthorityGranted?: boolean): Promise<Document>;
|
|
539
|
-
export declare function indexDocumentNow(db: Database, objectStorage: ObjectStorage, workspaceId: string, documentId: string, services?: DocumentServices,
|
|
533
|
+
export declare function indexDocumentNow(db: Database, objectStorage: ObjectStorage, workspaceId: string, documentId: string, services?: DocumentServices, access?: DocumentAccessFilter): Promise<Document>;
|
|
540
534
|
export declare function searchDocuments(db: Database, input: DocumentSearchInput, services?: Pick<DocumentServices, "embedder">): Promise<DocumentSearchResult[]>;
|
|
541
535
|
export type DocumentCandidateRelevanceFloor = {
|
|
542
536
|
vectorScore: number;
|
package/dist/index.js
CHANGED
|
@@ -1,3 +1,34 @@
|
|
|
1
|
+
// src/knowledge-index.ts
|
|
2
|
+
function* knowledgeIndexChunks(entry) {
|
|
3
|
+
const field = entry.content.length ? "content" : "title";
|
|
4
|
+
const text = field === "content" ? entry.content : entry.title;
|
|
5
|
+
const size = 1200;
|
|
6
|
+
const overlap = 160;
|
|
7
|
+
let start = 0;
|
|
8
|
+
let index = 0;
|
|
9
|
+
const scalarBoundary = (at) => {
|
|
10
|
+
const code = text.charCodeAt(at);
|
|
11
|
+
const previous = text.charCodeAt(at - 1);
|
|
12
|
+
return code >= 56320 && code <= 57343 && previous >= 55296 && previous <= 56319 ? at - 1 : at;
|
|
13
|
+
};
|
|
14
|
+
while (start < text.length) {
|
|
15
|
+
const end = scalarBoundary(Math.min(start + size, text.length));
|
|
16
|
+
const excerpt = text.slice(start, end);
|
|
17
|
+
yield {
|
|
18
|
+
index,
|
|
19
|
+
field,
|
|
20
|
+
start,
|
|
21
|
+
end,
|
|
22
|
+
text: excerpt,
|
|
23
|
+
embeddingInput: field === "content" ? `${entry.title}
|
|
24
|
+
${excerpt}` : excerpt
|
|
25
|
+
};
|
|
26
|
+
if (end === text.length) return;
|
|
27
|
+
start = scalarBoundary(end - overlap);
|
|
28
|
+
index += 1;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
1
32
|
// src/index.ts
|
|
2
33
|
import {
|
|
3
34
|
KnowledgeProviderCitation,
|
|
@@ -13,6 +44,8 @@ import {
|
|
|
13
44
|
OrganizationDocumentAuthorityReclassification
|
|
14
45
|
} from "@opengeni/contracts";
|
|
15
46
|
import {
|
|
47
|
+
claimKnowledgeDocumentPreparation,
|
|
48
|
+
completeKnowledgeDocumentPreparation,
|
|
16
49
|
createPersonalDocumentAuthority,
|
|
17
50
|
getFilesForSubject,
|
|
18
51
|
resolveDocumentOriginalFileForSubject,
|
|
@@ -232,12 +265,12 @@ var LiteParseDocumentParser = class {
|
|
|
232
265
|
name = DEFAULT_DOCUMENT_PARSER;
|
|
233
266
|
parseQueue = Promise.resolve();
|
|
234
267
|
async parse(bytes, file) {
|
|
235
|
-
const text = isTextLike(file) ? Buffer.from(bytes).toString("utf8")
|
|
268
|
+
const text = isTextLike(file) ? Buffer.from(bytes).toString("utf8") : await this.parseWithLiteParse(bytes);
|
|
236
269
|
if (!text.trim()) {
|
|
237
270
|
throw new Error(`Parsed document is empty: ${file.filename}`);
|
|
238
271
|
}
|
|
239
272
|
return {
|
|
240
|
-
text
|
|
273
|
+
text,
|
|
241
274
|
metadata: {
|
|
242
275
|
parser: this.name,
|
|
243
276
|
filename: file.filename,
|
|
@@ -251,7 +284,7 @@ var LiteParseDocumentParser = class {
|
|
|
251
284
|
const parser = new LiteParse({ ocrEnabled: true, numWorkers: 1, quiet: true });
|
|
252
285
|
const result = await parser.parse(Buffer.from(bytes));
|
|
253
286
|
const text = typeof result?.text === "string" ? result.text : "";
|
|
254
|
-
return text
|
|
287
|
+
return text;
|
|
255
288
|
});
|
|
256
289
|
}
|
|
257
290
|
async enqueueParse(task) {
|
|
@@ -1434,7 +1467,7 @@ function assertOrganizationDocumentAuthority(authorityKind, organizationAuthorit
|
|
|
1434
1467
|
throw new Error("organization document mutations require exact account authority");
|
|
1435
1468
|
}
|
|
1436
1469
|
}
|
|
1437
|
-
async function indexDocumentNow(db, objectStorage, workspaceId, documentId, services = createDocumentServices(),
|
|
1470
|
+
async function indexDocumentNow(db, objectStorage, workspaceId, documentId, services = createDocumentServices(), access) {
|
|
1438
1471
|
const [loadedDocument] = await withDocumentRls(
|
|
1439
1472
|
db,
|
|
1440
1473
|
workspaceId,
|
|
@@ -1444,13 +1477,7 @@ async function indexDocumentNow(db, objectStorage, workspaceId, documentId, serv
|
|
|
1444
1477
|
).limit(1)
|
|
1445
1478
|
);
|
|
1446
1479
|
if (!loadedDocument) throw new Error(`Document not found: ${documentId}`);
|
|
1447
|
-
|
|
1448
|
-
const file = await requireReadyFile(db, {
|
|
1449
|
-
accountId: document.accountId,
|
|
1450
|
-
workspaceId,
|
|
1451
|
-
subjectId: cleanString(document.createdBy) ?? null,
|
|
1452
|
-
fileId: document.fileId
|
|
1453
|
-
});
|
|
1480
|
+
const document = loadedDocument;
|
|
1454
1481
|
await withDocumentRls(db, workspaceId, access, async (scopedDb) => {
|
|
1455
1482
|
await scopedDb.update(schema.documents).set({
|
|
1456
1483
|
status: "indexing",
|
|
@@ -1462,88 +1489,46 @@ async function indexDocumentNow(db, objectStorage, workspaceId, documentId, serv
|
|
|
1462
1489
|
);
|
|
1463
1490
|
});
|
|
1464
1491
|
try {
|
|
1465
|
-
const
|
|
1466
|
-
async () => objectStorage.getObjectBytes(file.objectKey)
|
|
1467
|
-
);
|
|
1468
|
-
if (!object) throw new Error("document source object is missing");
|
|
1469
|
-
const bytes = object.bytes;
|
|
1470
|
-
const parsed = await services.parser.parse(bytes, file);
|
|
1471
|
-
if (document.curationStatus === "pending") {
|
|
1472
|
-
document = await curateDroppedDocument(db, services, document, parsed, file);
|
|
1473
|
-
}
|
|
1474
|
-
const chunks = services.chunker.chunk(parsed, file);
|
|
1475
|
-
await hooks.beforeEmbed?.({
|
|
1492
|
+
const identity = {
|
|
1476
1493
|
accountId: document.accountId,
|
|
1477
|
-
workspaceId
|
|
1494
|
+
workspaceId,
|
|
1478
1495
|
documentId,
|
|
1479
|
-
|
|
1480
|
-
}
|
|
1481
|
-
const
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
);
|
|
1496
|
+
fileId: document.fileId
|
|
1497
|
+
};
|
|
1498
|
+
const preparation = await claimKnowledgeDocumentPreparation(db, identity);
|
|
1499
|
+
let chunkCount = 0;
|
|
1500
|
+
if (preparation.status === "prepare") {
|
|
1501
|
+
const file = preparation.file;
|
|
1502
|
+
const object = await retryWhileMissing(() => objectStorage.getObjectBytes(file.objectKey));
|
|
1503
|
+
if (!object) throw new Error("document source object is missing");
|
|
1504
|
+
const bytes = object.bytes;
|
|
1505
|
+
if (bytes.length !== file.sizeBytes) throw new Error("document source size changed");
|
|
1506
|
+
const sourceVersion = createHash("sha256").update(bytes).digest("hex");
|
|
1507
|
+
if (file.sha256 && file.sha256 !== sourceVersion)
|
|
1508
|
+
throw new Error("document source hash changed");
|
|
1509
|
+
const parsed = await services.parser.parse(bytes, file);
|
|
1510
|
+
await completeKnowledgeDocumentPreparation(db, {
|
|
1511
|
+
...identity,
|
|
1512
|
+
leaseId: preparation.leaseId,
|
|
1513
|
+
title: document.title,
|
|
1514
|
+
content: parsed.text,
|
|
1515
|
+
sourceVersion
|
|
1516
|
+
});
|
|
1517
|
+
for (const _chunk of knowledgeIndexChunks({ title: document.title, content: parsed.text }))
|
|
1518
|
+
chunkCount++;
|
|
1486
1519
|
}
|
|
1487
|
-
await withDocumentRls(
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
await tx.insert(schema.documentChunks).values(
|
|
1500
|
-
chunks.map((chunk, index) => ({
|
|
1501
|
-
accountId: document.accountId,
|
|
1502
|
-
workspaceId: document.workspaceId,
|
|
1503
|
-
documentId,
|
|
1504
|
-
baseId: document.baseId,
|
|
1505
|
-
fileId: file.id,
|
|
1506
|
-
authorityKind: document.authorityKind,
|
|
1507
|
-
authorityWorkspaceId: document.authorityWorkspaceId,
|
|
1508
|
-
authoritySubjectId: document.authoritySubjectId,
|
|
1509
|
-
chunkIndex: index,
|
|
1510
|
-
text: chunk.text,
|
|
1511
|
-
metadata: {
|
|
1512
|
-
...chunk.metadata,
|
|
1513
|
-
documentTitle: document.title,
|
|
1514
|
-
sourceKind: document.sourceKind,
|
|
1515
|
-
sourceUri: document.sourceUri,
|
|
1516
|
-
sourceExternalId: document.sourceExternalId,
|
|
1517
|
-
sourceTitle: document.sourceTitle,
|
|
1518
|
-
sourceAuthor: document.sourceAuthor,
|
|
1519
|
-
sourceCreatedAt: document.sourceCreatedAt?.toISOString() ?? null,
|
|
1520
|
-
sourceUpdatedAt: document.sourceUpdatedAt?.toISOString() ?? null,
|
|
1521
|
-
sourceVersion: document.sourceVersion,
|
|
1522
|
-
aclTags: document.aclTags
|
|
1523
|
-
},
|
|
1524
|
-
embedding: validateEmbedding(
|
|
1525
|
-
embeddings[index] ?? [],
|
|
1526
|
-
services.embedder.dimensions,
|
|
1527
|
-
services.embedder.model
|
|
1528
|
-
),
|
|
1529
|
-
embeddingModel: services.embedder.model
|
|
1530
|
-
}))
|
|
1531
|
-
);
|
|
1532
|
-
}
|
|
1533
|
-
await tx.update(schema.documents).set({
|
|
1534
|
-
status: "ready",
|
|
1535
|
-
parser: services.parser.name,
|
|
1536
|
-
chunkCount: chunks.length,
|
|
1537
|
-
error: null,
|
|
1538
|
-
updatedAt: /* @__PURE__ */ new Date()
|
|
1539
|
-
}).where(
|
|
1540
|
-
and(
|
|
1541
|
-
eq(schema.documents.workspaceId, workspaceId),
|
|
1542
|
-
eq(schema.documents.id, documentId)
|
|
1543
|
-
)
|
|
1544
|
-
);
|
|
1545
|
-
})
|
|
1546
|
-
);
|
|
1520
|
+
await withDocumentRls(db, workspaceId, access, async (scopedDb) => {
|
|
1521
|
+
await scopedDb.update(schema.documents).set({
|
|
1522
|
+
status: "ready",
|
|
1523
|
+
parser: services.parser.name,
|
|
1524
|
+
chunkCount,
|
|
1525
|
+
error: null,
|
|
1526
|
+
...document.curationStatus === "pending" ? { curationStatus: "none" } : {},
|
|
1527
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
1528
|
+
}).where(
|
|
1529
|
+
and(eq(schema.documents.workspaceId, workspaceId), eq(schema.documents.id, documentId))
|
|
1530
|
+
);
|
|
1531
|
+
});
|
|
1547
1532
|
} catch (error) {
|
|
1548
1533
|
const [failed] = await withDocumentRls(
|
|
1549
1534
|
db,
|
|
@@ -1564,105 +1549,6 @@ async function indexDocumentNow(db, objectStorage, workspaceId, documentId, serv
|
|
|
1564
1549
|
if (!updated) throw new Error(`Document disappeared after indexing: ${documentId}`);
|
|
1565
1550
|
return updated;
|
|
1566
1551
|
}
|
|
1567
|
-
async function curateDroppedDocument(db, services, document, parsed, file) {
|
|
1568
|
-
if (!services.curator) {
|
|
1569
|
-
const [updated2] = await withDocumentRls(
|
|
1570
|
-
db,
|
|
1571
|
-
document.workspaceId,
|
|
1572
|
-
{ viewerSubjectId: document.authoritySubjectId },
|
|
1573
|
-
async (scopedDb) => await scopedDb.update(schema.documents).set({
|
|
1574
|
-
curationStatus: "none",
|
|
1575
|
-
summary: null,
|
|
1576
|
-
topics: [],
|
|
1577
|
-
curation: null,
|
|
1578
|
-
updatedAt: /* @__PURE__ */ new Date()
|
|
1579
|
-
}).where(
|
|
1580
|
-
and(
|
|
1581
|
-
eq(schema.documents.workspaceId, document.workspaceId),
|
|
1582
|
-
eq(schema.documents.id, document.id)
|
|
1583
|
-
)
|
|
1584
|
-
).returning()
|
|
1585
|
-
);
|
|
1586
|
-
return updated2 ?? document;
|
|
1587
|
-
}
|
|
1588
|
-
const bases = await listDocumentBases(db, document.workspaceId);
|
|
1589
|
-
const candidates = bases.filter((base) => base.id !== document.baseId).map((base) => ({
|
|
1590
|
-
id: base.id,
|
|
1591
|
-
name: base.name,
|
|
1592
|
-
description: base.description
|
|
1593
|
-
}));
|
|
1594
|
-
const input = {
|
|
1595
|
-
text: parsed.text.slice(0, DOCUMENT_CURATION_MAX_INPUT_CHARS),
|
|
1596
|
-
filename: file.filename,
|
|
1597
|
-
title: document.title,
|
|
1598
|
-
bases: candidates
|
|
1599
|
-
};
|
|
1600
|
-
let outcome;
|
|
1601
|
-
let model;
|
|
1602
|
-
let failure = null;
|
|
1603
|
-
try {
|
|
1604
|
-
outcome = await services.curator.curate(input);
|
|
1605
|
-
model = services.curator.model;
|
|
1606
|
-
} catch (error) {
|
|
1607
|
-
failure = error instanceof Error ? error.message : String(error);
|
|
1608
|
-
console.warn("document curation failed; applying heuristic fallback", {
|
|
1609
|
-
workspaceId: document.workspaceId,
|
|
1610
|
-
documentId: document.id,
|
|
1611
|
-
error: failure
|
|
1612
|
-
});
|
|
1613
|
-
outcome = heuristicCuration(input, file.contentType);
|
|
1614
|
-
model = "heuristic";
|
|
1615
|
-
}
|
|
1616
|
-
const suggestedBase = candidates.find((base) => base.id === outcome.targetBaseId) ?? null;
|
|
1617
|
-
let moveToBaseId = null;
|
|
1618
|
-
if (suggestedBase && failure === null && outcome.confidence >= DOCUMENT_CURATION_AUTO_FILE_CONFIDENCE) {
|
|
1619
|
-
const conflict = await withDocumentRls(
|
|
1620
|
-
db,
|
|
1621
|
-
document.workspaceId,
|
|
1622
|
-
{ viewerSubjectId: document.authoritySubjectId },
|
|
1623
|
-
async (scopedDb) => await scopedDb.select({ id: schema.documents.id }).from(schema.documents).where(
|
|
1624
|
-
and(
|
|
1625
|
-
eq(schema.documents.workspaceId, document.workspaceId),
|
|
1626
|
-
eq(schema.documents.baseId, suggestedBase.id),
|
|
1627
|
-
eq(schema.documents.fileId, document.fileId)
|
|
1628
|
-
)
|
|
1629
|
-
).limit(1)
|
|
1630
|
-
);
|
|
1631
|
-
if (conflict.length === 0) {
|
|
1632
|
-
moveToBaseId = suggestedBase.id;
|
|
1633
|
-
}
|
|
1634
|
-
}
|
|
1635
|
-
const curation = {
|
|
1636
|
-
suggestedBaseId: suggestedBase?.id ?? null,
|
|
1637
|
-
suggestedBaseName: suggestedBase?.name ?? null,
|
|
1638
|
-
confidence: outcome.confidence,
|
|
1639
|
-
reason: failure ? `curation failed (${failure}); heuristic fallback applied` : outcome.reason,
|
|
1640
|
-
originalTitle: document.title,
|
|
1641
|
-
model
|
|
1642
|
-
};
|
|
1643
|
-
const curationStatus = failure ? "failed" : moveToBaseId ? "auto_filed" : "suggested";
|
|
1644
|
-
const [updated] = await withDocumentRls(
|
|
1645
|
-
db,
|
|
1646
|
-
document.workspaceId,
|
|
1647
|
-
{ viewerSubjectId: document.authoritySubjectId },
|
|
1648
|
-
async (scopedDb) => await scopedDb.update(schema.documents).set({
|
|
1649
|
-
title: outcome.title ?? document.title,
|
|
1650
|
-
summary: outcome.summary,
|
|
1651
|
-
topics: outcome.topics,
|
|
1652
|
-
...outcome.sourceKind ? { sourceKind: outcome.sourceKind } : {},
|
|
1653
|
-
...moveToBaseId ? { baseId: moveToBaseId } : {},
|
|
1654
|
-
curationStatus,
|
|
1655
|
-
curation,
|
|
1656
|
-
updatedAt: /* @__PURE__ */ new Date()
|
|
1657
|
-
}).where(
|
|
1658
|
-
and(
|
|
1659
|
-
eq(schema.documents.workspaceId, document.workspaceId),
|
|
1660
|
-
eq(schema.documents.id, document.id)
|
|
1661
|
-
)
|
|
1662
|
-
).returning()
|
|
1663
|
-
);
|
|
1664
|
-
return updated ?? document;
|
|
1665
|
-
}
|
|
1666
1552
|
async function searchDocuments(db, input, services = createDocumentServices()) {
|
|
1667
1553
|
return (await searchDocumentCandidates(db, input, services)).results;
|
|
1668
1554
|
}
|
|
@@ -3145,6 +3031,7 @@ export {
|
|
|
3145
3031
|
getEffectiveKnowledgeRecord,
|
|
3146
3032
|
heuristicCuration,
|
|
3147
3033
|
indexDocumentNow,
|
|
3034
|
+
knowledgeIndexChunks,
|
|
3148
3035
|
listAccessibleDocuments,
|
|
3149
3036
|
listDocumentAuthorityReclassifications,
|
|
3150
3037
|
listDocumentBases,
|