@ppagent/memory 0.3.1 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +1201 -0
- package/dist/index.d.ts +153 -12
- package/dist/index.js +1146 -358
- package/dist/{provider.resolver-ZLQ766IO.js → provider.resolver-2KS2YYNV.js} +32 -1
- package/llms.txt +874 -820
- package/package.json +5 -2
package/llms.txt
CHANGED
|
@@ -1,862 +1,916 @@
|
|
|
1
|
-
# @ppagent/memory
|
|
2
|
-
|
|
3
|
-
> `@ppagent/memory` is an independent TypeScript/Node.js long-term memory package for AI agents. It stores conversation
|
|
4
|
-
|
|
5
|
-
This file is written for AI coding agents and assistants. Use it as the primary package guide when generating code that depends on `@ppagent/memory`.
|
|
6
|
-
|
|
7
|
-
## Package Identity
|
|
8
|
-
|
|
9
|
-
- Package name: `@ppagent/memory`
|
|
10
|
-
- Module format: ESM
|
|
11
|
-
- Main entry: `dist/index.js`
|
|
12
|
-
- Type entry: `dist/index.d.ts`
|
|
13
|
-
- Source entry during local workspace development: `src/index.ts`
|
|
14
|
-
- Runtime target: Node.js with global `fetch`
|
|
15
|
-
- Storage engines:
|
|
16
|
-
- Vector store (dual backend, auto-detected): LanceDB or SQLite (better-sqlite3 + sqlite-vec + FTS5 with jieba Chinese tokenization) for messages, topics, facts, sessions, documents, and chunks. LanceDB requires native prebuilt binaries (unavailable on Intel macOS since 0.23.0); SQLite works everywhere and is the automatic fallback. RRF hybrid-search fusion runs in the shared domain layer, so search behavior is identical across backends.
|
|
17
|
-
- Grafeo for entity/relation graph storage and graph search
|
|
18
|
-
-
|
|
19
|
-
- LLM APIs: OpenAI-compatible `/chat/completions`
|
|
20
|
-
- Embedding APIs: OpenAI-compatible `/embeddings`
|
|
21
|
-
|
|
22
|
-
## When To Use This Package
|
|
23
|
-
|
|
24
|
-
Use `@ppagent/memory` when an AI agent needs:
|
|
25
|
-
|
|
26
|
-
- Long-term conversation memory across sessions.
|
|
27
|
-
- Recent message storage plus compressed historical context windows.
|
|
28
|
-
- User-level or chat-level facts such as preferences, profile information, project decisions, or instructions that should persist.
|
|
29
|
-
- Hybrid semantic/full-text search over remembered topics and messages.
|
|
30
|
-
- Optional graph search for entity-relationship questions.
|
|
31
|
-
- Markdown knowledge-base ingestion with chunking, vector search, document-level coarse recall, and optional knowledge graph construction.
|
|
32
|
-
- Session management APIs for agent dashboards or memory administration pages.
|
|
33
|
-
- A standalone memory layer that does not depend on the larger PPAgent runtime.
|
|
34
|
-
|
|
35
|
-
Do not use it as a full agent framework by itself. It does not send chat messages, call tools for users, route platform webhooks, or run a frontend. It is a memory and retrieval module.
|
|
36
|
-
|
|
37
|
-
## Install
|
|
38
|
-
|
|
39
|
-
```bash
|
|
40
|
-
pnpm add @ppagent/memory
|
|
41
|
-
```
|
|
42
|
-
|
|
43
|
-
```bash
|
|
44
|
-
npm install @ppagent/memory
|
|
45
|
-
```
|
|
46
|
-
|
|
47
|
-
## Imports
|
|
48
|
-
|
|
49
|
-
```ts
|
|
50
|
-
import {
|
|
51
|
-
MemoryManager,
|
|
52
|
-
MemoryStore,
|
|
53
|
-
DEFAULT_NODE_TYPES,
|
|
54
|
-
DEFAULT_RELATION_TYPES,
|
|
55
|
-
KIND_CONVERSATION,
|
|
56
|
-
KIND_KNOWLEDGE,
|
|
57
|
-
} from "@ppagent/memory";
|
|
58
|
-
|
|
59
|
-
import type {
|
|
60
|
-
MemoryConfig,
|
|
61
|
-
RawMessage,
|
|
62
|
-
SearchOptions,
|
|
63
|
-
SearchResult,
|
|
64
|
-
AddDocumentOptions,
|
|
65
|
-
KnowledgeSearchOptions,
|
|
1
|
+
# @ppagent/memory
|
|
2
|
+
|
|
3
|
+
> `@ppagent/memory` is an independent TypeScript/Node.js long-term memory package for AI agents. It stores complete raw conversation messages in a pluggable vector store (LanceDB or SQLite/sqlite-vec, auto-detected per platform), builds importance-aware Topic summaries, returns dynamically budgeted Topics plus recent uncompressed messages as one context window, extracts and searches entity relationships with Grafeo, supports cached user/chat facts, and ingests Markdown documents as a searchable knowledge base.
|
|
4
|
+
|
|
5
|
+
This file is written for AI coding agents and assistants. Use it as the primary package guide when generating code that depends on `@ppagent/memory`.
|
|
6
|
+
|
|
7
|
+
## Package Identity
|
|
8
|
+
|
|
9
|
+
- Package name: `@ppagent/memory`
|
|
10
|
+
- Module format: ESM
|
|
11
|
+
- Main entry: `dist/index.js`
|
|
12
|
+
- Type entry: `dist/index.d.ts`
|
|
13
|
+
- Source entry during local workspace development: `src/index.ts`
|
|
14
|
+
- Runtime target: Node.js with global `fetch`
|
|
15
|
+
- Storage engines:
|
|
16
|
+
- Vector store (dual backend, auto-detected): LanceDB or SQLite (better-sqlite3 + sqlite-vec + FTS5 with jieba Chinese tokenization) for messages, topics, facts, sessions, documents, and chunks. LanceDB requires native prebuilt binaries (unavailable on Intel macOS since 0.23.0); SQLite works everywhere and is the automatic fallback. RRF hybrid-search fusion runs in the shared domain layer, so search behavior is identical across backends.
|
|
17
|
+
- Grafeo for entity/relation graph storage and graph search
|
|
18
|
+
- Runtime configuration principle: `MemoryManager`/`MemoryStore` never read environment variables, config files, or implicit global state; every parameter enters through `MemoryConfig`. The separate manual migration CLI reads only the file explicitly supplied with `--config`.
|
|
19
|
+
- LLM APIs: OpenAI-compatible `/chat/completions`
|
|
20
|
+
- Embedding APIs: OpenAI-compatible `/embeddings`
|
|
21
|
+
|
|
22
|
+
## When To Use This Package
|
|
23
|
+
|
|
24
|
+
Use `@ppagent/memory` when an AI agent needs:
|
|
25
|
+
|
|
26
|
+
- Long-term conversation memory across sessions.
|
|
27
|
+
- Recent message storage plus compressed historical context windows.
|
|
28
|
+
- User-level or chat-level facts such as preferences, profile information, project decisions, or instructions that should persist.
|
|
29
|
+
- Hybrid semantic/full-text search over remembered topics and messages.
|
|
30
|
+
- Optional graph search for entity-relationship questions.
|
|
31
|
+
- Markdown knowledge-base ingestion with chunking, vector search, document-level coarse recall, and optional knowledge graph construction.
|
|
32
|
+
- Session management APIs for agent dashboards or memory administration pages.
|
|
33
|
+
- A standalone memory layer that does not depend on the larger PPAgent runtime.
|
|
34
|
+
|
|
35
|
+
Do not use it as a full agent framework by itself. It does not send chat messages, call tools for users, route platform webhooks, or run a frontend. It is a memory and retrieval module.
|
|
36
|
+
|
|
37
|
+
## Install
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
pnpm add @ppagent/memory
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
npm install @ppagent/memory
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Imports
|
|
48
|
+
|
|
49
|
+
```ts
|
|
50
|
+
import {
|
|
51
|
+
MemoryManager,
|
|
52
|
+
MemoryStore,
|
|
53
|
+
DEFAULT_NODE_TYPES,
|
|
54
|
+
DEFAULT_RELATION_TYPES,
|
|
55
|
+
KIND_CONVERSATION,
|
|
56
|
+
KIND_KNOWLEDGE,
|
|
57
|
+
} from "@ppagent/memory";
|
|
58
|
+
|
|
59
|
+
import type {
|
|
60
|
+
MemoryConfig,
|
|
61
|
+
RawMessage,
|
|
62
|
+
SearchOptions,
|
|
63
|
+
SearchResult,
|
|
64
|
+
AddDocumentOptions,
|
|
65
|
+
KnowledgeSearchOptions,
|
|
66
66
|
KnowledgeSearchResult,
|
|
67
|
+
MemoryContextWindow,
|
|
67
68
|
} from "@ppagent/memory";
|
|
68
|
-
```
|
|
69
|
-
|
|
70
|
-
Most consumers should only instantiate `MemoryManager`. `MemoryStore` (plus the `VectorStoreProvider` / `Filter` types) is exported for advanced storage tests or custom infrastructure work. Breaking change in 0.2.0: `LanceService` was replaced by `MemoryStore` + provider abstraction; string SQL filters were replaced by structured `Filter` objects.
|
|
71
|
-
|
|
72
|
-
## Minimal Example
|
|
73
|
-
|
|
74
|
-
```ts
|
|
75
|
-
import { MemoryManager } from "@ppagent/memory";
|
|
76
|
-
|
|
77
|
-
const memory = new MemoryManager({
|
|
78
|
-
lancedbPath: "./data/memory/lance",
|
|
79
|
-
grafeoPath: "./data/memory/grafeo",
|
|
80
|
-
|
|
81
|
-
llmBaseUrl: process.env.LLM_BASE_URL ?? "https://api.openai.com/v1",
|
|
82
|
-
llmApiKey: process.env.LLM_API_KEY ?? "",
|
|
83
|
-
llmModel: process.env.LLM_MODEL ?? "gpt-4o-mini",
|
|
84
|
-
|
|
85
|
-
embeddingBaseUrl: process.env.EMBED_BASE_URL ?? process.env.LLM_BASE_URL ?? "https://api.openai.com/v1",
|
|
86
|
-
embeddingApiKey: process.env.EMBED_API_KEY ?? process.env.LLM_API_KEY ?? "",
|
|
87
|
-
embeddingModel: process.env.EMBED_MODEL ?? "text-embedding-3-small",
|
|
88
|
-
embeddingDimension: Number(process.env.EMBED_DIMENSION ?? 1536),
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
await memory.init();
|
|
92
|
-
|
|
93
|
-
await memory.updateChat(
|
|
94
|
-
[
|
|
95
|
-
{ talkerId: "user", content: "我叫 Bob,正在做一个 Rust 项目。" },
|
|
96
|
-
{ talkerId: "assistant", content: "好的,我会记住你在做 Rust 项目。" },
|
|
97
|
-
],
|
|
98
|
-
{
|
|
99
|
-
userId: "user-bob",
|
|
100
|
-
chatId: "agent-dev",
|
|
101
|
-
sessionId: "session-001",
|
|
102
|
-
sessionTitle: "Bob 的开发对话",
|
|
103
|
-
}
|
|
104
|
-
);
|
|
105
|
-
|
|
106
|
-
const results = await memory.search({
|
|
107
|
-
query: "Bob 正在做什么项目?",
|
|
108
|
-
scope: "user",
|
|
109
|
-
scopeId: "user-bob",
|
|
110
|
-
mode: "auto",
|
|
111
|
-
});
|
|
112
|
-
|
|
113
|
-
console.log(results);
|
|
114
|
-
|
|
115
|
-
const answer = await memory.ask({
|
|
116
|
-
query: "Bob 正在做什么项目?",
|
|
117
|
-
scope: "user",
|
|
118
|
-
scopeId: "user-bob",
|
|
119
|
-
maxChars: 120,
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
console.log(answer);
|
|
123
|
-
|
|
124
|
-
memory.destroy();
|
|
125
|
-
```
|
|
126
|
-
|
|
127
|
-
## Complete Example
|
|
128
|
-
|
|
129
|
-
```ts
|
|
130
|
-
import { MemoryManager } from "@ppagent/memory";
|
|
131
|
-
import type { Entity, Relation } from "@ppagent/memory";
|
|
132
|
-
|
|
133
|
-
const memory = new MemoryManager({
|
|
134
|
-
lancedbPath: "./data/memory/lance",
|
|
135
|
-
grafeoPath: "./data/memory/grafeo",
|
|
136
|
-
|
|
137
|
-
llmBaseUrl: process.env.LLM_BASE_URL ?? "https://api.openai.com/v1",
|
|
138
|
-
llmApiKey: process.env.LLM_API_KEY ?? "",
|
|
139
|
-
llmModel: process.env.LLM_MODEL ?? "gpt-4o-mini",
|
|
140
|
-
|
|
141
|
-
embeddingBaseUrl: process.env.EMBED_BASE_URL ?? "",
|
|
142
|
-
embeddingApiKey: process.env.EMBED_API_KEY ?? "",
|
|
143
|
-
embeddingModel: process.env.EMBED_MODEL ?? "text-embedding-3-small",
|
|
144
|
-
embeddingDimension: 1536,
|
|
145
|
-
|
|
146
|
-
httpTimeoutMs: 60_000,
|
|
147
|
-
httpMaxRetries: 2,
|
|
148
|
-
embeddingBatchSize: 20,
|
|
149
|
-
embeddingConcurrency: 2,
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Most consumers should only instantiate `MemoryManager`. `MemoryStore` (plus the `VectorStoreProvider` / `Filter` types) is exported for advanced storage tests or custom infrastructure work. Breaking change in 0.2.0: `LanceService` was replaced by `MemoryStore` + provider abstraction; string SQL filters were replaced by structured `Filter` objects.
|
|
72
|
+
|
|
73
|
+
## Minimal Example
|
|
74
|
+
|
|
75
|
+
```ts
|
|
76
|
+
import { MemoryManager } from "@ppagent/memory";
|
|
77
|
+
|
|
78
|
+
const memory = new MemoryManager({
|
|
79
|
+
lancedbPath: "./data/memory/lance",
|
|
80
|
+
grafeoPath: "./data/memory/grafeo",
|
|
81
|
+
|
|
82
|
+
llmBaseUrl: process.env.LLM_BASE_URL ?? "https://api.openai.com/v1",
|
|
83
|
+
llmApiKey: process.env.LLM_API_KEY ?? "",
|
|
84
|
+
llmModel: process.env.LLM_MODEL ?? "gpt-4o-mini",
|
|
85
|
+
|
|
86
|
+
embeddingBaseUrl: process.env.EMBED_BASE_URL ?? process.env.LLM_BASE_URL ?? "https://api.openai.com/v1",
|
|
87
|
+
embeddingApiKey: process.env.EMBED_API_KEY ?? process.env.LLM_API_KEY ?? "",
|
|
88
|
+
embeddingModel: process.env.EMBED_MODEL ?? "text-embedding-3-small",
|
|
89
|
+
embeddingDimension: Number(process.env.EMBED_DIMENSION ?? 1536),
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
await memory.init();
|
|
93
|
+
|
|
94
|
+
await memory.updateChat(
|
|
95
|
+
[
|
|
96
|
+
{ talkerId: "user", content: "我叫 Bob,正在做一个 Rust 项目。" },
|
|
97
|
+
{ talkerId: "assistant", content: "好的,我会记住你在做 Rust 项目。" },
|
|
98
|
+
],
|
|
99
|
+
{
|
|
100
|
+
userId: "user-bob",
|
|
101
|
+
chatId: "agent-dev",
|
|
102
|
+
sessionId: "session-001",
|
|
103
|
+
sessionTitle: "Bob 的开发对话",
|
|
104
|
+
}
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
const results = await memory.search({
|
|
108
|
+
query: "Bob 正在做什么项目?",
|
|
109
|
+
scope: "user",
|
|
110
|
+
scopeId: "user-bob",
|
|
111
|
+
mode: "auto",
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
console.log(results);
|
|
115
|
+
|
|
116
|
+
const answer = await memory.ask({
|
|
117
|
+
query: "Bob 正在做什么项目?",
|
|
118
|
+
scope: "user",
|
|
119
|
+
scopeId: "user-bob",
|
|
120
|
+
maxChars: 120,
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
console.log(answer);
|
|
124
|
+
|
|
125
|
+
await memory.destroy();
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
## Complete Example
|
|
129
|
+
|
|
130
|
+
```ts
|
|
131
|
+
import { MemoryManager } from "@ppagent/memory";
|
|
132
|
+
import type { Entity, Relation } from "@ppagent/memory";
|
|
133
|
+
|
|
134
|
+
const memory = new MemoryManager({
|
|
135
|
+
lancedbPath: "./data/memory/lance",
|
|
136
|
+
grafeoPath: "./data/memory/grafeo",
|
|
137
|
+
|
|
138
|
+
llmBaseUrl: process.env.LLM_BASE_URL ?? "https://api.openai.com/v1",
|
|
139
|
+
llmApiKey: process.env.LLM_API_KEY ?? "",
|
|
140
|
+
llmModel: process.env.LLM_MODEL ?? "gpt-4o-mini",
|
|
141
|
+
|
|
142
|
+
embeddingBaseUrl: process.env.EMBED_BASE_URL ?? "",
|
|
143
|
+
embeddingApiKey: process.env.EMBED_API_KEY ?? "",
|
|
144
|
+
embeddingModel: process.env.EMBED_MODEL ?? "text-embedding-3-small",
|
|
145
|
+
embeddingDimension: 1536,
|
|
146
|
+
|
|
147
|
+
httpTimeoutMs: 60_000,
|
|
148
|
+
httpMaxRetries: 2,
|
|
149
|
+
embeddingBatchSize: 20,
|
|
150
|
+
embeddingConcurrency: 2,
|
|
151
|
+
|
|
152
|
+
compressedContextTokenLimit: 16 * 1024,
|
|
153
|
+
compressedContextRatio: 0.10,
|
|
154
|
+
topicCompactionSyncRatio: 1.25,
|
|
155
|
+
contextUsageRatio: 0.75,
|
|
156
|
+
precompressionRatio: 0.75,
|
|
157
|
+
compressionBatchRatio: 0.5,
|
|
158
|
+
compressionBatchTokenLimit: 0,
|
|
159
|
+
topicSummaryMaxTokens: 2048,
|
|
160
|
+
defaultModelContextTokens: 256 * 1024,
|
|
161
|
+
maxHistoryAgeMs: 0,
|
|
162
|
+
sessionIdleTtlMs: 30 * 60_000,
|
|
163
|
+
sessionSweepIntervalMs: 60_000,
|
|
157
164
|
maxConcurrentCompressions: 3,
|
|
158
|
-
entitySimilarityThreshold: 0.92,
|
|
159
|
-
defaultSearchLimit: 10,
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
const
|
|
180
|
-
const
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
"
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
{ name: "
|
|
214
|
-
{ name: "
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
{ from: "
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
- `
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
- `
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
├──
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
- `
|
|
305
|
-
- `
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
- `
|
|
311
|
-
- `
|
|
312
|
-
- `
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
- If `
|
|
318
|
-
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
- `
|
|
324
|
-
- `
|
|
325
|
-
- `
|
|
326
|
-
|
|
327
|
-
|
|
165
|
+
entitySimilarityThreshold: 0.92,
|
|
166
|
+
defaultSearchLimit: 10,
|
|
167
|
+
|
|
168
|
+
chunkStrategy: "markdown-heading",
|
|
169
|
+
chunkMaxTokens: 800,
|
|
170
|
+
chunkOverlap: 0,
|
|
171
|
+
knowledgeTopK: 8,
|
|
172
|
+
docCoarseTopK: 5,
|
|
173
|
+
buildGraphDefault: "auto",
|
|
174
|
+
chunkRedundantIds: true,
|
|
175
|
+
knowledgeGraphTriggerScore: 0.78,
|
|
176
|
+
knowledgeGraphEntityTopK: 10,
|
|
177
|
+
knowledgeGraphAnchorTopK: 3,
|
|
178
|
+
knowledgeGraphHopLimit: 10,
|
|
179
|
+
graphExtractConcurrency: 2,
|
|
180
|
+
graphBuildTimeoutMs: 120_000,
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
await memory.init();
|
|
184
|
+
|
|
185
|
+
const userId = "u-001";
|
|
186
|
+
const chatId = "agent-work";
|
|
187
|
+
const sessionId = "s-2026-06-12";
|
|
188
|
+
|
|
189
|
+
await memory.updateChat(
|
|
190
|
+
[
|
|
191
|
+
{
|
|
192
|
+
talkerId: "user",
|
|
193
|
+
content: "我叫 Alice,在 ChatMe 项目中负责后端架构。",
|
|
194
|
+
metadata: { source: "chat" },
|
|
195
|
+
},
|
|
196
|
+
{
|
|
197
|
+
talkerId: "assistant",
|
|
198
|
+
content: "明白,我会记住你负责 ChatMe 后端架构。",
|
|
199
|
+
},
|
|
200
|
+
],
|
|
201
|
+
{
|
|
202
|
+
userId,
|
|
203
|
+
chatId,
|
|
204
|
+
sessionId,
|
|
205
|
+
sessionTitle: "ChatMe 架构讨论",
|
|
206
|
+
sessionMetadata: { product: "ChatMe" },
|
|
207
|
+
}
|
|
208
|
+
);
|
|
209
|
+
|
|
210
|
+
await memory.updateFacts(
|
|
211
|
+
"Alice 负责 ChatMe 项目的后端架构",
|
|
212
|
+
"user",
|
|
213
|
+
userId,
|
|
214
|
+
chatId,
|
|
215
|
+
sessionId
|
|
216
|
+
);
|
|
217
|
+
|
|
218
|
+
const entities: Entity[] = [
|
|
219
|
+
{ name: "Alice", type: "Person", meta: { role: "backend architect", userId, chatId, sessionId } },
|
|
220
|
+
{ name: "ChatMe", type: "Project", meta: { userId, chatId, sessionId } },
|
|
221
|
+
{ name: "TypeScript", type: "Technology", meta: { userId, chatId, sessionId } },
|
|
222
|
+
];
|
|
223
|
+
|
|
224
|
+
const relations: Relation[] = [
|
|
225
|
+
{ from: "Alice", to: "ChatMe", type: "works_on", meta: { userId, chatId, sessionId } },
|
|
226
|
+
{ from: "ChatMe", to: "TypeScript", type: "built_with", meta: { userId, chatId, sessionId } },
|
|
227
|
+
];
|
|
228
|
+
|
|
229
|
+
await memory.updateEntity(entities, relations, { userId, chatId, sessionId });
|
|
230
|
+
|
|
231
|
+
await memory.flushChat(sessionId, { wait: true });
|
|
232
|
+
|
|
233
|
+
const remembered = await memory.search({
|
|
234
|
+
query: "Alice 在 ChatMe 中负责什么?",
|
|
235
|
+
scope: "user",
|
|
236
|
+
scopeId: userId,
|
|
237
|
+
mode: "all",
|
|
238
|
+
limit: 5,
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
console.log(remembered);
|
|
242
|
+
|
|
243
|
+
const doc = await memory.addDocument({
|
|
244
|
+
content: `# ChatMe 技术说明
|
|
245
|
+
|
|
246
|
+
ChatMe 使用 TypeScript 构建,后端包含长期记忆系统。
|
|
247
|
+
|
|
248
|
+
## Memory
|
|
249
|
+
|
|
250
|
+
长期记忆系统使用向量存储(LanceDB 或 SQLite,自动探测)存储向量,使用 Grafeo 存储知识图谱。`,
|
|
251
|
+
userId,
|
|
252
|
+
chatId,
|
|
253
|
+
sessionId,
|
|
254
|
+
sourceName: "chatme-memory.md",
|
|
255
|
+
metadata: { product: "ChatMe" },
|
|
256
|
+
buildGraph: true,
|
|
257
|
+
wait: true,
|
|
258
|
+
waitGraph: true,
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
const knowledge = await memory.searchKnowledge({
|
|
262
|
+
query: "ChatMe 的长期记忆系统用了什么存储?",
|
|
263
|
+
scope: "chat",
|
|
264
|
+
scopeId: chatId,
|
|
265
|
+
mode: "auto",
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
console.log(doc.docId, knowledge);
|
|
269
|
+
|
|
270
|
+
const answer = await memory.ask({
|
|
271
|
+
query: "ChatMe 的长期记忆系统用了什么存储?",
|
|
272
|
+
scope: "chat",
|
|
273
|
+
scopeId: chatId,
|
|
274
|
+
includeKnowledge: true,
|
|
275
|
+
maxChars: 200,
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
console.log(answer);
|
|
279
|
+
|
|
280
|
+
await memory.destroy();
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
## Configuration Reference
|
|
284
|
+
|
|
285
|
+
`MemoryManager` accepts a `MemoryConfig` object.
|
|
286
|
+
|
|
287
|
+
Required storage fields:
|
|
288
|
+
|
|
289
|
+
- `lancedbPath: string` - local LanceDB directory (used when the lancedb backend is active; its parent directory also hosts the backend marker file `.memory-provider`).
|
|
290
|
+
- `grafeoPath: string` - Grafeo database path.
|
|
291
|
+
|
|
292
|
+
Optional storage fields:
|
|
293
|
+
|
|
294
|
+
- `provider?: "auto" | "lancedb" | "sqlite"` - vector store backend. Default `"auto"`: probes the @lancedb/lancedb native binding at init and falls back to sqlite when unavailable (e.g. Intel macOS, musl without prebuilds). The first resolved backend is persisted in a marker file; auto mode never silently switches an existing data directory to a different backend (it throws with migration guidance instead).
|
|
295
|
+
- `sqlitePath?: string` - SQLite database file for the sqlite backend. Default: `path.join(path.dirname(lancedbPath), "memory.sqlite3")`, i.e. a `memory.sqlite3` file in the parent directory of `lancedbPath`. Parent directories are created automatically on init.
|
|
296
|
+
|
|
297
|
+
Storage layout example with `lancedbPath: "./data/memory/lance"` and defaults:
|
|
298
|
+
|
|
299
|
+
```
|
|
300
|
+
data/memory/
|
|
301
|
+
├── .memory-provider # backend marker written on first init ("lancedb" or "sqlite")
|
|
302
|
+
├── lance/ # LanceDB tables (lancedb backend only)
|
|
303
|
+
└── memory.sqlite3 # SQLite database (sqlite backend only; plus -wal/-shm files while open)
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
Backend data files are not interchangeable; switching backends requires an explicit export/import migration.
|
|
307
|
+
|
|
308
|
+
Required LLM fields:
|
|
309
|
+
|
|
310
|
+
- `llmBaseUrl: string` - OpenAI-compatible base URL, for example `https://api.openai.com/v1`.
|
|
311
|
+
- `llmApiKey: string` - API key. Never hard-code real secrets in examples.
|
|
312
|
+
- `llmModel: string` - chat model used for summaries, graph decisions, entity extraction, and final `ask` answers.
|
|
313
|
+
|
|
314
|
+
Required embedding fields:
|
|
315
|
+
|
|
316
|
+
- `embeddingBaseUrl: string` - OpenAI-compatible embedding base URL.
|
|
317
|
+
- `embeddingApiKey: string` - embedding API key.
|
|
318
|
+
- `embeddingModel: string` - embedding model name.
|
|
319
|
+
- `embeddingDimension: number` - vector dimension. This must match the embedding model and the existing vector store schema.
|
|
320
|
+
|
|
321
|
+
Embedding fallback behavior:
|
|
322
|
+
|
|
323
|
+
- If `embeddingBaseUrl` is empty, it falls back to `llmBaseUrl` and logs a warning.
|
|
324
|
+
- If `embeddingApiKey` is empty, it falls back to `llmApiKey` and logs a warning.
|
|
325
|
+
- This is convenient when one provider serves both chat and embeddings.
|
|
326
|
+
|
|
327
|
+
HTTP and retry fields:
|
|
328
|
+
|
|
329
|
+
- `httpTimeoutMs?: number` - timeout per LLM or embedding HTTP attempt. Default: `60000`.
|
|
330
|
+
- `httpMaxRetries?: number` - retry count after the first attempt for network errors, timeout/abort, HTTP `429`, and HTTP `5xx`. Default: `2`.
|
|
331
|
+
- `embeddingBatchSize?: number` - max texts per embedding request. Default: `20`.
|
|
332
|
+
- `embeddingConcurrency?: number` - concurrent embedding batches. Default: `2`.
|
|
333
|
+
|
|
328
334
|
Conversation memory fields:
|
|
329
335
|
|
|
330
|
-
- `
|
|
331
|
-
- `
|
|
332
|
-
- `
|
|
333
|
-
- `
|
|
334
|
-
- `
|
|
335
|
-
- `
|
|
336
|
+
- `compressedContextTokenLimit?: number` - global storage cap for compressed Topics. Default: `16384`; startup logs a warning above `32768`.
|
|
337
|
+
- `compressedContextRatio?: number` - maximum fraction of the usable model-history window assigned to Topics. Default: `0.10`. The effective Topic budget is the smaller of this ratio and `compressedContextTokenLimit`.
|
|
338
|
+
- `topicCompactionSyncRatio?: number` - a cold-start Topic overage blocks `getHistoryWindow` only after it exceeds this multiple of the effective Topic budget, unless Topic plus raw history already exceeds the usable window. Default: `1.25`.
|
|
339
|
+
- `contextUsageRatio?: number` - fraction of the current model context available to conversation history. Default: `0.75`.
|
|
340
|
+
- `precompressionRatio?: number` - start background raw-message compression when raw usage reaches this fraction of its dynamic budget. Default: `0.75`.
|
|
341
|
+
- `compressionBatchRatio?: number` - target fraction of the raw-message budget compressed in one batch. Default: `0.5`.
|
|
342
|
+
- `compressionBatchTokenLimit?: number` - optional hard limit for one compression batch. Default: `0` (ratio only).
|
|
343
|
+
- `topicSummaryMaxTokens?: number` - hard maximum for one Topic summary. The LLM uses less for low-value chat and more for facts, experience, decisions, preferences, constraints, and reusable knowledge. Default: `2048`.
|
|
344
|
+
- `defaultModelContextTokens?: number` - used when `getHistoryWindow` receives no model size; a warning is logged. Default: `262144`.
|
|
345
|
+
- `maxHistoryAgeMs?: number` - maximum Topic and raw-message age restored during lazy cold start. Default: `0` (all history).
|
|
346
|
+
- `sessionIdleTtlMs?: number` - idle time before a session cache is released. Persistent data is not deleted. Default: `1800000`.
|
|
347
|
+
- `sessionSweepIntervalMs?: number` - idle cache sweep interval. Default: `60000`.
|
|
336
348
|
- `maxConcurrentCompressions?: number` - global semaphore limit for concurrent compression and knowledge ingestion tasks. Default: `3`.
|
|
337
349
|
- `entitySimilarityThreshold?: number` - graph entity similarity threshold. Default: `0.92`.
|
|
338
350
|
- `defaultSearchLimit?: number` - default `search` result limit. Default: `10`.
|
|
339
|
-
- `recallBoostMs?: number` - each topic recall count acts like this many milliseconds of freshness boost. Default: `3_600_000`.
|
|
340
|
-
|
|
341
|
-
Knowledge-base fields:
|
|
342
|
-
|
|
343
|
-
- `chunkStrategy?: "markdown-heading"` - current chunking strategy. Default: `"markdown-heading"`.
|
|
344
|
-
- `chunkMaxTokens?: number` - max tokens per chunk; long heading sections are split again. Default: `800`.
|
|
345
|
-
- `chunkOverlap?: number` - overlap tokens between chunks. Default: `0`.
|
|
346
|
-
- `knowledgeTopK?: number` - default `searchKnowledge` chunk return count. Default: `8`.
|
|
347
|
-
- `docCoarseTopK?: number` - document-level coarse recall count before chunk search. Set `0` to disable coarse recall. Default: `5`.
|
|
348
|
-
- `buildGraphDefault?: boolean | "auto"` - default graph build behavior for `addDocument`. Default: `"auto"`.
|
|
349
|
-
- `chunkRedundantIds?: boolean` - whether chunks redundantly store `userId`, `chatId`, and `sessionId` for direct filtering. Default: `true`.
|
|
350
|
-
- `knowledgeGraphTriggerScore?: number` - in `mode: "auto"`, graph expansion only starts when top chunk cosine score reaches this threshold. Default: `0.78`.
|
|
351
|
-
- `knowledgeGraphEntityTopK?: number` - entity vector search candidate count for knowledge graph expansion. Default: `10`.
|
|
352
|
-
- `knowledgeGraphAnchorTopK?: number` - top entity anchors used for one-hop expansion. Default: `3`.
|
|
353
|
-
- `knowledgeGraphHopLimit?: number` - max one-hop relations per anchor. Default: `10`.
|
|
354
|
-
- `graphExtractConcurrency?: number` - concurrent chunk-level entity extraction tasks during document graph construction. Default: `2`.
|
|
355
|
-
- `graphBuildTimeoutMs?: number` - timeout used only when a caller explicitly waits for graph construction with `waitGraph: true`. Default: `120000`.
|
|
356
|
-
|
|
357
|
-
## Critical Async And Wait Semantics
|
|
358
|
-
|
|
359
|
-
The package intentionally separates durable base writes from expensive graph construction.
|
|
360
351
|
|
|
352
|
+
`sessionTokenLimit`, `historyWindowTokenLimit`, `topicRatio`, `detailMaxTokens`, and `conciseMaxTokens` remain only as deprecated 0.3.x configuration/read compatibility fields. New code should not use them.
|
|
353
|
+
|
|
354
|
+
Knowledge-base fields:
|
|
355
|
+
|
|
356
|
+
- `chunkStrategy?: "markdown-heading"` - current chunking strategy. Default: `"markdown-heading"`.
|
|
357
|
+
- `chunkMaxTokens?: number` - max tokens per chunk; long heading sections are split again. Default: `800`.
|
|
358
|
+
- `chunkOverlap?: number` - overlap tokens between chunks. Default: `0`.
|
|
359
|
+
- `knowledgeTopK?: number` - default `searchKnowledge` chunk return count. Default: `8`.
|
|
360
|
+
- `docCoarseTopK?: number` - document-level coarse recall count before chunk search. Set `0` to disable coarse recall. Default: `5`.
|
|
361
|
+
- `buildGraphDefault?: boolean | "auto"` - default graph build behavior for `addDocument`. Default: `"auto"`.
|
|
362
|
+
- `chunkRedundantIds?: boolean` - whether chunks redundantly store `userId`, `chatId`, and `sessionId` for direct filtering. Default: `true`.
|
|
363
|
+
- `knowledgeGraphTriggerScore?: number` - in `mode: "auto"`, graph expansion only starts when top chunk cosine score reaches this threshold. Default: `0.78`.
|
|
364
|
+
- `knowledgeGraphEntityTopK?: number` - entity vector search candidate count for knowledge graph expansion. Default: `10`.
|
|
365
|
+
- `knowledgeGraphAnchorTopK?: number` - top entity anchors used for one-hop expansion. Default: `3`.
|
|
366
|
+
- `knowledgeGraphHopLimit?: number` - max one-hop relations per anchor. Default: `10`.
|
|
367
|
+
- `graphExtractConcurrency?: number` - concurrent chunk-level entity extraction tasks during document graph construction. Default: `2`.
|
|
368
|
+
- `graphBuildTimeoutMs?: number` - timeout used only when a caller explicitly waits for graph construction with `waitGraph: true`. Default: `120000`.
|
|
369
|
+
|
|
370
|
+
## Critical Async And Wait Semantics
|
|
371
|
+
|
|
372
|
+
The package intentionally separates durable base writes from expensive graph construction.
|
|
373
|
+
|
|
361
374
|
`updateChat(messages, opts)`:
|
|
362
375
|
|
|
363
|
-
-
|
|
376
|
+
- Registers its write synchronously, so a caller may intentionally fire-and-forget it and a following `getHistoryWindow` will still wait for that write.
|
|
377
|
+
- Calculates tokens over `content`, `parts`, `payload`, and `metadata`, embeds the searchable text, and upserts raw messages by stable `messageId` before resolving.
|
|
364
378
|
- Creates or updates the session record.
|
|
365
379
|
- Updates the in-memory session cache.
|
|
366
|
-
-
|
|
367
|
-
-
|
|
368
|
-
|
|
380
|
+
- Once `getHistoryWindow` has supplied the current model size, reaching the precompression threshold starts background compression.
|
|
381
|
+
- Topic storage and cache replacement finish before conversation graph extraction; graph work remains asynchronous.
|
|
382
|
+
|
|
369
383
|
`flushChat(sessionId?, opts?)`:
|
|
370
|
-
|
|
371
|
-
- Forces compression for the current cached messages even if
|
|
372
|
-
- `wait: true` waits for topic creation, cache clearing, and history-window rebuild.
|
|
373
|
-
- `wait: true` does not wait for graph extraction/persistence.
|
|
374
|
-
- `waitGraph: true` waits for graph extraction/persistence too.
|
|
384
|
+
|
|
385
|
+
- Forces compression for the current cached raw messages even if the dynamic precompression threshold was not reached.
|
|
386
|
+
- `wait: true` waits for topic creation, cache clearing, and history-window rebuild.
|
|
387
|
+
- `wait: true` does not wait for graph extraction/persistence.
|
|
388
|
+
- `waitGraph: true` waits for graph extraction/persistence too.
|
|
375
389
|
- When `waitGraph: true`, graph waiting is bounded by `graphBuildTimeoutMs` and may throw a timeout error.
|
|
376
390
|
|
|
377
|
-
`
|
|
378
|
-
|
|
379
|
-
-
|
|
380
|
-
-
|
|
381
|
-
-
|
|
382
|
-
-
|
|
383
|
-
-
|
|
384
|
-
-
|
|
385
|
-
- `
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
`
|
|
393
|
-
|
|
394
|
-
- `
|
|
395
|
-
- `
|
|
396
|
-
- `
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
391
|
+
`getHistoryWindow(sessionId, modelContextTokens?, options?)`:
|
|
392
|
+
|
|
393
|
+
- Lazily hydrates the session from persistent Topics plus raw messages after the newest Topic boundary.
|
|
394
|
+
- Waits for registered message writes. If the hard raw budget is exceeded, it also waits for or starts compression until the returned context fits.
|
|
395
|
+
- Returns `{ compressedContext, recentMessages, usage }`. `recentMessages` preserves the `RawMessage` input shape, including `messageId`, `parts`, `payload`, `metadata`, and `createdAt`.
|
|
396
|
+
- The effective Topic budget is `min(compressedContextTokenLimit, usableContextTokens * compressedContextRatio)`; the remaining usable history budget is reserved for raw messages.
|
|
397
|
+
- Cold hydration always restores all persisted Topics and never silently truncates them. Each rollup summarizes the oldest approximately half of the current Topic tokens; a single oversized Topic is re-summarized by itself.
|
|
398
|
+
- A small Topic-only overage returns immediately and schedules a transient background rollup for the next read. It blocks only when the overage exceeds `topicCompactionSyncRatio` or Topic plus raw history cannot fit the usable window.
|
|
399
|
+
- `options.onBlockingCompression` receives `{ phase: "start" | "end", reason }` only when this read actually waits for compression. Silent precompression and non-blocking Topic rollups do not invoke it. Callback failures are contained.
|
|
400
|
+
|
|
401
|
+
`addDocument(opts)`:
|
|
402
|
+
|
|
403
|
+
- Writes the document row first.
|
|
404
|
+
- Chunk embedding and chunk insertion run through the ingestion pipeline.
|
|
405
|
+
- Graph construction, when enabled, runs after chunks are ready.
|
|
406
|
+
- With neither `wait` nor `waitGraph`, the method returns after document storage; chunks and graph are background work.
|
|
407
|
+
- `wait: true` waits until chunks are inserted and searchable.
|
|
408
|
+
- `wait: true` does not wait for graph construction.
|
|
409
|
+
- `waitGraph: true` implies waiting for chunks and then waits for graph construction.
|
|
410
|
+
- When `waitGraph: true`, graph waiting is bounded by `graphBuildTimeoutMs` and may throw a timeout error.
|
|
411
|
+
|
|
412
|
+
Use `wait: true` when the next line of code must immediately call `searchKnowledge` and find chunks. Use `waitGraph: true` only when `hasGraph` or graph hits must be ready immediately.
|
|
413
|
+
|
|
414
|
+
## Data Model
|
|
415
|
+
|
|
416
|
+
`RawMessage` is input to `updateChat`:
|
|
417
|
+
|
|
418
|
+
- `messageId?: string` - generated with UUID if omitted.
|
|
419
|
+
- `talkerId?: string` - speaker id. Default: `"user"`.
|
|
420
|
+
- `chatId?: string` - conversation/chat/agent id.
|
|
421
|
+
- `userId?: string` - owner user id.
|
|
422
|
+
- `sessionId?: string` - session id.
|
|
423
|
+
- `type?: "text" | "image" | "file"` - default: `"text"`.
|
|
424
|
+
- `content: string` - required plain text used for embedding and retrieval.
|
|
401
425
|
- `parts?: ContentPart[]` - optional multimodal content parts; stored serialized.
|
|
426
|
+
- `payload?: unknown` - host-framework message payload stored and returned without interpretation.
|
|
402
427
|
- `usage?: number` - token count; estimated with `tiktoken` if omitted.
|
|
403
|
-
- `metadata?: Record<string, unknown>` - custom metadata stored as JSON.
|
|
404
|
-
- `createdAt?: number` - Unix milliseconds; default is current time.
|
|
405
|
-
|
|
406
|
-
`ContentPart` aligns with common multimodal content shapes:
|
|
407
|
-
|
|
408
|
-
- Text: `{ type: "text", text: "..." }`
|
|
409
|
-
- Image: `{ type: "image_url", image_url: { url: "https://..." } }`
|
|
410
|
-
- File: `{ type: "file_url", file_url: { url: "https://...", name: "file.pdf" } }`
|
|
411
|
-
|
|
412
|
-
`StoredMessage` is persisted message output with required ids, serialized `parts`, serialized `metadata`, generated vector, and `createdAt`.
|
|
413
|
-
|
|
428
|
+
- `metadata?: Record<string, unknown>` - custom metadata stored as JSON.
|
|
429
|
+
- `createdAt?: number` - Unix milliseconds; default is current time.
|
|
430
|
+
|
|
431
|
+
`ContentPart` aligns with common multimodal content shapes:
|
|
432
|
+
|
|
433
|
+
- Text: `{ type: "text", text: "..." }`
|
|
434
|
+
- Image: `{ type: "image_url", image_url: { url: "https://..." } }`
|
|
435
|
+
- File: `{ type: "file_url", file_url: { url: "https://...", name: "file.pdf" } }`
|
|
436
|
+
|
|
437
|
+
`StoredMessage` is persisted message output with required ids, serialized `parts`, serialized `metadata`, generated vector, and `createdAt`.
|
|
438
|
+
|
|
414
439
|
`Topic` is a compressed memory item:
|
|
415
440
|
|
|
416
441
|
- `title` - short topic title.
|
|
417
|
-
- `
|
|
418
|
-
- `
|
|
419
|
-
- `
|
|
442
|
+
- `summary` - the only compressed body; its actual length is importance-aware and bounded by `topicSummaryMaxTokens`.
|
|
443
|
+
- `tokens` - summary token count stored when the Topic is created.
|
|
444
|
+
- `startMessageId` / `endMessageId` - exact raw-message boundaries, in addition to timestamps.
|
|
420
445
|
- `startTime` and `endTime` - covered message time range.
|
|
421
|
-
- `recallCount` -
|
|
422
|
-
|
|
446
|
+
- `recallCount` - incremented best-effort when search recalls the Topic; it is not used to reorder the continuous history window.
|
|
447
|
+
|
|
423
448
|
`Fact` is a user/chat scoped manual memory:
|
|
424
|
-
|
|
425
|
-
- `level: "user" | "chat"`
|
|
426
|
-
- `userId`, `chatId`, optional `sessionId`
|
|
449
|
+
|
|
450
|
+
- `level: "user" | "chat"`
|
|
451
|
+
- `userId`, `chatId`, optional `sessionId`
|
|
427
452
|
- `content`
|
|
428
|
-
- `
|
|
429
|
-
|
|
430
|
-
`Entity` and `Relation` are graph records:
|
|
431
|
-
|
|
432
|
-
- Entity: `{ name, type, meta }`
|
|
433
|
-
- Relation: `{ from, to, type, happenedAt?, meta }`
|
|
434
|
-
- `meta` should usually include `userId`, `chatId`, `sessionId`, and source details.
|
|
435
|
-
|
|
436
|
-
`SessionView` is the in-memory session representation:
|
|
437
|
-
|
|
438
|
-
- `sessionId`
|
|
439
|
-
- `chatId`
|
|
440
|
-
- `userId`
|
|
441
|
-
- `title`
|
|
442
|
-
- `metadata: Record<string, unknown>`
|
|
453
|
+
- optional stable `key` for immediate cache + database upsert
|
|
443
454
|
- `createdAt`
|
|
444
455
|
- `updatedAt`
|
|
445
|
-
|
|
446
|
-
`
|
|
447
|
-
|
|
448
|
-
- `
|
|
449
|
-
-
|
|
450
|
-
- `
|
|
451
|
-
|
|
452
|
-
-
|
|
453
|
-
|
|
454
|
-
- `
|
|
455
|
-
- `
|
|
456
|
-
- `
|
|
457
|
-
- `
|
|
458
|
-
- `
|
|
459
|
-
|
|
460
|
-
`
|
|
461
|
-
|
|
462
|
-
-
|
|
463
|
-
|
|
464
|
-
- `
|
|
465
|
-
- `
|
|
466
|
-
- `
|
|
467
|
-
- `
|
|
468
|
-
- `
|
|
469
|
-
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
- `
|
|
479
|
-
|
|
456
|
+
|
|
457
|
+
`Entity` and `Relation` are graph records:
|
|
458
|
+
|
|
459
|
+
- Entity: `{ name, type, meta }`
|
|
460
|
+
- Relation: `{ from, to, type, happenedAt?, meta }`
|
|
461
|
+
- `meta` should usually include `userId`, `chatId`, `sessionId`, and source details.
|
|
462
|
+
|
|
463
|
+
`SessionView` is the in-memory session representation:
|
|
464
|
+
|
|
465
|
+
- `sessionId`
|
|
466
|
+
- `chatId`
|
|
467
|
+
- `userId`
|
|
468
|
+
- `title`
|
|
469
|
+
- `metadata: Record<string, unknown>`
|
|
470
|
+
- `createdAt`
|
|
471
|
+
- `updatedAt`
|
|
472
|
+
|
|
473
|
+
`Document` is a knowledge-base document row:
|
|
474
|
+
|
|
475
|
+
- `docId`
|
|
476
|
+
- `userId`, `chatId`, `sessionId`
|
|
477
|
+
- `title`, `sourceName`
|
|
478
|
+
- `fullContent`
|
|
479
|
+
- `contentHash`
|
|
480
|
+
- `summary`
|
|
481
|
+
- `summaryVector`
|
|
482
|
+
- `chunkCount`
|
|
483
|
+
- `hasGraph`
|
|
484
|
+
- `metadata`
|
|
485
|
+
- `createdAt`, `updatedAt`
|
|
486
|
+
|
|
487
|
+
`Chunk` is a knowledge-base chunk row:
|
|
488
|
+
|
|
489
|
+
- `chunkId`
|
|
490
|
+
- `docId`
|
|
491
|
+
- `content`
|
|
492
|
+
- `headingPath`
|
|
493
|
+
- `ordinal`
|
|
494
|
+
- `tokens`
|
|
495
|
+
- `vector`
|
|
496
|
+
- optional redundant domain ids depending on `chunkRedundantIds`
|
|
497
|
+
|
|
498
|
+
## Scope And Search Modes
|
|
499
|
+
|
|
500
|
+
Scopes:
|
|
501
|
+
|
|
502
|
+
- `scope: "session"` - filter by `sessionId`; pass `scopeId`.
|
|
503
|
+
- `scope: "chat"` - filter by `chatId`; pass `scopeId`.
|
|
504
|
+
- `scope: "user"` - filter by `userId`; pass `scopeId`.
|
|
505
|
+
- `scope: "all"` - no domain filter; `scopeId` is ignored.
|
|
506
|
+
|
|
480
507
|
Search modes:
|
|
481
|
-
|
|
482
|
-
- `mode: "fast"` - only vector-store hybrid/vector retrieval. No graph search.
|
|
483
|
-
- `mode: "auto"` - conversation search asks the LLM whether graph search is needed; knowledge search triggers graph expansion only when top chunk score is high enough.
|
|
508
|
+
|
|
509
|
+
- `mode: "fast"` - only vector-store hybrid/vector retrieval. No graph search.
|
|
510
|
+
- `mode: "auto"` - conversation search asks the LLM whether graph search is needed; knowledge search triggers graph expansion only when top chunk score is high enough.
|
|
484
511
|
- `mode: "all"` - include graph search and merge graph results with vector/full-text results.
|
|
485
512
|
|
|
486
|
-
For deterministic low-latency calls, use `fast`. For richer entity relationship answers, use `auto` or `all`.
|
|
487
|
-
|
|
488
|
-
## API Reference
|
|
489
|
-
|
|
490
|
-
### `new MemoryManager(config)`
|
|
491
|
-
|
|
492
|
-
Creates the manager and resolves defaults. It does not connect to storage until `init()`.
|
|
493
|
-
|
|
513
|
+
Conversation search always searches both compressed Topics and raw messages, including raw rows already represented by a Topic. For deterministic low-latency calls, use `fast`. For richer entity relationship answers, use `auto` or `all`.
|
|
514
|
+
|
|
515
|
+
## API Reference
|
|
516
|
+
|
|
517
|
+
### `new MemoryManager(config)`
|
|
518
|
+
|
|
519
|
+
Creates the manager and resolves defaults. It does not connect to storage until `init()`.
|
|
520
|
+
|
|
494
521
|
### `init(): Promise<void>`
|
|
495
522
|
|
|
496
|
-
Initializes tiktoken, the vector store (backend auto-detection happens here), Grafeo, facts cache
|
|
497
|
-
|
|
498
|
-
### `updateChat(messages, opts?): Promise<void>`
|
|
499
|
-
|
|
500
|
-
Stores new chat messages.
|
|
501
|
-
|
|
502
|
-
Options:
|
|
503
|
-
|
|
504
|
-
- `userId?: string`
|
|
505
|
-
- `chatId?: string`
|
|
506
|
-
- `sessionId?: string`
|
|
507
|
-
- `sessionTitle?: string`
|
|
508
|
-
- `sessionMetadata?: Record<string, unknown>`
|
|
509
|
-
|
|
510
|
-
Defaults for ids are `"default"`.
|
|
511
|
-
|
|
512
|
-
Use for every new user/assistant message that should become retrievable memory.
|
|
513
|
-
|
|
514
|
-
### `flushChat(sessionId?, opts?): Promise<void>`
|
|
515
|
-
|
|
516
|
-
Forces compression of a session cache.
|
|
517
|
-
|
|
518
|
-
Options:
|
|
519
|
-
|
|
520
|
-
- `wait?: boolean` - wait for topic/history work.
|
|
521
|
-
- `waitGraph?: boolean` - also wait for graph extraction/persistence, bounded by `graphBuildTimeoutMs`.
|
|
522
|
-
|
|
523
|
-
If `sessionId` is omitted, `"default"` is used.
|
|
524
|
-
|
|
525
|
-
### `search(opts): Promise<SearchResult[]>`
|
|
526
|
-
|
|
527
|
-
Searches conversation memory.
|
|
528
|
-
|
|
529
|
-
Options:
|
|
530
|
-
|
|
531
|
-
- `query: string`
|
|
532
|
-
- `scope?: "session" | "chat" | "user" | "all"`
|
|
533
|
-
- `scopeId?: string`
|
|
534
|
-
- `mode?: "fast" | "auto" | "all"`
|
|
535
|
-
- `limit?: number`
|
|
536
|
-
|
|
537
|
-
Returns results with:
|
|
538
|
-
|
|
539
|
-
- `type: "message" | "topic" | "entity" | "relation"`
|
|
540
|
-
- `content`
|
|
541
|
-
- `score`
|
|
542
|
-
- `meta`
|
|
543
|
-
|
|
544
|
-
### `ask(opts): Promise<string>`
|
|
545
|
-
|
|
546
|
-
Searches memory and asks the configured LLM to summarize the hits into a natural-language answer.
|
|
547
|
-
|
|
548
|
-
Options are `SearchOptions` plus:
|
|
549
|
-
|
|
550
|
-
- `maxChars?: number`
|
|
551
|
-
- `includeKnowledge?: boolean`
|
|
552
|
-
|
|
553
|
-
When `includeKnowledge: true`, the method also calls `searchKnowledge` and merges knowledge chunks and graph hits into the answer context.
|
|
554
|
-
|
|
555
|
-
### `updateFacts(content, level, userId, chatId, sessionId?): Promise<void>`
|
|
556
|
-
|
|
557
|
-
Adds a fact to persistent storage and in-memory cache.
|
|
558
|
-
|
|
559
|
-
- `level` is `"user"` or `"chat"`.
|
|
560
|
-
- User facts apply across chats for that user.
|
|
561
|
-
- Chat facts apply to a specific chat/agent context.
|
|
562
|
-
|
|
563
|
-
### `getFacts(level, id): Promise<string>`
|
|
564
|
-
|
|
565
|
-
Returns facts as newline-separated text in `ISO_TIME:content` format. Use `level: "user"` with `userId`, or `level: "chat"` with `chatId`.
|
|
566
|
-
|
|
567
|
-
### `getFactsForContext(userId, chatId): Promise<string>`
|
|
568
|
-
|
|
569
|
-
Returns merged user-level facts for `userId` and chat-level facts for `chatId`, sorted by time. This is useful for injecting stable facts into an agent prompt.
|
|
570
|
-
|
|
571
|
-
### `addFact(content, level, userId, chatId, sessionId?): Promise<void>`
|
|
572
|
-
|
|
573
|
-
Alias-style management API for adding a fact manually.
|
|
574
|
-
|
|
575
|
-
### `deleteFact(factId): Promise<boolean>`
|
|
576
|
-
|
|
577
|
-
Deletes one fact and returns whether it was found.
|
|
578
|
-
|
|
579
|
-
### `updateEntity(entities, relations, context?): Promise<void>`
|
|
580
|
-
|
|
581
|
-
Upserts graph entities and relations. The method embeds entity names and stores graph data in Grafeo.
|
|
582
|
-
|
|
583
|
-
Context:
|
|
584
|
-
|
|
585
|
-
- `sessionId?: string`
|
|
586
|
-
- `chatId?: string`
|
|
587
|
-
- `userId?: string`
|
|
588
|
-
|
|
589
|
-
### `getHistoryWindow(sessionId):
|
|
590
|
-
|
|
591
|
-
Returns the
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
- `
|
|
523
|
+
Initializes tiktoken, the vector store (backend auto-detection happens here), Grafeo, and the facts cache. Conversation sessions are hydrated lazily on first access and evicted after the configured idle timeout. Always call before using read/write APIs.
|
|
524
|
+
|
|
525
|
+
### `updateChat(messages, opts?): Promise<void>`
|
|
526
|
+
|
|
527
|
+
Stores new chat messages.
|
|
528
|
+
|
|
529
|
+
Options:
|
|
530
|
+
|
|
531
|
+
- `userId?: string`
|
|
532
|
+
- `chatId?: string`
|
|
533
|
+
- `sessionId?: string`
|
|
534
|
+
- `sessionTitle?: string`
|
|
535
|
+
- `sessionMetadata?: Record<string, unknown>`
|
|
536
|
+
|
|
537
|
+
Defaults for ids are `"default"`.
|
|
538
|
+
|
|
539
|
+
Use for every new user/assistant message that should become retrievable memory.
|
|
540
|
+
|
|
541
|
+
### `flushChat(sessionId?, opts?): Promise<void>`
|
|
542
|
+
|
|
543
|
+
Forces compression of a session cache.
|
|
544
|
+
|
|
545
|
+
Options:
|
|
546
|
+
|
|
547
|
+
- `wait?: boolean` - wait for topic/history work.
|
|
548
|
+
- `waitGraph?: boolean` - also wait for graph extraction/persistence, bounded by `graphBuildTimeoutMs`.
|
|
549
|
+
|
|
550
|
+
If `sessionId` is omitted, `"default"` is used.
|
|
551
|
+
|
|
552
|
+
### `search(opts): Promise<SearchResult[]>`
|
|
553
|
+
|
|
554
|
+
Searches conversation memory.
|
|
555
|
+
|
|
556
|
+
Options:
|
|
557
|
+
|
|
558
|
+
- `query: string`
|
|
559
|
+
- `scope?: "session" | "chat" | "user" | "all"`
|
|
560
|
+
- `scopeId?: string`
|
|
561
|
+
- `mode?: "fast" | "auto" | "all"`
|
|
562
|
+
- `limit?: number`
|
|
563
|
+
|
|
564
|
+
Returns results with:
|
|
565
|
+
|
|
566
|
+
- `type: "message" | "topic" | "entity" | "relation"`
|
|
567
|
+
- `content`
|
|
568
|
+
- `score`
|
|
569
|
+
- `meta`
|
|
570
|
+
|
|
571
|
+
### `ask(opts): Promise<string>`
|
|
572
|
+
|
|
573
|
+
Searches memory and asks the configured LLM to summarize the hits into a natural-language answer.
|
|
574
|
+
|
|
575
|
+
Options are `SearchOptions` plus:
|
|
576
|
+
|
|
577
|
+
- `maxChars?: number`
|
|
578
|
+
- `includeKnowledge?: boolean`
|
|
579
|
+
|
|
580
|
+
When `includeKnowledge: true`, the method also calls `searchKnowledge` and merges knowledge chunks and graph hits into the answer context.
|
|
581
|
+
|
|
582
|
+
### `updateFacts(content, level, userId, chatId, sessionId?): Promise<void>`
|
|
583
|
+
|
|
584
|
+
Adds a fact to persistent storage and in-memory cache.
|
|
585
|
+
|
|
586
|
+
- `level` is `"user"` or `"chat"`.
|
|
587
|
+
- User facts apply across chats for that user.
|
|
588
|
+
- Chat facts apply to a specific chat/agent context.
|
|
589
|
+
|
|
590
|
+
### `getFacts(level, id): Promise<string>`
|
|
591
|
+
|
|
592
|
+
Returns facts as newline-separated text in `ISO_TIME:content` format. Use `level: "user"` with `userId`, or `level: "chat"` with `chatId`.
|
|
593
|
+
|
|
594
|
+
### `getFactsForContext(userId, chatId): Promise<string>`
|
|
595
|
+
|
|
596
|
+
Returns merged user-level facts for `userId` and chat-level facts for `chatId`, sorted by time. This is useful for injecting stable facts into an agent prompt.
|
|
597
|
+
|
|
598
|
+
### `addFact(content, level, userId, chatId, sessionId?): Promise<void>`
|
|
599
|
+
|
|
600
|
+
Alias-style management API for adding a fact manually.
|
|
601
|
+
|
|
602
|
+
### `deleteFact(factId): Promise<boolean>`
|
|
603
|
+
|
|
604
|
+
Deletes one fact and returns whether it was found.
|
|
605
|
+
|
|
606
|
+
### `updateEntity(entities, relations, context?): Promise<void>`
|
|
607
|
+
|
|
608
|
+
Upserts graph entities and relations. The method embeds entity names and stores graph data in Grafeo.
|
|
609
|
+
|
|
610
|
+
Context:
|
|
611
|
+
|
|
612
|
+
- `sessionId?: string`
|
|
613
|
+
- `chatId?: string`
|
|
614
|
+
- `userId?: string`
|
|
615
|
+
|
|
616
|
+
### `getHistoryWindow(sessionId, modelContextTokens?, options?): Promise<MemoryContextWindow>`
|
|
617
|
+
|
|
618
|
+
Returns the complete model-history window:
|
|
619
|
+
|
|
620
|
+
- `compressedContext: string` - chronological Topic summaries for the current model-size budget. A soft cold-start overage may be returned once while its transient rollup runs in the background.
|
|
621
|
+
- `recentMessages: MemoryRawMessage[]` - all currently uncompressed raw messages in chronological order, preserving the host payload and metadata.
|
|
622
|
+
- `usage` - model size, usable history size, compressed usage, and dynamic raw-message budget/usage.
|
|
623
|
+
|
|
624
|
+
Pass the active model's context size on every read. Omitting it uses `defaultModelContextTokens` (256K by default) and logs a warning.
|
|
625
|
+
|
|
626
|
+
Pass `options.onBlockingCompression` when the host needs a foreground-wait UI signal. It is deliberately not a general compression-progress callback.
|
|
627
|
+
|
|
628
|
+
### `getRecentMessages(sessionId, limit): Promise<StoredMessage[]>`
|
|
629
|
+
|
|
630
|
+
Returns the globally latest `limit` messages for a session in chronological order (oldest to newest within the returned window). The storage query orders by `createdAt` and uses `messageId` as a deterministic same-timestamp tiebreak before applying the limit, so long sessions do not return an early candidate window.
|
|
631
|
+
|
|
632
|
+
### Session APIs
|
|
633
|
+
|
|
634
|
+
- `getSession(sessionId): SessionView | null`
|
|
635
|
+
- `getSessionsByUserId(userId, opts?): SessionView[]`
|
|
636
|
+
- `getSessionsByChatId(chatId, opts?): SessionView[]`
|
|
637
|
+
- `searchSessions(opts): SessionView[]`
|
|
638
|
+
- `updateSession(sessionId, opts): Promise<SessionView | null>`
|
|
639
|
+
- `deleteSession(sessionId): Promise<boolean>`
|
|
640
|
+
- `listSessions(filter?): SessionView[]`
|
|
641
|
+
|
|
642
|
+
Session update options:
|
|
643
|
+
|
|
644
|
+
- `title?: string`
|
|
610
645
|
- `metadata?: Record<string, unknown>`
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
- `
|
|
616
|
-
- `
|
|
617
|
-
- `
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
- `
|
|
623
|
-
- `
|
|
624
|
-
- `
|
|
625
|
-
- `
|
|
626
|
-
- `
|
|
627
|
-
- `
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
- `
|
|
637
|
-
- `
|
|
638
|
-
- `
|
|
639
|
-
- `
|
|
640
|
-
- `
|
|
641
|
-
- `
|
|
642
|
-
- `
|
|
643
|
-
- `
|
|
644
|
-
- `
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
- `buildGraph:
|
|
655
|
-
- `buildGraph:
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
- `
|
|
665
|
-
- `
|
|
666
|
-
- `
|
|
667
|
-
- `
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
- `
|
|
673
|
-
- `
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
- `
|
|
691
|
-
- `
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
646
|
+
- `scope?: "session" | "chat" | "user" | "all"` - content-hash deduplication boundary; default is `session`.
|
|
647
|
+
|
|
648
|
+
Session search options:
|
|
649
|
+
|
|
650
|
+
- `title?: string`
|
|
651
|
+
- `userId?: string`
|
|
652
|
+
- `chatId?: string`
|
|
653
|
+
- `limit?: number`
|
|
654
|
+
|
|
655
|
+
### Management APIs
|
|
656
|
+
|
|
657
|
+
- `stats(): Promise<{ sessions; messages; topics; facts; entities; relations; documents; chunks }>` - total counts.
|
|
658
|
+
- `trend(days?): Promise<Array<{ date; sessions; messages; facts }>>` - daily activity trend. `days` defaults to `30` and is clamped to `1..365`.
|
|
659
|
+
- `listMessages(sessionId, limit?): Promise<StoredMessage[]>`
|
|
660
|
+
- `listTopics(filter?): Promise<Topic[]>`
|
|
661
|
+
- `listFacts(filter?): Fact[]`
|
|
662
|
+
- `listEntities(): Promise<Entity[]>`
|
|
663
|
+
- `listRelations(): Promise<Relation[]>`
|
|
664
|
+
|
|
665
|
+
### `addDocument(opts): Promise<{ docId: string }>`
|
|
666
|
+
|
|
667
|
+
Ingests a Markdown document into the knowledge base.
|
|
668
|
+
|
|
669
|
+
Options:
|
|
670
|
+
|
|
671
|
+
- `content: string` - required Markdown content.
|
|
672
|
+
- `userId?: string`
|
|
673
|
+
- `chatId?: string`
|
|
674
|
+
- `sessionId?: string`
|
|
675
|
+
- `title?: string` - inferred from first Markdown heading or first non-empty line when omitted.
|
|
676
|
+
- `sourceName?: string`
|
|
677
|
+
- `metadata?: Record<string, unknown>`
|
|
678
|
+
- `buildGraph?: boolean | "auto"` - default from `config.buildGraphDefault`.
|
|
679
|
+
- `wait?: boolean` - wait for chunks to be embedded and inserted.
|
|
680
|
+
- `waitGraph?: boolean` - wait for chunks and graph construction.
|
|
681
|
+
|
|
682
|
+
Deduplication:
|
|
683
|
+
|
|
684
|
+
- Documents are deduplicated by `sha256(content)` within the requested scope (`session` by default, or `chat` / `user` / `all`).
|
|
685
|
+
- If a duplicate exists, `addDocument` returns the existing `docId`.
|
|
686
|
+
|
|
687
|
+
Graph behavior:
|
|
688
|
+
|
|
689
|
+
- `buildGraph: false` disables graph construction.
|
|
690
|
+
- `buildGraph: true` builds a graph.
|
|
691
|
+
- `buildGraph: "auto"` uses the LLM document summary call to decide whether the document has valuable entity relationships.
|
|
692
|
+
|
|
693
|
+
### `searchKnowledge(opts): Promise<KnowledgeSearchResult>`
|
|
694
|
+
|
|
695
|
+
Searches Markdown knowledge-base chunks.
|
|
696
|
+
|
|
697
|
+
Options:
|
|
698
|
+
|
|
699
|
+
- `query: string`
|
|
700
|
+
- `scope?: "session" | "chat" | "user" | "all"`
|
|
701
|
+
- `scopeId?: string`
|
|
702
|
+
- `mode?: "fast" | "auto" | "all"`
|
|
703
|
+
- `limit?: number`
|
|
704
|
+
|
|
705
|
+
Returns:
|
|
706
|
+
|
|
707
|
+
- `chunks: Array<{ chunkId; docId; content; headingPath; score }>`
|
|
708
|
+
- `documents: Array<{ docId; title; matchedChunkCount }>`
|
|
709
|
+
- `graphHits: SearchResult[]`
|
|
710
|
+
|
|
711
|
+
The method uses document-level coarse recall when `docCoarseTopK > 0`, then chunk-level search. In `auto` mode, knowledge graph expansion only happens when the top chunk score reaches `knowledgeGraphTriggerScore`.
|
|
712
|
+
|
|
713
|
+
### `getDocument(docId): Promise<Document | null>`
|
|
714
|
+
|
|
715
|
+
Returns a document including `fullContent`.
|
|
716
|
+
|
|
717
|
+
### `deleteDocument(docId): Promise<boolean>`
|
|
718
|
+
|
|
719
|
+
Deletes the document, its chunks, and best-effort Grafeo knowledge graph entries for that document.
|
|
720
|
+
|
|
721
|
+
### `listDocuments(filter?): Promise<Document[]>`
|
|
722
|
+
|
|
723
|
+
Lists documents, optionally filtered by:
|
|
724
|
+
|
|
725
|
+
- `userId`
|
|
726
|
+
- `chatId`
|
|
727
|
+
- `sessionId`
|
|
728
|
+
|
|
729
|
+
### `destroy(): Promise<void>`
|
|
730
|
+
|
|
731
|
+
Waits for registered writes, compression, graph work, and storage maintenance, then closes Grafeo and the vector store. It does not delete data.
|
|
732
|
+
|
|
697
733
|
## Common Recipes
|
|
698
734
|
|
|
699
|
-
###
|
|
735
|
+
### Migrate A 0.3.x Store
|
|
700
736
|
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
],
|
|
707
|
-
{ userId: "u1", chatId: "assistant-main", sessionId: "s1" }
|
|
708
|
-
);
|
|
737
|
+
Stop all processes that write the store, then run the manual, idempotent migration. It backfills single-field Topic summaries/tokens/boundaries, raw-message usage, and fact timestamps without deleting raw messages or rerunning the LLM.
|
|
738
|
+
|
|
739
|
+
```bash
|
|
740
|
+
ppagent-memory migrate --config ./config.json --dry-run
|
|
741
|
+
ppagent-memory migrate --config ./config.json --backup ./memory-backup
|
|
709
742
|
```
|
|
710
743
|
|
|
744
|
+
The config loader accepts a direct `MemoryConfig`, `memory`, `app.memory.auto`, or `service.memory` object. `--dry-run` migrates a temporary copy. `--backup` copies both supported vector-store files/directories plus Grafeo data and writes rollback guidance before mutating the live store.
|
|
745
|
+
|
|
746
|
+
### Store User And Assistant Messages
|
|
747
|
+
|
|
748
|
+
```ts
|
|
749
|
+
await memory.updateChat(
|
|
750
|
+
[
|
|
751
|
+
{ talkerId: "user", content: "我喜欢 TypeScript,不太喜欢写纯 JavaScript。" },
|
|
752
|
+
{ talkerId: "assistant", content: "记住了,你偏好 TypeScript。" },
|
|
753
|
+
],
|
|
754
|
+
{ userId: "u1", chatId: "assistant-main", sessionId: "s1" }
|
|
755
|
+
);
|
|
756
|
+
```
|
|
757
|
+
|
|
711
758
|
### Force A Prompt-Ready History Window
|
|
712
759
|
|
|
713
760
|
```ts
|
|
714
761
|
await memory.flushChat("s1", { wait: true });
|
|
715
|
-
const history = memory.getHistoryWindow("s1");
|
|
716
|
-
|
|
717
|
-
const
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
wait: true,
|
|
725
|
-
waitGraph: true,
|
|
726
|
-
});
|
|
727
|
-
```
|
|
728
|
-
|
|
729
|
-
Use this only when graph data must be ready immediately. It can throw if graph construction exceeds `graphBuildTimeoutMs`.
|
|
730
|
-
|
|
731
|
-
### Store Stable Facts
|
|
732
|
-
|
|
733
|
-
```ts
|
|
734
|
-
await memory.updateFacts("用户偏好 TypeScript", "user", "u1", "assistant-main", "s1");
|
|
735
|
-
|
|
736
|
-
const facts = await memory.getFactsForContext("u1", "assistant-main");
|
|
737
|
-
```
|
|
738
|
-
|
|
739
|
-
### Search Fast Without Graph
|
|
740
|
-
|
|
741
|
-
```ts
|
|
742
|
-
const hits = await memory.search({
|
|
743
|
-
query: "用户偏好什么语言?",
|
|
744
|
-
scope: "user",
|
|
745
|
-
scopeId: "u1",
|
|
746
|
-
mode: "fast",
|
|
747
|
-
});
|
|
748
|
-
```
|
|
749
|
-
|
|
750
|
-
### Search With Graph
|
|
751
|
-
|
|
752
|
-
```ts
|
|
753
|
-
const hits = await memory.search({
|
|
754
|
-
query: "Alice 负责哪个项目,项目用了什么技术?",
|
|
755
|
-
scope: "chat",
|
|
756
|
-
scopeId: "work-agent",
|
|
757
|
-
mode: "all",
|
|
758
|
-
});
|
|
759
|
-
```
|
|
760
|
-
|
|
761
|
-
### Ingest Markdown And Search Immediately
|
|
762
|
-
|
|
763
|
-
```ts
|
|
764
|
-
const { docId } = await memory.addDocument({
|
|
765
|
-
content: "# Rust\n\nRust 的所有权系统保证内存安全。",
|
|
766
|
-
userId: "u1",
|
|
767
|
-
chatId: "dev-agent",
|
|
768
|
-
sourceName: "rust.md",
|
|
769
|
-
wait: true,
|
|
770
|
-
});
|
|
771
|
-
|
|
772
|
-
const result = await memory.searchKnowledge({
|
|
773
|
-
query: "Rust 如何保证内存安全?",
|
|
774
|
-
scope: "chat",
|
|
775
|
-
scopeId: "dev-agent",
|
|
776
|
-
mode: "fast",
|
|
777
|
-
});
|
|
778
|
-
```
|
|
779
|
-
|
|
780
|
-
### Ingest Markdown And Wait For Knowledge Graph
|
|
781
|
-
|
|
782
|
-
```ts
|
|
783
|
-
await memory.addDocument({
|
|
784
|
-
content: markdown,
|
|
785
|
-
userId: "u1",
|
|
786
|
-
chatId: "work-agent",
|
|
787
|
-
sourceName: "project.md",
|
|
788
|
-
buildGraph: true,
|
|
789
|
-
waitGraph: true,
|
|
790
|
-
});
|
|
791
|
-
```
|
|
792
|
-
|
|
793
|
-
### Ask With Conversation Memory And Knowledge Base
|
|
794
|
-
|
|
795
|
-
```ts
|
|
796
|
-
const answer = await memory.ask({
|
|
797
|
-
query: "这个项目的技术栈是什么?结合我之前说过的内容回答。",
|
|
798
|
-
scope: "chat",
|
|
799
|
-
scopeId: "work-agent",
|
|
800
|
-
mode: "auto",
|
|
801
|
-
includeKnowledge: true,
|
|
802
|
-
maxChars: 300,
|
|
803
|
-
});
|
|
762
|
+
const history = await memory.getHistoryWindow("s1", 256 * 1024);
|
|
763
|
+
|
|
764
|
+
const promptMessages = [
|
|
765
|
+
{ role: "system", content: `更早对话摘要:\n${history.compressedContext}` },
|
|
766
|
+
...history.recentMessages.map((message) => ({
|
|
767
|
+
role: message.talkerId === "assistant" ? "assistant" : "user",
|
|
768
|
+
content: message.payload ?? message.content,
|
|
769
|
+
})),
|
|
770
|
+
];
|
|
804
771
|
```
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
772
|
+
|
|
773
|
+
### Wait For Conversation Graph Data
|
|
774
|
+
|
|
775
|
+
```ts
|
|
776
|
+
await memory.flushChat("s1", {
|
|
777
|
+
wait: true,
|
|
778
|
+
waitGraph: true,
|
|
779
|
+
});
|
|
780
|
+
```
|
|
781
|
+
|
|
782
|
+
Use this only when graph data must be ready immediately. It can throw if graph construction exceeds `graphBuildTimeoutMs`.
|
|
783
|
+
|
|
784
|
+
### Store Stable Facts
|
|
785
|
+
|
|
786
|
+
```ts
|
|
787
|
+
await memory.updateFacts("用户偏好 TypeScript", "user", "u1", "assistant-main", "s1");
|
|
788
|
+
|
|
789
|
+
const facts = await memory.getFactsForContext("u1", "assistant-main");
|
|
790
|
+
```
|
|
791
|
+
|
|
792
|
+
### Search Fast Without Graph
|
|
793
|
+
|
|
794
|
+
```ts
|
|
795
|
+
const hits = await memory.search({
|
|
796
|
+
query: "用户偏好什么语言?",
|
|
797
|
+
scope: "user",
|
|
798
|
+
scopeId: "u1",
|
|
799
|
+
mode: "fast",
|
|
800
|
+
});
|
|
801
|
+
```
|
|
802
|
+
|
|
803
|
+
### Search With Graph
|
|
804
|
+
|
|
805
|
+
```ts
|
|
806
|
+
const hits = await memory.search({
|
|
807
|
+
query: "Alice 负责哪个项目,项目用了什么技术?",
|
|
808
|
+
scope: "chat",
|
|
809
|
+
scopeId: "work-agent",
|
|
810
|
+
mode: "all",
|
|
811
|
+
});
|
|
812
|
+
```
|
|
813
|
+
|
|
814
|
+
### Ingest Markdown And Search Immediately
|
|
815
|
+
|
|
816
|
+
```ts
|
|
817
|
+
const { docId } = await memory.addDocument({
|
|
818
|
+
content: "# Rust\n\nRust 的所有权系统保证内存安全。",
|
|
819
|
+
userId: "u1",
|
|
820
|
+
chatId: "dev-agent",
|
|
821
|
+
sourceName: "rust.md",
|
|
822
|
+
wait: true,
|
|
823
|
+
});
|
|
824
|
+
|
|
825
|
+
const result = await memory.searchKnowledge({
|
|
826
|
+
query: "Rust 如何保证内存安全?",
|
|
827
|
+
scope: "chat",
|
|
828
|
+
scopeId: "dev-agent",
|
|
829
|
+
mode: "fast",
|
|
830
|
+
});
|
|
831
|
+
```
|
|
832
|
+
|
|
833
|
+
### Ingest Markdown And Wait For Knowledge Graph
|
|
834
|
+
|
|
835
|
+
```ts
|
|
836
|
+
await memory.addDocument({
|
|
837
|
+
content: markdown,
|
|
838
|
+
userId: "u1",
|
|
839
|
+
chatId: "work-agent",
|
|
840
|
+
sourceName: "project.md",
|
|
841
|
+
buildGraph: true,
|
|
842
|
+
waitGraph: true,
|
|
843
|
+
});
|
|
844
|
+
```
|
|
845
|
+
|
|
846
|
+
### Ask With Conversation Memory And Knowledge Base
|
|
847
|
+
|
|
848
|
+
```ts
|
|
849
|
+
const answer = await memory.ask({
|
|
850
|
+
query: "这个项目的技术栈是什么?结合我之前说过的内容回答。",
|
|
851
|
+
scope: "chat",
|
|
852
|
+
scopeId: "work-agent",
|
|
853
|
+
mode: "auto",
|
|
854
|
+
includeKnowledge: true,
|
|
855
|
+
maxChars: 300,
|
|
856
|
+
});
|
|
857
|
+
```
|
|
858
|
+
|
|
859
|
+
## Node And Relation Types
|
|
860
|
+
|
|
861
|
+
Default node types:
|
|
862
|
+
|
|
863
|
+
`Person`, `Group`, `Organization`, `Project`, `Task`, `Decision`, `Plan`, `Event`, `Product`, `Technology`, `Data`, `Document`, `Topic`, `Concept`, `Preference`, `Habit`, `Goal`, `Skill`, `Attribute`, `Value`, `Status`, `Time`, `Location`, `Resource`, `Relationship`.
|
|
864
|
+
|
|
865
|
+
Default relation types:
|
|
866
|
+
|
|
867
|
+
`is_a`, `part_of`, `belongs_to`, `contains`, `has_member`, `mentioned_in`, `refers_to`, `same_as`, `alias_of`, `related_to`, `associated_with`, `uses`, `creates`, `updates`, `buys`, `owns`, `consumes`, `works_on`, `prefers`, `likes`, `dislikes`, `interested_in`, `favorite`, `plans`, `decides`, `habit_of`, `tends_to`, `avoids`, `skilled_in`, `learning`, `knows`, `friends_with`, `married_to`, `parent_of`, `child_of`, `lives_with`, `depends_on`, `built_with`, `integrates_with`, `deployed_on`, `inputs`, `outputs`, `trained_on`, `predicts`, `happens_at`, `started_at`, `ended_at`, `affects`, `causes`, `leads_to`, `improves`, `reduces`, `assigned_to`, `executed_by`, `blocks`, `completes`, `describes`, `explains`, `references`.
|
|
868
|
+
|
|
869
|
+
Graph kind constants:
|
|
870
|
+
|
|
871
|
+
- `KIND_CONVERSATION = "conversation"`
|
|
872
|
+
- `KIND_KNOWLEDGE = "knowledge"`
|
|
873
|
+
|
|
874
|
+
Conversation graph search only targets conversation graph data. Knowledge graph search only targets knowledge graph data.
|
|
875
|
+
|
|
876
|
+
## Best Practices
|
|
877
|
+
|
|
878
|
+
- Always call `await memory.init()` before using the manager.
|
|
879
|
+
- Always call `await memory.destroy()` during shutdown to drain background work and close resources.
|
|
880
|
+
- Use stable `userId`, `chatId`, and `sessionId` values. They define memory isolation and retrieval scope.
|
|
881
|
+
- Use `scope: "user"` for personal memory, `scope: "chat"` for one agent/conversation channel, and `scope: "session"` for one short-lived conversation.
|
|
882
|
+
- Use `getFactsForContext` for stable facts and `getHistoryWindow` for compressed Topics plus recent raw session history.
|
|
883
|
+
- Use `updateFacts` for explicit facts that should not depend on LLM extraction.
|
|
884
|
+
- Use `updateEntity` when you already know structured entities and relations.
|
|
885
|
+
- Use `flushChat(..., { wait: true })` before reading `getHistoryWindow` in tests or immediate workflows.
|
|
886
|
+
- Use `addDocument(..., { wait: true })` before immediate knowledge search.
|
|
887
|
+
- Use `waitGraph: true` sparingly because graph extraction depends on LLM calls and can be slower.
|
|
888
|
+
- Keep `embeddingDimension` unchanged for an existing vector store unless you rebuild the database.
|
|
889
|
+
- Keep examples free of real API keys, tokens, personal data, and proprietary customer content.
|
|
890
|
+
|
|
891
|
+
## Common Pitfalls
|
|
892
|
+
|
|
893
|
+
- Forgetting `init()` before `updateChat` or search.
|
|
894
|
+
- Treating `getHistoryWindow` as synchronous or string-only. Since 0.4 it is async and returns a structured complete window.
|
|
895
|
+
- Omitting stable `messageId` when the host can update an existing assistant message; without it, an update becomes another raw message.
|
|
896
|
+
- Expecting `flushChat(..., { wait: true })` to wait for graph data. Use `waitGraph: true`.
|
|
897
|
+
- Expecting `addDocument(..., { wait: true })` to wait for graph data. Use `waitGraph: true`.
|
|
898
|
+
- Setting `waitGraph: true` without allowing enough `graphBuildTimeoutMs` for large documents.
|
|
899
|
+
- Using `scope: "session"` with a `chatId`, or `scope: "chat"` with a `userId`. `scopeId` must match the selected scope.
|
|
900
|
+
- Searching immediately after `addDocument` without `wait: true`; chunks may still be ingesting.
|
|
901
|
+
- Mixing embedding models with different vector dimensions in the same existing vector store.
|
|
902
|
+
- Treating `metadata` as indexed arbitrary JSON. Store important filter dimensions in stable top-level ids where possible.
|
|
903
|
+
- Using `mode: "all"` for every query. It is richer but can be slower than `fast`.
|
|
904
|
+
|
|
905
|
+
## Security And Privacy
|
|
906
|
+
|
|
907
|
+
- Never hard-code real `llmApiKey` or `embeddingApiKey`.
|
|
908
|
+
- Treat vector store (LanceDB/SQLite) and Grafeo files as sensitive data stores.
|
|
909
|
+
- `content`, `metadata`, facts, and graph `meta` may contain private information.
|
|
910
|
+
- `destroy()` closes resources; it does not scrub data.
|
|
911
|
+
- Implement your own retention/deletion policy around `deleteSession`, `deleteDocument`, `deleteFact`, and storage directory cleanup.
|
|
912
|
+
- If exposing memory search through an API, enforce user authorization before passing `scope` and `scopeId`.
|
|
913
|
+
|
|
914
|
+
## Publication Notes
|
|
915
|
+
|
|
916
|
+
This file is included in the npm package root through the `files` field in `packages/memory/package.json`. When public APIs, config defaults, wait behavior, search semantics, or examples change, update this `llms.txt` in the same change.
|