koishi-plugin-chatluna-long-memory 1.3.1 → 1.3.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -4,4 +4,4 @@
4
4
 
5
5
  > 提供长期记忆支持的插件
6
6
 
7
- [长期记忆文档](https://chatluna.chat/ecosystem/renderer/image.html)
7
+ [长期记忆文档](https://chatluna.chat/ecosystem/plugin/long-term-memory.html)
package/lib/index.cjs CHANGED
@@ -2001,6 +2001,153 @@ async function createVectorStoreRetriever(ctx, config, longMemoryId) {
2001
2001
  }
2002
2002
  __name(createVectorStoreRetriever, "createVectorStoreRetriever");
2003
2003
 
2004
+ // src/layers/emgas/extractor.ts
2005
+ var import_js_yaml2 = __toESM(require("js-yaml"), 1);
2006
+ var import_string3 = require("koishi-plugin-chatluna/utils/string");
2007
+ var GRAPH_ELEMENTS_EXTRACTION_PROMPT = /* @__PURE__ */ __name((text) => `
2008
+ You are an expert in knowledge extraction. Your task is to analyze the following text to identify key concepts and overarching topics.
2009
+
2010
+ Text to analyze:
2011
+ """
2012
+ ${text}
2013
+ """
2014
+
2015
+ Respond with a single, valid YAML block with two keys: 'concepts' and 'topics'.
2016
+
2017
+ Guidelines:
2018
+ 1. **concepts**: A list of the most important keywords, and named entities from the text. Use the base form or most common form.
2019
+ 2. **topics**: A list of 1-3 higher-level topics that categorize the concepts. These should be broader categories.
2020
+ 3. If no meaningful data can be extracted, return empty lists for both keys.
2021
+
2022
+ Example:
2023
+ Text: "The user is asking about setting up a new React project with Vite. They are having trouble with the HMR (Hot Module Replacement) configuration."
2024
+
2025
+ YAML Output:
2026
+ \`\`\`yaml
2027
+ concepts:
2028
+ - React
2029
+ - Vite
2030
+ - HMR
2031
+ - project setup
2032
+ topics:
2033
+ - Frontend Development
2034
+ - Build Tools
2035
+ \`\`\`
2036
+
2037
+ YAML Output:
2038
+ `, "GRAPH_ELEMENTS_EXTRACTION_PROMPT");
2039
+ var STOP_WORDS = /* @__PURE__ */ new Set([
2040
+ "a",
2041
+ "an",
2042
+ "and",
2043
+ "are",
2044
+ "as",
2045
+ "at",
2046
+ "be",
2047
+ "by",
2048
+ "for",
2049
+ "from",
2050
+ "how",
2051
+ "in",
2052
+ "is",
2053
+ "it",
2054
+ "of",
2055
+ "on",
2056
+ "or",
2057
+ "that",
2058
+ "the",
2059
+ "their",
2060
+ "they",
2061
+ "this",
2062
+ "to",
2063
+ "with",
2064
+ "用户",
2065
+ "我们",
2066
+ "你们",
2067
+ "他们",
2068
+ "以及",
2069
+ "但是",
2070
+ "如果",
2071
+ "因为",
2072
+ "所以"
2073
+ ]);
2074
+ function format(items) {
2075
+ return items.map((item) => String(item).trim().replace(/\s+/g, " ")).filter(Boolean).map(
2076
+ (item) => /^[\x00-\x7F]+$/.test(item) ? item.toLowerCase() : item
2077
+ );
2078
+ }
2079
+ __name(format, "format");
2080
+ function extractLocal(text) {
2081
+ const counts = /* @__PURE__ */ new Map();
2082
+ const groups = [];
2083
+ for (const item of text.match(
2084
+ /[A-Za-z][A-Za-z0-9+#./-]{1,}|[\u3400-\u9fff]{2,}/g
2085
+ ) ?? []) {
2086
+ if (/^[A-Za-z]/.test(item)) {
2087
+ const word = item.toLowerCase();
2088
+ if (STOP_WORDS.has(word)) {
2089
+ continue;
2090
+ }
2091
+ counts.set(word, (counts.get(word) ?? 0) + 1);
2092
+ if (word.length >= 4) {
2093
+ groups.push(word);
2094
+ }
2095
+ continue;
2096
+ }
2097
+ if (item.length >= 2) {
2098
+ groups.push(item.length > 12 ? item.slice(0, 12) : item);
2099
+ }
2100
+ if (item.length <= 8) {
2101
+ counts.set(item, (counts.get(item) ?? 0) + 1);
2102
+ continue;
2103
+ }
2104
+ for (let i = 0; i < item.length - 1; i++) {
2105
+ const word = item.slice(i, i + 2);
2106
+ counts.set(word, (counts.get(word) ?? 0) + 1);
2107
+ }
2108
+ }
2109
+ const concepts = Array.from(counts.entries()).sort((a, b) => b[1] - a[1] || b[0].length - a[0].length).map(([item]) => item).slice(0, 12);
2110
+ const topics = Array.from(new Set(format(groups))).filter((item) => !STOP_WORDS.has(item)).slice(0, 3);
2111
+ return {
2112
+ concepts,
2113
+ topics: topics.length > 0 ? topics : concepts.slice(0, 3)
2114
+ };
2115
+ }
2116
+ __name(extractLocal, "extractLocal");
2117
+ async function extractGraphElements(modelRef, text) {
2118
+ const model = modelRef?.value;
2119
+ if (model == null) {
2120
+ return extractLocal(text);
2121
+ }
2122
+ try {
2123
+ const prompt = GRAPH_ELEMENTS_EXTRACTION_PROMPT(text);
2124
+ const res = await model.invoke(prompt);
2125
+ const content = (0, import_string3.getMessageContent)(res.content).trim();
2126
+ const yamlMatch = content.match(
2127
+ /```(?:ya?ml)?\s*\r?\n([\s\S]*?)\r?\n```/i
2128
+ );
2129
+ const parsed = import_js_yaml2.default.load(yamlMatch?.[1] ?? content);
2130
+ if (parsed && typeof parsed === "object") {
2131
+ const concepts = format(
2132
+ Array.isArray(parsed.concepts) ? parsed.concepts : []
2133
+ );
2134
+ const topics = format(
2135
+ Array.isArray(parsed.topics) ? parsed.topics : []
2136
+ );
2137
+ if (concepts.length > 0 || topics.length > 0) {
2138
+ return {
2139
+ concepts,
2140
+ topics: topics.length > 0 ? topics : concepts.slice(0, 3)
2141
+ };
2142
+ }
2143
+ }
2144
+ return extractLocal(text);
2145
+ } catch {
2146
+ return extractLocal(text);
2147
+ }
2148
+ }
2149
+ __name(extractGraphElements, "extractGraphElements");
2150
+
2004
2151
  // src/layers/emgas/graph.ts
2005
2152
  var MemoryGraph = class _MemoryGraph {
2006
2153
  static {
@@ -2209,12 +2356,16 @@ var MemoryGraph = class _MemoryGraph {
2209
2356
  return this.nodes.values();
2210
2357
  }
2211
2358
  toJSON() {
2212
- const nodes = Array.from(this.nodes.values()).map(
2213
- (node) => ({
2214
- ...node,
2215
- sourcePassageIds: node.sourcePassageIds ? Array.from(node.sourcePassageIds) : []
2216
- })
2217
- );
2359
+ const nodes = Array.from(
2360
+ this.nodes.values()
2361
+ ).map((node) => ({
2362
+ id: node.id,
2363
+ type: node.type,
2364
+ baseActivation: node.baseActivation,
2365
+ createdAt: node.createdAt.toISOString(),
2366
+ lastAccessed: node.lastAccessed.toISOString(),
2367
+ sourcePassageIds: node.sourcePassageIds ? Array.from(node.sourcePassageIds) : []
2368
+ }));
2218
2369
  const edges = [];
2219
2370
  const seenEdges = /* @__PURE__ */ new Set();
2220
2371
  for (const source of this.edges.values()) {
@@ -2239,9 +2390,11 @@ var MemoryGraph = class _MemoryGraph {
2239
2390
  if (!serializedGraph) return graph;
2240
2391
  for (const nodeData of serializedGraph.nodes || []) {
2241
2392
  graph.nodes.set(nodeData.id, {
2242
- ...nodeData,
2243
2393
  createdAt: new Date(nodeData.createdAt),
2244
2394
  lastAccessed: new Date(nodeData.lastAccessed),
2395
+ id: nodeData.id,
2396
+ type: nodeData.type,
2397
+ baseActivation: nodeData.baseActivation,
2245
2398
  sourcePassageIds: new Set(nodeData.sourcePassageIds || [])
2246
2399
  });
2247
2400
  }
@@ -2260,87 +2413,7 @@ var import_crypto6 = require("crypto");
2260
2413
  var import_fs = require("fs");
2261
2414
  var path2 = __toESM(require("path"), 1);
2262
2415
  var import_vectorstores2 = require("koishi-plugin-chatluna/llm-core/vectorstores");
2263
-
2264
- // src/layers/emgas/extractor.ts
2265
- var import_js_yaml2 = __toESM(require("js-yaml"), 1);
2266
- var import_string3 = require("koishi-plugin-chatluna/utils/string");
2267
- var import_error = require("koishi-plugin-chatluna/utils/error");
2268
- var GRAPH_ELEMENTS_EXTRACTION_PROMPT = /* @__PURE__ */ __name((text) => `
2269
- You are an expert in knowledge extraction. Your task is to analyze the following text to identify key concepts and overarching topics.
2270
-
2271
- Text to analyze:
2272
- """
2273
- ${text}
2274
- """
2275
-
2276
- Respond with a single, valid YAML block with two keys: 'concepts' and 'topics'.
2277
-
2278
- Guidelines:
2279
- 1. **concepts**: A list of the most important keywords, and named entities from the text. Use the base form or most common form.
2280
- 2. **topics**: A list of 1-3 higher-level topics that categorize the concepts. These should be broader categories.
2281
- 3. If no meaningful data can be extracted, return empty lists for both keys.
2282
-
2283
- Example:
2284
- Text: "The user is asking about setting up a new React project with Vite. They are having trouble with the HMR (Hot Module Replacement) configuration."
2285
-
2286
- YAML Output:
2287
- \`\`\`yaml
2288
- concepts:
2289
- - React
2290
- - Vite
2291
- - HMR
2292
- - project setup
2293
- topics:
2294
- - Frontend Development
2295
- - Build Tools
2296
- \`\`\`
2297
-
2298
- YAML Output:
2299
- `, "GRAPH_ELEMENTS_EXTRACTION_PROMPT");
2300
- async function extractGraphElements(modelRef, text) {
2301
- if (!modelRef.value) {
2302
- throw new import_error.ChatLunaError(
2303
- import_error.ChatLunaErrorCode.MODEL_NOT_FOUND,
2304
- new Error(
2305
- "LLM-based extractor is disabled. Cannot extract graph elements."
2306
- )
2307
- );
2308
- }
2309
- const model = modelRef.value;
2310
- try {
2311
- const prompt = GRAPH_ELEMENTS_EXTRACTION_PROMPT(text);
2312
- const res = await model.invoke(prompt);
2313
- const content = (0, import_string3.getMessageContent)(res.content);
2314
- const yamlMatch = content.match(
2315
- /```(?:ya?ml)?\s*\r?\n([\s\S]*?)\r?\n```/i
2316
- );
2317
- if (yamlMatch && yamlMatch[1]) {
2318
- const parsed = import_js_yaml2.default.load(yamlMatch[1]);
2319
- return {
2320
- concepts: [].concat(parsed.concepts || []).map((item) => String(item).trim()).filter(Boolean),
2321
- topics: [].concat(parsed.topics || []).map((item) => String(item).trim()).filter(Boolean)
2322
- };
2323
- }
2324
- throw new import_error.ChatLunaError(
2325
- import_error.ChatLunaErrorCode.MODEL_RESPONSE_IS_EMPTY,
2326
- new Error(
2327
- "LLM did not return a valid YAML for graph element extraction."
2328
- )
2329
- );
2330
- } catch (error) {
2331
- if (error instanceof import_error.ChatLunaError) {
2332
- throw error;
2333
- }
2334
- throw new import_error.ChatLunaError(
2335
- import_error.ChatLunaErrorCode.API_REQUEST_FAILED,
2336
- error
2337
- );
2338
- }
2339
- }
2340
- __name(extractGraphElements, "extractGraphElements");
2341
-
2342
- // src/layers/emgas/layer.ts
2343
- function getGraphFilePath(baseDir, memoryId) {
2416
+ function getGraphFilePath(baseDir, memoryId, dir = "emgas") {
2344
2417
  if (!memoryId || typeof memoryId !== "string") {
2345
2418
  throw new Error("Invalid memoryId: must be a non-empty string.");
2346
2419
  }
@@ -2350,7 +2423,10 @@ function getGraphFilePath(baseDir, memoryId) {
2350
2423
  }
2351
2424
  return path2.join(
2352
2425
  baseDir,
2353
- "data/chatluna/long-memory/emgasa",
2426
+ "data",
2427
+ "chatluna",
2428
+ "long-memory",
2429
+ dir,
2354
2430
  `${sanitizedId}.json`
2355
2431
  );
2356
2432
  }
@@ -2374,28 +2450,48 @@ var EmgasMemoryLayer = class extends BaseMemoryRetrievalLayer {
2374
2450
  `Initializing EMGAS layer for memory ID: ${this.info.memoryId}`
2375
2451
  );
2376
2452
  const baseDir = this.ctx.baseDir || process.cwd();
2377
- const filePath = getGraphFilePath(baseDir, this.info.memoryId);
2378
- try {
2379
- const data = await import_fs.promises.readFile(filePath, "utf-8");
2380
- const serialized = JSON.parse(data);
2381
- this.memoryGraph = MemoryGraph.fromJSON(serialized);
2382
- logger2.debug(`EMGAS graph loaded from ${filePath}`);
2383
- } catch (error) {
2384
- const err = error;
2385
- if (err && err.code === "ENOENT") {
2386
- logger2.debug(
2387
- `No existing EMGAS graph found for ${this.info.memoryId}. A new one will be created.`
2388
- );
2389
- } else {
2453
+ let loaded = false;
2454
+ for (const filePath of [
2455
+ getGraphFilePath(baseDir, this.info.memoryId),
2456
+ getGraphFilePath(baseDir, this.info.memoryId, "emgasa")
2457
+ ]) {
2458
+ try {
2459
+ const data = await import_fs.promises.readFile(filePath, "utf-8");
2460
+ const serialized = JSON.parse(data);
2461
+ this.memoryGraph = MemoryGraph.fromJSON(serialized);
2462
+ logger2.debug(`EMGAS graph loaded from ${filePath}`);
2463
+ loaded = true;
2464
+ break;
2465
+ } catch (error) {
2466
+ const err = error;
2467
+ if (err && err.code === "ENOENT") {
2468
+ continue;
2469
+ }
2390
2470
  logger2.error(
2391
- `Failed to load EMGAS graph from ${filePath}:`,
2471
+ `Failed to load EMGAS graph for ${this.info.memoryId}:`,
2392
2472
  error
2393
2473
  );
2474
+ break;
2394
2475
  }
2395
2476
  }
2396
- this.extractModel = await this.ctx.chatluna.createChatModel(
2397
- this.config.emgasExtractModel
2398
- );
2477
+ if (!loaded) {
2478
+ logger2.debug(
2479
+ `No existing EMGAS graph found for ${this.info.memoryId}. A new one will be created.`
2480
+ );
2481
+ }
2482
+ const modelName = this.config.emgasExtractModel !== "无" ? this.config.emgasExtractModel : this.config.longMemoryExtractModel !== "无" ? this.config.longMemoryExtractModel : this.config.hippoExtractModel !== "无" ? this.config.hippoExtractModel : void 0;
2483
+ this.extractModel = void 0;
2484
+ if (modelName?.includes("/")) {
2485
+ this.extractModel = await this.ctx.chatluna.createChatModel(modelName);
2486
+ } else if (modelName != null) {
2487
+ logger2.warn(
2488
+ `Invalid EMGAS extractor model: ${modelName}. Falling back to local concept extraction.`
2489
+ );
2490
+ } else {
2491
+ logger2.debug(
2492
+ "No EMGAS extractor model configured. Falling back to local concept extraction."
2493
+ );
2494
+ }
2399
2495
  this.docstore = new import_vectorstores2.DataBaseDocstore(
2400
2496
  this.ctx,
2401
2497
  resolveLongMemoryId(this.info)
package/lib/index.mjs CHANGED
@@ -1964,6 +1964,153 @@ async function createVectorStoreRetriever(ctx, config, longMemoryId) {
1964
1964
  }
1965
1965
  __name(createVectorStoreRetriever, "createVectorStoreRetriever");
1966
1966
 
1967
+ // src/layers/emgas/extractor.ts
1968
+ import YAML2 from "js-yaml";
1969
+ import { getMessageContent as getMessageContent3 } from "koishi-plugin-chatluna/utils/string";
1970
+ var GRAPH_ELEMENTS_EXTRACTION_PROMPT = /* @__PURE__ */ __name((text) => `
1971
+ You are an expert in knowledge extraction. Your task is to analyze the following text to identify key concepts and overarching topics.
1972
+
1973
+ Text to analyze:
1974
+ """
1975
+ ${text}
1976
+ """
1977
+
1978
+ Respond with a single, valid YAML block with two keys: 'concepts' and 'topics'.
1979
+
1980
+ Guidelines:
1981
+ 1. **concepts**: A list of the most important keywords, and named entities from the text. Use the base form or most common form.
1982
+ 2. **topics**: A list of 1-3 higher-level topics that categorize the concepts. These should be broader categories.
1983
+ 3. If no meaningful data can be extracted, return empty lists for both keys.
1984
+
1985
+ Example:
1986
+ Text: "The user is asking about setting up a new React project with Vite. They are having trouble with the HMR (Hot Module Replacement) configuration."
1987
+
1988
+ YAML Output:
1989
+ \`\`\`yaml
1990
+ concepts:
1991
+ - React
1992
+ - Vite
1993
+ - HMR
1994
+ - project setup
1995
+ topics:
1996
+ - Frontend Development
1997
+ - Build Tools
1998
+ \`\`\`
1999
+
2000
+ YAML Output:
2001
+ `, "GRAPH_ELEMENTS_EXTRACTION_PROMPT");
2002
+ var STOP_WORDS = /* @__PURE__ */ new Set([
2003
+ "a",
2004
+ "an",
2005
+ "and",
2006
+ "are",
2007
+ "as",
2008
+ "at",
2009
+ "be",
2010
+ "by",
2011
+ "for",
2012
+ "from",
2013
+ "how",
2014
+ "in",
2015
+ "is",
2016
+ "it",
2017
+ "of",
2018
+ "on",
2019
+ "or",
2020
+ "that",
2021
+ "the",
2022
+ "their",
2023
+ "they",
2024
+ "this",
2025
+ "to",
2026
+ "with",
2027
+ "用户",
2028
+ "我们",
2029
+ "你们",
2030
+ "他们",
2031
+ "以及",
2032
+ "但是",
2033
+ "如果",
2034
+ "因为",
2035
+ "所以"
2036
+ ]);
2037
+ function format(items) {
2038
+ return items.map((item) => String(item).trim().replace(/\s+/g, " ")).filter(Boolean).map(
2039
+ (item) => /^[\x00-\x7F]+$/.test(item) ? item.toLowerCase() : item
2040
+ );
2041
+ }
2042
+ __name(format, "format");
2043
+ function extractLocal(text) {
2044
+ const counts = /* @__PURE__ */ new Map();
2045
+ const groups = [];
2046
+ for (const item of text.match(
2047
+ /[A-Za-z][A-Za-z0-9+#./-]{1,}|[\u3400-\u9fff]{2,}/g
2048
+ ) ?? []) {
2049
+ if (/^[A-Za-z]/.test(item)) {
2050
+ const word = item.toLowerCase();
2051
+ if (STOP_WORDS.has(word)) {
2052
+ continue;
2053
+ }
2054
+ counts.set(word, (counts.get(word) ?? 0) + 1);
2055
+ if (word.length >= 4) {
2056
+ groups.push(word);
2057
+ }
2058
+ continue;
2059
+ }
2060
+ if (item.length >= 2) {
2061
+ groups.push(item.length > 12 ? item.slice(0, 12) : item);
2062
+ }
2063
+ if (item.length <= 8) {
2064
+ counts.set(item, (counts.get(item) ?? 0) + 1);
2065
+ continue;
2066
+ }
2067
+ for (let i = 0; i < item.length - 1; i++) {
2068
+ const word = item.slice(i, i + 2);
2069
+ counts.set(word, (counts.get(word) ?? 0) + 1);
2070
+ }
2071
+ }
2072
+ const concepts = Array.from(counts.entries()).sort((a, b) => b[1] - a[1] || b[0].length - a[0].length).map(([item]) => item).slice(0, 12);
2073
+ const topics = Array.from(new Set(format(groups))).filter((item) => !STOP_WORDS.has(item)).slice(0, 3);
2074
+ return {
2075
+ concepts,
2076
+ topics: topics.length > 0 ? topics : concepts.slice(0, 3)
2077
+ };
2078
+ }
2079
+ __name(extractLocal, "extractLocal");
2080
+ async function extractGraphElements(modelRef, text) {
2081
+ const model = modelRef?.value;
2082
+ if (model == null) {
2083
+ return extractLocal(text);
2084
+ }
2085
+ try {
2086
+ const prompt = GRAPH_ELEMENTS_EXTRACTION_PROMPT(text);
2087
+ const res = await model.invoke(prompt);
2088
+ const content = getMessageContent3(res.content).trim();
2089
+ const yamlMatch = content.match(
2090
+ /```(?:ya?ml)?\s*\r?\n([\s\S]*?)\r?\n```/i
2091
+ );
2092
+ const parsed = YAML2.load(yamlMatch?.[1] ?? content);
2093
+ if (parsed && typeof parsed === "object") {
2094
+ const concepts = format(
2095
+ Array.isArray(parsed.concepts) ? parsed.concepts : []
2096
+ );
2097
+ const topics = format(
2098
+ Array.isArray(parsed.topics) ? parsed.topics : []
2099
+ );
2100
+ if (concepts.length > 0 || topics.length > 0) {
2101
+ return {
2102
+ concepts,
2103
+ topics: topics.length > 0 ? topics : concepts.slice(0, 3)
2104
+ };
2105
+ }
2106
+ }
2107
+ return extractLocal(text);
2108
+ } catch {
2109
+ return extractLocal(text);
2110
+ }
2111
+ }
2112
+ __name(extractGraphElements, "extractGraphElements");
2113
+
1967
2114
  // src/layers/emgas/graph.ts
1968
2115
  var MemoryGraph = class _MemoryGraph {
1969
2116
  static {
@@ -2172,12 +2319,16 @@ var MemoryGraph = class _MemoryGraph {
2172
2319
  return this.nodes.values();
2173
2320
  }
2174
2321
  toJSON() {
2175
- const nodes = Array.from(this.nodes.values()).map(
2176
- (node) => ({
2177
- ...node,
2178
- sourcePassageIds: node.sourcePassageIds ? Array.from(node.sourcePassageIds) : []
2179
- })
2180
- );
2322
+ const nodes = Array.from(
2323
+ this.nodes.values()
2324
+ ).map((node) => ({
2325
+ id: node.id,
2326
+ type: node.type,
2327
+ baseActivation: node.baseActivation,
2328
+ createdAt: node.createdAt.toISOString(),
2329
+ lastAccessed: node.lastAccessed.toISOString(),
2330
+ sourcePassageIds: node.sourcePassageIds ? Array.from(node.sourcePassageIds) : []
2331
+ }));
2181
2332
  const edges = [];
2182
2333
  const seenEdges = /* @__PURE__ */ new Set();
2183
2334
  for (const source of this.edges.values()) {
@@ -2202,9 +2353,11 @@ var MemoryGraph = class _MemoryGraph {
2202
2353
  if (!serializedGraph) return graph;
2203
2354
  for (const nodeData of serializedGraph.nodes || []) {
2204
2355
  graph.nodes.set(nodeData.id, {
2205
- ...nodeData,
2206
2356
  createdAt: new Date(nodeData.createdAt),
2207
2357
  lastAccessed: new Date(nodeData.lastAccessed),
2358
+ id: nodeData.id,
2359
+ type: nodeData.type,
2360
+ baseActivation: nodeData.baseActivation,
2208
2361
  sourcePassageIds: new Set(nodeData.sourcePassageIds || [])
2209
2362
  });
2210
2363
  }
@@ -2223,90 +2376,7 @@ import { randomUUID as randomUUID4 } from "crypto";
2223
2376
  import { promises as fs2 } from "fs";
2224
2377
  import * as path2 from "path";
2225
2378
  import { DataBaseDocstore } from "koishi-plugin-chatluna/llm-core/vectorstores";
2226
-
2227
- // src/layers/emgas/extractor.ts
2228
- import YAML2 from "js-yaml";
2229
- import { getMessageContent as getMessageContent3 } from "koishi-plugin-chatluna/utils/string";
2230
- import {
2231
- ChatLunaError,
2232
- ChatLunaErrorCode
2233
- } from "koishi-plugin-chatluna/utils/error";
2234
- var GRAPH_ELEMENTS_EXTRACTION_PROMPT = /* @__PURE__ */ __name((text) => `
2235
- You are an expert in knowledge extraction. Your task is to analyze the following text to identify key concepts and overarching topics.
2236
-
2237
- Text to analyze:
2238
- """
2239
- ${text}
2240
- """
2241
-
2242
- Respond with a single, valid YAML block with two keys: 'concepts' and 'topics'.
2243
-
2244
- Guidelines:
2245
- 1. **concepts**: A list of the most important keywords, and named entities from the text. Use the base form or most common form.
2246
- 2. **topics**: A list of 1-3 higher-level topics that categorize the concepts. These should be broader categories.
2247
- 3. If no meaningful data can be extracted, return empty lists for both keys.
2248
-
2249
- Example:
2250
- Text: "The user is asking about setting up a new React project with Vite. They are having trouble with the HMR (Hot Module Replacement) configuration."
2251
-
2252
- YAML Output:
2253
- \`\`\`yaml
2254
- concepts:
2255
- - React
2256
- - Vite
2257
- - HMR
2258
- - project setup
2259
- topics:
2260
- - Frontend Development
2261
- - Build Tools
2262
- \`\`\`
2263
-
2264
- YAML Output:
2265
- `, "GRAPH_ELEMENTS_EXTRACTION_PROMPT");
2266
- async function extractGraphElements(modelRef, text) {
2267
- if (!modelRef.value) {
2268
- throw new ChatLunaError(
2269
- ChatLunaErrorCode.MODEL_NOT_FOUND,
2270
- new Error(
2271
- "LLM-based extractor is disabled. Cannot extract graph elements."
2272
- )
2273
- );
2274
- }
2275
- const model = modelRef.value;
2276
- try {
2277
- const prompt = GRAPH_ELEMENTS_EXTRACTION_PROMPT(text);
2278
- const res = await model.invoke(prompt);
2279
- const content = getMessageContent3(res.content);
2280
- const yamlMatch = content.match(
2281
- /```(?:ya?ml)?\s*\r?\n([\s\S]*?)\r?\n```/i
2282
- );
2283
- if (yamlMatch && yamlMatch[1]) {
2284
- const parsed = YAML2.load(yamlMatch[1]);
2285
- return {
2286
- concepts: [].concat(parsed.concepts || []).map((item) => String(item).trim()).filter(Boolean),
2287
- topics: [].concat(parsed.topics || []).map((item) => String(item).trim()).filter(Boolean)
2288
- };
2289
- }
2290
- throw new ChatLunaError(
2291
- ChatLunaErrorCode.MODEL_RESPONSE_IS_EMPTY,
2292
- new Error(
2293
- "LLM did not return a valid YAML for graph element extraction."
2294
- )
2295
- );
2296
- } catch (error) {
2297
- if (error instanceof ChatLunaError) {
2298
- throw error;
2299
- }
2300
- throw new ChatLunaError(
2301
- ChatLunaErrorCode.API_REQUEST_FAILED,
2302
- error
2303
- );
2304
- }
2305
- }
2306
- __name(extractGraphElements, "extractGraphElements");
2307
-
2308
- // src/layers/emgas/layer.ts
2309
- function getGraphFilePath(baseDir, memoryId) {
2379
+ function getGraphFilePath(baseDir, memoryId, dir = "emgas") {
2310
2380
  if (!memoryId || typeof memoryId !== "string") {
2311
2381
  throw new Error("Invalid memoryId: must be a non-empty string.");
2312
2382
  }
@@ -2316,7 +2386,10 @@ function getGraphFilePath(baseDir, memoryId) {
2316
2386
  }
2317
2387
  return path2.join(
2318
2388
  baseDir,
2319
- "data/chatluna/long-memory/emgasa",
2389
+ "data",
2390
+ "chatluna",
2391
+ "long-memory",
2392
+ dir,
2320
2393
  `${sanitizedId}.json`
2321
2394
  );
2322
2395
  }
@@ -2340,28 +2413,48 @@ var EmgasMemoryLayer = class extends BaseMemoryRetrievalLayer {
2340
2413
  `Initializing EMGAS layer for memory ID: ${this.info.memoryId}`
2341
2414
  );
2342
2415
  const baseDir = this.ctx.baseDir || process.cwd();
2343
- const filePath = getGraphFilePath(baseDir, this.info.memoryId);
2344
- try {
2345
- const data = await fs2.readFile(filePath, "utf-8");
2346
- const serialized = JSON.parse(data);
2347
- this.memoryGraph = MemoryGraph.fromJSON(serialized);
2348
- logger2.debug(`EMGAS graph loaded from ${filePath}`);
2349
- } catch (error) {
2350
- const err = error;
2351
- if (err && err.code === "ENOENT") {
2352
- logger2.debug(
2353
- `No existing EMGAS graph found for ${this.info.memoryId}. A new one will be created.`
2354
- );
2355
- } else {
2416
+ let loaded = false;
2417
+ for (const filePath of [
2418
+ getGraphFilePath(baseDir, this.info.memoryId),
2419
+ getGraphFilePath(baseDir, this.info.memoryId, "emgasa")
2420
+ ]) {
2421
+ try {
2422
+ const data = await fs2.readFile(filePath, "utf-8");
2423
+ const serialized = JSON.parse(data);
2424
+ this.memoryGraph = MemoryGraph.fromJSON(serialized);
2425
+ logger2.debug(`EMGAS graph loaded from ${filePath}`);
2426
+ loaded = true;
2427
+ break;
2428
+ } catch (error) {
2429
+ const err = error;
2430
+ if (err && err.code === "ENOENT") {
2431
+ continue;
2432
+ }
2356
2433
  logger2.error(
2357
- `Failed to load EMGAS graph from ${filePath}:`,
2434
+ `Failed to load EMGAS graph for ${this.info.memoryId}:`,
2358
2435
  error
2359
2436
  );
2437
+ break;
2360
2438
  }
2361
2439
  }
2362
- this.extractModel = await this.ctx.chatluna.createChatModel(
2363
- this.config.emgasExtractModel
2364
- );
2440
+ if (!loaded) {
2441
+ logger2.debug(
2442
+ `No existing EMGAS graph found for ${this.info.memoryId}. A new one will be created.`
2443
+ );
2444
+ }
2445
+ const modelName = this.config.emgasExtractModel !== "无" ? this.config.emgasExtractModel : this.config.longMemoryExtractModel !== "无" ? this.config.longMemoryExtractModel : this.config.hippoExtractModel !== "无" ? this.config.hippoExtractModel : void 0;
2446
+ this.extractModel = void 0;
2447
+ if (modelName?.includes("/")) {
2448
+ this.extractModel = await this.ctx.chatluna.createChatModel(modelName);
2449
+ } else if (modelName != null) {
2450
+ logger2.warn(
2451
+ `Invalid EMGAS extractor model: ${modelName}. Falling back to local concept extraction.`
2452
+ );
2453
+ } else {
2454
+ logger2.debug(
2455
+ "No EMGAS extractor model configured. Falling back to local concept extraction."
2456
+ );
2457
+ }
2365
2458
  this.docstore = new DataBaseDocstore(
2366
2459
  this.ctx,
2367
2460
  resolveLongMemoryId(this.info)
@@ -1,14 +1,11 @@
1
- import { ChatLunaChatModel } from 'koishi-plugin-chatluna/llm-core/platform/model';
2
1
  import { ComputedRef } from 'koishi-plugin-chatluna';
3
- export interface ExtractedGraphElements {
4
- concepts: string[];
5
- topics: string[];
6
- }
2
+ import { ChatLunaChatModel } from 'koishi-plugin-chatluna/llm-core/platform/model';
3
+ import { ExtractedGraphElements } from './types';
7
4
  /**
8
- * Extracts key concepts and topics from a text chunk using an LLM.
9
- * @param ctx The Koishi context.
10
- * @param config The plugin configuration.
5
+ * Extracts key concepts and topics from a text chunk.
6
+ * It prefers the configured LLM and falls back to local extraction.
7
+ * @param modelRef The configured extractor model.
11
8
  * @param text The text to analyze.
12
9
  * @returns A promise that resolves to an object containing concepts and topics.
13
10
  */
14
- export declare function extractGraphElements(modelRef: ComputedRef<ChatLunaChatModel>, text: string): Promise<ExtractedGraphElements>;
11
+ export declare function extractGraphElements(modelRef: ComputedRef<ChatLunaChatModel | undefined> | undefined, text: string): Promise<ExtractedGraphElements>;
@@ -1,5 +1,4 @@
1
- import { ExtractedGraphElements } from './extractor';
2
- import { MemoryNode, SerializedMemoryGraph, SpreadingActivationOptions } from './types';
1
+ import { ExtractedGraphElements, MemoryNode, SerializedMemoryGraph, SpreadingActivationOptions } from './types';
3
2
  /**
4
3
  * A class that implements the EMGAS (Episodic Memory Graph with Activation Spreading) framework.
5
4
  * It manages a dynamic graph of concepts, handles incremental updates, and performs retrieval
@@ -1,3 +1,4 @@
1
1
  export * from './types';
2
+ export { extractGraphElements } from './extractor';
2
3
  export { MemoryGraph } from './graph';
3
4
  export { EmgasMemoryLayer } from './layer';
@@ -2,6 +2,10 @@
2
2
  * Core data structures for the EMGAS (Episodic Memory Graph with Activation Spreading) model.
3
3
  * Based on the TypeScript blueprint provided in the design document.
4
4
  */
5
+ export interface ExtractedGraphElements {
6
+ concepts: string[];
7
+ topics: string[];
8
+ }
5
9
  /**
6
10
  * Options for the Spreading Activation retrieval algorithm.
7
11
  */
@@ -62,6 +66,11 @@ export interface MemoryNode {
62
66
  */
63
67
  sourcePassageIds?: Set<string>;
64
68
  }
69
+ export interface SerializedMemoryNode extends Omit<MemoryNode, 'createdAt' | 'lastAccessed' | 'sourcePassageIds'> {
70
+ createdAt: string;
71
+ lastAccessed: string;
72
+ sourcePassageIds: string[];
73
+ }
65
74
  /**
66
75
  * Represents a weighted edge in the memory graph, connecting two nodes.
67
76
  */
@@ -90,7 +99,7 @@ export interface MemoryEdge {
90
99
  * Represents the entire memory graph, including nodes, edges, and persistence data.
91
100
  */
92
101
  export interface SerializedMemoryGraph {
93
- nodes: MemoryNode[];
102
+ nodes: SerializedMemoryNode[];
94
103
  edges: MemoryEdge[];
95
104
  conceptCounts: [string, number][];
96
105
  pairCounts: [string, number][];
@@ -35,27 +35,27 @@ export declare class MemoryAddTool extends StructuredTool {
35
35
  type: z.ZodNativeEnum<typeof MemoryType>;
36
36
  importance: z.ZodNumber;
37
37
  }, "strip", z.ZodTypeAny, {
38
- type?: MemoryType;
39
38
  content?: string;
39
+ type?: MemoryType;
40
40
  importance?: number;
41
41
  }, {
42
- type?: MemoryType;
43
42
  content?: string;
43
+ type?: MemoryType;
44
44
  importance?: number;
45
45
  }>, "many">;
46
46
  layer: z.ZodArray<z.ZodUnion<[z.ZodLiteral<"user">, z.ZodLiteral<"preset">, z.ZodLiteral<"guild">, z.ZodLiteral<"global">]>, "many">;
47
47
  }, "strip", z.ZodTypeAny, {
48
48
  layer?: ("global" | "preset" | "user" | "guild")[];
49
49
  memories?: {
50
- type?: MemoryType;
51
50
  content?: string;
51
+ type?: MemoryType;
52
52
  importance?: number;
53
53
  }[];
54
54
  }, {
55
55
  layer?: ("global" | "preset" | "user" | "guild")[];
56
56
  memories?: {
57
- type?: MemoryType;
58
57
  content?: string;
58
+ type?: MemoryType;
59
59
  importance?: number;
60
60
  }[];
61
61
  }>;
@@ -94,12 +94,12 @@ export declare class MemoryUpdateTool extends StructuredTool {
94
94
  type: z.ZodNativeEnum<typeof MemoryType>;
95
95
  importance: z.ZodNumber;
96
96
  }, "strip", z.ZodTypeAny, {
97
- type?: MemoryType;
98
97
  content?: string;
98
+ type?: MemoryType;
99
99
  importance?: number;
100
100
  }, {
101
- type?: MemoryType;
102
101
  content?: string;
102
+ type?: MemoryType;
103
103
  importance?: number;
104
104
  }>, "many">;
105
105
  layer: z.ZodArray<z.ZodUnion<[z.ZodLiteral<"user">, z.ZodLiteral<"preset">, z.ZodLiteral<"guild">, z.ZodLiteral<"global">]>, "many">;
@@ -107,16 +107,16 @@ export declare class MemoryUpdateTool extends StructuredTool {
107
107
  layer?: ("global" | "preset" | "user" | "guild")[];
108
108
  memoryIds?: string[];
109
109
  newMemories?: {
110
- type?: MemoryType;
111
110
  content?: string;
111
+ type?: MemoryType;
112
112
  importance?: number;
113
113
  }[];
114
114
  }, {
115
115
  layer?: ("global" | "preset" | "user" | "guild")[];
116
116
  memoryIds?: string[];
117
117
  newMemories?: {
118
- type?: MemoryType;
119
118
  content?: string;
119
+ type?: MemoryType;
120
120
  importance?: number;
121
121
  }[];
122
122
  }>;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "koishi-plugin-chatluna-long-memory",
3
3
  "description": "long memory for chatluna",
4
- "version": "1.3.1",
4
+ "version": "1.3.3",
5
5
  "main": "lib/index.cjs",
6
6
  "module": "lib/index.mjs",
7
7
  "typings": "lib/index.d.ts",
@@ -62,7 +62,7 @@
62
62
  },
63
63
  "peerDependencies": {
64
64
  "koishi": "^4.18.9",
65
- "koishi-plugin-chatluna": "^1.3.8"
65
+ "koishi-plugin-chatluna": "^1.3.33"
66
66
  },
67
67
  "resolutions": {
68
68
  "@langchain/core": "^0.3.80",