@aipack-ai/memory 0.0.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.
@@ -0,0 +1,836 @@
1
+ import * as _aipack_ai_agent from '@aipack-ai/agent';
2
+ import { BaseExtension, RuntimeHooks, ExtensionContext, BaseTransformer, ContextResource, Extension, ContextTransformer, Tool } from '@aipack-ai/agent';
3
+ export { ContextTransformer, Extension, Tool, ToolResult } from '@aipack-ai/agent';
4
+
5
+ /**
6
+ * aipack-memory - 核心类型定义
7
+ *
8
+ * 记忆条目、存储契约、检索结果、Embedder 与摘要函数接口。
9
+ * 不依赖任何外部实现,是整个插件的类型基础。
10
+ */
11
+ /** 记忆来源 */
12
+ type MemorySource = 'capture' | 'tool' | 'consolidation';
13
+ /**
14
+ * 单条记忆条目。
15
+ * 参考 agentmemory:每条记忆含置信度、生命周期、检索统计与可选向量。
16
+ */
17
+ interface MemoryEntry {
18
+ /** 唯一标识 */
19
+ id: string;
20
+ /** 记忆正文 */
21
+ content: string;
22
+ /** 关键词 / 概念标签(用于 BM25 索引与展示) */
23
+ concepts: string[];
24
+ /** 置信度 0..1(捕获默认 0.6,摘要 0.8,合并后累加并截断到 1,可衰减) */
25
+ confidence: number;
26
+ /** 来源 */
27
+ source: MemorySource;
28
+ /** 来源会话 key(capture 时记录) */
29
+ sessionKey?: string;
30
+ /** 创建时间(ms 时间戳) */
31
+ createdAt: number;
32
+ /** 最后更新时间(ms 时间戳) */
33
+ updatedAt: number;
34
+ /** 最后一次被检索注入的时间 */
35
+ lastRecalledAt?: number;
36
+ /** 被检索注入次数 */
37
+ recallCount: number;
38
+ /** 可选向量(当配置 Embedder 时填充,用于混合检索) */
39
+ embedding?: number[];
40
+ /** 过期时间(ms 时间戳),由 save 时的 ttlMs 换算,或直接指定 */
41
+ expiresAt?: number;
42
+ /** 额外元数据 */
43
+ meta?: Record<string, unknown>;
44
+ }
45
+ type MatchedBy = 'bm25' | 'embedding' | 'hybrid';
46
+ interface MemorySearchResult {
47
+ entry: MemoryEntry;
48
+ /** 分数:默认检索为 min-max 归一化 0..1;raw 模式(合并器用)为 BM25/cosine 原始分数 */
49
+ score: number;
50
+ matchedBy: MatchedBy;
51
+ }
52
+ /**
53
+ * 向量化接口(可选)。
54
+ * 默认不提供,退化为纯 BM25 检索(零依赖、零 API Key)。
55
+ * 用户可自行实现以接入 ollama / @huggingface/transformers / OpenAI embedding 等。
56
+ */
57
+ interface Embedder {
58
+ /** 将文本转为向量 */
59
+ embed(text: string): Promise<number[]>;
60
+ /** 向量维度(可选,用于校验) */
61
+ dimension?: number;
62
+ }
63
+ /**
64
+ * 可选摘要函数(默认关闭,零 token 消耗)。
65
+ * 提供后,capture 会把整轮对话压成一句精炼记忆与可选概念。
66
+ * 返回 null 表示放弃摘要(回退到零-LLM 抽取)。
67
+ */
68
+ type SummarizeFn = (input: {
69
+ userMessage: string;
70
+ assistantContent: string;
71
+ toolsUsed: string[];
72
+ }) => Promise<{
73
+ summary: string;
74
+ concepts?: string[];
75
+ } | null>;
76
+ /** Consolidator 的最小契约(避免 store ↔ consolidator 循环依赖) */
77
+ interface ConsolidatorLike {
78
+ run(options?: ConsolidateOptions): Promise<{
79
+ merged: number;
80
+ pruned: number;
81
+ }>;
82
+ }
83
+ interface ConsolidateOptions {
84
+ /** 相似度阈值,>= 该值视为可合并(默认 0.85)。按检索原始分数(BM25 raw / cosine)判定 */
85
+ similarityThreshold?: number;
86
+ /** 合并后记忆数量上限(保留置信度最高的,默认无限制) */
87
+ maxMemories?: number;
88
+ /** 最大保留时长(ms),超过则修剪 */
89
+ maxAgeMs?: number;
90
+ /** 最低置信度,低于则修剪(默认 0.1) */
91
+ minConfidence?: number;
92
+ }
93
+ /** 文件存储选项 */
94
+ interface FileMemoryStoreOptions {
95
+ /** 存储根目录(支持 ~ 开头,默认 <cwd>/.aipack/memory) */
96
+ baseDir?: string;
97
+ /** 过期时间(ms),超过 updatedAt 的记忆在加载时惰性清理 */
98
+ maxAge?: number;
99
+ }
100
+ /** 保存入参:content/concepts/confidence/source 必填,其余可选;ttlMs 换算为 expiresAt */
101
+ type MemorySaveInput = Pick<MemoryEntry, 'content' | 'concepts' | 'confidence' | 'source'> & Partial<Omit<MemoryEntry, 'content' | 'concepts' | 'confidence' | 'source'>> & {
102
+ /** 可选 TTL(ms):保存时换算为 expiresAt = createdAt + ttlMs(优先于显式 expiresAt) */
103
+ ttlMs?: number;
104
+ };
105
+ /** 记忆系统事件(失败/整理等关键节点,供外部监控与日志) */
106
+ type MemoryEvent = {
107
+ type: 'store:load';
108
+ loaded: number;
109
+ skipped: number;
110
+ ms: number;
111
+ } | {
112
+ type: 'store:corrupt';
113
+ file: string;
114
+ } | {
115
+ type: 'embedding:error';
116
+ id?: string;
117
+ error: string;
118
+ } | {
119
+ type: 'capture:failed';
120
+ sessionKey?: string;
121
+ error: string;
122
+ } | {
123
+ type: 'consolidate:failed';
124
+ error: string;
125
+ } | {
126
+ type: 'consolidate';
127
+ merged: number;
128
+ pruned: number;
129
+ ms: number;
130
+ } | {
131
+ type: 'prune';
132
+ removed: number;
133
+ };
134
+ /** 事件接收器(可注入;默认把失败类事件打印到 console.warn) */
135
+ type MemoryEventSink = (event: MemoryEvent) => void;
136
+ /** 记忆库统计快照 */
137
+ interface MemoryStats {
138
+ /** 记忆总数 */
139
+ count: number;
140
+ /** 带向量的条目数 */
141
+ embeddingCount: number;
142
+ /** 按来源计数 */
143
+ bySource: Record<MemorySource, number>;
144
+ /** 平均置信度 0..1 */
145
+ avgConfidence: number;
146
+ /** 检索注入总次数 */
147
+ recallTotal: number;
148
+ /** 最近一次被检索注入的时间 */
149
+ lastRecalledAt?: number;
150
+ /** 最近一次合并(consolidate)时间 */
151
+ lastConsolidatedAt?: number;
152
+ }
153
+ /**
154
+ * 记忆存储适配器接口。
155
+ * 实现需自行保证 search 的检索能力(内置 BM25Retriever 可复用)。
156
+ */
157
+ interface MemoryStore {
158
+ /**
159
+ * 保存记忆(新建或更新)。
160
+ * 调用方可省略 id/createdAt/updatedAt/recallCount,由 store 填充。
161
+ */
162
+ save(entry: MemorySaveInput): Promise<MemoryEntry>;
163
+ /** 读取单条 */
164
+ get(id: string): Promise<MemoryEntry | null>;
165
+ /** 删除单条,返回是否删除成功 */
166
+ delete(id: string): Promise<boolean>;
167
+ /** 列出记忆(按 updatedAt 降序,可限数量) */
168
+ list(limit?: number): Promise<MemoryEntry[]>;
169
+ /** 基于查询检索(默认纯 BM25) */
170
+ search(query: string, limit?: number): Promise<MemorySearchResult[]>;
171
+ /**
172
+ * 基于向量检索(可选能力):返回 cosine 降序的 top-N。
173
+ * 未配置 embedding 时返回空数组。默认 store 在 config embedder 后可用。
174
+ */
175
+ searchVectors(queryVec: number[], limit?: number): Promise<MemorySearchResult[]>;
176
+ /** 更新某条记忆的检索统计(lastRecalledAt / recallCount++) */
177
+ touchRecall(id: string, at?: number): Promise<void>;
178
+ /** 合并相似记忆 + 修剪过期/低置信度,返回合并与修剪计数 */
179
+ consolidate(options?: ConsolidateOptions): Promise<{
180
+ merged: number;
181
+ pruned: number;
182
+ }>;
183
+ /** 仅修剪(按 maxAge / minConfidence / expiresAt),返回修剪数量 */
184
+ prune(options?: {
185
+ maxAgeMs?: number;
186
+ minConfidence?: number;
187
+ }): Promise<number>;
188
+ /** 记忆总数 */
189
+ count(): Promise<number>;
190
+ /** 注入合并器(由插件层装配,用于 consolidate()) */
191
+ setConsolidator(consolidator: ConsolidatorLike): void;
192
+ /** 记录最近一次合并时间(驱动增量合并的候选窗口) */
193
+ markConsolidated(at?: number): void;
194
+ /** 统计快照(可观测性) */
195
+ stats(): Promise<MemoryStats>;
196
+ /** 释放资源(热重载场景;当前实现为无操作占位) */
197
+ dispose(): void;
198
+ }
199
+
200
+ /**
201
+ * 混合检索器:BM25(必选)+ 可选 Embedding(向量)双路独立召回后加权融合。
202
+ *
203
+ * 默认无 embedder 时退化为纯 BM25。
204
+ * 有 embedder 时:
205
+ * - BM25 路独立召回 top-`limit*3`
206
+ * - 向量路独立召回 top-`limit*3`(依赖 store 的 searchVectors / VectorIndex,
207
+ * 不受 BM25 候选池封顶 —— 修复「向量召回被 BM25 top-K 限制」问题)
208
+ * - 两路各自 min-max 归一化后按权重融合:final = w_bm25 * bm25_norm + w_embed * cos_norm
209
+ * - 过滤 < minScore,截 limit
210
+ *
211
+ * 兼容路径:
212
+ * - 配置了 embedder 但 store 不支持向量检索(无 searchVectors 能力):
213
+ * 退化为「BM25 候选 + 向量重排」(旧行为),并在 minScore 处诚实标注。
214
+ */
215
+
216
+ /**
217
+ * 检索器最小契约:任何具备 search(query, limit) 的对象均可作为 BM25 候选源。
218
+ * BM25Retriever 天然满足此接口;插件层也可用 StoreBackedRetriever 包装自定义 store。
219
+ */
220
+ interface RetrieverLike {
221
+ search(query: string, limit?: number): Promise<MemorySearchResult[]>;
222
+ }
223
+ /** 向量检索源契约(由 store 实现,或插件层包装) */
224
+ interface VectorSearchLike {
225
+ searchVectors(queryVec: number[], limit?: number): Promise<MemorySearchResult[]>;
226
+ }
227
+ interface HybridRetrieverOptions {
228
+ /** BM25 候选源(BM25Retriever 或任何 RetrieverLike 实现) */
229
+ bm25: RetrieverLike;
230
+ /** 可选向量源(store.searchVectors);有 embedder 但无此源时退化为候选重排 */
231
+ vector?: VectorSearchLike;
232
+ embedder?: Embedder;
233
+ /** BM25 权重,默认 0.5 */
234
+ bm25Weight?: number;
235
+ /** embedding 权重,默认 0.5 */
236
+ embedWeight?: number;
237
+ /** 注入阶段最低分数阈值,默认 0.1 */
238
+ minScore?: number;
239
+ }
240
+ interface HybridSearchOptions {
241
+ /** 覆盖默认 minScore(如注入转换器携带自己的阈值,不再篡改共享 retriever) */
242
+ minScore?: number;
243
+ /**
244
+ * 原始分数模式:不做 min-max 归一化,保留 BM25 / cosine 原始分数。
245
+ * 供合并器等需要「绝对相似度阈值」的场景使用 —— min-max 会把同一查询内
246
+ * 的次优分数压到 0,使绝对阈值(如 0.85)失去意义。
247
+ */
248
+ raw?: boolean;
249
+ }
250
+ declare class HybridRetriever {
251
+ bm25: RetrieverLike;
252
+ vector?: VectorSearchLike;
253
+ embedder?: Embedder;
254
+ bm25Weight: number;
255
+ embedWeight: number;
256
+ minScore: number;
257
+ constructor(options: HybridRetrieverOptions);
258
+ /** 是否启用了向量检索 */
259
+ get hasEmbedder(): boolean;
260
+ search(query: string, limit?: number, opts?: HybridSearchOptions): Promise<MemorySearchResult[]>;
261
+ /** 单路(纯 BM25):归一化 + 过滤 + 截断 */
262
+ private fuseSingle;
263
+ /** 候选重排(兼容路径):BM25 候选 + 向量 cosine 加权融合 */
264
+ private rerank;
265
+ /**
266
+ * 原始分数融合(raw 模式):保留检索源原始分数,不做 min-max 归一化,
267
+ * 供合并器按「绝对相似度阈值」判定可合并项。双路命中时取 max
268
+ * (任一来源判定相似即相似),避免归一化把次优分数抹平。
269
+ *
270
+ * 量纲说明:BM25 分数在 BM25Retriever 层已按 Σidf 归一化到 [0,1]
271
+ * (完全同文≈1),cosine 本身在 [0,1],因此这里的绝对阈值(如 0.85)
272
+ * 对两路统一成立。
273
+ */
274
+ private fuseRaw;
275
+ /** 双路独立召回融合:按 id 取并集,加权求和,再按权重和归一化 */
276
+ private fuseDual;
277
+ }
278
+
279
+ /**
280
+ * 记忆捕获扩展。
281
+ *
282
+ * 在 Runtime 生命周期中「静默」捕获每轮对话要点并存为可检索记忆。
283
+ * 参考 agentmemory 的 capture 阶段。
284
+ *
285
+ * 机制:
286
+ * - beforeRun:暂存本轮用户消息(单会话,Runtime 持有唯一 sessionKey)。
287
+ * - done:与本轮结果配对捕获。sessionKey 来自 ExtensionContext(Runtime 级),
288
+ * 与结果配对写入记忆库。框架保证同一 Runtime 的 run 串行执行,无并发错配。
289
+ * - failed:本轮失败不捕获(仅成功回合入库),无残留状态需清理。
290
+ *
291
+ * 每 consolidateEvery 次捕获触发一次合并。
292
+ */
293
+
294
+ interface CaptureOptions {
295
+ enabled?: boolean;
296
+ /** 可选 LLM 摘要函数(默认关闭,零 token) */
297
+ summarizeFn?: SummarizeFn;
298
+ /** 最小用户消息长度(小于则跳过捕获),默认 12 */
299
+ minLength?: number;
300
+ /** 概念数上限,默认 8 */
301
+ maxConcepts?: number;
302
+ /** content 最大字符数,默认 2000 */
303
+ maxContentChars?: number;
304
+ /** 每 N 次捕获触发一次 consolidate(0=不自动,默认 0) */
305
+ consolidateEvery?: number;
306
+ /** 捕获记忆 TTL(ms),过期后 prune 时清理 */
307
+ ttlMs?: number;
308
+ /** 事件接收器(捕获失败等) */
309
+ onEvent?: MemoryEventSink;
310
+ }
311
+ declare class MemoryCaptureExtension extends BaseExtension {
312
+ readonly name = "memory-capture";
313
+ private store;
314
+ private summarizeFn?;
315
+ private minLength;
316
+ private maxConcepts;
317
+ private maxContentChars;
318
+ private consolidateEvery;
319
+ private ttlMs?;
320
+ private onEvent?;
321
+ /** 本轮待捕获的用户消息(sessionKey -> 请求信息) */
322
+ private pending;
323
+ /** 捕获计数(用于触发 consolidate) */
324
+ private captureCount;
325
+ constructor(store: MemoryStore, options?: CaptureOptions);
326
+ protected setup(hooks: RuntimeHooks, context: ExtensionContext): void;
327
+ private captureFromResult;
328
+ }
329
+
330
+ interface InjectionOptions {
331
+ /** 注入 top-K,默认 5 */
332
+ maxMemories?: number;
333
+ /** 最低分数阈值,默认 0.1 */
334
+ minScore?: number;
335
+ /** 可选:对检索 query 做变换(如抽取提问主体) */
336
+ queryTransform?: (latestUserText: string) => string;
337
+ /** 可选:命中记忆后的回调(插件层装配为 store.touchRecall,更新检索统计) */
338
+ onRecall?: (ids: string[]) => void | Promise<void>;
339
+ }
340
+ declare class MemoryInjectionTransformer extends BaseTransformer {
341
+ readonly name = "memory-injection";
342
+ private retriever;
343
+ private maxMemories;
344
+ private minScore;
345
+ private queryTransform?;
346
+ private onRecall?;
347
+ constructor(retriever: HybridRetriever, options?: InjectionOptions);
348
+ protected run(resources: ContextResource[], _context: _aipack_ai_agent.TransformContext): Promise<ContextResource[]>;
349
+ /** 从资源中抽取纯文本(兼容 string 与 ContentBlock[]) */
350
+ private extractUserText;
351
+ private isTextBlock;
352
+ /** 剥除单个资源中的 sentinel 块,返回新资源(若无需剥除返回原资源) */
353
+ private stripResource;
354
+ /** 把记忆块前插进资源内容 */
355
+ private injectIntoResource;
356
+ /** 重建资源(保持 id/type/role/timestamp/dependencies/meta/pinned,替换 content) */
357
+ private rebuildResource;
358
+ }
359
+
360
+ /**
361
+ * 插件聚合入口:createMemoryPlugin(options) → { store, retriever, extensions, transformers, tools, install() }
362
+ *
363
+ * 把 MemoryStore + HybridRetriever + MemoryCaptureExtension + MemoryInjectionTransformer
364
+ * + Consolidator + memory tools 装配成一个开箱即用的插件。
365
+ *
366
+ * 用法(aipack.config.js):
367
+ * import { createMemoryPlugin } from '@aipack-ai/memory';
368
+ * const mem = createMemoryPlugin({ baseDir: '~/.aipack/memory' });
369
+ * const r = mem.install();
370
+ * export default {
371
+ * ..., // provider, model, systemPrompt, sessions...
372
+ * extensions: r.extensions,
373
+ * transformers: r.transformers,
374
+ * tools: r.tools,
375
+ * };
376
+ *
377
+ * 默认配置:FileMemoryStore(持久化)+ 纯 BM25 检索(零依赖)+ 自动捕获 + 自动注入 + 4 个记忆工具。
378
+ * 提供 embedder 后自动升级为 BM25 + 向量混合检索(双路独立召回);提供 summarizeFn 后 capture 走 LLM 摘要。
379
+ */
380
+
381
+ interface MemoryPluginOptions {
382
+ /** FileMemoryStore 存储目录(支持 ~ 开头),默认 <cwd>/.aipack/memory */
383
+ baseDir?: string;
384
+ /** 自定义 store(覆盖默认 FileMemoryStore;需自行保证 search 能力) */
385
+ store?: MemoryStore;
386
+ /** 注入 top-K 上限,默认 5 */
387
+ maxMemories?: number;
388
+ /** 最低相关度阈值,默认 0.1 */
389
+ minScore?: number;
390
+ /** 捕获开关 / 选项,默认 true */
391
+ capture?: boolean | CaptureOptions;
392
+ /** 注入开关 / 选项,默认 true */
393
+ inject?: boolean | InjectionOptions;
394
+ /** 记忆工具开关,默认 true */
395
+ tools?: boolean;
396
+ /** 可选向量化器,配置后启用 BM25 + 向量混合检索 */
397
+ embedder?: Embedder;
398
+ /** 可选 LLM 摘要函数,配置后 capture 走 LLM 摘要(默认零-LLM 要点抽取) */
399
+ summarizeFn?: SummarizeFn;
400
+ /** 每 N 次捕获自动触发一次 consolidate(0=不自动,默认 0) */
401
+ consolidateEvery?: number;
402
+ /** 捕获记忆 TTL(ms),过期后 prune 清理 */
403
+ captureTtlMs?: number;
404
+ /** save_memory 工具保存的记忆 TTL(ms),过期后 prune 清理 */
405
+ toolTtlMs?: number;
406
+ /** 事件接收器(失败/整理/统计等关键节点;默认打印失败告警) */
407
+ onEvent?: MemoryEventSink;
408
+ }
409
+ interface MemoryPlugin {
410
+ /** 装配好的 store(可直接编程式调用 save/search/consolidate 等) */
411
+ store: MemoryStore;
412
+ /** 装配好的混合检索器 */
413
+ retriever: HybridRetriever;
414
+ /** 扩展列表(capture) */
415
+ extensions: Extension[];
416
+ /** 转换器列表(injection) */
417
+ transformers: ContextTransformer[];
418
+ /** 工具列表(save/search/list/delete) */
419
+ tools: Tool[];
420
+ /** 返回 { extensions, transformers, tools },供 aipack.config.js 展开 */
421
+ install(): {
422
+ extensions: Extension[];
423
+ transformers: ContextTransformer[];
424
+ tools: Tool[];
425
+ };
426
+ /** 释放资源(热重载场景) */
427
+ dispose(): void;
428
+ }
429
+ declare function createMemoryPlugin(options?: MemoryPluginOptions): MemoryPlugin;
430
+
431
+ /**
432
+ * BM25 倒排索引与检索器(零依赖)。
433
+ *
434
+ * 经典 BM25 公式:
435
+ * score(q, d) = Σ_t idf(t) * (tf(t,d) * (k1+1)) / (tf(t,d) + k1*(1 - b + b*|d|/avgdl))
436
+ * idf(t) = ln((N - df(t) + 0.5) / (df(t) + 0.5) + 1)
437
+ *
438
+ * 分数不归一化(由 HybridRetriever 负责 min-max 归一化)。
439
+ */
440
+
441
+ interface BM25Options {
442
+ /** 词频饱和参数,默认 1.5 */
443
+ k1?: number;
444
+ /** 文档长度归一化参数,默认 0.75 */
445
+ b?: number;
446
+ }
447
+ declare class BM25Index {
448
+ private k1;
449
+ private b;
450
+ /** 文档集合 */
451
+ private docs;
452
+ /** 倒排表:token -> 文档 id 集合 */
453
+ private inverted;
454
+ private totalLength;
455
+ constructor(options?: BM25Options);
456
+ /** 平均文档长度 */
457
+ private get avgdl();
458
+ /** 文档总数 */
459
+ get size(): number;
460
+ /** 添加或替换文档(同 id 覆盖) */
461
+ add(id: string, tokens: string[]): void;
462
+ /** 移除文档 */
463
+ remove(id: string): void;
464
+ /** 清空索引 */
465
+ clear(): void;
466
+ /**
467
+ * 检索:返回 top-N 的 {id, score}(按分数降序)。
468
+ * 只扫描 query token 命中的文档,避免全量计算。
469
+ */
470
+ search(queryTokens: string[], limit?: number): Array<{
471
+ id: string;
472
+ score: number;
473
+ }>;
474
+ /**
475
+ * 单个 token 的 idf(未出现返回 0)。
476
+ * 供上层计算查询的理论满分(完全同文、tf=1、len≈avgdl 时的分数),
477
+ * 把无界 BM25 原始分规范化为 [0,1] 相似度。
478
+ */
479
+ idf(token: string): number;
480
+ }
481
+ /**
482
+ * 基于 BM25Index 的检索器,包装为 MemorySearchResult。
483
+ * entries 与 index 由外部维护(store 持有并增量同步)。
484
+ *
485
+ * 分数语义:BM25 原始分无界(取决于词频/idf 与库规模),与 embedding 的
486
+ * cosine 相似度(0..1)量纲不匹配 —— 若直接作为绝对相似度与
487
+ * similarityThreshold(如 0.85)比较,合并几乎永远不会触发。这里除以
488
+ * 查询的理论满分(Σidf,即完全同文时的分数)并截断到 [0,1]:
489
+ * 完全同文 ≈ 1,部分命中按比例衰减,使绝对阈值对 BM25 / cosine 统一成立。
490
+ * 该变换是单调的,对普通检索路径的 min-max 归一化幂等(排序不变)。
491
+ */
492
+ declare class BM25Retriever {
493
+ private index;
494
+ private entries;
495
+ constructor(index: BM25Index, entries: Map<string, MemoryEntry>);
496
+ search(query: string, limit?: number): Promise<MemorySearchResult[]>;
497
+ }
498
+
499
+ /**
500
+ * 内存索引:维护 entries 集合 + BM25 倒排索引,供两个 store 复用。
501
+ *
502
+ * 职责:get / list / count / search / add / remove。
503
+ * 不负责持久化(由具体 store 处理)。
504
+ */
505
+
506
+ declare class MemoryIndex {
507
+ private entries;
508
+ private index;
509
+ /** 独立向量索引:保证向量召回不被 BM25 候选池封顶 */
510
+ private vectors;
511
+ private retriever;
512
+ /** 共享底层索引与条目表,返回一个 BM25Retriever(与 store 实时同步) */
513
+ getRetriever(): BM25Retriever;
514
+ get(id: string): MemoryEntry | null;
515
+ /** 全部条目(updatedAt 降序) */
516
+ all(): MemoryEntry[];
517
+ list(limit?: number): MemoryEntry[];
518
+ count(): number;
519
+ /** 索引内容 = content + concepts(提升概念命中) */
520
+ add(entry: MemoryEntry): void;
521
+ remove(id: string): boolean;
522
+ clear(): void;
523
+ search(query: string, limit?: number): Promise<MemorySearchResult[]>;
524
+ searchVectors(queryVec: number[], limit?: number): Promise<MemorySearchResult[]>;
525
+ /** 统计快照 */
526
+ stats(): MemoryStats;
527
+ }
528
+
529
+ /**
530
+ * 文件记忆存储(默认实现)。
531
+ *
532
+ * 每条记忆一个 JSON 文件:<baseDir>/<encodeURIComponent(id)>.json。
533
+ * 写入采用 temp + rename 原子替换(镜像 aipack FileSessionStorage)。
534
+ * 内存缓存 MemoryIndex + BM25 增量索引;首次访问时懒加载并按 maxAge/expiresAt 惰性清理。
535
+ *
536
+ * 并发安全:同 id 的写操作(save/delete/touchRecall)经 keyed mutex 串行,
537
+ * 避免 read-modify-write 竞态(如 embedding 计算期间丢失另一路更新)。
538
+ *
539
+ * 加载优化:懒加载时并发批量读文件(默认 64 并发),避免逐文件串行 IO;
540
+ * 检索要求条目常驻内存(BM25 倒排 + 向量索引),内存占用与记忆规模成正比
541
+ * (详见 README 限制说明)。
542
+ */
543
+
544
+ declare class FileMemoryStore implements MemoryStore {
545
+ private baseDir;
546
+ private maxAge?;
547
+ private idx;
548
+ private loaded;
549
+ private loading;
550
+ private consolidator?;
551
+ private embedder?;
552
+ private onEvent?;
553
+ /** 同 id 写互斥(save/delete/touchRecall) */
554
+ private writeLocks;
555
+ private lastConsolidatedAt?;
556
+ constructor(options?: FileMemoryStoreOptions & {
557
+ index?: MemoryIndex;
558
+ embedder?: Embedder;
559
+ onEvent?: MemoryEventSink;
560
+ });
561
+ get dir(): string;
562
+ setConsolidator(consolidator: ConsolidatorLike): void;
563
+ /** 懒加载:读取目录所有 JSON → 内存索引。并发安全。 */
564
+ private ensureLoaded;
565
+ private entryPath;
566
+ private writeEntry;
567
+ save(entry: MemorySaveInput): Promise<MemoryEntry>;
568
+ get(id: string): Promise<MemoryEntry | null>;
569
+ delete(id: string): Promise<boolean>;
570
+ list(limit?: number): Promise<MemoryEntry[]>;
571
+ search(query: string, limit?: number): Promise<MemorySearchResult[]>;
572
+ searchVectors(queryVec: number[], limit?: number): Promise<MemorySearchResult[]>;
573
+ touchRecall(id: string, at?: number): Promise<void>;
574
+ consolidate(options?: ConsolidateOptions): Promise<{
575
+ merged: number;
576
+ pruned: number;
577
+ }>;
578
+ prune(options?: {
579
+ maxAgeMs?: number;
580
+ minConfidence?: number;
581
+ }): Promise<number>;
582
+ count(): Promise<number>;
583
+ markConsolidated(at?: number): void;
584
+ stats(): Promise<MemoryStats>;
585
+ dispose(): void;
586
+ }
587
+ declare function createFileMemoryStore(options?: FileMemoryStoreOptions): MemoryStore;
588
+
589
+ /**
590
+ * 内存记忆存储(测试与临时场景)。
591
+ * 进程退出即丢失,无持久化。API 与 FileMemoryStore 完全一致。
592
+ *
593
+ * 并发安全:同 id 的写操作(save/delete/touchRecall)经 keyed mutex 串行,
594
+ * 避免 embedder 异步计算期间的 read-modify-write 竞态;跨 id 互不阻塞。
595
+ */
596
+
597
+ /** 填充默认字段(id / createdAt / updatedAt / recallCount / expiresAt) */
598
+ declare function finalizeEntry(entry: MemorySaveInput, now?: number): MemoryEntry;
599
+ interface InMemoryStoreOptions {
600
+ /** 可选向量化器:配置后 save 时自动计算 embedding(供混合检索) */
601
+ embedder?: Embedder;
602
+ /** 事件接收器(失败/整理等关键节点) */
603
+ onEvent?: MemoryEventSink;
604
+ }
605
+ declare class InMemoryStore implements MemoryStore {
606
+ private idx;
607
+ private consolidator?;
608
+ private embedder?;
609
+ private onEvent?;
610
+ private writeLock;
611
+ private lastConsolidatedAt?;
612
+ constructor(options?: InMemoryStoreOptions);
613
+ setConsolidator(consolidator: ConsolidatorLike): void;
614
+ save(entry: MemorySaveInput): Promise<MemoryEntry>;
615
+ get(id: string): Promise<MemoryEntry | null>;
616
+ delete(id: string): Promise<boolean>;
617
+ list(limit?: number): Promise<MemoryEntry[]>;
618
+ search(query: string, limit?: number): Promise<MemorySearchResult[]>;
619
+ searchVectors(queryVec: number[], limit?: number): Promise<MemorySearchResult[]>;
620
+ touchRecall(id: string, at?: number): Promise<void>;
621
+ consolidate(options?: ConsolidateOptions): Promise<{
622
+ merged: number;
623
+ pruned: number;
624
+ }>;
625
+ prune(options?: {
626
+ maxAgeMs?: number;
627
+ minConfidence?: number;
628
+ }): Promise<number>;
629
+ count(): Promise<number>;
630
+ markConsolidated(at?: number): void;
631
+ stats(): Promise<MemoryStats>;
632
+ dispose(): void;
633
+ }
634
+ declare function createInMemoryStore(): MemoryStore;
635
+
636
+ /**
637
+ * 分词器:支持 latin(小写化 + 按非字母数字分割)与 CJK(逐字符)。
638
+ * 零依赖,适配中英文混合文本的 BM25 索引。
639
+ */
640
+ /** 判断字符是否为 CJK 文字:汉字(含扩展/兼容区)、日本假名、韩国谚文 */
641
+ declare function isCJK(ch: string): boolean;
642
+ /**
643
+ * 常见停用词(中英文小集合),用于概念抽取去停用词。
644
+ * BM25 索引本身不去停用词(保留其 IDF 权重),仅概念抽取时过滤。
645
+ */
646
+ declare const STOPWORDS: Set<string>;
647
+ /**
648
+ * 分词器:支持 latin(小写化 + 按非字母数字分割)与 CJK(字符 bigram)。
649
+ * 零依赖,适配中日韩英混合文本的 BM25 索引。
650
+ *
651
+ * CJK 采用「相邻两字 bigram」而非逐字 unigram:
652
+ * - bigram 区分度远高于单字(「数据库」vs「数据科学」共享「数据」但不再全靠
653
+ * 「数/据」这种高 df 低 idf 的单字强匹配);
654
+ * - 奇数长度 CJK 串尾部遗留单字,保证单字查询仍可命中。
655
+ * 支持汉字(含扩展/兼容)、日文假名、韩文谚文。
656
+ */
657
+ /**
658
+ * 将文本切分为 token 数组。
659
+ * - latin 部分:小写化后按非字母数字字符分割。
660
+ * - CJK 部分:相邻两字 bigram(奇数串尾部补单字)。
661
+ * - 标点 / 空白忽略。
662
+ */
663
+ declare function tokenize(text: string): string[];
664
+ /** 概念抽取:返回非停用词 token 的频次 top-N(用于记忆 concepts 字段) */
665
+ declare function extractConcepts(text: string, maxConcepts?: number): string[];
666
+
667
+ /**
668
+ * Embedder 接口与余弦相似度工具。
669
+ *
670
+ * 默认不提供任何 embedder 实现(零依赖、零 API Key)。
671
+ * 用户可自行实现 Embedder 接入 ollama / @huggingface/transformers / OpenAI 等。
672
+ */
673
+
674
+ /** 余弦相似度。任一为零向量返回 0。 */
675
+ declare function cosine(a: number[], b: number[]): number;
676
+ /**
677
+ * 将一组分数 min-max 归一化到 0..1。
678
+ * 单元素或全相同时返回等分(避免除零)。
679
+ */
680
+ declare function minMaxNormalize(scores: number[]): number[];
681
+
682
+ /**
683
+ * VectorIndex —— 零依赖简化 ANN(近似最近邻)向量索引。
684
+ *
685
+ * 用途:为混合检索提供「独立于 BM25 的向量召回」。
686
+ * 之前向量检索被 BM25 top-K 候选池封顶,本索引保证向量路可独立召回语义相似
687
+ * 而关键词不重叠的记忆。
688
+ *
689
+ * 策略(按规模取舍):
690
+ * - 默认:精确 brute-force cosine(预计算范数),中小规模(< 5 万条)足够快;
691
+ * - 可选 `ivfBuckets`:按主导维度分桶的简化 IVF,查询时探测邻近分桶,
692
+ * 降低大库扫描量(近似召回,用于更大规模)。
693
+ *
694
+ * 共享 entry.embedding 引用(不复制向量),内存开销仅为索引结构本身。
695
+ */
696
+ /** 向量检索命中 */
697
+ interface VectorSearchResult {
698
+ id: string;
699
+ score: number;
700
+ }
701
+ interface VectorIndexOptions {
702
+ /** IVF 分桶数(0 = 纯 brute-force 精确检索,默认 0) */
703
+ ivfBuckets?: number;
704
+ }
705
+ declare class VectorIndex {
706
+ private entries;
707
+ /** bucket -> id 列表(仅 ivfBuckets > 0 时使用) */
708
+ private buckets;
709
+ private dim;
710
+ private bucketCount;
711
+ constructor(options?: VectorIndexOptions);
712
+ get size(): number;
713
+ /** 添加或替换向量(同 id 覆盖)。维度不一致时忽略,返回是否生效 */
714
+ add(id: string, vector: number[]): boolean;
715
+ remove(id: string): boolean;
716
+ clear(): void;
717
+ /** 检索 top-k(cosine 降序,score > 0) */
718
+ search(query: number[], k: number): VectorSearchResult[];
719
+ /** 主导维度(argmax |v|)分桶 */
720
+ private bucketOf;
721
+ /** 从查询桶开始探测邻近分桶,直到凑够 minCandidates */
722
+ private probeCandidates;
723
+ private removeFromBucket;
724
+ }
725
+
726
+ /**
727
+ * 捕获抽取器:把一轮对话(用户消息 + 助手回答 + 工具)压成一条记忆。
728
+ *
729
+ * - 零-LLM 模式(默认):抽取关键词概念,content 截断为 `Q: ...\nA: ...`。
730
+ * - LLM 模式(可选 summarizeFn):调用外部摘要函数,失败回退到零-LLM。
731
+ */
732
+
733
+ interface ExtractResult {
734
+ content: string;
735
+ concepts: string[];
736
+ /** 是否经过 LLM 摘要 */
737
+ summarized: boolean;
738
+ }
739
+ interface ExtractorOptions {
740
+ /** 概念数上限,默认 8 */
741
+ maxConcepts?: number;
742
+ /** content 最大字符数,默认 2000 */
743
+ maxChars?: number;
744
+ }
745
+ /**
746
+ * 零-LLM 抽取(要点压缩):content = 用户首句 + 助手首句 + 工具(截断)。
747
+ *
748
+ * 相比直接转储整轮对话:
749
+ * - 只保留「提问主体 + 回答主旨」,避免整段原文进索引(防止索引膨胀、
750
+ * 问题文本干扰检索命中);
751
+ * - concepts = 关键词 top-N(供 BM25 与展示)。
752
+ */
753
+ declare function extractFromTurn(userMessage: string, assistantContent: string, toolsUsed: string[], options?: ExtractorOptions): ExtractResult;
754
+ /**
755
+ * 运行抽取:若提供 summarizeFn 则先尝试 LLM 摘要,失败/返回 null 回退到零-LLM。
756
+ */
757
+ declare function runCaptureExtractor(input: {
758
+ userMessage: string;
759
+ assistantContent: string;
760
+ toolsUsed: string[];
761
+ summarizeFn?: SummarizeFn;
762
+ }, options?: ExtractorOptions): Promise<ExtractResult>;
763
+
764
+ /**
765
+ * 注入哨兵(sentinel):在 user 消息内容中包裹记忆块。
766
+ *
767
+ * 为何用 sentinel 而非 resource.meta:
768
+ * aipack 的 messageToResource/resourceToMessage 对 user 消息不保留 meta
769
+ * (context-resource/index.ts:30-38, 112-119)。sentinel 是 content 的一部分,
770
+ * 随消息持久化,下一轮 messagesToResources 重建后仍可在 content 文本中识别并剥离。
771
+ */
772
+
773
+ declare const MEMORY_BLOCK_START = "<<<AIPACK_MEMORY>>>";
774
+ declare const MEMORY_BLOCK_END = "<<</AIPACK_MEMORY>>>";
775
+ /** 剥离 sentinel 包裹块(含其后的多余空行),返回干净原文 */
776
+ declare function stripMemoryBlock(text: string): string;
777
+ /** 判断文本是否包含 sentinel 块 */
778
+ declare function hasMemoryBlock(text: string): boolean;
779
+ /** 将若干行用 sentinel 包裹(含头尾换行,便于前插进 content) */
780
+ declare function wrapMemoryBlock(lines: string[]): string;
781
+ /** 由检索结果构造可读的记忆块文本 */
782
+ declare function buildMemoryBlock(results: MemorySearchResult[]): string;
783
+
784
+ /**
785
+ * 合并器:去重 / 合并相似记忆 + 修剪过期与低置信度。
786
+ *
787
+ * 参考 agentmemory 的 consolidate 阶段:
788
+ * - 候选窗口 = 自上次合并以来内容有更新的条目(增量合并,避免每次对全库 N 条
789
+ * 逐一检索的 O(N²) 塌陷;首次合并或跨进程重启后为全量)。
790
+ * - 对每个候选:以其 content 作为 query 检索相似记忆,相似度 >= 阈值则合并。
791
+ * - 合并:content 取较长、concepts 并集、置信度取 max + 小奖励(避免旧「累加」
792
+ * 使置信度快速饱和到 1.0 而丧失排序/修剪信号)。
793
+ * - 修剪过期 / 低置信度;超过 maxMemories 时淘汰置信度最低的。
794
+ */
795
+
796
+ interface ConsolidatorOptions {
797
+ similarityThreshold?: number;
798
+ /** 事件接收器(合并结果/失败) */
799
+ onEvent?: MemoryEventSink;
800
+ }
801
+ declare class Consolidator implements ConsolidatorLike {
802
+ private store;
803
+ private retriever;
804
+ private similarityThreshold;
805
+ private onEvent?;
806
+ constructor(store: MemoryStore, retriever: HybridRetriever, options?: ConsolidatorOptions);
807
+ run(options?: ConsolidateOptions): Promise<{
808
+ merged: number;
809
+ pruned: number;
810
+ }>;
811
+ }
812
+
813
+ /**
814
+ * Agent 可调用的记忆工具。
815
+ *
816
+ * 参考 agentmemory 的 MCP 工具(save / recall / sessions 等),
817
+ * 但以 aipack 原生 Tool 形式提供,parameters 用纯 JSON Schema(不依赖 TypeBox)。
818
+ *
819
+ * 工具列表:
820
+ * - save_memory(content, concepts?)
821
+ * - search_memory(query, limit?)
822
+ * - list_memories(limit?)
823
+ * - delete_memory(id)
824
+ */
825
+
826
+ interface MemoryToolsOptions {
827
+ /** list_memories 默认返回上限,默认 20 */
828
+ listLimit?: number;
829
+ /** search_memory 默认返回上限,默认 5 */
830
+ searchLimit?: number;
831
+ /** save_memory 保存的记忆 TTL(ms),过期后 prune 时清理 */
832
+ saveTtlMs?: number;
833
+ }
834
+ declare function createMemoryTools(store: MemoryStore, options?: MemoryToolsOptions): Tool[];
835
+
836
+ export { BM25Index, type BM25Options, BM25Retriever, type CaptureOptions, type ConsolidateOptions, Consolidator, type ConsolidatorLike, type ConsolidatorOptions, type Embedder, type ExtractResult, type ExtractorOptions, FileMemoryStore, type FileMemoryStoreOptions, HybridRetriever, type HybridRetrieverOptions, type HybridSearchOptions, InMemoryStore, type InjectionOptions, MEMORY_BLOCK_END, MEMORY_BLOCK_START, type MatchedBy, MemoryCaptureExtension, type MemoryEntry, type MemoryEvent, type MemoryEventSink, MemoryIndex, MemoryInjectionTransformer, type MemoryPlugin, type MemoryPluginOptions, type MemorySaveInput, type MemorySearchResult, type MemorySource, type MemoryStats, type MemoryStore, type MemoryToolsOptions, type RetrieverLike, STOPWORDS, type SummarizeFn, VectorIndex, type VectorIndexOptions, type VectorSearchLike, type VectorSearchResult, buildMemoryBlock, cosine, createFileMemoryStore, createInMemoryStore, createMemoryPlugin, createMemoryTools, extractConcepts, extractFromTurn, finalizeEntry, hasMemoryBlock, isCJK, minMaxNormalize, runCaptureExtractor, stripMemoryBlock, tokenize, wrapMemoryBlock };