@ppagent/memory 0.1.1 → 0.1.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/dist/index.d.ts +173 -117
- package/dist/index.js +355 -159
- package/llms.txt +845 -0
- package/package.json +10 -8
package/dist/index.js
CHANGED
|
@@ -25,9 +25,10 @@ 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 ?? 6e4,
|
|
29
29
|
httpMaxRetries: config.httpMaxRetries ?? 2,
|
|
30
30
|
embeddingBatchSize: config.embeddingBatchSize ?? 20,
|
|
31
|
+
embeddingConcurrency: config.embeddingConcurrency ?? 2,
|
|
31
32
|
maxConcurrentCompressions: config.maxConcurrentCompressions ?? 3,
|
|
32
33
|
entitySimilarityThreshold: config.entitySimilarityThreshold ?? 0.92,
|
|
33
34
|
defaultSearchLimit: config.defaultSearchLimit ?? 10,
|
|
@@ -42,7 +43,13 @@ function resolveConfig(config) {
|
|
|
42
43
|
knowledgeGraphTriggerScore: config.knowledgeGraphTriggerScore ?? 0.78,
|
|
43
44
|
knowledgeGraphEntityTopK: config.knowledgeGraphEntityTopK ?? 10,
|
|
44
45
|
knowledgeGraphAnchorTopK: config.knowledgeGraphAnchorTopK ?? 3,
|
|
45
|
-
knowledgeGraphHopLimit: config.knowledgeGraphHopLimit ?? 10
|
|
46
|
+
knowledgeGraphHopLimit: config.knowledgeGraphHopLimit ?? 10,
|
|
47
|
+
graphExtractConcurrency: config.graphExtractConcurrency ?? 2,
|
|
48
|
+
graphBuildTimeoutMs: config.graphBuildTimeoutMs ?? 12e4,
|
|
49
|
+
autoOptimizeOnInit: config.autoOptimizeOnInit ?? true,
|
|
50
|
+
autoOptimizeIntervalMs: config.autoOptimizeIntervalMs ?? 6 * 36e5,
|
|
51
|
+
optimizeVersionRetentionMs: config.optimizeVersionRetentionMs ?? 0,
|
|
52
|
+
restoreConcurrency: config.restoreConcurrency ?? 8
|
|
46
53
|
};
|
|
47
54
|
}
|
|
48
55
|
|
|
@@ -162,6 +169,16 @@ import { GrafeoDB } from "@grafeo-db/js";
|
|
|
162
169
|
import { get_encoding } from "@dqbd/tiktoken";
|
|
163
170
|
|
|
164
171
|
// src/llm/http.ts
|
|
172
|
+
function normalizeFetchError(err, timeoutMs, errorLabel) {
|
|
173
|
+
if (isAbortError(err)) {
|
|
174
|
+
return new Error(`${errorLabel} request timed out after ${timeoutMs}ms`, { cause: err });
|
|
175
|
+
}
|
|
176
|
+
if (err instanceof Error) return err;
|
|
177
|
+
return new Error(String(err));
|
|
178
|
+
}
|
|
179
|
+
function isAbortError(err) {
|
|
180
|
+
return typeof err === "object" && err !== null && "name" in err && err.name === "AbortError";
|
|
181
|
+
}
|
|
165
182
|
function backoffDelay(attempt) {
|
|
166
183
|
return Math.min(1e3 * 2 ** attempt, 8e3);
|
|
167
184
|
}
|
|
@@ -170,7 +187,9 @@ async function postJsonWithRetry(url, headers, body, opts, errorLabel = "HTTP")
|
|
|
170
187
|
let lastErr;
|
|
171
188
|
for (let attempt = 0; attempt <= opts.maxRetries; attempt++) {
|
|
172
189
|
const controller = new AbortController();
|
|
173
|
-
const timer = setTimeout(() =>
|
|
190
|
+
const timer = setTimeout(() => {
|
|
191
|
+
controller.abort(new Error(`${errorLabel} request timed out after ${opts.timeoutMs}ms`));
|
|
192
|
+
}, opts.timeoutMs);
|
|
174
193
|
let res;
|
|
175
194
|
try {
|
|
176
195
|
res = await fetch(url, {
|
|
@@ -181,12 +200,12 @@ async function postJsonWithRetry(url, headers, body, opts, errorLabel = "HTTP")
|
|
|
181
200
|
});
|
|
182
201
|
} catch (err) {
|
|
183
202
|
clearTimeout(timer);
|
|
184
|
-
lastErr = err;
|
|
203
|
+
lastErr = normalizeFetchError(err, opts.timeoutMs, errorLabel);
|
|
185
204
|
if (attempt < opts.maxRetries) {
|
|
186
205
|
await sleep(backoffDelay(attempt));
|
|
187
206
|
continue;
|
|
188
207
|
}
|
|
189
|
-
throw
|
|
208
|
+
throw lastErr;
|
|
190
209
|
}
|
|
191
210
|
clearTimeout(timer);
|
|
192
211
|
if (res.ok) {
|
|
@@ -226,27 +245,45 @@ var EmbedService = class {
|
|
|
226
245
|
const inputs = Array.isArray(texts) ? texts : [texts];
|
|
227
246
|
const url = `${this.config.embeddingBaseUrl.replace(/\/$/, "")}/embeddings`;
|
|
228
247
|
const batchSize = Math.max(1, this.config.embeddingBatchSize);
|
|
248
|
+
const concurrency = Math.max(1, this.config.embeddingConcurrency);
|
|
229
249
|
const retryOpts = { timeoutMs: this.config.httpTimeoutMs, maxRetries: this.config.httpMaxRetries };
|
|
230
|
-
const
|
|
250
|
+
const batches = [];
|
|
231
251
|
for (let i = 0; i < inputs.length; i += batchSize) {
|
|
232
|
-
|
|
252
|
+
batches.push({ index: batches.length, texts: inputs.slice(i, i + batchSize) });
|
|
253
|
+
}
|
|
254
|
+
const batchResults = await mapLimit(batches, concurrency, async (batch) => {
|
|
233
255
|
const data = await postJsonWithRetry(
|
|
234
256
|
url,
|
|
235
257
|
{ Authorization: `Bearer ${this.config.embeddingApiKey}` },
|
|
236
|
-
{ model: this.config.embeddingModel, input: batch },
|
|
258
|
+
{ model: this.config.embeddingModel, input: batch.texts },
|
|
237
259
|
retryOpts,
|
|
238
260
|
"Embedding API"
|
|
239
261
|
);
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
262
|
+
return {
|
|
263
|
+
index: batch.index,
|
|
264
|
+
vectors: data.data.sort((a, b) => a.index - b.index).map((d) => d.embedding)
|
|
265
|
+
};
|
|
266
|
+
});
|
|
267
|
+
return batchResults.sort((a, b) => a.index - b.index).flatMap((b) => b.vectors);
|
|
244
268
|
}
|
|
245
269
|
async embedOne(text) {
|
|
246
270
|
const result = await this.embed([text]);
|
|
247
271
|
return result[0];
|
|
248
272
|
}
|
|
249
273
|
};
|
|
274
|
+
async function mapLimit(items, limit, mapper) {
|
|
275
|
+
const results = new Array(items.length);
|
|
276
|
+
let next = 0;
|
|
277
|
+
const workers = Array.from({ length: Math.min(limit, items.length) }, async () => {
|
|
278
|
+
while (true) {
|
|
279
|
+
const index = next++;
|
|
280
|
+
if (index >= items.length) return;
|
|
281
|
+
results[index] = await mapper(items[index], index);
|
|
282
|
+
}
|
|
283
|
+
});
|
|
284
|
+
await Promise.all(workers);
|
|
285
|
+
return results;
|
|
286
|
+
}
|
|
250
287
|
function cosineSimilarity(a, b) {
|
|
251
288
|
let dot = 0;
|
|
252
289
|
let normA = 0;
|
|
@@ -979,6 +1016,24 @@ function safeParseObject(s) {
|
|
|
979
1016
|
return {};
|
|
980
1017
|
}
|
|
981
1018
|
}
|
|
1019
|
+
var MESSAGES_INDEXES = [
|
|
1020
|
+
{ column: "message_id" },
|
|
1021
|
+
{ column: "session_id" },
|
|
1022
|
+
{ column: "content", fts: true },
|
|
1023
|
+
{ column: "metadata", fts: true }
|
|
1024
|
+
// 支持 metadata 内容全文检索
|
|
1025
|
+
];
|
|
1026
|
+
var TOPICS_INDEXES = [
|
|
1027
|
+
{ column: "chat_id" },
|
|
1028
|
+
{ column: "detail", fts: true }
|
|
1029
|
+
];
|
|
1030
|
+
var SESSIONS_INDEXES = [{ column: "session_id" }];
|
|
1031
|
+
var DOCUMENTS_INDEXES = [{ column: "doc_id" }];
|
|
1032
|
+
var CHUNKS_INDEXES = [
|
|
1033
|
+
{ column: "doc_id" },
|
|
1034
|
+
{ column: "content", fts: true }
|
|
1035
|
+
// 知识片段混合检索
|
|
1036
|
+
];
|
|
982
1037
|
var LanceService = class {
|
|
983
1038
|
config;
|
|
984
1039
|
conn;
|
|
@@ -991,6 +1046,7 @@ var LanceService = class {
|
|
|
991
1046
|
// Scalar indexes cannot be created on empty tables (LanceDB btree limitation).
|
|
992
1047
|
// These flags defer creation to the first insert.
|
|
993
1048
|
isNewMessagesTable = false;
|
|
1049
|
+
isNewTopicsTable = false;
|
|
994
1050
|
isNewSessionsTable = false;
|
|
995
1051
|
isNewDocumentsTable = false;
|
|
996
1052
|
isNewChunksTable = false;
|
|
@@ -1004,7 +1060,6 @@ var LanceService = class {
|
|
|
1004
1060
|
if (existingTables.includes(MESSAGES_TABLE)) {
|
|
1005
1061
|
this.messagesTable = await this.conn.openTable(MESSAGES_TABLE);
|
|
1006
1062
|
await this._ensurePartsColumn();
|
|
1007
|
-
await this.ensureScalarIndex(this.messagesTable, "message_id");
|
|
1008
1063
|
} else {
|
|
1009
1064
|
this.messagesTable = await this.conn.createEmptyTable(
|
|
1010
1065
|
MESSAGES_TABLE,
|
|
@@ -1020,6 +1075,7 @@ var LanceService = class {
|
|
|
1020
1075
|
TOPICS_TABLE,
|
|
1021
1076
|
topicsSchema(dim)
|
|
1022
1077
|
);
|
|
1078
|
+
this.isNewTopicsTable = true;
|
|
1023
1079
|
}
|
|
1024
1080
|
if (existingTables.includes(FACTS_TABLE)) {
|
|
1025
1081
|
this.factsTable = await this.conn.openTable(FACTS_TABLE);
|
|
@@ -1028,43 +1084,83 @@ var LanceService = class {
|
|
|
1028
1084
|
}
|
|
1029
1085
|
if (existingTables.includes(SESSIONS_TABLE)) {
|
|
1030
1086
|
this.sessionsTable = await this.conn.openTable(SESSIONS_TABLE);
|
|
1031
|
-
await this.ensureScalarIndex(this.sessionsTable, "session_id");
|
|
1032
1087
|
} else {
|
|
1033
1088
|
this.sessionsTable = await this.conn.createEmptyTable(SESSIONS_TABLE, sessionsSchema());
|
|
1034
1089
|
this.isNewSessionsTable = true;
|
|
1035
1090
|
}
|
|
1036
1091
|
if (existingTables.includes(DOCUMENTS_TABLE)) {
|
|
1037
1092
|
this.documentsTable = await this.conn.openTable(DOCUMENTS_TABLE);
|
|
1038
|
-
await this.ensureScalarIndex(this.documentsTable, "doc_id");
|
|
1039
1093
|
} else {
|
|
1040
1094
|
this.documentsTable = await this.conn.createEmptyTable(DOCUMENTS_TABLE, documentsSchema(dim));
|
|
1041
1095
|
this.isNewDocumentsTable = true;
|
|
1042
1096
|
}
|
|
1043
1097
|
if (existingTables.includes(CHUNKS_TABLE)) {
|
|
1044
1098
|
this.chunksTable = await this.conn.openTable(CHUNKS_TABLE);
|
|
1045
|
-
await this.ensureScalarIndex(this.chunksTable, "doc_id");
|
|
1046
1099
|
} else {
|
|
1047
1100
|
this.chunksTable = await this.conn.createEmptyTable(CHUNKS_TABLE, chunksSchema(dim));
|
|
1048
1101
|
this.isNewChunksTable = true;
|
|
1049
1102
|
}
|
|
1050
|
-
await this.
|
|
1103
|
+
await this.ensureIndexes(this.messagesTable, MESSAGES_INDEXES);
|
|
1104
|
+
await this.ensureIndexes(this.topicsTable, TOPICS_INDEXES);
|
|
1105
|
+
await this.ensureIndexes(this.sessionsTable, SESSIONS_INDEXES);
|
|
1106
|
+
await this.ensureIndexes(this.documentsTable, DOCUMENTS_INDEXES);
|
|
1107
|
+
await this.ensureIndexes(this.chunksTable, CHUNKS_INDEXES);
|
|
1051
1108
|
}
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1109
|
+
/**
|
|
1110
|
+
* 按需补齐索引:先经 listIndices 判存在,缺失的列才 createIndex(且 replace:false)。
|
|
1111
|
+
* createIndex 默认 replace:true 会在每次启动时全量重建索引并提交新表版本——
|
|
1112
|
+
* 这正是历史上「启动越来越慢 + _versions 目录膨胀」的根源,绝不可回退到无条件 createIndex。
|
|
1113
|
+
*/
|
|
1114
|
+
async ensureIndexes(table, specs) {
|
|
1115
|
+
let indexed;
|
|
1116
|
+
try {
|
|
1117
|
+
indexed = new Set((await table.listIndices()).flatMap((i) => i.columns));
|
|
1118
|
+
} catch {
|
|
1119
|
+
indexed = /* @__PURE__ */ new Set();
|
|
1120
|
+
}
|
|
1121
|
+
for (const { column, fts } of specs) {
|
|
1122
|
+
if (indexed.has(column)) continue;
|
|
1062
1123
|
try {
|
|
1063
|
-
await table.createIndex(
|
|
1124
|
+
await table.createIndex(
|
|
1125
|
+
column,
|
|
1126
|
+
fts ? { config: lancedb.Index.fts(), replace: false } : { replace: false }
|
|
1127
|
+
);
|
|
1064
1128
|
} catch {
|
|
1065
1129
|
}
|
|
1066
1130
|
}
|
|
1067
1131
|
}
|
|
1132
|
+
/**
|
|
1133
|
+
* 存储压实:逐表执行碎片合并 + 清理 retentionMs 之前的历史版本。
|
|
1134
|
+
* 嵌入式场景下 LanceDB 不会自动做这件事,长期运行后版本/碎片无限累积会显著拖慢启动与查询。
|
|
1135
|
+
*/
|
|
1136
|
+
async optimizeStorage(retentionMs = 0) {
|
|
1137
|
+
const cutoff = new Date(Date.now() - Math.max(0, retentionMs));
|
|
1138
|
+
const targets = [
|
|
1139
|
+
[MESSAGES_TABLE, this.messagesTable],
|
|
1140
|
+
[TOPICS_TABLE, this.topicsTable],
|
|
1141
|
+
[FACTS_TABLE, this.factsTable],
|
|
1142
|
+
[SESSIONS_TABLE, this.sessionsTable],
|
|
1143
|
+
[DOCUMENTS_TABLE, this.documentsTable],
|
|
1144
|
+
[CHUNKS_TABLE, this.chunksTable]
|
|
1145
|
+
];
|
|
1146
|
+
const results = [];
|
|
1147
|
+
for (const [name, table] of targets) {
|
|
1148
|
+
try {
|
|
1149
|
+
const stats = await table.optimize({ cleanupOlderThan: cutoff });
|
|
1150
|
+
results.push({
|
|
1151
|
+
table: name,
|
|
1152
|
+
fragmentsRemoved: stats.compaction.fragmentsRemoved,
|
|
1153
|
+
fragmentsAdded: stats.compaction.fragmentsAdded,
|
|
1154
|
+
filesRemoved: stats.compaction.filesRemoved,
|
|
1155
|
+
oldVersionsRemoved: stats.prune.oldVersionsRemoved,
|
|
1156
|
+
bytesRemoved: Number(stats.prune.bytesRemoved)
|
|
1157
|
+
});
|
|
1158
|
+
} catch (err) {
|
|
1159
|
+
console.warn(`[LanceService] optimize table "${name}" failed:`, err);
|
|
1160
|
+
}
|
|
1161
|
+
}
|
|
1162
|
+
return results;
|
|
1163
|
+
}
|
|
1068
1164
|
/**
|
|
1069
1165
|
* 为存量 messages 表添加 parts 列(如果缺失)。
|
|
1070
1166
|
* LanceDB 0.14+ 支持 addColumns;旧版本会 throw,此时 rowToMessage 的 ?? "[]" 兜底。
|
|
@@ -1101,24 +1197,20 @@ var LanceService = class {
|
|
|
1101
1197
|
} catch {
|
|
1102
1198
|
}
|
|
1103
1199
|
}
|
|
1104
|
-
// LanceDB btree scalar indexes require at least one row.
|
|
1105
|
-
// For existing tables (opened in init) this is safe; for new tables we defer via isNew*Table flags.
|
|
1106
|
-
async ensureScalarIndex(table, column) {
|
|
1107
|
-
try {
|
|
1108
|
-
await table.createIndex(column);
|
|
1109
|
-
} catch {
|
|
1110
|
-
}
|
|
1111
|
-
}
|
|
1112
1200
|
async addMessages(messages) {
|
|
1113
1201
|
if (messages.length === 0) return;
|
|
1114
1202
|
await this.messagesTable.add(messages.map(messageToRow));
|
|
1115
1203
|
if (this.isNewMessagesTable) {
|
|
1116
|
-
await this.
|
|
1204
|
+
await this.ensureIndexes(this.messagesTable, MESSAGES_INDEXES);
|
|
1117
1205
|
this.isNewMessagesTable = false;
|
|
1118
1206
|
}
|
|
1119
1207
|
}
|
|
1120
1208
|
async addTopic(topic) {
|
|
1121
1209
|
await this.topicsTable.add([topicToRow(topic)]);
|
|
1210
|
+
if (this.isNewTopicsTable) {
|
|
1211
|
+
await this.ensureIndexes(this.topicsTable, TOPICS_INDEXES);
|
|
1212
|
+
this.isNewTopicsTable = false;
|
|
1213
|
+
}
|
|
1122
1214
|
}
|
|
1123
1215
|
async updateTopicRecallCount(summaryId, count) {
|
|
1124
1216
|
await this.topicsTable.update({
|
|
@@ -1160,9 +1252,13 @@ var LanceService = class {
|
|
|
1160
1252
|
}
|
|
1161
1253
|
}
|
|
1162
1254
|
async getLatestMessages(sessionId, limit) {
|
|
1255
|
+
if (limit <= 0) return [];
|
|
1163
1256
|
try {
|
|
1164
|
-
const rows = await this.messagesTable.query().where(eqFilter("session_id", sessionId)).
|
|
1165
|
-
|
|
1257
|
+
const rows = await this.messagesTable.query().where(eqFilter("session_id", sessionId)).orderBy([
|
|
1258
|
+
{ columnName: "created_at", ascending: false },
|
|
1259
|
+
{ columnName: "message_id", ascending: false }
|
|
1260
|
+
]).limit(limit).toArray();
|
|
1261
|
+
return rows.reverse().map(rowToMessage);
|
|
1166
1262
|
} catch {
|
|
1167
1263
|
return [];
|
|
1168
1264
|
}
|
|
@@ -1251,7 +1347,7 @@ var LanceService = class {
|
|
|
1251
1347
|
async insertSession(session) {
|
|
1252
1348
|
await this.sessionsTable.add([sessionToRow(session)]);
|
|
1253
1349
|
if (this.isNewSessionsTable) {
|
|
1254
|
-
await this.
|
|
1350
|
+
await this.ensureIndexes(this.sessionsTable, SESSIONS_INDEXES);
|
|
1255
1351
|
this.isNewSessionsTable = false;
|
|
1256
1352
|
}
|
|
1257
1353
|
}
|
|
@@ -1357,7 +1453,7 @@ var LanceService = class {
|
|
|
1357
1453
|
async addDocument(doc) {
|
|
1358
1454
|
await this.documentsTable.add([documentToRow(doc)]);
|
|
1359
1455
|
if (this.isNewDocumentsTable) {
|
|
1360
|
-
await this.
|
|
1456
|
+
await this.ensureIndexes(this.documentsTable, DOCUMENTS_INDEXES);
|
|
1361
1457
|
this.isNewDocumentsTable = false;
|
|
1362
1458
|
}
|
|
1363
1459
|
}
|
|
@@ -1365,7 +1461,7 @@ var LanceService = class {
|
|
|
1365
1461
|
if (chunks.length === 0) return;
|
|
1366
1462
|
await this.chunksTable.add(chunks.map(chunkToRow));
|
|
1367
1463
|
if (this.isNewChunksTable) {
|
|
1368
|
-
await this.
|
|
1464
|
+
await this.ensureIndexes(this.chunksTable, CHUNKS_INDEXES);
|
|
1369
1465
|
this.isNewChunksTable = false;
|
|
1370
1466
|
}
|
|
1371
1467
|
}
|
|
@@ -1488,6 +1584,40 @@ var LanceService = class {
|
|
|
1488
1584
|
}
|
|
1489
1585
|
};
|
|
1490
1586
|
|
|
1587
|
+
// src/manager/semaphore.ts
|
|
1588
|
+
var Semaphore = class {
|
|
1589
|
+
count;
|
|
1590
|
+
queue = [];
|
|
1591
|
+
constructor(max) {
|
|
1592
|
+
this.count = max;
|
|
1593
|
+
}
|
|
1594
|
+
async run(fn) {
|
|
1595
|
+
await this.acquire();
|
|
1596
|
+
try {
|
|
1597
|
+
return await fn();
|
|
1598
|
+
} finally {
|
|
1599
|
+
this.release();
|
|
1600
|
+
}
|
|
1601
|
+
}
|
|
1602
|
+
acquire() {
|
|
1603
|
+
if (this.count > 0) {
|
|
1604
|
+
this.count--;
|
|
1605
|
+
return Promise.resolve();
|
|
1606
|
+
}
|
|
1607
|
+
return new Promise((resolve) => {
|
|
1608
|
+
this.queue.push(resolve);
|
|
1609
|
+
});
|
|
1610
|
+
}
|
|
1611
|
+
release() {
|
|
1612
|
+
const next = this.queue.shift();
|
|
1613
|
+
if (next) {
|
|
1614
|
+
next();
|
|
1615
|
+
} else {
|
|
1616
|
+
this.count++;
|
|
1617
|
+
}
|
|
1618
|
+
}
|
|
1619
|
+
};
|
|
1620
|
+
|
|
1491
1621
|
// src/llm/llm.service.ts
|
|
1492
1622
|
var ENTITY_EXTRACTOR_TOOL = {
|
|
1493
1623
|
type: "function",
|
|
@@ -1602,7 +1732,7 @@ var LlmService = class {
|
|
|
1602
1732
|
return data.choices[0].message;
|
|
1603
1733
|
}
|
|
1604
1734
|
// ── 第一步:生成三级摘要(JSON 输出)──────────────────────────────────────────
|
|
1605
|
-
async
|
|
1735
|
+
async summarizeMessages(messages) {
|
|
1606
1736
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1607
1737
|
const { detailMaxTokens, summaryMaxTokens, conciseMaxTokens } = this.config;
|
|
1608
1738
|
const systemPrompt = `\u4F60\u662F\u4E00\u4E2A\u5BF9\u8BDD\u8BB0\u5FC6\u7BA1\u7406\u52A9\u624B\u3002\u5F53\u524D\u7CFB\u7EDF\u65F6\u95F4\uFF1A${now}\u3002
|
|
@@ -1624,7 +1754,7 @@ ${formatted}`);
|
|
|
1624
1754
|
return { title: parsed.title ?? "", detail: parsed.detail, summary: parsed.summary, concise: parsed.concise };
|
|
1625
1755
|
}
|
|
1626
1756
|
// ── 第二步:通过工具调用提取实体和关系 ──────────────────────────────────────
|
|
1627
|
-
async
|
|
1757
|
+
async extractEntitiesFromMessages(messages) {
|
|
1628
1758
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1629
1759
|
const systemPrompt = `\u4F60\u662F\u4E00\u4E2A\u5B9E\u4F53\u5173\u7CFB\u62BD\u53D6\u52A9\u624B\u3002\u5F53\u524D\u7CFB\u7EDF\u65F6\u95F4\uFF1A${now}\u3002
|
|
1630
1760
|
\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
|
|
@@ -1652,15 +1782,15 @@ ${formatted}`
|
|
|
1652
1782
|
}
|
|
1653
1783
|
}
|
|
1654
1784
|
} catch (err) {
|
|
1655
|
-
console.error("[LlmService]
|
|
1785
|
+
console.error("[LlmService] extractEntitiesFromMessages failed:", err);
|
|
1656
1786
|
}
|
|
1657
1787
|
return { entities: [], relations: [] };
|
|
1658
1788
|
}
|
|
1659
1789
|
// ── 对外主接口:压缩 + 实体抽取(并发执行两步)────────────────────────────────
|
|
1660
1790
|
async compress(messages) {
|
|
1661
1791
|
const [summary, extraction] = await Promise.all([
|
|
1662
|
-
this.
|
|
1663
|
-
this.
|
|
1792
|
+
this.summarizeMessages(messages),
|
|
1793
|
+
this.extractEntitiesFromMessages(messages)
|
|
1664
1794
|
]);
|
|
1665
1795
|
return {
|
|
1666
1796
|
title: summary.title,
|
|
@@ -1758,42 +1888,6 @@ ${context}`, false);
|
|
|
1758
1888
|
|
|
1759
1889
|
// src/manager/compress.manager.ts
|
|
1760
1890
|
import { v4 as uuidv4 } from "uuid";
|
|
1761
|
-
|
|
1762
|
-
// src/manager/semaphore.ts
|
|
1763
|
-
var Semaphore = class {
|
|
1764
|
-
count;
|
|
1765
|
-
queue = [];
|
|
1766
|
-
constructor(max) {
|
|
1767
|
-
this.count = max;
|
|
1768
|
-
}
|
|
1769
|
-
async run(fn) {
|
|
1770
|
-
await this.acquire();
|
|
1771
|
-
try {
|
|
1772
|
-
return await fn();
|
|
1773
|
-
} finally {
|
|
1774
|
-
this.release();
|
|
1775
|
-
}
|
|
1776
|
-
}
|
|
1777
|
-
acquire() {
|
|
1778
|
-
if (this.count > 0) {
|
|
1779
|
-
this.count--;
|
|
1780
|
-
return Promise.resolve();
|
|
1781
|
-
}
|
|
1782
|
-
return new Promise((resolve) => {
|
|
1783
|
-
this.queue.push(resolve);
|
|
1784
|
-
});
|
|
1785
|
-
}
|
|
1786
|
-
release() {
|
|
1787
|
-
const next = this.queue.shift();
|
|
1788
|
-
if (next) {
|
|
1789
|
-
next();
|
|
1790
|
-
} else {
|
|
1791
|
-
this.count++;
|
|
1792
|
-
}
|
|
1793
|
-
}
|
|
1794
|
-
};
|
|
1795
|
-
|
|
1796
|
-
// src/manager/compress.manager.ts
|
|
1797
1891
|
var CompressManager = class {
|
|
1798
1892
|
config;
|
|
1799
1893
|
lance;
|
|
@@ -1812,17 +1906,17 @@ var CompressManager = class {
|
|
|
1812
1906
|
this.sessionCache = sessionCache;
|
|
1813
1907
|
this.semaphore = new Semaphore(config.maxConcurrentCompressions);
|
|
1814
1908
|
}
|
|
1815
|
-
triggerCompress(sessionId, force = false) {
|
|
1909
|
+
triggerCompress(sessionId, force = false, waitGraph = false) {
|
|
1816
1910
|
const chain = this.sessionChain.get(sessionId) ?? Promise.resolve();
|
|
1817
1911
|
const next = chain.then(
|
|
1818
|
-
() => this.semaphore.run(() => this.doCompress(sessionId, force))
|
|
1912
|
+
() => this.semaphore.run(() => this.doCompress(sessionId, force, waitGraph))
|
|
1819
1913
|
);
|
|
1820
1914
|
this.sessionChain.set(sessionId, next.catch(() => {
|
|
1821
1915
|
}));
|
|
1822
1916
|
return next;
|
|
1823
1917
|
}
|
|
1824
|
-
async doCompress(sessionId, force) {
|
|
1825
|
-
const messages = this.sessionCache.getSessionMessages(sessionId);
|
|
1918
|
+
async doCompress(sessionId, force, waitGraph) {
|
|
1919
|
+
const messages = this.sessionCache.getSessionMessages(sessionId).slice();
|
|
1826
1920
|
if (messages.length === 0) return;
|
|
1827
1921
|
const entry = this.sessionCache.getEntry(sessionId);
|
|
1828
1922
|
if (!entry) return;
|
|
@@ -1830,15 +1924,15 @@ var CompressManager = class {
|
|
|
1830
1924
|
const { chatId, userId } = entry.ids;
|
|
1831
1925
|
const startTime = messages[0].createdAt;
|
|
1832
1926
|
const endTime = messages[messages.length - 1].createdAt;
|
|
1833
|
-
let
|
|
1927
|
+
let summary;
|
|
1834
1928
|
try {
|
|
1835
|
-
|
|
1929
|
+
summary = await this.llm.summarizeMessages(messages);
|
|
1836
1930
|
} catch (err) {
|
|
1837
|
-
console.error(`[CompressManager] LLM compress failed for session ${sessionId}
|
|
1931
|
+
console.error(`[CompressManager] LLM compress failed for session ${sessionId}: ${formatError(err)}`);
|
|
1838
1932
|
return;
|
|
1839
1933
|
}
|
|
1840
|
-
const { title, detail, summary, concise
|
|
1841
|
-
const topicTextForEmbed = [detail,
|
|
1934
|
+
const { title, detail, summary: summaryText, concise } = summary;
|
|
1935
|
+
const topicTextForEmbed = [detail, summaryText, concise].find((t) => t.length > 0) ?? "";
|
|
1842
1936
|
let topicVector = [];
|
|
1843
1937
|
try {
|
|
1844
1938
|
topicVector = await this.embed.embedOne(topicTextForEmbed);
|
|
@@ -1852,7 +1946,7 @@ var CompressManager = class {
|
|
|
1852
1946
|
chatId,
|
|
1853
1947
|
title,
|
|
1854
1948
|
detail,
|
|
1855
|
-
summary,
|
|
1949
|
+
summary: summaryText,
|
|
1856
1950
|
concise,
|
|
1857
1951
|
startTime,
|
|
1858
1952
|
endTime,
|
|
@@ -1866,9 +1960,6 @@ var CompressManager = class {
|
|
|
1866
1960
|
} catch (err) {
|
|
1867
1961
|
console.error(`[CompressManager] Save topic failed:`, err);
|
|
1868
1962
|
}
|
|
1869
|
-
if (entities.length > 0 || relations.length > 0) {
|
|
1870
|
-
await this.persistGraph(entities, relations, sessionId, chatId, userId, endTime);
|
|
1871
|
-
}
|
|
1872
1963
|
this.sessionCache.clearMessages(sessionId, endTime);
|
|
1873
1964
|
const [n1, n2, n3] = this.config.topicRatio;
|
|
1874
1965
|
try {
|
|
@@ -1877,6 +1968,19 @@ var CompressManager = class {
|
|
|
1877
1968
|
} catch (err) {
|
|
1878
1969
|
console.error(`[CompressManager] Rebuild history window failed:`, err);
|
|
1879
1970
|
}
|
|
1971
|
+
const graphTask = this.extractAndPersistGraph(messages, sessionId, chatId, userId, endTime);
|
|
1972
|
+
if (waitGraph) {
|
|
1973
|
+
await withTimeout(graphTask, this.config.graphBuildTimeoutMs, "flushChat graph build");
|
|
1974
|
+
} else {
|
|
1975
|
+
graphTask.catch((err) => {
|
|
1976
|
+
console.error(`[CompressManager] Background graph persist failed:`, err);
|
|
1977
|
+
});
|
|
1978
|
+
}
|
|
1979
|
+
}
|
|
1980
|
+
async extractAndPersistGraph(messages, sessionId, chatId, userId, endTime) {
|
|
1981
|
+
const { entities, relations } = await this.llm.extractEntitiesFromMessages(messages);
|
|
1982
|
+
if (entities.length === 0 && relations.length === 0) return;
|
|
1983
|
+
await this.persistGraph(entities, relations, sessionId, chatId, userId, endTime);
|
|
1880
1984
|
}
|
|
1881
1985
|
async persistGraph(rawEntities, rawRelations, sessionId, chatId, userId, messageTime) {
|
|
1882
1986
|
const entities = rawEntities.map((e) => ({
|
|
@@ -1919,6 +2023,16 @@ var CompressManager = class {
|
|
|
1919
2023
|
}
|
|
1920
2024
|
}
|
|
1921
2025
|
};
|
|
2026
|
+
function formatError(err) {
|
|
2027
|
+
return err instanceof Error ? err.message : String(err);
|
|
2028
|
+
}
|
|
2029
|
+
function withTimeout(promise, timeoutMs, label) {
|
|
2030
|
+
let timer;
|
|
2031
|
+
const timeout = new Promise((_, reject) => {
|
|
2032
|
+
timer = setTimeout(() => reject(new Error(`${label} timed out after ${timeoutMs}ms`)), timeoutMs);
|
|
2033
|
+
});
|
|
2034
|
+
return Promise.race([promise, timeout]).finally(() => clearTimeout(timer));
|
|
2035
|
+
}
|
|
1922
2036
|
|
|
1923
2037
|
// src/manager/fact.cache.ts
|
|
1924
2038
|
import { v4 as uuidv42 } from "uuid";
|
|
@@ -2178,20 +2292,29 @@ var KnowledgeManager = class {
|
|
|
2178
2292
|
updatedAt: now
|
|
2179
2293
|
};
|
|
2180
2294
|
await this.lance.addDocument(doc);
|
|
2181
|
-
const
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2295
|
+
const chunkIngest = this.semaphore.run(() => this.ingestChunks(doc, pieces));
|
|
2296
|
+
const graphBuild = chunkIngest.then((chunks) => {
|
|
2297
|
+
if (!shouldBuildGraph) return;
|
|
2298
|
+
return this.buildDocumentGraph(doc, chunks);
|
|
2299
|
+
});
|
|
2300
|
+
if (opts.wait || opts.waitGraph) {
|
|
2301
|
+
await chunkIngest;
|
|
2186
2302
|
} else {
|
|
2187
|
-
|
|
2188
|
-
console.error("[KnowledgeManager] background ingest failed:", err);
|
|
2303
|
+
chunkIngest.catch((err) => {
|
|
2304
|
+
console.error("[KnowledgeManager] background chunk ingest failed:", err);
|
|
2305
|
+
});
|
|
2306
|
+
}
|
|
2307
|
+
if (opts.waitGraph) {
|
|
2308
|
+
await withTimeout2(graphBuild, this.config.graphBuildTimeoutMs, "document graph build");
|
|
2309
|
+
} else {
|
|
2310
|
+
graphBuild.catch((err) => {
|
|
2311
|
+
console.error("[KnowledgeManager] background graph build failed:", err);
|
|
2189
2312
|
});
|
|
2190
2313
|
}
|
|
2191
2314
|
return { docId };
|
|
2192
2315
|
}
|
|
2193
|
-
async
|
|
2194
|
-
if (pieces.length === 0) return;
|
|
2316
|
+
async ingestChunks(doc, pieces) {
|
|
2317
|
+
if (pieces.length === 0) return [];
|
|
2195
2318
|
const redundant = this.config.chunkRedundantIds;
|
|
2196
2319
|
const embedInputs = pieces.map(
|
|
2197
2320
|
(p) => p.headingPath ? `${p.headingPath}
|
|
@@ -2202,7 +2325,7 @@ ${p.content}` : p.content
|
|
|
2202
2325
|
vectors = await this.embed.embed(embedInputs);
|
|
2203
2326
|
} catch (err) {
|
|
2204
2327
|
console.error("[KnowledgeManager] embed chunks failed:", err);
|
|
2205
|
-
return;
|
|
2328
|
+
return [];
|
|
2206
2329
|
}
|
|
2207
2330
|
const chunks = pieces.map((p, i) => ({
|
|
2208
2331
|
chunkId: uuidv43(),
|
|
@@ -2219,17 +2342,12 @@ ${p.content}` : p.content
|
|
|
2219
2342
|
createdAt: doc.createdAt
|
|
2220
2343
|
}));
|
|
2221
2344
|
await this.lance.addChunks(chunks);
|
|
2222
|
-
|
|
2223
|
-
await this.buildDocumentGraph(doc, chunks).catch((err) => {
|
|
2224
|
-
console.error("[KnowledgeManager] build graph failed:", err);
|
|
2225
|
-
});
|
|
2226
|
-
}
|
|
2345
|
+
return chunks;
|
|
2227
2346
|
}
|
|
2228
2347
|
async buildDocumentGraph(doc, chunks) {
|
|
2229
|
-
|
|
2230
|
-
for (const chunk of chunks) {
|
|
2348
|
+
const extracted = await mapLimit2(chunks, this.config.graphExtractConcurrency, async (chunk) => {
|
|
2231
2349
|
const { entities: rawEntities, relations: rawRelations } = await this.llm.extractEntitiesFromText(chunk.content);
|
|
2232
|
-
if (rawEntities.length === 0 && rawRelations.length === 0)
|
|
2350
|
+
if (rawEntities.length === 0 && rawRelations.length === 0) return null;
|
|
2233
2351
|
const meta = {
|
|
2234
2352
|
userId: doc.userId,
|
|
2235
2353
|
chatId: doc.chatId,
|
|
@@ -2238,46 +2356,47 @@ ${p.content}` : p.content
|
|
|
2238
2356
|
chunkId: chunk.chunkId,
|
|
2239
2357
|
messageTime: doc.createdAt
|
|
2240
2358
|
};
|
|
2241
|
-
const
|
|
2359
|
+
const entities2 = rawEntities.map((e) => ({
|
|
2242
2360
|
name: e.name,
|
|
2243
2361
|
type: e.type,
|
|
2244
2362
|
meta: { ...meta, ...e.meta }
|
|
2245
2363
|
}));
|
|
2246
|
-
const
|
|
2364
|
+
const relations2 = rawRelations.map((r) => ({
|
|
2247
2365
|
from: r.from,
|
|
2248
2366
|
to: r.to,
|
|
2249
2367
|
type: r.type,
|
|
2250
2368
|
happenedAt: r.happenedAt ?? void 0,
|
|
2251
2369
|
meta: { ...meta, ...r.meta }
|
|
2252
2370
|
}));
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
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
|
-
});
|
|
2371
|
+
return { entities: entities2, relations: relations2 };
|
|
2372
|
+
});
|
|
2373
|
+
const entities = extracted.flatMap((item) => item?.entities ?? []);
|
|
2374
|
+
const relations = extracted.flatMap((item) => item?.relations ?? []);
|
|
2375
|
+
if (entities.length === 0 && relations.length === 0) return;
|
|
2376
|
+
const allNames = [
|
|
2377
|
+
.../* @__PURE__ */ new Set([
|
|
2378
|
+
...entities.map((e) => e.name),
|
|
2379
|
+
...relations.flatMap((r) => [r.from, r.to])
|
|
2380
|
+
])
|
|
2381
|
+
];
|
|
2382
|
+
if (allNames.length === 0) return;
|
|
2383
|
+
const embeddings = /* @__PURE__ */ new Map();
|
|
2384
|
+
try {
|
|
2385
|
+
const vecs = await this.embed.embed(allNames);
|
|
2386
|
+
allNames.forEach((name, i) => embeddings.set(name, vecs[i]));
|
|
2387
|
+
} catch (err) {
|
|
2388
|
+
console.error("[KnowledgeManager] embed entity names failed:", err);
|
|
2389
|
+
return;
|
|
2280
2390
|
}
|
|
2391
|
+
await this.grafeo.upsertEntitiesAndRelations(
|
|
2392
|
+
entities,
|
|
2393
|
+
relations,
|
|
2394
|
+
embeddings,
|
|
2395
|
+
KIND_KNOWLEDGE
|
|
2396
|
+
);
|
|
2397
|
+
await this.lance.updateDocumentGraphFlag(doc.docId, true).catch((err) => {
|
|
2398
|
+
console.error(`[KnowledgeManager] Failed to update graph flag for doc ${doc.docId}:`, err);
|
|
2399
|
+
});
|
|
2281
2400
|
}
|
|
2282
2401
|
// ── 检索 ───────────────────────────────────────────────────────────────────────
|
|
2283
2402
|
async searchKnowledge(opts) {
|
|
@@ -2439,6 +2558,26 @@ function inferTitle(markdown) {
|
|
|
2439
2558
|
}
|
|
2440
2559
|
return void 0;
|
|
2441
2560
|
}
|
|
2561
|
+
async function mapLimit2(items, limit, mapper) {
|
|
2562
|
+
const results = new Array(items.length);
|
|
2563
|
+
let next = 0;
|
|
2564
|
+
const workers = Array.from({ length: Math.min(Math.max(1, limit), items.length) }, async () => {
|
|
2565
|
+
while (true) {
|
|
2566
|
+
const index = next++;
|
|
2567
|
+
if (index >= items.length) return;
|
|
2568
|
+
results[index] = await mapper(items[index], index);
|
|
2569
|
+
}
|
|
2570
|
+
});
|
|
2571
|
+
await Promise.all(workers);
|
|
2572
|
+
return results;
|
|
2573
|
+
}
|
|
2574
|
+
function withTimeout2(promise, timeoutMs, label) {
|
|
2575
|
+
let timer;
|
|
2576
|
+
const timeout = new Promise((_, reject) => {
|
|
2577
|
+
timer = setTimeout(() => reject(new Error(`${label} timed out after ${timeoutMs}ms`)), timeoutMs);
|
|
2578
|
+
});
|
|
2579
|
+
return Promise.race([promise, timeout]).finally(() => clearTimeout(timer));
|
|
2580
|
+
}
|
|
2442
2581
|
|
|
2443
2582
|
// src/manager/session.cache.ts
|
|
2444
2583
|
var SessionCache = class {
|
|
@@ -2524,6 +2663,8 @@ var MemoryManager = class {
|
|
|
2524
2663
|
compressManager;
|
|
2525
2664
|
knowledgeManager;
|
|
2526
2665
|
sessionMap = /* @__PURE__ */ new Map();
|
|
2666
|
+
optimizeTimer;
|
|
2667
|
+
optimizeRunning = false;
|
|
2527
2668
|
constructor(config) {
|
|
2528
2669
|
this.config = resolveConfig(config);
|
|
2529
2670
|
this.lance = new LanceService(this.config);
|
|
@@ -2558,27 +2699,78 @@ var MemoryManager = class {
|
|
|
2558
2699
|
this.sessionMap.set(s.sessionId, this.deserializeSession(s));
|
|
2559
2700
|
}
|
|
2560
2701
|
await this.restoreFromStorage();
|
|
2702
|
+
if (this.config.autoOptimizeOnInit) {
|
|
2703
|
+
void this.runBackgroundOptimize(this.config.optimizeVersionRetentionMs);
|
|
2704
|
+
}
|
|
2705
|
+
if (this.config.autoOptimizeIntervalMs > 0) {
|
|
2706
|
+
const retention = Math.max(this.config.optimizeVersionRetentionMs, 6e4);
|
|
2707
|
+
this.optimizeTimer = setInterval(() => {
|
|
2708
|
+
void this.runBackgroundOptimize(retention);
|
|
2709
|
+
}, this.config.autoOptimizeIntervalMs);
|
|
2710
|
+
this.optimizeTimer.unref?.();
|
|
2711
|
+
}
|
|
2712
|
+
}
|
|
2713
|
+
/** 后台压实的统一入口:防重入(上一轮未结束则跳过),失败仅告警不影响服务。*/
|
|
2714
|
+
async runBackgroundOptimize(retentionMs) {
|
|
2715
|
+
if (this.optimizeRunning) return;
|
|
2716
|
+
this.optimizeRunning = true;
|
|
2717
|
+
try {
|
|
2718
|
+
const results = await this.optimizeStorage(retentionMs);
|
|
2719
|
+
const versions = results.reduce((s, r) => s + r.oldVersionsRemoved, 0);
|
|
2720
|
+
const fragments = results.reduce((s, r) => s + r.fragmentsRemoved, 0);
|
|
2721
|
+
if (versions > 0 || fragments > 0) {
|
|
2722
|
+
console.info(
|
|
2723
|
+
`[MemoryManager] \u540E\u53F0\u5B58\u50A8\u538B\u5B9E\u5B8C\u6210\uFF1A\u6E05\u7406\u5386\u53F2\u7248\u672C ${versions} \u4E2A\uFF0C\u5408\u5E76\u788E\u7247 ${fragments} \u4E2A`
|
|
2724
|
+
);
|
|
2725
|
+
}
|
|
2726
|
+
} catch (err) {
|
|
2727
|
+
console.warn("[MemoryManager] \u540E\u53F0\u5B58\u50A8\u538B\u5B9E\u5931\u8D25\uFF08\u4E0D\u5F71\u54CD\u670D\u52A1\uFF09:", err);
|
|
2728
|
+
} finally {
|
|
2729
|
+
this.optimizeRunning = false;
|
|
2730
|
+
}
|
|
2731
|
+
}
|
|
2732
|
+
/**
|
|
2733
|
+
* 手动触发存储压实与历史版本清理(init 后台会自动执行一次;上层维护任务也可调用)。
|
|
2734
|
+
* @param retentionMs 保留多久内的历史版本,默认取配置 optimizeVersionRetentionMs
|
|
2735
|
+
*/
|
|
2736
|
+
async optimizeStorage(retentionMs) {
|
|
2737
|
+
return this.lance.optimizeStorage(retentionMs ?? this.config.optimizeVersionRetentionMs);
|
|
2561
2738
|
}
|
|
2562
2739
|
async restoreFromStorage() {
|
|
2563
2740
|
const [n1, n2, n3] = this.config.topicRatio;
|
|
2564
2741
|
const sessionIds = await this.lance.getAllSessionIds();
|
|
2565
2742
|
if (sessionIds.length === 0) return;
|
|
2566
|
-
|
|
2567
|
-
|
|
2568
|
-
|
|
2569
|
-
|
|
2570
|
-
|
|
2571
|
-
|
|
2572
|
-
|
|
2573
|
-
if (rawMessages.length > 0) {
|
|
2574
|
-
this.sessionCache.addMessages(sessionId, rawMessages, chatId, userId);
|
|
2575
|
-
}
|
|
2576
|
-
if (topicGroups.detail.length + topicGroups.summary.length + topicGroups.concise.length > 0) {
|
|
2577
|
-
this.sessionCache.buildHistoryWindow(sessionId, topicGroups);
|
|
2578
|
-
} else {
|
|
2579
|
-
this.sessionCache.setHistoryWindow(sessionId, "");
|
|
2743
|
+
const topicsCache = /* @__PURE__ */ new Map();
|
|
2744
|
+
const getTopics = (chatId, userId) => {
|
|
2745
|
+
const key = `${chatId}\0${userId}`;
|
|
2746
|
+
let cached = topicsCache.get(key);
|
|
2747
|
+
if (!cached) {
|
|
2748
|
+
cached = this.lance.getRecentTopics(chatId, userId, n1, n2, n3);
|
|
2749
|
+
topicsCache.set(key, cached);
|
|
2580
2750
|
}
|
|
2581
|
-
|
|
2751
|
+
return cached;
|
|
2752
|
+
};
|
|
2753
|
+
const sem = new Semaphore(this.config.restoreConcurrency);
|
|
2754
|
+
await Promise.all(
|
|
2755
|
+
sessionIds.map(
|
|
2756
|
+
(sessionId) => sem.run(async () => {
|
|
2757
|
+
const recentMessages = await this.lance.getLatestMessages(sessionId, 1);
|
|
2758
|
+
if (recentMessages.length === 0) return;
|
|
2759
|
+
const { chatId, userId } = recentMessages[0];
|
|
2760
|
+
const topicGroups = await getTopics(chatId, userId);
|
|
2761
|
+
const n1EndTime = topicGroups.detail.length > 0 ? topicGroups.detail[topicGroups.detail.length - 1].endTime : 0;
|
|
2762
|
+
const rawMessages = n1EndTime > 0 ? await this.lance.getMessagesSince(sessionId, n1EndTime) : await this.lance.getLatestMessages(sessionId, 100);
|
|
2763
|
+
if (rawMessages.length > 0) {
|
|
2764
|
+
this.sessionCache.addMessages(sessionId, rawMessages, chatId, userId);
|
|
2765
|
+
}
|
|
2766
|
+
if (topicGroups.detail.length + topicGroups.summary.length + topicGroups.concise.length > 0) {
|
|
2767
|
+
this.sessionCache.buildHistoryWindow(sessionId, topicGroups);
|
|
2768
|
+
} else {
|
|
2769
|
+
this.sessionCache.setHistoryWindow(sessionId, "");
|
|
2770
|
+
}
|
|
2771
|
+
})
|
|
2772
|
+
)
|
|
2773
|
+
);
|
|
2582
2774
|
}
|
|
2583
2775
|
async updateChat(messages, opts) {
|
|
2584
2776
|
if (messages.length === 0) return;
|
|
@@ -2653,7 +2845,7 @@ var MemoryManager = class {
|
|
|
2653
2845
|
}
|
|
2654
2846
|
async flushChat(sessionId, opts) {
|
|
2655
2847
|
const sid = sessionId ?? DEFAULT_SESSION_ID;
|
|
2656
|
-
const promise = this.compressManager.triggerCompress(sid, true);
|
|
2848
|
+
const promise = this.compressManager.triggerCompress(sid, true, opts?.waitGraph === true);
|
|
2657
2849
|
if (opts?.wait) await promise;
|
|
2658
2850
|
}
|
|
2659
2851
|
async updateFacts(content, level, userId, chatId, sessionId) {
|
|
@@ -2944,6 +3136,10 @@ var MemoryManager = class {
|
|
|
2944
3136
|
return page ? this.knowledgeManager.listDocuments(filter, page) : this.knowledgeManager.listDocuments(filter);
|
|
2945
3137
|
}
|
|
2946
3138
|
destroy() {
|
|
3139
|
+
if (this.optimizeTimer) {
|
|
3140
|
+
clearInterval(this.optimizeTimer);
|
|
3141
|
+
this.optimizeTimer = void 0;
|
|
3142
|
+
}
|
|
2947
3143
|
this.grafeo.close();
|
|
2948
3144
|
}
|
|
2949
3145
|
};
|