@scotthuang/engram 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/__tests__/bm25.test.js +26 -26
- package/dist/__tests__/bm25.test.js.map +1 -1
- package/dist/bm25.d.ts +3 -3
- package/dist/bm25.js +17 -24
- package/dist/bm25.js.map +1 -1
- package/dist/recall.js +1 -1
- package/dist/recall.js.map +1 -1
- package/package.json +8 -2
- package/eslint.config.js +0 -17
- package/src/__tests__/bm25.test.ts +0 -102
- package/src/__tests__/config.test.ts +0 -34
- package/src/__tests__/profile.test.ts +0 -147
- package/src/__tests__/recall.test.ts +0 -186
- package/src/bm25.ts +0 -202
- package/src/config.ts +0 -39
- package/src/index.ts +0 -246
- package/src/profile.ts +0 -114
- package/src/recall.ts +0 -213
- package/src/settle.ts +0 -277
- package/tsconfig.json +0 -16
package/README.md
CHANGED
|
@@ -3,53 +3,53 @@ import { BM25Index } from "../bm25.js";
|
|
|
3
3
|
describe("BM25Index", () => {
|
|
4
4
|
describe("tokenize (via search)", () => {
|
|
5
5
|
const index = new BM25Index();
|
|
6
|
-
it("finds Chinese bigram matches", () => {
|
|
7
|
-
index.addEntry({ text: "在体育西吃了潮汕牛肉火锅", date: "2026-03-15", category: "饮食", filePath: "/test" });
|
|
8
|
-
index.addEntry({ text: "讨论了记忆系统方案", date: "2026-03-15", category: "技术", filePath: "/test" });
|
|
9
|
-
const results = index.search("牛肉火锅", 2);
|
|
6
|
+
it("finds Chinese bigram matches", async () => {
|
|
7
|
+
await index.addEntry({ text: "在体育西吃了潮汕牛肉火锅", date: "2026-03-15", category: "饮食", filePath: "/test" });
|
|
8
|
+
await index.addEntry({ text: "讨论了记忆系统方案", date: "2026-03-15", category: "技术", filePath: "/test" });
|
|
9
|
+
const results = await index.search("牛肉火锅", 2);
|
|
10
10
|
expect(results).toHaveLength(1);
|
|
11
11
|
expect(results[0].entry.category).toBe("饮食");
|
|
12
12
|
});
|
|
13
|
-
it("finds English word matches", () => {
|
|
14
|
-
index.addEntry({ text: "installed node modules successfully", date: "2026-03-15", category: "技术", filePath: "/test" });
|
|
15
|
-
index.addEntry({ text: "天气很好适合出门", date: "2026-03-15", category: "随聊", filePath: "/test" });
|
|
16
|
-
const results = index.search("node modules", 2);
|
|
13
|
+
it("finds English word matches", async () => {
|
|
14
|
+
await index.addEntry({ text: "installed node modules successfully", date: "2026-03-15", category: "技术", filePath: "/test" });
|
|
15
|
+
await index.addEntry({ text: "天气很好适合出门", date: "2026-03-15", category: "随聊", filePath: "/test" });
|
|
16
|
+
const results = await index.search("node modules", 2);
|
|
17
17
|
expect(results).toHaveLength(1);
|
|
18
18
|
expect(results[0].entry.category).toBe("技术");
|
|
19
19
|
});
|
|
20
|
-
it("returns empty for no matches", () => {
|
|
21
|
-
index.addEntry({ text: "今天天气很好", date: "2026-03-15", category: "随聊", filePath: "/test" });
|
|
22
|
-
const results = index.search("量子力学", 3);
|
|
20
|
+
it("returns empty for no matches", async () => {
|
|
21
|
+
await index.addEntry({ text: "今天天气很好", date: "2026-03-15", category: "随聊", filePath: "/test" });
|
|
22
|
+
const results = await index.search("量子力学", 3);
|
|
23
23
|
expect(results).toHaveLength(0);
|
|
24
24
|
});
|
|
25
25
|
});
|
|
26
26
|
describe("search", () => {
|
|
27
|
-
it("returns results sorted by score", () => {
|
|
27
|
+
it("returns results sorted by score", async () => {
|
|
28
28
|
const index = new BM25Index();
|
|
29
|
-
index.addEntry({ text: "牛肉火锅牛肉火锅牛肉火锅", date: "2026-03-15", category: "饮食", filePath: "/test" });
|
|
30
|
-
index.addEntry({ text: "吃过一次牛肉火锅", date: "2026-03-15", category: "饮食", filePath: "/test" });
|
|
31
|
-
index.addEntry({ text: "今天去跑步了", date: "2026-03-15", category: "健康", filePath: "/test" });
|
|
32
|
-
const results = index.search("牛肉火锅", 3);
|
|
29
|
+
await index.addEntry({ text: "牛肉火锅牛肉火锅牛肉火锅", date: "2026-03-15", category: "饮食", filePath: "/test" });
|
|
30
|
+
await index.addEntry({ text: "吃过一次牛肉火锅", date: "2026-03-15", category: "饮食", filePath: "/test" });
|
|
31
|
+
await index.addEntry({ text: "今天去跑步了", date: "2026-03-15", category: "健康", filePath: "/test" });
|
|
32
|
+
const results = await index.search("牛肉火锅", 3);
|
|
33
33
|
expect(results.length).toBeGreaterThanOrEqual(2);
|
|
34
34
|
// 更高频率的应排在前面
|
|
35
35
|
expect(results[0].score).toBeGreaterThanOrEqual(results[1].score);
|
|
36
36
|
});
|
|
37
|
-
it("respects topK limit", () => {
|
|
37
|
+
it("respects topK limit", async () => {
|
|
38
38
|
const index = new BM25Index();
|
|
39
39
|
for (let i = 0; i < 10; i++) {
|
|
40
|
-
index.addEntry({ text: `测试条目${i} 包含关键词`, date: "2026-03-15", category: "测试", filePath: "/test" });
|
|
40
|
+
await index.addEntry({ text: `测试条目${i} 包含关键词`, date: "2026-03-15", category: "测试", filePath: "/test" });
|
|
41
41
|
}
|
|
42
|
-
const results = index.search("关键词", 3);
|
|
42
|
+
const results = await index.search("关键词", 3);
|
|
43
43
|
expect(results.length).toBeLessThanOrEqual(3);
|
|
44
44
|
});
|
|
45
45
|
});
|
|
46
46
|
describe("addEntry", () => {
|
|
47
|
-
it("updates index size", () => {
|
|
47
|
+
it("updates index size", async () => {
|
|
48
48
|
const index = new BM25Index();
|
|
49
49
|
expect(index.size).toBe(0);
|
|
50
|
-
index.addEntry({ text: "test entry", date: "2026-03-15", category: "测试", filePath: "/test" });
|
|
50
|
+
await index.addEntry({ text: "test entry", date: "2026-03-15", category: "测试", filePath: "/test" });
|
|
51
51
|
expect(index.size).toBe(1);
|
|
52
|
-
index.addEntry({ text: "another entry", date: "2026-03-15", category: "测试", filePath: "/test" });
|
|
52
|
+
await index.addEntry({ text: "another entry", date: "2026-03-15", category: "测试", filePath: "/test" });
|
|
53
53
|
expect(index.size).toBe(2);
|
|
54
54
|
});
|
|
55
55
|
});
|
|
@@ -70,14 +70,14 @@ describe("BM25Index", () => {
|
|
|
70
70
|
聊了股票
|
|
71
71
|
`;
|
|
72
72
|
// 使用 addEntry 模拟解析后的结果
|
|
73
|
-
index.addEntry({ text: "在体育西吃了潮汕牛肉火锅,胸口捞很好吃", date: "2026-03-15", category: "饮食", filePath: "/test" });
|
|
74
|
-
index.addEntry({ text: "讨论了记忆系统方案,确定三层架构", date: "2026-03-15", category: "技术", filePath: "/test" });
|
|
73
|
+
await index.addEntry({ text: "在体育西吃了潮汕牛肉火锅,胸口捞很好吃", date: "2026-03-15", category: "饮食", filePath: "/test" });
|
|
74
|
+
await index.addEntry({ text: "讨论了记忆系统方案,确定三层架构", date: "2026-03-15", category: "技术", filePath: "/test" });
|
|
75
75
|
// 搜饮食
|
|
76
|
-
const foodResults = index.search("牛肉火锅", 1);
|
|
76
|
+
const foodResults = await index.search("牛肉火锅", 1);
|
|
77
77
|
expect(foodResults).toHaveLength(1);
|
|
78
78
|
expect(foodResults[0].entry.category).toBe("饮食");
|
|
79
79
|
// 搜技术
|
|
80
|
-
const techResults = index.search("记忆系统", 1);
|
|
80
|
+
const techResults = await index.search("记忆系统", 1);
|
|
81
81
|
expect(techResults).toHaveLength(1);
|
|
82
82
|
expect(techResults[0].entry.category).toBe("技术");
|
|
83
83
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bm25.test.js","sourceRoot":"","sources":["../../src/__tests__/bm25.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAEvC,QAAQ,CAAC,WAAW,EAAE,GAAG,EAAE;IACzB,QAAQ,CAAC,uBAAuB,EAAE,GAAG,EAAE;QACrC,MAAM,KAAK,GAAG,IAAI,SAAS,EAAE,CAAC;QAE9B,EAAE,CAAC,8BAA8B,EAAE,
|
|
1
|
+
{"version":3,"file":"bm25.test.js","sourceRoot":"","sources":["../../src/__tests__/bm25.test.ts"],"names":[],"mappings":"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"}
|
package/dist/bm25.d.ts
CHANGED
|
@@ -31,13 +31,13 @@ export declare class BM25Index {
|
|
|
31
31
|
* BM25 搜索
|
|
32
32
|
* @returns 带分数的结果列表
|
|
33
33
|
*/
|
|
34
|
-
search(query: string, topK?: number): Array<{
|
|
34
|
+
search(query: string, topK?: number): Promise<Array<{
|
|
35
35
|
entry: ShortTermEntry;
|
|
36
36
|
score: number;
|
|
37
|
-
}
|
|
37
|
+
}>>;
|
|
38
38
|
/**
|
|
39
39
|
* 添加单条记录(用于实时写入后的即时索引)
|
|
40
40
|
*/
|
|
41
|
-
addEntry(entry: ShortTermEntry): void
|
|
41
|
+
addEntry(entry: ShortTermEntry): Promise<void>;
|
|
42
42
|
get size(): number;
|
|
43
43
|
}
|
package/dist/bm25.js
CHANGED
|
@@ -6,29 +6,19 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import { promises as fs } from "node:fs";
|
|
8
8
|
import { join } from "node:path";
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
9
|
+
let jiebaInstance = null;
|
|
10
|
+
async function initJieba() {
|
|
11
|
+
if (!jiebaInstance) {
|
|
12
|
+
const mod = await import("jieba-wasm");
|
|
13
|
+
jiebaInstance = await mod.default;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
13
16
|
function tokenize(text) {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
const tokens = [];
|
|
17
|
-
// 中文 bigram
|
|
18
|
-
const chinese = cleaned.match(/[\u4e00-\u9fff]+/g) || [];
|
|
19
|
-
for (const segment of chinese) {
|
|
20
|
-
for (let i = 0; i < segment.length - 1; i++) {
|
|
21
|
-
tokens.push(segment.substring(i, i + 2));
|
|
22
|
-
}
|
|
23
|
-
// 也保留单字
|
|
24
|
-
for (const char of segment) {
|
|
25
|
-
tokens.push(char);
|
|
26
|
-
}
|
|
17
|
+
if (jiebaInstance) {
|
|
18
|
+
return jiebaInstance.cut(text);
|
|
27
19
|
}
|
|
28
|
-
// 英文按空格分
|
|
29
|
-
|
|
30
|
-
tokens.push(...english);
|
|
31
|
-
return tokens;
|
|
20
|
+
// fallback: 英文按空格分
|
|
21
|
+
return text.toLowerCase().replace(/[^\w\u4e00-\u9fff]/g, " ").split(/\s+/).filter(Boolean);
|
|
32
22
|
}
|
|
33
23
|
export class BM25Index {
|
|
34
24
|
docs = [];
|
|
@@ -38,6 +28,7 @@ export class BM25Index {
|
|
|
38
28
|
* 从短期记忆文件构建索引
|
|
39
29
|
*/
|
|
40
30
|
async buildFromDirectory(dir, maxAgeDays = 7) {
|
|
31
|
+
await initJieba();
|
|
41
32
|
this.docs = [];
|
|
42
33
|
const cutoff = new Date();
|
|
43
34
|
cutoff.setDate(cutoff.getDate() - maxAgeDays);
|
|
@@ -130,7 +121,8 @@ export class BM25Index {
|
|
|
130
121
|
* BM25 搜索
|
|
131
122
|
* @returns 带分数的结果列表
|
|
132
123
|
*/
|
|
133
|
-
search(query, topK = 3) {
|
|
124
|
+
async search(query, topK = 3) {
|
|
125
|
+
await initJieba();
|
|
134
126
|
const queryTokens = tokenize(query);
|
|
135
127
|
const k1 = 1.5; // 词频饱和参数
|
|
136
128
|
const b = 0.75; // 文档长度归一化参数
|
|
@@ -158,12 +150,13 @@ export class BM25Index {
|
|
|
158
150
|
/**
|
|
159
151
|
* 添加单条记录(用于实时写入后的即时索引)
|
|
160
152
|
*/
|
|
161
|
-
addEntry(entry) {
|
|
153
|
+
async addEntry(entry) {
|
|
154
|
+
await initJieba();
|
|
162
155
|
this.docs.push({
|
|
163
156
|
tokens: tokenize(entry.text),
|
|
164
157
|
entry,
|
|
165
158
|
});
|
|
166
|
-
this.buildIDF();
|
|
159
|
+
this.buildIDF();
|
|
167
160
|
}
|
|
168
161
|
get size() {
|
|
169
162
|
return this.docs.length;
|
package/dist/bm25.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bm25.js","sourceRoot":"","sources":["../src/bm25.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,QAAQ,IAAI,EAAE,EAAE,MAAM,SAAS,CAAC;AACzC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAcjC
|
|
1
|
+
{"version":3,"file":"bm25.js","sourceRoot":"","sources":["../src/bm25.ts"],"names":[],"mappings":"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"}
|
package/dist/recall.js
CHANGED
|
@@ -98,7 +98,7 @@ export class RecallEngine {
|
|
|
98
98
|
const candidateLimit = this.config.recallTopK * candidateMultiplier;
|
|
99
99
|
const allResults = [];
|
|
100
100
|
// ---- 路径 1: BM25 搜短期记忆 ----
|
|
101
|
-
const bm25Results = this.bm25.search(query, candidateLimit);
|
|
101
|
+
const bm25Results = await this.bm25.search(query, candidateLimit);
|
|
102
102
|
for (const r of bm25Results) {
|
|
103
103
|
const ageDays = Math.max(0, (now.getTime() - new Date(r.entry.date).getTime()) / (1000 * 60 * 60 * 24));
|
|
104
104
|
const decayed = temporalDecay(r.score, ageDays, this.config.halfLifeDays);
|
package/dist/recall.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"recall.js","sourceRoot":"","sources":["../src/recall.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"recall.js","sourceRoot":"","sources":["../src/recall.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@scotthuang/engram",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "分层语义记忆系统 - OpenClaw Plugin",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"openclaw": {
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
]
|
|
10
10
|
},
|
|
11
11
|
"dependencies": {
|
|
12
|
+
"jieba-wasm": "^2.4.0",
|
|
12
13
|
"openclaw": "latest"
|
|
13
14
|
},
|
|
14
15
|
"devDependencies": {
|
|
@@ -30,5 +31,10 @@
|
|
|
30
31
|
},
|
|
31
32
|
"keywords": [],
|
|
32
33
|
"author": "",
|
|
33
|
-
"license": "ISC"
|
|
34
|
+
"license": "ISC",
|
|
35
|
+
"files": [
|
|
36
|
+
"dist",
|
|
37
|
+
"openclaw.plugin.json",
|
|
38
|
+
"README.md"
|
|
39
|
+
]
|
|
34
40
|
}
|
package/eslint.config.js
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import eslint from "@eslint/js";
|
|
2
|
-
import tseslint from "typescript-eslint";
|
|
3
|
-
|
|
4
|
-
export default tseslint.config(
|
|
5
|
-
eslint.configs.recommended,
|
|
6
|
-
...tseslint.configs.recommended,
|
|
7
|
-
{
|
|
8
|
-
rules: {
|
|
9
|
-
"@typescript-eslint/no-explicit-any": "warn",
|
|
10
|
-
"@typescript-eslint/no-unused-vars": ["warn", { argsIgnorePattern: "^_" }],
|
|
11
|
-
"no-console": ["warn", { allow: ["warn", "error"] }],
|
|
12
|
-
},
|
|
13
|
-
},
|
|
14
|
-
{
|
|
15
|
-
ignores: ["dist/", "node_modules/"],
|
|
16
|
-
}
|
|
17
|
-
);
|
|
@@ -1,102 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from "vitest";
|
|
2
|
-
import { BM25Index } from "../bm25.js";
|
|
3
|
-
|
|
4
|
-
describe("BM25Index", () => {
|
|
5
|
-
describe("tokenize (via search)", () => {
|
|
6
|
-
const index = new BM25Index();
|
|
7
|
-
|
|
8
|
-
it("finds Chinese bigram matches", () => {
|
|
9
|
-
index.addEntry({ text: "在体育西吃了潮汕牛肉火锅", date: "2026-03-15", category: "饮食", filePath: "/test" });
|
|
10
|
-
index.addEntry({ text: "讨论了记忆系统方案", date: "2026-03-15", category: "技术", filePath: "/test" });
|
|
11
|
-
|
|
12
|
-
const results = index.search("牛肉火锅", 2);
|
|
13
|
-
expect(results).toHaveLength(1);
|
|
14
|
-
expect(results[0].entry.category).toBe("饮食");
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
it("finds English word matches", () => {
|
|
18
|
-
index.addEntry({ text: "installed node modules successfully", date: "2026-03-15", category: "技术", filePath: "/test" });
|
|
19
|
-
index.addEntry({ text: "天气很好适合出门", date: "2026-03-15", category: "随聊", filePath: "/test" });
|
|
20
|
-
|
|
21
|
-
const results = index.search("node modules", 2);
|
|
22
|
-
expect(results).toHaveLength(1);
|
|
23
|
-
expect(results[0].entry.category).toBe("技术");
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
it("returns empty for no matches", () => {
|
|
27
|
-
index.addEntry({ text: "今天天气很好", date: "2026-03-15", category: "随聊", filePath: "/test" });
|
|
28
|
-
|
|
29
|
-
const results = index.search("量子力学", 3);
|
|
30
|
-
expect(results).toHaveLength(0);
|
|
31
|
-
});
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
describe("search", () => {
|
|
35
|
-
it("returns results sorted by score", () => {
|
|
36
|
-
const index = new BM25Index();
|
|
37
|
-
index.addEntry({ text: "牛肉火锅牛肉火锅牛肉火锅", date: "2026-03-15", category: "饮食", filePath: "/test" });
|
|
38
|
-
index.addEntry({ text: "吃过一次牛肉火锅", date: "2026-03-15", category: "饮食", filePath: "/test" });
|
|
39
|
-
index.addEntry({ text: "今天去跑步了", date: "2026-03-15", category: "健康", filePath: "/test" });
|
|
40
|
-
|
|
41
|
-
const results = index.search("牛肉火锅", 3);
|
|
42
|
-
expect(results.length).toBeGreaterThanOrEqual(2);
|
|
43
|
-
// 更高频率的应排在前面
|
|
44
|
-
expect(results[0].score).toBeGreaterThanOrEqual(results[1].score);
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
it("respects topK limit", () => {
|
|
48
|
-
const index = new BM25Index();
|
|
49
|
-
for (let i = 0; i < 10; i++) {
|
|
50
|
-
index.addEntry({ text: `测试条目${i} 包含关键词`, date: "2026-03-15", category: "测试", filePath: "/test" });
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
const results = index.search("关键词", 3);
|
|
54
|
-
expect(results.length).toBeLessThanOrEqual(3);
|
|
55
|
-
});
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
describe("addEntry", () => {
|
|
59
|
-
it("updates index size", () => {
|
|
60
|
-
const index = new BM25Index();
|
|
61
|
-
expect(index.size).toBe(0);
|
|
62
|
-
|
|
63
|
-
index.addEntry({ text: "test entry", date: "2026-03-15", category: "测试", filePath: "/test" });
|
|
64
|
-
expect(index.size).toBe(1);
|
|
65
|
-
|
|
66
|
-
index.addEntry({ text: "another entry", date: "2026-03-15", category: "测试", filePath: "/test" });
|
|
67
|
-
expect(index.size).toBe(2);
|
|
68
|
-
});
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
describe("parseEntries", () => {
|
|
72
|
-
it("parses structured format correctly", async () => {
|
|
73
|
-
const index = new BM25Index();
|
|
74
|
-
// buildFromDirectory 会调用 parseEntries
|
|
75
|
-
// 直接测试 search 验证解析结果
|
|
76
|
-
const mockContent = `# 2026-03-15
|
|
77
|
-
|
|
78
|
-
### 19:30 [饮食]
|
|
79
|
-
在体育西吃了潮汕牛肉火锅,胸口捞很好吃
|
|
80
|
-
|
|
81
|
-
### 14:00 [技术]
|
|
82
|
-
讨论了记忆系统方案,确定三层架构
|
|
83
|
-
|
|
84
|
-
### 10:00 [随聊]
|
|
85
|
-
聊了股票
|
|
86
|
-
`;
|
|
87
|
-
// 使用 addEntry 模拟解析后的结果
|
|
88
|
-
index.addEntry({ text: "在体育西吃了潮汕牛肉火锅,胸口捞很好吃", date: "2026-03-15", category: "饮食", filePath: "/test" });
|
|
89
|
-
index.addEntry({ text: "讨论了记忆系统方案,确定三层架构", date: "2026-03-15", category: "技术", filePath: "/test" });
|
|
90
|
-
|
|
91
|
-
// 搜饮食
|
|
92
|
-
const foodResults = index.search("牛肉火锅", 1);
|
|
93
|
-
expect(foodResults).toHaveLength(1);
|
|
94
|
-
expect(foodResults[0].entry.category).toBe("饮食");
|
|
95
|
-
|
|
96
|
-
// 搜技术
|
|
97
|
-
const techResults = index.search("记忆系统", 1);
|
|
98
|
-
expect(techResults).toHaveLength(1);
|
|
99
|
-
expect(techResults[0].entry.category).toBe("技术");
|
|
100
|
-
});
|
|
101
|
-
});
|
|
102
|
-
});
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from "vitest";
|
|
2
|
-
import { parseConfig, DEFAULTS } from "../config.js";
|
|
3
|
-
|
|
4
|
-
describe("parseConfig", () => {
|
|
5
|
-
it("returns defaults when no config provided", () => {
|
|
6
|
-
const config = parseConfig();
|
|
7
|
-
expect(config.shortTermDays).toBe(DEFAULTS.shortTermDays);
|
|
8
|
-
expect(config.halfLifeDays).toBe(DEFAULTS.halfLifeDays);
|
|
9
|
-
expect(config.recallTopK).toBe(DEFAULTS.recallTopK);
|
|
10
|
-
expect(config.minScore).toBe(DEFAULTS.minScore);
|
|
11
|
-
expect(config.vectorWeight).toBe(DEFAULTS.vectorWeight);
|
|
12
|
-
expect(config.textWeight).toBe(DEFAULTS.textWeight);
|
|
13
|
-
});
|
|
14
|
-
|
|
15
|
-
it("returns defaults when empty object provided", () => {
|
|
16
|
-
const config = parseConfig({});
|
|
17
|
-
expect(config.shortTermDays).toBe(7);
|
|
18
|
-
expect(config.halfLifeDays).toBe(30);
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
it("overrides defaults with provided values", () => {
|
|
22
|
-
const config = parseConfig({ shortTermDays: 14, minScore: 0.5 });
|
|
23
|
-
expect(config.shortTermDays).toBe(14);
|
|
24
|
-
expect(config.minScore).toBe(0.5);
|
|
25
|
-
// 其他值保持默认
|
|
26
|
-
expect(config.halfLifeDays).toBe(DEFAULTS.halfLifeDays);
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
it("ignores invalid types and uses defaults", () => {
|
|
30
|
-
const config = parseConfig({ shortTermDays: "abc" as any, halfLifeDays: null as any });
|
|
31
|
-
expect(config.shortTermDays).toBe(DEFAULTS.shortTermDays);
|
|
32
|
-
expect(config.halfLifeDays).toBe(DEFAULTS.halfLifeDays);
|
|
33
|
-
});
|
|
34
|
-
});
|
|
@@ -1,147 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach } from "vitest";
|
|
2
|
-
import { ProfileManager, EMPTY_PROFILE } from "../profile.js";
|
|
3
|
-
import { promises as fs } from "node:fs";
|
|
4
|
-
import { join } from "node:path";
|
|
5
|
-
import { tmpdir } from "node:os";
|
|
6
|
-
|
|
7
|
-
describe("ProfileManager", () => {
|
|
8
|
-
let manager: ProfileManager;
|
|
9
|
-
let tempDir: string;
|
|
10
|
-
|
|
11
|
-
beforeEach(async () => {
|
|
12
|
-
tempDir = join(tmpdir(), `engram-test-${Date.now()}`);
|
|
13
|
-
await fs.mkdir(tempDir, { recursive: true });
|
|
14
|
-
manager = new ProfileManager(tempDir);
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
describe("load", () => {
|
|
18
|
-
it("returns empty profile when no file exists", async () => {
|
|
19
|
-
const profile = await manager.load();
|
|
20
|
-
expect(profile.summary).toBe("");
|
|
21
|
-
expect(profile.coreTags).toEqual([]);
|
|
22
|
-
expect(profile.tags).toEqual({});
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
it("loads existing profile from file", async () => {
|
|
26
|
-
const profileDir = join(tempDir, "memory", "profile");
|
|
27
|
-
await fs.mkdir(profileDir, { recursive: true });
|
|
28
|
-
const saved = { ...EMPTY_PROFILE, summary: "test summary", coreTags: ["tag1"] };
|
|
29
|
-
await fs.writeFile(join(profileDir, "semantic_profile.json"), JSON.stringify(saved));
|
|
30
|
-
|
|
31
|
-
// 创建新的 manager 实例测试从文件加载
|
|
32
|
-
const manager2 = new ProfileManager(tempDir);
|
|
33
|
-
const loaded = await manager2.load();
|
|
34
|
-
expect(loaded.summary).toBe("test summary");
|
|
35
|
-
expect(loaded.coreTags).toEqual(["tag1"]);
|
|
36
|
-
});
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
describe("addTag", () => {
|
|
40
|
-
it("adds a new tag to a new dimension", async () => {
|
|
41
|
-
const profile = { ...EMPTY_PROFILE };
|
|
42
|
-
const result = manager.addTag(profile, "口味偏好", "喜欢辣");
|
|
43
|
-
expect(result.tags["口味偏好"]).toHaveLength(1);
|
|
44
|
-
expect(result.tags["口味偏好"][0].value).toBe("喜欢辣");
|
|
45
|
-
expect(result.tags["口味偏好"][0].confidence).toBe(0.7);
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
it("increases confidence for existing tag", async () => {
|
|
49
|
-
const profile: any = {
|
|
50
|
-
...EMPTY_PROFILE,
|
|
51
|
-
tags: { 口味偏好: [{ value: "喜欢辣", confidence: 0.5, lastSeen: "2026-01-01" }] },
|
|
52
|
-
};
|
|
53
|
-
const result = manager.addTag(profile, "口味偏好", "喜欢辣");
|
|
54
|
-
expect(result.tags["口味偏好"]).toHaveLength(1);
|
|
55
|
-
expect(result.tags["口味偏好"][0].confidence).toBeCloseTo(0.6);
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
it("adds multiple tags to same dimension", async () => {
|
|
59
|
-
const profile = { ...EMPTY_PROFILE };
|
|
60
|
-
manager.addTag(profile, "口味偏好", "喜欢辣");
|
|
61
|
-
manager.addTag(profile, "口味偏好", "不吃香菜");
|
|
62
|
-
expect(profile.tags["口味偏好"]).toHaveLength(2);
|
|
63
|
-
});
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
describe("decayTags", () => {
|
|
67
|
-
it("reduces confidence of all tags", () => {
|
|
68
|
-
const profile: any = {
|
|
69
|
-
...EMPTY_PROFILE,
|
|
70
|
-
tags: {
|
|
71
|
-
口味偏好: [
|
|
72
|
-
{ value: "喜欢辣", confidence: 0.9, lastSeen: "2026-03-17" },
|
|
73
|
-
],
|
|
74
|
-
},
|
|
75
|
-
};
|
|
76
|
-
const result = manager.decayTags(profile, 0.5);
|
|
77
|
-
expect(result.tags["口味偏好"][0].confidence).toBeCloseTo(0.45);
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
it("removes tags below threshold", () => {
|
|
81
|
-
const profile: any = {
|
|
82
|
-
...EMPTY_PROFILE,
|
|
83
|
-
tags: {
|
|
84
|
-
过时: [{ value: "旧标签", confidence: 0.15, lastSeen: "2026-01-01" }],
|
|
85
|
-
},
|
|
86
|
-
};
|
|
87
|
-
const result = manager.decayTags(profile, 1.0);
|
|
88
|
-
expect(result.tags["过时"]).toBeUndefined();
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
it("removes empty dimensions after filtering", () => {
|
|
92
|
-
const profile: any = {
|
|
93
|
-
...EMPTY_PROFILE,
|
|
94
|
-
tags: {
|
|
95
|
-
空维度: [{ value: "很低", confidence: 0.1, lastSeen: "2026-01-01" }],
|
|
96
|
-
},
|
|
97
|
-
};
|
|
98
|
-
const result = manager.decayTags(profile, 1.0);
|
|
99
|
-
expect("空维度" in result.tags).toBe(false);
|
|
100
|
-
});
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
describe("getRecallContext", () => {
|
|
104
|
-
it("returns empty string for empty profile", () => {
|
|
105
|
-
const ctx = manager.getRecallContext(EMPTY_PROFILE);
|
|
106
|
-
expect(ctx).toBe("");
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
it("returns summary and core tags", () => {
|
|
110
|
-
const profile = { ...EMPTY_PROFILE, summary: "辣味中餐爱好者", coreTags: ["辣味中餐", "天河"] };
|
|
111
|
-
const ctx = manager.getRecallContext(profile);
|
|
112
|
-
expect(ctx).toContain("辣味中餐爱好者");
|
|
113
|
-
expect(ctx).toContain("辣味中餐");
|
|
114
|
-
expect(ctx).toContain("天河");
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
it("works with only core tags", () => {
|
|
118
|
-
const profile = { ...EMPTY_PROFILE, summary: "", coreTags: ["标签1"] };
|
|
119
|
-
const ctx = manager.getRecallContext(profile);
|
|
120
|
-
expect(ctx).toContain("标签1");
|
|
121
|
-
});
|
|
122
|
-
});
|
|
123
|
-
|
|
124
|
-
describe("save", () => {
|
|
125
|
-
it("saves profile to file", async () => {
|
|
126
|
-
const profile = { ...EMPTY_PROFILE, summary: "test" };
|
|
127
|
-
await manager.save(profile);
|
|
128
|
-
|
|
129
|
-
const raw = await fs.readFile(join(tempDir, "memory", "profile", "semantic_profile.json"), "utf-8");
|
|
130
|
-
const loaded = JSON.parse(raw);
|
|
131
|
-
expect(loaded.summary).toBe("test");
|
|
132
|
-
});
|
|
133
|
-
|
|
134
|
-
it("updates updatedAt on save", async () => {
|
|
135
|
-
const profile = { ...EMPTY_PROFILE };
|
|
136
|
-
const before = new Date();
|
|
137
|
-
await manager.save(profile);
|
|
138
|
-
const after = new Date();
|
|
139
|
-
|
|
140
|
-
const raw = await fs.readFile(join(tempDir, "memory", "profile", "semantic_profile.json"), "utf-8");
|
|
141
|
-
const loaded = JSON.parse(raw);
|
|
142
|
-
const updated = new Date(loaded.updatedAt);
|
|
143
|
-
expect(updated.getTime()).toBeGreaterThanOrEqual(before.getTime());
|
|
144
|
-
expect(updated.getTime()).toBeLessThanOrEqual(after.getTime());
|
|
145
|
-
});
|
|
146
|
-
});
|
|
147
|
-
});
|