moomem v0.2.2 | moonbitlang/x@0.5.5 | mizchi/llm@0.3.2(仅 llm_extractor / CLI --llm)| Apache-2.0moon add moonbitlang/x # moomem 依赖它做磁盘持久化(仅 fs 子包)
moon add mizchi/llm # 仅使用 LLM 提取适配包(src/llm_extractor)时需要{
"deps": { "moonbitlang/x": "0.5.5", "<你的用户名>/moomem": "0.1.0" }
}当前仓库尚未发布 mooncakes;本地开发直接以本仓库为 path 依赖或将 src/ 拷入工程即可。
fn main {
// ① 打开(首次创建,二次恢复)记忆库目录
let mem = @moomem.MemoryStore::open("./memory").unwrap()
// ② 对话结束后写入:内部完成 提取→去重→冲突→嵌入→双索引→落盘
let summary = mem.add("user-42", "我对花生过敏").unwrap()
println("AI 理解了什么: extracted=\{summary.extracted} inserted=\{summary.inserted}")
// ③ LLM 调用前召回:向量+BM25 双路融合,只返回该用户自己的记忆
let hits = mem.recall("user-42", "花生过敏", top_k=3).unwrap()
for e in hits {
println("[\{e.created_at}] \{e.content}")
}
ignore(mem.close()) // 安全关闭并刷盘
}moon build src/cli --target native
BIN=_build/native/debug/build/src/cli/cli.exe
$BIN add --db ./mem --user user-42 --text "我对花生过敏"
$BIN recall --db ./mem --user user-42 --query "花生过敏"
$BIN list --db ./mem --user user-42
$BIN stats --db ./mem
$BIN export --db ./mem > backup.jsonl
$BIN import --db ./mem --file backup.jsonl
# 可选:LLM 提取(OpenAI 兼容端点;密钥只读环境变量)
# export MOOMEM_LLM_API_KEY=...
# export MOOMEM_LLM_BASE_URL=https://api.deepseek.com # 可选
# export MOOMEM_LLM_MODEL=deepseek-chat # 可选,缺省 gpt-4o-mini
$BIN add --db ./mem --user user-42 --text "你好!我对花生过敏" --llm
# 同时启用 LLM 冲突判定:加 --llm-judge(隐含 --llm)moon test # native 后端,全绿(核心 + llm_extractor + cli)
moon test --target wasm # 四后端行为一致(AC-06);native 专属的磁盘与 CLI 测试除外
moon test --target wasm-gc
moon test --target js
moon run ci/tuning --target native # 零网络离线调参台(检索/冲突参数网格扫描)| 方法 | 语义 | 返回 |
|---|---|---|
| open(path, config?) | 打开或创建记忆库(目录不存在则创建) | Result[MemoryStore, MoomemError] |
| add(user_id, text) | 写入:提取→去重→冲突→双索引→落盘 | Result[AddSummary, MoomemError] |
| recall(user_id, query, top_k?) | 混合检索融合排序,只返回 active/unstructured | Result[Array[MemoryEntry], MoomemError] |
| forget(user_id, target) | 软删除(ById(id) / All),快照保留可审计 | Result[Int, MoomemError] |
| stats() | 条目计数 / 状态分布 / 字节数 / 恢复截断计数 | Result[StoreStats, MoomemError] |
| close() | flush 后关闭;幂等 | Result[Unit, MoomemError] |
| trait | 缺省实现(确定性、离线) | 生产替换 |
|---|---|---|
| Embedder | HashingEmbedder(256 维词袋哈希,TF + L2 归一) | vcdb / 宿主嵌入服务适配 |
| Extractor | RawExtractor(原文 kind=unstructured 直通,零 Key) | src/llm_extractor 包的 LlmExtractor(见下节) |
| ConflictJudge | SimilarityJudge(余弦+关键词 Jaccard,阈值 0.82,候选窗口 8) | src/llm_extractor 包的 LlmConflictJudge(见下节) |
| PersistenceBackend | native: FsBackend;wasm/js: MemoryBackend | 浏览器 IndexedDB 胶水等 |
| Clock | LogicalClock(快照续接的单调逻辑时钟) | 系统时钟 |
let cfg : Config = Config::{
..Config::default(),
judge : Some(MyLlmJudge::new() as &ConflictJudge),
clock : Some(FixedClock::new(42L) as &Clock), // 测试精确断言 created_at
}
let store = MemoryStore::open("./memory", config=cfg).unwrap()// 宿主 moon.pkg.json import:
// "heyq02/moomem/src" "heyq02/moomem/src/llm_extractor" "mizchi/llm/openai"
// ① 构造 LLM 客户端(OpenAI 兼容端点:OpenAI/OpenRouter/Ollama/自建网关)
let provider = @openai.OpenAIProvider::new(
"sk-...",
endpoint=OpenAIEndpoint::OpenAI, // 或 Custom(base_url="https://...")
model="gpt-4o-mini",
)
// ② 包装为 moomem 注入点(LLM 客户端可注入 = 可 mock 测试)
let extractor = @llm_extractor.LlmExtractor::new(provider)
let judge = @llm_extractor.LlmConflictJudge::new(provider)
// ③ 注入 MemoryStore
let cfg : Config = Config::{
..Config::default(),
extractor : Some(extractor as &Extractor),
judge : Some(judge as &ConflictJudge),
}
let store = MemoryStore::open("./memory", config=cfg).unwrap()
// add("user-42", "你好!我对花生过敏") → 只入库 Fact("用户对花生过敏", span="我对花生过敏")| 场景 | 行为 | 失败原因可见处 |
|---|---|---|
| 正常 | 单次提取 1 次 LLM 调用 | — |
| 非法 JSON | 携纠正指令重试 1 次(共 2 次,调用预算上限) | — |
| 重试仍非法 | 降级:原文以 kind=unstructured 入库,不抛错(缺省 ReturnRaw 策略) | AddSummary.degraded / notes / metadata.extraction_degraded + last_failure_reason() |
| 网络/鉴权失败 | 不重试,直接降级(1 次调用;原因前缀 transport error:) | 同上 |
| 冲突判定失败 | Err → store 降级为"两条并存",AddSummary.degraded=true | AddSummary.notes、judge.last_failure_reason() |
<path>/moomem/slot0.jsonl # 快照槽 A:首行 header + 每行一条 entry JSON
<path>/moomem/slot1.jsonl # 快照槽 B
<path>/moomem/head # "0" 或 "1",指向当前有效槽(空) → Active → Superseded(冲突 Replace,记录 superseded_by)
→ Deleted(forget 软删除)src/
├── lib.mbt 版本常量与包说明
├── types.mbt 核心类型 / user_id 校验 / id 生成
├── errors.mbt MoomemError 统一错误
├── json_codec.mbt 唯一 JSON 编解码处
├── embedder.mbt Embedder/Clock trait + 缺省实现
├── extractor.mbt Extractor trait + RawExtractor
├── conflict.mbt ConflictJudge trait + SimilarityJudge
├── dedup.mbt 内容指纹去重
├── index_vector.mbt 每 user 分片余弦近邻
├── index_keyword.mbt 每 user 分片 BM25(CJK 单字+二元组分词)
├── ranker.mbt RRF 融合
├── persist.mbt PersistenceBackend + FsBackend/MemoryBackend(唯一 fs 调用点)
├── store.mbt MemoryStore 编排(open/add/recall/forget/stats/close)
├── *_test.mbt 黑盒测试(58 个,覆盖 AC-01/02/04/05 与崩溃恢复;其中 3 个为 native 专属)
├── llm_extractor/ W2 LLM 适配包:LlmExtractor + LlmConflictJudge(依赖 mizchi/llm,
│ 核心包不依赖;35 个零网络 mock 黑盒测试)
└── cli/ CLI 工具(add/recall/list/stats/export/import)
examples/ W2 可运行示例:moon run examples(mock 驱动,零网络)