@ppagent/memory 0.1.0 → 0.1.1
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 +86 -19
- package/dist/index.js +292 -211
- package/package.json +49 -50
- package/llms.txt +0 -845
package/dist/index.js
CHANGED
|
@@ -25,10 +25,9 @@ function resolveConfig(config) {
|
|
|
25
25
|
detailMaxTokens: config.detailMaxTokens ?? 2048,
|
|
26
26
|
summaryMaxTokens: config.summaryMaxTokens ?? 512,
|
|
27
27
|
conciseMaxTokens: config.conciseMaxTokens ?? 128,
|
|
28
|
-
httpTimeoutMs: config.httpTimeoutMs ??
|
|
28
|
+
httpTimeoutMs: config.httpTimeoutMs ?? 3e4,
|
|
29
29
|
httpMaxRetries: config.httpMaxRetries ?? 2,
|
|
30
30
|
embeddingBatchSize: config.embeddingBatchSize ?? 20,
|
|
31
|
-
embeddingConcurrency: config.embeddingConcurrency ?? 2,
|
|
32
31
|
maxConcurrentCompressions: config.maxConcurrentCompressions ?? 3,
|
|
33
32
|
entitySimilarityThreshold: config.entitySimilarityThreshold ?? 0.92,
|
|
34
33
|
defaultSearchLimit: config.defaultSearchLimit ?? 10,
|
|
@@ -43,9 +42,7 @@ function resolveConfig(config) {
|
|
|
43
42
|
knowledgeGraphTriggerScore: config.knowledgeGraphTriggerScore ?? 0.78,
|
|
44
43
|
knowledgeGraphEntityTopK: config.knowledgeGraphEntityTopK ?? 10,
|
|
45
44
|
knowledgeGraphAnchorTopK: config.knowledgeGraphAnchorTopK ?? 3,
|
|
46
|
-
knowledgeGraphHopLimit: config.knowledgeGraphHopLimit ?? 10
|
|
47
|
-
graphExtractConcurrency: config.graphExtractConcurrency ?? 2,
|
|
48
|
-
graphBuildTimeoutMs: config.graphBuildTimeoutMs ?? 12e4
|
|
45
|
+
knowledgeGraphHopLimit: config.knowledgeGraphHopLimit ?? 10
|
|
49
46
|
};
|
|
50
47
|
}
|
|
51
48
|
|
|
@@ -229,45 +226,27 @@ var EmbedService = class {
|
|
|
229
226
|
const inputs = Array.isArray(texts) ? texts : [texts];
|
|
230
227
|
const url = `${this.config.embeddingBaseUrl.replace(/\/$/, "")}/embeddings`;
|
|
231
228
|
const batchSize = Math.max(1, this.config.embeddingBatchSize);
|
|
232
|
-
const concurrency = Math.max(1, this.config.embeddingConcurrency);
|
|
233
229
|
const retryOpts = { timeoutMs: this.config.httpTimeoutMs, maxRetries: this.config.httpMaxRetries };
|
|
234
|
-
const
|
|
230
|
+
const out = [];
|
|
235
231
|
for (let i = 0; i < inputs.length; i += batchSize) {
|
|
236
|
-
|
|
237
|
-
}
|
|
238
|
-
const batchResults = await mapLimit(batches, concurrency, async (batch) => {
|
|
232
|
+
const batch = inputs.slice(i, i + batchSize);
|
|
239
233
|
const data = await postJsonWithRetry(
|
|
240
234
|
url,
|
|
241
235
|
{ Authorization: `Bearer ${this.config.embeddingApiKey}` },
|
|
242
|
-
{ model: this.config.embeddingModel, input: batch
|
|
236
|
+
{ model: this.config.embeddingModel, input: batch },
|
|
243
237
|
retryOpts,
|
|
244
238
|
"Embedding API"
|
|
245
239
|
);
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
});
|
|
251
|
-
return batchResults.sort((a, b) => a.index - b.index).flatMap((b) => b.vectors);
|
|
240
|
+
const sorted = data.data.sort((a, b) => a.index - b.index).map((d) => d.embedding);
|
|
241
|
+
out.push(...sorted);
|
|
242
|
+
}
|
|
243
|
+
return out;
|
|
252
244
|
}
|
|
253
245
|
async embedOne(text) {
|
|
254
246
|
const result = await this.embed([text]);
|
|
255
247
|
return result[0];
|
|
256
248
|
}
|
|
257
249
|
};
|
|
258
|
-
async function mapLimit(items, limit, mapper) {
|
|
259
|
-
const results = new Array(items.length);
|
|
260
|
-
let next = 0;
|
|
261
|
-
const workers = Array.from({ length: Math.min(limit, items.length) }, async () => {
|
|
262
|
-
while (true) {
|
|
263
|
-
const index = next++;
|
|
264
|
-
if (index >= items.length) return;
|
|
265
|
-
results[index] = await mapper(items[index], index);
|
|
266
|
-
}
|
|
267
|
-
});
|
|
268
|
-
await Promise.all(workers);
|
|
269
|
-
return results;
|
|
270
|
-
}
|
|
271
250
|
function cosineSimilarity(a, b) {
|
|
272
251
|
let dot = 0;
|
|
273
252
|
let normA = 0;
|
|
@@ -281,8 +260,24 @@ function cosineSimilarity(a, b) {
|
|
|
281
260
|
return dot / (Math.sqrt(normA) * Math.sqrt(normB));
|
|
282
261
|
}
|
|
283
262
|
|
|
263
|
+
// src/page.util.ts
|
|
264
|
+
function normalizePage(page) {
|
|
265
|
+
const offset = Math.max(0, Math.floor(page.offset ?? 0));
|
|
266
|
+
const limit = page.limit != null && page.limit > 0 ? Math.floor(page.limit) : 0;
|
|
267
|
+
return { offset, limit };
|
|
268
|
+
}
|
|
269
|
+
function paginate(items, page) {
|
|
270
|
+
const total = items.length;
|
|
271
|
+
const { offset, limit } = normalizePage(page);
|
|
272
|
+
const slice = limit > 0 ? items.slice(offset, offset + limit) : items.slice(offset);
|
|
273
|
+
return { items: slice, total, offset, limit };
|
|
274
|
+
}
|
|
275
|
+
function cmpStr(a, b) {
|
|
276
|
+
return a < b ? -1 : a > b ? 1 : 0;
|
|
277
|
+
}
|
|
278
|
+
|
|
284
279
|
// src/db/grafeo.service.ts
|
|
285
|
-
var GrafeoService = class {
|
|
280
|
+
var GrafeoService = class _GrafeoService {
|
|
286
281
|
config;
|
|
287
282
|
db;
|
|
288
283
|
constructor(config) {
|
|
@@ -459,16 +454,17 @@ var GrafeoService = class {
|
|
|
459
454
|
const r = row;
|
|
460
455
|
return {
|
|
461
456
|
type: "relation",
|
|
462
|
-
content: `${r["
|
|
457
|
+
content: `${r["fromName"]} -[${r["relType"]}]-> ${r["toName"]}`,
|
|
463
458
|
score: 1,
|
|
464
|
-
meta: { happenedAt: r["
|
|
459
|
+
meta: { happenedAt: r["happenedAt"] }
|
|
465
460
|
};
|
|
466
461
|
});
|
|
462
|
+
const RETURN_CLAUSE = "RETURN a.name AS fromName, type(r) AS relType, b.name AS toName, r.happenedAt AS happenedAt";
|
|
467
463
|
try {
|
|
468
464
|
const result = await this.db.execute(
|
|
469
465
|
`MATCH (a:Entity {name: $name, userId: $userId, chatId: $chatId, kind: $kind})-[r]->(b)
|
|
470
466
|
WHERE r.chatId = $chatId AND r.userId = $userId
|
|
471
|
-
|
|
467
|
+
${RETURN_CLAUSE} LIMIT $limit`,
|
|
472
468
|
{ name: entityName, userId, chatId, kind, limit }
|
|
473
469
|
);
|
|
474
470
|
return mapRows(result.toArray());
|
|
@@ -477,7 +473,7 @@ var GrafeoService = class {
|
|
|
477
473
|
try {
|
|
478
474
|
const result = await this.db.execute(
|
|
479
475
|
`MATCH (a:Entity {name: $name, userId: $userId, chatId: $chatId, kind: $kind})-[r]->(b)
|
|
480
|
-
|
|
476
|
+
${RETURN_CLAUSE} LIMIT $limit`,
|
|
481
477
|
{ name: entityName, userId, chatId, kind, limit }
|
|
482
478
|
);
|
|
483
479
|
return mapRows(result.toArray());
|
|
@@ -500,56 +496,176 @@ var GrafeoService = class {
|
|
|
500
496
|
}
|
|
501
497
|
}
|
|
502
498
|
// ── 管理面板用:全量读取实体 / 关系 ────────────────────────────────────────────────
|
|
503
|
-
/**
|
|
504
|
-
|
|
499
|
+
/**
|
|
500
|
+
* 构建按域过滤的内联属性子句(如 ` {userId: $userId, chatId: $chatId}`)。
|
|
501
|
+
* 在 LIMIT 之前生效,避免「先截断再过滤」导致结果不足。
|
|
502
|
+
*/
|
|
503
|
+
buildScopeMatch(filter, params) {
|
|
504
|
+
const props = [];
|
|
505
|
+
if (filter?.userId) {
|
|
506
|
+
props.push("userId: $userId");
|
|
507
|
+
params.userId = filter.userId;
|
|
508
|
+
}
|
|
509
|
+
if (filter?.chatId) {
|
|
510
|
+
props.push("chatId: $chatId");
|
|
511
|
+
params.chatId = filter.chatId;
|
|
512
|
+
}
|
|
513
|
+
return props.length > 0 ? ` {${props.join(", ")}}` : "";
|
|
514
|
+
}
|
|
515
|
+
// 关系查询的统一 RETURN 子句:用别名(AS)取列,避免函数表达式 type(r)/id(r) 的默认列名
|
|
516
|
+
// 被归一为 "type(...)"/"id(...)"(按 r["type(r)"] 取会得到 undefined)。id(r) 同时用于 ORDER BY。
|
|
517
|
+
static RELATION_RETURN = `RETURN a.name AS fromName, b.name AS toName, type(r) AS relType,
|
|
518
|
+
r.happenedAt AS happenedAt, r.userId AS userId, r.chatId AS chatId,
|
|
519
|
+
r.sessionId AS sessionId, id(r) AS rid`;
|
|
520
|
+
/** 节点 → Entity 映射(剔除体积大的 embedding 字段)*/
|
|
521
|
+
nodeToEntity(node) {
|
|
522
|
+
const props = node.properties();
|
|
523
|
+
const { embedding: _embedding, name, userId, chatId, sessionId, ...rest } = props;
|
|
524
|
+
const type = (node.labels ?? []).filter((l) => l !== "Entity").join(", ") || "\u672A\u77E5";
|
|
525
|
+
return {
|
|
526
|
+
name: String(name ?? ""),
|
|
527
|
+
type,
|
|
528
|
+
meta: {
|
|
529
|
+
userId: String(userId ?? ""),
|
|
530
|
+
chatId: String(chatId ?? ""),
|
|
531
|
+
sessionId: String(sessionId ?? ""),
|
|
532
|
+
...rest
|
|
533
|
+
}
|
|
534
|
+
};
|
|
535
|
+
}
|
|
536
|
+
/** 关系查询结果行 → Relation 映射(依赖 RELATION_RETURN 的别名列)*/
|
|
537
|
+
rowToRelation(row) {
|
|
538
|
+
const r = row;
|
|
539
|
+
return {
|
|
540
|
+
from: String(r["fromName"] ?? ""),
|
|
541
|
+
to: String(r["toName"] ?? ""),
|
|
542
|
+
type: String(r["relType"] ?? ""),
|
|
543
|
+
happenedAt: r["happenedAt"] != null ? Number(r["happenedAt"]) : void 0,
|
|
544
|
+
meta: {
|
|
545
|
+
userId: String(r["userId"] ?? ""),
|
|
546
|
+
chatId: String(r["chatId"] ?? ""),
|
|
547
|
+
sessionId: String(r["sessionId"] ?? "")
|
|
548
|
+
}
|
|
549
|
+
};
|
|
550
|
+
}
|
|
551
|
+
/** 统计实体数(可选按域过滤),用于分页 total */
|
|
552
|
+
async countEntities(filter) {
|
|
553
|
+
try {
|
|
554
|
+
const params = {};
|
|
555
|
+
const scope = this.buildScopeMatch(filter, params);
|
|
556
|
+
const res = await this.db.execute(`MATCH (n:Entity${scope}) RETURN count(n) AS cnt`, params);
|
|
557
|
+
return Number(res.toArray()[0]?.cnt ?? 0);
|
|
558
|
+
} catch {
|
|
559
|
+
return 0;
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
/** 统计关系数(可选按域过滤),用于分页 total */
|
|
563
|
+
async countRelations(filter) {
|
|
505
564
|
try {
|
|
506
|
-
const
|
|
565
|
+
const params = {};
|
|
566
|
+
const scope = this.buildScopeMatch(filter, params);
|
|
567
|
+
const res = await this.db.execute(
|
|
568
|
+
`MATCH (a:Entity${scope})-[r]->(b:Entity) RETURN count(r) AS cnt`,
|
|
569
|
+
params
|
|
570
|
+
);
|
|
571
|
+
return Number(res.toArray()[0]?.cnt ?? 0);
|
|
572
|
+
} catch {
|
|
573
|
+
return 0;
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
/**
|
|
577
|
+
* 全量读取实体节点(管理面板/知识图谱用,剔除体积大的 embedding 字段)。
|
|
578
|
+
* 可选按 userId / chatId 过滤(在节点属性上匹配,LIMIT 前生效)。
|
|
579
|
+
* 按节点内部 id 升序,确保「同样条件 → 同样顺序」。
|
|
580
|
+
* 注意:此方法带安全上限 limit,超出会截断;需要准确 total 与翻页请用 pageEntities。
|
|
581
|
+
*/
|
|
582
|
+
async getAllEntities(filter, limit = 1e3) {
|
|
583
|
+
try {
|
|
584
|
+
const params = { limit };
|
|
585
|
+
const scope = this.buildScopeMatch(filter, params);
|
|
586
|
+
const result = await this.db.execute(`MATCH (n:Entity${scope}) RETURN n LIMIT $limit`, params);
|
|
507
587
|
const nodes = result.nodes();
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
const { embedding: _embedding, name, userId, chatId, sessionId, ...rest } = props;
|
|
511
|
-
const type = (node.labels ?? []).filter((l) => l !== "Entity").join(", ") || "\u672A\u77E5";
|
|
512
|
-
return {
|
|
513
|
-
name: String(name ?? ""),
|
|
514
|
-
type,
|
|
515
|
-
meta: {
|
|
516
|
-
userId: String(userId ?? ""),
|
|
517
|
-
chatId: String(chatId ?? ""),
|
|
518
|
-
sessionId: String(sessionId ?? ""),
|
|
519
|
-
...rest
|
|
520
|
-
}
|
|
521
|
-
};
|
|
522
|
-
});
|
|
588
|
+
nodes.sort((a, b) => a.id - b.id);
|
|
589
|
+
return nodes.map((node) => this.nodeToEntity(node));
|
|
523
590
|
} catch {
|
|
524
591
|
return [];
|
|
525
592
|
}
|
|
526
593
|
}
|
|
527
|
-
/**
|
|
528
|
-
|
|
594
|
+
/**
|
|
595
|
+
* 分页读取实体节点:把 ORDER BY id(n) + SKIP/LIMIT 下推到查询层,并用 count 取准确 total。
|
|
596
|
+
* 不受 getAllEntities 安全上限约束。返回当前页 items 与过滤后的 total。
|
|
597
|
+
*/
|
|
598
|
+
async pageEntities(filter, page) {
|
|
599
|
+
const { offset, limit } = normalizePage(page);
|
|
600
|
+
try {
|
|
601
|
+
const total = await this.countEntities(filter);
|
|
602
|
+
const effLimit = limit > 0 ? limit : Math.max(0, total - offset);
|
|
603
|
+
if (effLimit <= 0) return { items: [], total };
|
|
604
|
+
const params = {};
|
|
605
|
+
const scope = this.buildScopeMatch(filter, params);
|
|
606
|
+
params.offset = offset;
|
|
607
|
+
params.limit = effLimit;
|
|
608
|
+
const res = await this.db.execute(
|
|
609
|
+
`MATCH (n:Entity${scope}) RETURN id(n) AS nid ORDER BY nid SKIP $offset LIMIT $limit`,
|
|
610
|
+
params
|
|
611
|
+
);
|
|
612
|
+
const items = [];
|
|
613
|
+
for (const row of res.toArray()) {
|
|
614
|
+
const nid = Number(row.nid);
|
|
615
|
+
const node = this.db.getNode(nid);
|
|
616
|
+
if (node) items.push(this.nodeToEntity(node));
|
|
617
|
+
}
|
|
618
|
+
return { items, total };
|
|
619
|
+
} catch {
|
|
620
|
+
return { items: [], total: 0 };
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
/**
|
|
624
|
+
* 全量读取关系边(管理面板/知识图谱用)。可选按 userId / chatId 过滤:在源实体节点的域属性上匹配
|
|
625
|
+
*(关系两端与边均同域,故按源节点过滤即可,且节点内联属性匹配在各 grafeo 版本均可靠)。
|
|
626
|
+
* 按 id(r) 升序确保顺序确定。
|
|
627
|
+
* 注意:此方法带安全上限 limit,超出会截断;需要准确 total 与翻页请用 pageRelations。
|
|
628
|
+
*/
|
|
629
|
+
async getAllRelations(filter, limit = 2e3) {
|
|
529
630
|
try {
|
|
631
|
+
const params = { limit };
|
|
632
|
+
const scope = this.buildScopeMatch(filter, params);
|
|
530
633
|
const result = await this.db.execute(
|
|
531
|
-
`MATCH (a:Entity)-[r]->(b:Entity)
|
|
532
|
-
|
|
533
|
-
|
|
634
|
+
`MATCH (a:Entity${scope})-[r]->(b:Entity)
|
|
635
|
+
${_GrafeoService.RELATION_RETURN}
|
|
636
|
+
ORDER BY rid LIMIT $limit`,
|
|
637
|
+
params
|
|
534
638
|
);
|
|
535
|
-
return result.toArray().map((row) =>
|
|
536
|
-
const r = row;
|
|
537
|
-
return {
|
|
538
|
-
from: String(r["a.name"] ?? ""),
|
|
539
|
-
to: String(r["b.name"] ?? ""),
|
|
540
|
-
type: String(r["type(r)"] ?? ""),
|
|
541
|
-
happenedAt: r["r.happenedAt"] != null ? Number(r["r.happenedAt"]) : void 0,
|
|
542
|
-
meta: {
|
|
543
|
-
userId: String(r["r.userId"] ?? ""),
|
|
544
|
-
chatId: String(r["r.chatId"] ?? ""),
|
|
545
|
-
sessionId: String(r["r.sessionId"] ?? "")
|
|
546
|
-
}
|
|
547
|
-
};
|
|
548
|
-
});
|
|
639
|
+
return result.toArray().map((row) => this.rowToRelation(row));
|
|
549
640
|
} catch {
|
|
550
641
|
return [];
|
|
551
642
|
}
|
|
552
643
|
}
|
|
644
|
+
/**
|
|
645
|
+
* 分页读取关系边:把 ORDER BY id(r) + SKIP/LIMIT 下推到查询层,并用 count 取准确 total。
|
|
646
|
+
* 不受 getAllRelations 安全上限约束。返回当前页 items 与过滤后的 total。
|
|
647
|
+
*/
|
|
648
|
+
async pageRelations(filter, page) {
|
|
649
|
+
const { offset, limit } = normalizePage(page);
|
|
650
|
+
try {
|
|
651
|
+
const total = await this.countRelations(filter);
|
|
652
|
+
const effLimit = limit > 0 ? limit : Math.max(0, total - offset);
|
|
653
|
+
if (effLimit <= 0) return { items: [], total };
|
|
654
|
+
const params = {};
|
|
655
|
+
const scope = this.buildScopeMatch(filter, params);
|
|
656
|
+
params.offset = offset;
|
|
657
|
+
params.limit = effLimit;
|
|
658
|
+
const res = await this.db.execute(
|
|
659
|
+
`MATCH (a:Entity${scope})-[r]->(b:Entity)
|
|
660
|
+
${_GrafeoService.RELATION_RETURN}
|
|
661
|
+
ORDER BY rid SKIP $offset LIMIT $limit`,
|
|
662
|
+
params
|
|
663
|
+
);
|
|
664
|
+
return { items: res.toArray().map((row) => this.rowToRelation(row)), total };
|
|
665
|
+
} catch {
|
|
666
|
+
return { items: [], total: 0 };
|
|
667
|
+
}
|
|
668
|
+
}
|
|
553
669
|
close() {
|
|
554
670
|
try {
|
|
555
671
|
this.db.close();
|
|
@@ -1051,6 +1167,15 @@ var LanceService = class {
|
|
|
1051
1167
|
return [];
|
|
1052
1168
|
}
|
|
1053
1169
|
}
|
|
1170
|
+
/** 取某会话的全部消息(按 createdAt 升序),供管理面板分页切片使用 */
|
|
1171
|
+
async getAllMessagesBySession(sessionId) {
|
|
1172
|
+
try {
|
|
1173
|
+
const rows = await this.messagesTable.query().where(eqFilter("session_id", sessionId)).toArray();
|
|
1174
|
+
return rows.sort((a, b) => Number(a.created_at) - Number(b.created_at) || cmpStr(a.message_id, b.message_id)).map(rowToMessage);
|
|
1175
|
+
} catch {
|
|
1176
|
+
return [];
|
|
1177
|
+
}
|
|
1178
|
+
}
|
|
1054
1179
|
async searchMessages(vector, filter, limit = 10) {
|
|
1055
1180
|
const q = this.messagesTable.vectorSearch(vector).limit(limit);
|
|
1056
1181
|
if (filter) q.where(filter);
|
|
@@ -1082,8 +1207,7 @@ var LanceService = class {
|
|
|
1082
1207
|
const total = rows.length;
|
|
1083
1208
|
return rows.map((r, idx) => ({
|
|
1084
1209
|
row: r,
|
|
1085
|
-
|
|
1086
|
-
score: 1 - idx / total + Math.log1p(Number(r.recall_count)) * 0.1
|
|
1210
|
+
score: total - idx + Math.log1p(Number(r.recall_count))
|
|
1087
1211
|
})).sort((a, b) => b.score - a.score).map((x) => rowToTopic(x.row));
|
|
1088
1212
|
}
|
|
1089
1213
|
} catch {
|
|
@@ -1151,7 +1275,7 @@ var LanceService = class {
|
|
|
1151
1275
|
async getAllTopics() {
|
|
1152
1276
|
try {
|
|
1153
1277
|
const rows = await this.topicsTable.query().toArray();
|
|
1154
|
-
return rows.sort((a, b) => Number(b.end_time) - Number(a.end_time)).map(rowToTopic);
|
|
1278
|
+
return rows.sort((a, b) => Number(b.end_time) - Number(a.end_time) || cmpStr(a.summary_id, b.summary_id)).map(rowToTopic);
|
|
1155
1279
|
} catch {
|
|
1156
1280
|
return [];
|
|
1157
1281
|
}
|
|
@@ -1324,7 +1448,7 @@ var LanceService = class {
|
|
|
1324
1448
|
async getAllDocuments() {
|
|
1325
1449
|
try {
|
|
1326
1450
|
const rows = await this.documentsTable.query().toArray();
|
|
1327
|
-
return rows.sort((a, b) => Number(b.updated_at) - Number(a.updated_at)).map(rowToDocument);
|
|
1451
|
+
return rows.sort((a, b) => Number(b.updated_at) - Number(a.updated_at) || cmpStr(a.doc_id, b.doc_id)).map(rowToDocument);
|
|
1328
1452
|
} catch {
|
|
1329
1453
|
return [];
|
|
1330
1454
|
}
|
|
@@ -1478,7 +1602,7 @@ var LlmService = class {
|
|
|
1478
1602
|
return data.choices[0].message;
|
|
1479
1603
|
}
|
|
1480
1604
|
// ── 第一步:生成三级摘要(JSON 输出)──────────────────────────────────────────
|
|
1481
|
-
async
|
|
1605
|
+
async getSummary(messages) {
|
|
1482
1606
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1483
1607
|
const { detailMaxTokens, summaryMaxTokens, conciseMaxTokens } = this.config;
|
|
1484
1608
|
const systemPrompt = `\u4F60\u662F\u4E00\u4E2A\u5BF9\u8BDD\u8BB0\u5FC6\u7BA1\u7406\u52A9\u624B\u3002\u5F53\u524D\u7CFB\u7EDF\u65F6\u95F4\uFF1A${now}\u3002
|
|
@@ -1500,7 +1624,7 @@ ${formatted}`);
|
|
|
1500
1624
|
return { title: parsed.title ?? "", detail: parsed.detail, summary: parsed.summary, concise: parsed.concise };
|
|
1501
1625
|
}
|
|
1502
1626
|
// ── 第二步:通过工具调用提取实体和关系 ──────────────────────────────────────
|
|
1503
|
-
async
|
|
1627
|
+
async extractEntitiesWithTool(messages) {
|
|
1504
1628
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1505
1629
|
const systemPrompt = `\u4F60\u662F\u4E00\u4E2A\u5B9E\u4F53\u5173\u7CFB\u62BD\u53D6\u52A9\u624B\u3002\u5F53\u524D\u7CFB\u7EDF\u65F6\u95F4\uFF1A${now}\u3002
|
|
1506
1630
|
\u68C0\u67E5\u7ED9\u5B9A\u7684\u5BF9\u8BDD\u5185\u5BB9\uFF0C\u5224\u65AD\u5176\u4E2D\u662F\u5426\u5B58\u5728\u503C\u5F97\u8BB0\u5F55\u7684\u547D\u540D\u5B9E\u4F53\uFF08\u4EBA\u7269\u3001\u7EC4\u7EC7\u3001\u9879\u76EE\u3001\u5730\u70B9\u3001\u6280\u672F\u7B49\uFF09\u548C\u5B83\u4EEC\u4E4B\u95F4\u7684\u5173\u7CFB\u3002
|
|
@@ -1528,15 +1652,15 @@ ${formatted}`
|
|
|
1528
1652
|
}
|
|
1529
1653
|
}
|
|
1530
1654
|
} catch (err) {
|
|
1531
|
-
console.error("[LlmService]
|
|
1655
|
+
console.error("[LlmService] extractEntitiesWithTool failed:", err);
|
|
1532
1656
|
}
|
|
1533
1657
|
return { entities: [], relations: [] };
|
|
1534
1658
|
}
|
|
1535
1659
|
// ── 对外主接口:压缩 + 实体抽取(并发执行两步)────────────────────────────────
|
|
1536
1660
|
async compress(messages) {
|
|
1537
1661
|
const [summary, extraction] = await Promise.all([
|
|
1538
|
-
this.
|
|
1539
|
-
this.
|
|
1662
|
+
this.getSummary(messages),
|
|
1663
|
+
this.extractEntitiesWithTool(messages)
|
|
1540
1664
|
]);
|
|
1541
1665
|
return {
|
|
1542
1666
|
title: summary.title,
|
|
@@ -1688,16 +1812,16 @@ var CompressManager = class {
|
|
|
1688
1812
|
this.sessionCache = sessionCache;
|
|
1689
1813
|
this.semaphore = new Semaphore(config.maxConcurrentCompressions);
|
|
1690
1814
|
}
|
|
1691
|
-
triggerCompress(sessionId, force = false
|
|
1815
|
+
triggerCompress(sessionId, force = false) {
|
|
1692
1816
|
const chain = this.sessionChain.get(sessionId) ?? Promise.resolve();
|
|
1693
1817
|
const next = chain.then(
|
|
1694
|
-
() => this.semaphore.run(() => this.doCompress(sessionId, force
|
|
1818
|
+
() => this.semaphore.run(() => this.doCompress(sessionId, force))
|
|
1695
1819
|
);
|
|
1696
1820
|
this.sessionChain.set(sessionId, next.catch(() => {
|
|
1697
1821
|
}));
|
|
1698
1822
|
return next;
|
|
1699
1823
|
}
|
|
1700
|
-
async doCompress(sessionId, force
|
|
1824
|
+
async doCompress(sessionId, force) {
|
|
1701
1825
|
const messages = this.sessionCache.getSessionMessages(sessionId);
|
|
1702
1826
|
if (messages.length === 0) return;
|
|
1703
1827
|
const entry = this.sessionCache.getEntry(sessionId);
|
|
@@ -1706,15 +1830,15 @@ var CompressManager = class {
|
|
|
1706
1830
|
const { chatId, userId } = entry.ids;
|
|
1707
1831
|
const startTime = messages[0].createdAt;
|
|
1708
1832
|
const endTime = messages[messages.length - 1].createdAt;
|
|
1709
|
-
let
|
|
1833
|
+
let compressOutput;
|
|
1710
1834
|
try {
|
|
1711
|
-
|
|
1835
|
+
compressOutput = await this.llm.compress(messages);
|
|
1712
1836
|
} catch (err) {
|
|
1713
1837
|
console.error(`[CompressManager] LLM compress failed for session ${sessionId}:`, err);
|
|
1714
1838
|
return;
|
|
1715
1839
|
}
|
|
1716
|
-
const { title, detail, summary
|
|
1717
|
-
const topicTextForEmbed = [detail,
|
|
1840
|
+
const { title, detail, summary, concise, entities, relations } = compressOutput;
|
|
1841
|
+
const topicTextForEmbed = [detail, summary, concise].find((t) => t.length > 0) ?? "";
|
|
1718
1842
|
let topicVector = [];
|
|
1719
1843
|
try {
|
|
1720
1844
|
topicVector = await this.embed.embedOne(topicTextForEmbed);
|
|
@@ -1728,7 +1852,7 @@ var CompressManager = class {
|
|
|
1728
1852
|
chatId,
|
|
1729
1853
|
title,
|
|
1730
1854
|
detail,
|
|
1731
|
-
summary
|
|
1855
|
+
summary,
|
|
1732
1856
|
concise,
|
|
1733
1857
|
startTime,
|
|
1734
1858
|
endTime,
|
|
@@ -1742,6 +1866,9 @@ var CompressManager = class {
|
|
|
1742
1866
|
} catch (err) {
|
|
1743
1867
|
console.error(`[CompressManager] Save topic failed:`, err);
|
|
1744
1868
|
}
|
|
1869
|
+
if (entities.length > 0 || relations.length > 0) {
|
|
1870
|
+
await this.persistGraph(entities, relations, sessionId, chatId, userId, endTime);
|
|
1871
|
+
}
|
|
1745
1872
|
this.sessionCache.clearMessages(sessionId, endTime);
|
|
1746
1873
|
const [n1, n2, n3] = this.config.topicRatio;
|
|
1747
1874
|
try {
|
|
@@ -1750,19 +1877,6 @@ var CompressManager = class {
|
|
|
1750
1877
|
} catch (err) {
|
|
1751
1878
|
console.error(`[CompressManager] Rebuild history window failed:`, err);
|
|
1752
1879
|
}
|
|
1753
|
-
const graphTask = this.extractAndPersistGraph(messages, sessionId, chatId, userId, endTime);
|
|
1754
|
-
if (waitGraph) {
|
|
1755
|
-
await withTimeout(graphTask, this.config.graphBuildTimeoutMs, "flushChat graph build");
|
|
1756
|
-
} else {
|
|
1757
|
-
graphTask.catch((err) => {
|
|
1758
|
-
console.error(`[CompressManager] Background graph persist failed:`, err);
|
|
1759
|
-
});
|
|
1760
|
-
}
|
|
1761
|
-
}
|
|
1762
|
-
async extractAndPersistGraph(messages, sessionId, chatId, userId, endTime) {
|
|
1763
|
-
const { entities, relations } = await this.llm.extractEntitiesFromMessages(messages);
|
|
1764
|
-
if (entities.length === 0 && relations.length === 0) return;
|
|
1765
|
-
await this.persistGraph(entities, relations, sessionId, chatId, userId, endTime);
|
|
1766
1880
|
}
|
|
1767
1881
|
async persistGraph(rawEntities, rawRelations, sessionId, chatId, userId, messageTime) {
|
|
1768
1882
|
const entities = rawEntities.map((e) => ({
|
|
@@ -1805,13 +1919,6 @@ var CompressManager = class {
|
|
|
1805
1919
|
}
|
|
1806
1920
|
}
|
|
1807
1921
|
};
|
|
1808
|
-
function withTimeout(promise, timeoutMs, label) {
|
|
1809
|
-
let timer;
|
|
1810
|
-
const timeout = new Promise((_, reject) => {
|
|
1811
|
-
timer = setTimeout(() => reject(new Error(`${label} timed out after ${timeoutMs}ms`)), timeoutMs);
|
|
1812
|
-
});
|
|
1813
|
-
return Promise.race([promise, timeout]).finally(() => clearTimeout(timer));
|
|
1814
|
-
}
|
|
1815
1922
|
|
|
1816
1923
|
// src/manager/fact.cache.ts
|
|
1817
1924
|
import { v4 as uuidv42 } from "uuid";
|
|
@@ -2071,29 +2178,20 @@ var KnowledgeManager = class {
|
|
|
2071
2178
|
updatedAt: now
|
|
2072
2179
|
};
|
|
2073
2180
|
await this.lance.addDocument(doc);
|
|
2074
|
-
const
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
if (opts.wait || opts.waitGraph) {
|
|
2080
|
-
await chunkIngest;
|
|
2081
|
-
} else {
|
|
2082
|
-
chunkIngest.catch((err) => {
|
|
2083
|
-
console.error("[KnowledgeManager] background chunk ingest failed:", err);
|
|
2084
|
-
});
|
|
2085
|
-
}
|
|
2086
|
-
if (opts.waitGraph) {
|
|
2087
|
-
await withTimeout2(graphBuild, this.config.graphBuildTimeoutMs, "document graph build");
|
|
2181
|
+
const background = this.semaphore.run(
|
|
2182
|
+
() => this.ingestChunksAndGraph(doc, pieces, shouldBuildGraph)
|
|
2183
|
+
);
|
|
2184
|
+
if (opts.wait) {
|
|
2185
|
+
await background;
|
|
2088
2186
|
} else {
|
|
2089
|
-
|
|
2090
|
-
console.error("[KnowledgeManager] background
|
|
2187
|
+
background.catch((err) => {
|
|
2188
|
+
console.error("[KnowledgeManager] background ingest failed:", err);
|
|
2091
2189
|
});
|
|
2092
2190
|
}
|
|
2093
2191
|
return { docId };
|
|
2094
2192
|
}
|
|
2095
|
-
async
|
|
2096
|
-
if (pieces.length === 0) return
|
|
2193
|
+
async ingestChunksAndGraph(doc, pieces, buildGraph) {
|
|
2194
|
+
if (pieces.length === 0) return;
|
|
2097
2195
|
const redundant = this.config.chunkRedundantIds;
|
|
2098
2196
|
const embedInputs = pieces.map(
|
|
2099
2197
|
(p) => p.headingPath ? `${p.headingPath}
|
|
@@ -2104,7 +2202,7 @@ ${p.content}` : p.content
|
|
|
2104
2202
|
vectors = await this.embed.embed(embedInputs);
|
|
2105
2203
|
} catch (err) {
|
|
2106
2204
|
console.error("[KnowledgeManager] embed chunks failed:", err);
|
|
2107
|
-
return
|
|
2205
|
+
return;
|
|
2108
2206
|
}
|
|
2109
2207
|
const chunks = pieces.map((p, i) => ({
|
|
2110
2208
|
chunkId: uuidv43(),
|
|
@@ -2121,12 +2219,17 @@ ${p.content}` : p.content
|
|
|
2121
2219
|
createdAt: doc.createdAt
|
|
2122
2220
|
}));
|
|
2123
2221
|
await this.lance.addChunks(chunks);
|
|
2124
|
-
|
|
2222
|
+
if (buildGraph) {
|
|
2223
|
+
await this.buildDocumentGraph(doc, chunks).catch((err) => {
|
|
2224
|
+
console.error("[KnowledgeManager] build graph failed:", err);
|
|
2225
|
+
});
|
|
2226
|
+
}
|
|
2125
2227
|
}
|
|
2126
2228
|
async buildDocumentGraph(doc, chunks) {
|
|
2127
|
-
|
|
2229
|
+
let extractedAny = false;
|
|
2230
|
+
for (const chunk of chunks) {
|
|
2128
2231
|
const { entities: rawEntities, relations: rawRelations } = await this.llm.extractEntitiesFromText(chunk.content);
|
|
2129
|
-
if (rawEntities.length === 0 && rawRelations.length === 0)
|
|
2232
|
+
if (rawEntities.length === 0 && rawRelations.length === 0) continue;
|
|
2130
2233
|
const meta = {
|
|
2131
2234
|
userId: doc.userId,
|
|
2132
2235
|
chatId: doc.chatId,
|
|
@@ -2135,47 +2238,46 @@ ${p.content}` : p.content
|
|
|
2135
2238
|
chunkId: chunk.chunkId,
|
|
2136
2239
|
messageTime: doc.createdAt
|
|
2137
2240
|
};
|
|
2138
|
-
const
|
|
2241
|
+
const entities = rawEntities.map((e) => ({
|
|
2139
2242
|
name: e.name,
|
|
2140
2243
|
type: e.type,
|
|
2141
2244
|
meta: { ...meta, ...e.meta }
|
|
2142
2245
|
}));
|
|
2143
|
-
const
|
|
2246
|
+
const relations = rawRelations.map((r) => ({
|
|
2144
2247
|
from: r.from,
|
|
2145
2248
|
to: r.to,
|
|
2146
2249
|
type: r.type,
|
|
2147
2250
|
happenedAt: r.happenedAt ?? void 0,
|
|
2148
2251
|
meta: { ...meta, ...r.meta }
|
|
2149
2252
|
}));
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2253
|
+
const allNames = [
|
|
2254
|
+
.../* @__PURE__ */ new Set([
|
|
2255
|
+
...entities.map((e) => e.name),
|
|
2256
|
+
...relations.flatMap((r) => [r.from, r.to])
|
|
2257
|
+
])
|
|
2258
|
+
];
|
|
2259
|
+
if (allNames.length === 0) continue;
|
|
2260
|
+
const embeddings = /* @__PURE__ */ new Map();
|
|
2261
|
+
try {
|
|
2262
|
+
const vecs = await this.embed.embed(allNames);
|
|
2263
|
+
allNames.forEach((name, i) => embeddings.set(name, vecs[i]));
|
|
2264
|
+
} catch (err) {
|
|
2265
|
+
console.error("[KnowledgeManager] embed entity names failed:", err);
|
|
2266
|
+
continue;
|
|
2267
|
+
}
|
|
2268
|
+
await this.grafeo.upsertEntitiesAndRelations(
|
|
2269
|
+
entities,
|
|
2270
|
+
relations,
|
|
2271
|
+
embeddings,
|
|
2272
|
+
KIND_KNOWLEDGE
|
|
2273
|
+
);
|
|
2274
|
+
extractedAny = true;
|
|
2275
|
+
}
|
|
2276
|
+
if (extractedAny) {
|
|
2277
|
+
await this.lance.updateDocumentGraphFlag(doc.docId, true).catch((err) => {
|
|
2278
|
+
console.error(`[KnowledgeManager] Failed to update graph flag for doc ${doc.docId}:`, err);
|
|
2279
|
+
});
|
|
2169
2280
|
}
|
|
2170
|
-
await this.grafeo.upsertEntitiesAndRelations(
|
|
2171
|
-
entities,
|
|
2172
|
-
relations,
|
|
2173
|
-
embeddings,
|
|
2174
|
-
KIND_KNOWLEDGE
|
|
2175
|
-
);
|
|
2176
|
-
await this.lance.updateDocumentGraphFlag(doc.docId, true).catch((err) => {
|
|
2177
|
-
console.error(`[KnowledgeManager] Failed to update graph flag for doc ${doc.docId}:`, err);
|
|
2178
|
-
});
|
|
2179
2281
|
}
|
|
2180
2282
|
// ── 检索 ───────────────────────────────────────────────────────────────────────
|
|
2181
2283
|
async searchKnowledge(opts) {
|
|
@@ -2293,12 +2395,12 @@ ${p.content}` : p.content
|
|
|
2293
2395
|
});
|
|
2294
2396
|
return true;
|
|
2295
2397
|
}
|
|
2296
|
-
async listDocuments(filter) {
|
|
2398
|
+
async listDocuments(filter, page) {
|
|
2297
2399
|
let docs = await this.lance.getAllDocuments();
|
|
2298
2400
|
if (filter?.userId) docs = docs.filter((d) => d.userId === filter.userId);
|
|
2299
2401
|
if (filter?.chatId) docs = docs.filter((d) => d.chatId === filter.chatId);
|
|
2300
2402
|
if (filter?.sessionId) docs = docs.filter((d) => d.sessionId === filter.sessionId);
|
|
2301
|
-
return docs;
|
|
2403
|
+
return page ? paginate(docs, page) : docs;
|
|
2302
2404
|
}
|
|
2303
2405
|
};
|
|
2304
2406
|
var NO_MATCH = Symbol("no-match");
|
|
@@ -2337,26 +2439,6 @@ function inferTitle(markdown) {
|
|
|
2337
2439
|
}
|
|
2338
2440
|
return void 0;
|
|
2339
2441
|
}
|
|
2340
|
-
async function mapLimit2(items, limit, mapper) {
|
|
2341
|
-
const results = new Array(items.length);
|
|
2342
|
-
let next = 0;
|
|
2343
|
-
const workers = Array.from({ length: Math.min(Math.max(1, limit), items.length) }, async () => {
|
|
2344
|
-
while (true) {
|
|
2345
|
-
const index = next++;
|
|
2346
|
-
if (index >= items.length) return;
|
|
2347
|
-
results[index] = await mapper(items[index], index);
|
|
2348
|
-
}
|
|
2349
|
-
});
|
|
2350
|
-
await Promise.all(workers);
|
|
2351
|
-
return results;
|
|
2352
|
-
}
|
|
2353
|
-
function withTimeout2(promise, timeoutMs, label) {
|
|
2354
|
-
let timer;
|
|
2355
|
-
const timeout = new Promise((_, reject) => {
|
|
2356
|
-
timer = setTimeout(() => reject(new Error(`${label} timed out after ${timeoutMs}ms`)), timeoutMs);
|
|
2357
|
-
});
|
|
2358
|
-
return Promise.race([promise, timeout]).finally(() => clearTimeout(timer));
|
|
2359
|
-
}
|
|
2360
2442
|
|
|
2361
2443
|
// src/manager/session.cache.ts
|
|
2362
2444
|
var SessionCache = class {
|
|
@@ -2571,7 +2653,7 @@ var MemoryManager = class {
|
|
|
2571
2653
|
}
|
|
2572
2654
|
async flushChat(sessionId, opts) {
|
|
2573
2655
|
const sid = sessionId ?? DEFAULT_SESSION_ID;
|
|
2574
|
-
const promise = this.compressManager.triggerCompress(sid, true
|
|
2656
|
+
const promise = this.compressManager.triggerCompress(sid, true);
|
|
2575
2657
|
if (opts?.wait) await promise;
|
|
2576
2658
|
}
|
|
2577
2659
|
async updateFacts(content, level, userId, chatId, sessionId) {
|
|
@@ -2778,33 +2860,28 @@ var MemoryManager = class {
|
|
|
2778
2860
|
const n = Math.min(365, Math.max(1, Math.floor(days)));
|
|
2779
2861
|
return this.lance.trendDaily(n);
|
|
2780
2862
|
}
|
|
2781
|
-
|
|
2782
|
-
listSessions(filter) {
|
|
2863
|
+
listSessions(filter, page) {
|
|
2783
2864
|
let results = [...this.sessionMap.values()];
|
|
2784
2865
|
if (filter?.userId) results = results.filter((s) => s.userId === filter.userId);
|
|
2785
2866
|
if (filter?.chatId) results = results.filter((s) => s.chatId === filter.chatId);
|
|
2786
|
-
|
|
2867
|
+
results.sort((a, b) => b.updatedAt - a.updatedAt || cmpStr(a.sessionId, b.sessionId));
|
|
2868
|
+
return page ? paginate(results, page) : results;
|
|
2787
2869
|
}
|
|
2788
|
-
|
|
2789
|
-
|
|
2790
|
-
|
|
2870
|
+
async listMessages(sessionId, limitOrPage) {
|
|
2871
|
+
if (typeof limitOrPage === "object") {
|
|
2872
|
+
const all = await this.lance.getAllMessagesBySession(sessionId);
|
|
2873
|
+
return paginate(all, limitOrPage);
|
|
2874
|
+
}
|
|
2875
|
+
return this.lance.getLatestMessages(sessionId, limitOrPage ?? 100);
|
|
2791
2876
|
}
|
|
2792
|
-
|
|
2793
|
-
async listTopics(filter) {
|
|
2877
|
+
async listTopics(filter, page) {
|
|
2794
2878
|
let topics = await this.lance.getAllTopics();
|
|
2795
2879
|
if (filter?.sessionId) topics = topics.filter((t) => t.sessionId === filter.sessionId);
|
|
2796
2880
|
if (filter?.userId) topics = topics.filter((t) => t.userId === filter.userId);
|
|
2797
2881
|
if (filter?.chatId) topics = topics.filter((t) => t.chatId === filter.chatId);
|
|
2798
|
-
return topics;
|
|
2882
|
+
return page ? paginate(topics, page) : topics;
|
|
2799
2883
|
}
|
|
2800
|
-
|
|
2801
|
-
* 列出事实,按创建时间倒序。过滤遵循 scope 层级包含语义:
|
|
2802
|
-
* - 按 chatId 过滤时,除该 chat 级 facts 外,还包含该 chat 所属 user 的 user 级 facts
|
|
2803
|
-
* (user 级对其下所有 chat/session 生效)。
|
|
2804
|
-
* - 按 userId 过滤时,返回该 user 名下全部 facts(两级都带 userId)。
|
|
2805
|
-
* - level 过滤在层级过滤之后再叠加。
|
|
2806
|
-
*/
|
|
2807
|
-
listFacts(filter) {
|
|
2884
|
+
listFacts(filter, page) {
|
|
2808
2885
|
let facts = this.factCache.all();
|
|
2809
2886
|
if (filter?.chatId) {
|
|
2810
2887
|
const chatId = filter.chatId;
|
|
@@ -2816,7 +2893,8 @@ var MemoryManager = class {
|
|
|
2816
2893
|
facts = facts.filter((f) => f.userId === filter.userId);
|
|
2817
2894
|
}
|
|
2818
2895
|
if (filter?.level) facts = facts.filter((f) => f.level === filter.level);
|
|
2819
|
-
|
|
2896
|
+
facts.sort((a, b) => b.createdAt - a.createdAt || cmpStr(a.factId, b.factId));
|
|
2897
|
+
return page ? paginate(facts, page) : facts;
|
|
2820
2898
|
}
|
|
2821
2899
|
/** 由 chatId 反查所属 userId:优先用会话表映射,兜底用 chat 级 fact 自身。 */
|
|
2822
2900
|
resolveChatOwner(chatId) {
|
|
@@ -2833,13 +2911,17 @@ var MemoryManager = class {
|
|
|
2833
2911
|
async deleteFact(factId) {
|
|
2834
2912
|
return this.factCache.remove(factId);
|
|
2835
2913
|
}
|
|
2836
|
-
|
|
2837
|
-
|
|
2838
|
-
|
|
2914
|
+
async listEntities(filter, page) {
|
|
2915
|
+
if (!page) return this.grafeo.getAllEntities(filter);
|
|
2916
|
+
const { offset, limit } = normalizePage(page);
|
|
2917
|
+
const { items, total } = await this.grafeo.pageEntities(filter, page);
|
|
2918
|
+
return { items, total, offset, limit };
|
|
2839
2919
|
}
|
|
2840
|
-
|
|
2841
|
-
|
|
2842
|
-
|
|
2920
|
+
async listRelations(filter, page) {
|
|
2921
|
+
if (!page) return this.grafeo.getAllRelations(filter);
|
|
2922
|
+
const { offset, limit } = normalizePage(page);
|
|
2923
|
+
const { items, total } = await this.grafeo.pageRelations(filter, page);
|
|
2924
|
+
return { items, total, offset, limit };
|
|
2843
2925
|
}
|
|
2844
2926
|
// ── 知识库 API ────────────────────────────────────────────────────────────────
|
|
2845
2927
|
/** 摄入一个文档(markdown)。document 落库即返回,切块/embedding/图谱后台执行(wait=true 可等待)。*/
|
|
@@ -2858,9 +2940,8 @@ var MemoryManager = class {
|
|
|
2858
2940
|
async deleteDocument(docId) {
|
|
2859
2941
|
return this.knowledgeManager.deleteDocument(docId);
|
|
2860
2942
|
}
|
|
2861
|
-
|
|
2862
|
-
|
|
2863
|
-
return this.knowledgeManager.listDocuments(filter);
|
|
2943
|
+
async listDocuments(filter, page) {
|
|
2944
|
+
return page ? this.knowledgeManager.listDocuments(filter, page) : this.knowledgeManager.listDocuments(filter);
|
|
2864
2945
|
}
|
|
2865
2946
|
destroy() {
|
|
2866
2947
|
this.grafeo.close();
|