topic-memory 0.1.0

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.
@@ -0,0 +1,345 @@
1
+ # 接入指南
2
+
3
+ [English](./USAGE.md) · [架构与容量说明](./ARCHITECTURE.zh-CN.md)
4
+
5
+ 这份文档只讲一件事:**怎么把 Topic Memory 接进你已经存在的聊天 App 或 Agent。**
6
+
7
+ 它的接入思路很简单:
8
+
9
+ > 你的 App 本来就会调用 Main LLM。Topic Memory 只是在这次调用旁边加一层长期记忆:先找回相关旧信息,再把 `memoryContext` 交给你。
10
+
11
+ 你不需要为了接这个 SDK 重写整套聊天架构。
12
+
13
+ ## 1. 先分清三个角色
14
+
15
+ ### Topic Worker
16
+
17
+ 后台的“记忆整理员”。
18
+
19
+ 它会把 completed exchanges 按主题整理成 topic,并保存 topic metadata 和指向原始 Canonical Transcript 的准确 spans。
20
+
21
+ ### Memory Selector
22
+
23
+ 后台的“记忆检索员”。
24
+
25
+ 每次新消息到来时,它会看当前问题、最近 5 个 completed exchanges 和 Topic Directory,然后最多挑 3 个相关旧 topic 打开。
26
+
27
+ ### 你的 Main LLM
28
+
29
+ 真正生成用户最终看到回复的模型。
30
+
31
+ v0.1 默认只需要配置一个 Memory LLM:
32
+
33
+ ```ts
34
+ createMemory({ storage, llm: memoryLlm })
35
+ ```
36
+
37
+ 这个 Memory LLM 同时承担 Topic Worker 和 Memory Selector。
38
+
39
+ **它不是你的 Main LLM。** Topic Memory SDK 不会替你调用 Main LLM。
40
+
41
+ ## 2. 创建 memory engine
42
+
43
+ ```ts
44
+ import {
45
+ createMemory,
46
+ createOpenAICompatibleMemoryLlm,
47
+ InMemoryStorage,
48
+ } from 'topic-memory';
49
+
50
+ const memoryLlm = createOpenAICompatibleMemoryLlm({
51
+ baseUrl: process.env.MEMORY_LLM_BASE_URL!,
52
+ apiKey: process.env.MEMORY_LLM_API_KEY,
53
+ model: process.env.MEMORY_LLM_MODEL!,
54
+ });
55
+
56
+ const memory = createMemory({
57
+ storage: new InMemoryStorage(),
58
+ llm: memoryLlm,
59
+ });
60
+ ```
61
+
62
+ `InMemoryStorage` 适合 demo 和测试,进程退出后会清空。
63
+
64
+ 浏览器持久化使用 `IndexedDbMemoryStorage`。正式服务端产品可以实现导出的 `MemoryStorage` interface,接自己的数据库。
65
+
66
+ ## 3. 包住一轮正常聊天
67
+
68
+ 正确顺序:
69
+
70
+ ```text
71
+ 用户发送消息
72
+
73
+
74
+ memory.begin()
75
+
76
+
77
+ memory.retrieve()
78
+
79
+
80
+ 你的 Main LLM
81
+
82
+
83
+ memory.completeExchange()
84
+
85
+
86
+ memory.maybeRunTopicWorker()
87
+ ```
88
+
89
+ 完整示例:
90
+
91
+ ```ts
92
+ async function handleUserMessage(userMessage: string) {
93
+ const pending = await memory.begin(userMessage);
94
+
95
+ try {
96
+ const retrieved = await memory.retrieve({ userMessage });
97
+
98
+ const assistantReply = await myOwnMainLlm({
99
+ userMessage,
100
+ memoryContext: retrieved.memoryContext,
101
+ recentContext: retrieved.recentContext,
102
+ });
103
+
104
+ await memory.completeExchange({
105
+ exchangeId: pending.id,
106
+ assistantText: assistantReply,
107
+ });
108
+
109
+ await memory.maybeRunTopicWorker();
110
+
111
+ return assistantReply;
112
+ } catch (error) {
113
+ await memory.failExchange({
114
+ exchangeId: pending.id,
115
+ failureReason: error instanceof Error ? error.message : String(error),
116
+ });
117
+
118
+ throw error;
119
+ }
120
+ }
121
+ ```
122
+
123
+ 这里的 `myOwnMainLlm()` 就是你自己 App 原来已有的主模型调用。SDK 内部不会调用它。
124
+
125
+ ## 4. 把长期记忆传给 Main LLM
126
+
127
+ `retrieve()` 会返回两层上下文:
128
+
129
+ - `recentContext`:最近 5 个 completed exchanges;
130
+ - `memoryContext`:只有当前问题真正需要时才恢复出来的旧 topic memory。
131
+
132
+ ```ts
133
+ const retrieved = await memory.retrieve({ userMessage });
134
+ ```
135
+
136
+ 完整返回值包括:
137
+
138
+ ```ts
139
+ {
140
+ recentContext,
141
+ topicDirectory,
142
+ selectedTopicIds,
143
+ openedTopicPackets,
144
+ memoryContext,
145
+ needsTimeMetadata,
146
+ trace,
147
+ }
148
+ ```
149
+
150
+ 一种常见的 Main LLM 接法:
151
+
152
+ ```ts
153
+ const assistantReply = await myOwnMainLlm({
154
+ messages: [
155
+ {
156
+ role: 'system',
157
+ content: [
158
+ baseSystemPrompt,
159
+ retrieved.memoryContext,
160
+ ].filter(Boolean).join('\n\n'),
161
+ },
162
+ {
163
+ role: 'user',
164
+ content: userMessage,
165
+ },
166
+ ],
167
+ });
168
+ ```
169
+
170
+ `memoryContext` 为空并不代表报错。
171
+
172
+ 可能只是:
173
+
174
+ - 还没有生成 topic;
175
+ - 当前问题不需要旧记忆;
176
+ - Selector 调用失败后安全降级为空。
177
+
178
+ ## 5. 这些 API 背后分别发生了什么
179
+
180
+ ### `memory.begin(userMessage)`
181
+
182
+ 在 Main LLM 开始回复前,先创建一个 pending Canonical Exchange。
183
+
184
+ ### `memory.retrieve({ userMessage })`
185
+
186
+ 准备最近 5 个 exchanges,构建 Topic Directory 给 Selector,选择相关旧 topic,然后根据 transcript spans 打开原始历史,最终返回 `memoryContext`。
187
+
188
+ ### 你的 Main LLM
189
+
190
+ 拿到当前消息和你决定注入的 memory fields,生成最终回复。
191
+
192
+ ### `memory.completeExchange(...)`
193
+
194
+ 把这一轮标记成 completed,并把最终 assistant reply 保存进 Canonical Transcript。
195
+
196
+ ### `memory.maybeRunTopicWorker()`
197
+
198
+ 检查 active tail 是否已经积累了足够 completed exchanges,需要时重新整理 topic。
199
+
200
+ 你可以每次成功回复后都调用它,SDK 自己会判断 gate,不需要你手动数轮数。
201
+
202
+ ## 6. 前 6 个 completed exchanges
203
+
204
+ 至少存在 6 个 completed exchanges 之前,Topic Worker 不运行。
205
+
206
+ 这段时间:
207
+
208
+ - Canonical Transcript 正常记录;
209
+ - `recentContext` 正常工作;
210
+ - 因为长期 Topic Store 还没建立,`memoryContext` 可能为空。
211
+
212
+ 这是正常启动阶段。
213
+
214
+ ## 7. Main LLM 调用失败怎么办
215
+
216
+ 如果 `begin()` 已经成功,但 Main LLM 后面超时或报错,不要让这个 exchange 永远停在 pending。
217
+
218
+ ```ts
219
+ await memory.failExchange({
220
+ exchangeId: pending.id,
221
+ failureReason: 'provider_timeout',
222
+ });
223
+ ```
224
+
225
+ Failed exchange 仍然属于 Canonical Transcript 生命周期的一部分,但 Topic Worker 不会把它当作 completed conversation evidence。
226
+
227
+ ## 8. 一个 Memory LLM 还是两个
228
+
229
+ 绝大多数 App 可以直接用一个:
230
+
231
+ ```ts
232
+ const memory = createMemory({
233
+ storage,
234
+ llm: memoryLlm,
235
+ });
236
+ ```
237
+
238
+ 它同时承担 Topic Worker 和 Selector。
239
+
240
+ 高级部署可以拆开:
241
+
242
+ ```ts
243
+ const memory = createMemory({
244
+ storage,
245
+ topicWorker: topicWorkerLlm,
246
+ selector: selectorLlm,
247
+ });
248
+ ```
249
+
250
+ 两个对象都实现 `MemoryLlm` interface。
251
+
252
+ 例如你可以让 Topic Worker 用能力更强的模型,而 Selector 用延迟更低、成本更低的模型。
253
+
254
+ 无论怎么拆,都不会改变宿主 Main LLM 仍由你的 App 控制这一点。
255
+
256
+ ## 9. 存储怎么选
257
+
258
+ ### 内存
259
+
260
+ ```ts
261
+ new InMemoryStorage()
262
+ ```
263
+
264
+ 适合本地 demo 和测试。进程退出后数据消失。
265
+
266
+ ### 浏览器 IndexedDB
267
+
268
+ ```ts
269
+ new IndexedDbMemoryStorage()
270
+ ```
271
+
272
+ 只在支持 IndexedDB 的环境使用。
273
+
274
+ ### 你自己的服务端数据库
275
+
276
+ 实现 `MemoryStorage` interface,就可以接 PostgreSQL、SQLite、Redis、KV store 等。
277
+
278
+ 真正的多用户产品应该按照自己的 tenancy 设计,为 conversation / user / agent identity 隔离 memory store。
279
+
280
+ ## 10. 怎么检查 Memory 里面到底存了什么
281
+
282
+ ```ts
283
+ const exchanges = await memory.listExchanges();
284
+ const topics = await memory.listTopics();
285
+ const latestWorkerRun = await memory.getLatestTopicWorkerRun();
286
+ ```
287
+
288
+ 适合做后台管理页、debug 面板,或者调查为什么某个旧 topic 没被找回来。
289
+
290
+ `retrieve().trace` 也会返回 Selector 相关诊断信息。
291
+
292
+ ## 11. 失败时会不会拖垮聊天
293
+
294
+ 设计目标是 fail soft:Memory 出问题时,宿主聊天尽量还能继续。
295
+
296
+ - **Topic Worker provider 失败:** 记录失败,已有 topics 保留;
297
+ - **Topic Worker JSON / validation 不合格:** 拒绝这次结果,不写入 Topic Store;
298
+ - **Memory Selector 失败:** 长期 `memoryContext` 降级为空;
299
+ - **当前问题没有相关旧 topic:** `memoryContext` 本来就为空。
300
+
301
+ 是否重试、记日志、报警,还是直接继续无长期记忆回复,由你的宿主应用决定。
302
+
303
+ ## 12. 推荐的生产架构
304
+
305
+ ```text
306
+ 客户端
307
+
308
+
309
+ 你的后端
310
+ ├── Topic Memory SDK
311
+ │ ├── Memory Storage
312
+ │ └── Memory LLM
313
+ │ ├── Topic Worker role
314
+ │ └── Memory Selector role
315
+
316
+ └── 你的 Main LLM
317
+ └── 生成用户最终回复
318
+ ```
319
+
320
+ 付费 provider 的密钥放在可信后端或代理,不要直接打进公开前端 bundle。
321
+
322
+ ## 13. 能扩展到多少轮?
323
+
324
+ Topic Memory 不会扩大模型 context window。它减少的是“每次都重放全部历史”的需求。
325
+
326
+ v0.1 会把完整 Canonical Transcript 保存在外部存储,只把轻量 Topic Directory 和最多三个相关旧 topic 放进一次 retrieval。
327
+
328
+ 在一组明确假设下,传统 raw-history 约 600 exchanges 的 prompt 预算,可以对应一个约 5,000 exchanges 的可索引、可按需恢复历史档案。这个例子约等于 8.3× 的历史跨度。
329
+
330
+ 详细公式和 43k–45k token 推算见 [架构与容量说明](./ARCHITECTURE.zh-CN.md)。
331
+
332
+ 这只是理论容量计算,不是硬上限或性能 benchmark。
333
+
334
+ ## 14. 验证 package
335
+
336
+ ```bash
337
+ npm install
338
+ npm run build
339
+ npm run typecheck
340
+ npm test
341
+ npm pack --dry-run
342
+ npm run smoke:consumer
343
+ ```
344
+
345
+ `smoke:consumer` 会打包 SDK,把 tarball 安装进一个全新的临时 Node 项目,只从 public package exports 导入,然后运行完整 memory pipeline,并验证模拟的宿主 Main LLM 确实收到非空 `memoryContext`。
package/package.json ADDED
@@ -0,0 +1,56 @@
1
+ {
2
+ "name": "topic-memory",
3
+ "version": "0.1.0",
4
+ "description": "A standalone TypeScript SDK that adds topic-based long-term conversation memory to existing LLM applications.",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.js"
12
+ }
13
+ },
14
+ "files": ["dist", "docs", "README.md", "README.zh-CN.md", "LICENSE"],
15
+ "scripts": {
16
+ "build": "tsc -p tsconfig.build.json",
17
+ "typecheck": "tsc -p tsconfig.build.json --noEmit",
18
+ "test": "node -e \"require('fs').rmSync('.test-dist',{recursive:true,force:true})\" && tsc -p tsconfig.test.json && node --test .test-dist/tests/*.test.js",
19
+ "smoke:consumer": "node scripts/consumer-smoke.mjs",
20
+ "prepack": "npm run build"
21
+ },
22
+ "keywords": [
23
+ "llm",
24
+ "llm-memory",
25
+ "long-term-memory",
26
+ "conversation-memory",
27
+ "agent-memory",
28
+ "ai-agent",
29
+ "context-management",
30
+ "conversational-ai",
31
+ "chatbot",
32
+ "typescript",
33
+ "sdk"
34
+ ],
35
+ "license": "MIT",
36
+ "repository": {
37
+ "type": "git",
38
+ "url": "git+https://github.com/ziningshu-code/memory-system-mvp.git"
39
+ },
40
+ "homepage": "https://github.com/ziningshu-code/memory-system-mvp#readme",
41
+ "bugs": {
42
+ "url": "https://github.com/ziningshu-code/memory-system-mvp/issues"
43
+ },
44
+ "publishConfig": {
45
+ "access": "public",
46
+ "provenance": true
47
+ },
48
+ "engines": {
49
+ "node": ">=18"
50
+ },
51
+ "devDependencies": {
52
+ "@types/node": "^26.1.1",
53
+ "fake-indexeddb": "^6.2.2",
54
+ "typescript": "^5.9.3"
55
+ }
56
+ }