diff --git a/docs/lightmem-p1-fact-extraction-plan.md b/docs/lightmem-p1-fact-extraction-plan.md new file mode 100644 index 0000000..274a3e8 --- /dev/null +++ b/docs/lightmem-p1-fact-extraction-plan.md @@ -0,0 +1,74 @@ +# P1: 织忆蒸馏引擎 LightMem 式逐条事实提取改造 + +> 2026-08-11 | 小唯 | 目标:把织忆 distill 从「整段摘要式提取」升级为「逐条事实提取」(借鉴 LightMem) +> 优先级:P1(织忆 distill 质量的根本提升) + +## 目标(Goal) + +当前 `callLLM5D` 用单 prompt 做整段摘要(decisions/conclusions/actions 各≤3条),对长对话信息密度高的场景丢失细节。 +改造为 LightMem 式**逐条事实提取**:提取所有可独立成句的事实 + 保留全部实体细节 + 轻量上下文补全。 + +## 修改文件(Files to modify) + +| 文件 | 修改 | +|------|------| +| `/tmp/memoryweave/go/internal/distill/engine.go` | `callLLM5D` prompt 重写 + LLMResponse 结构新增 FactsDetail | +| `/tmp/memoryweave/go/internal/distill/engine.go` | `extractFacts` 保留(fallback),新增 facts 组装逻辑 | + +## 实现细节(Implementation details) + +### 1. 重写 `callLLM5D` 的 prompt(第 265-284 行) + +新 prompt 要点(LightMem METADATA_GENERATE_PROMPT 精华移植): +- 逐条判断:**"处理每条用户消息,判断是否含事实;除非纯问候/填充,否则都提取"** +- 轻量上下文补全:`"My friend John is studying medicine"` → `"User's friend John is studying medicine."` +- **保留全部具体细节**:全名/地点/事件/数字/公司名——"The Name of the Wind by Patrick Rothfuss" 不是 "a book" +- 推断隐含信息:多个相关条目 → 推断一般模式,独立成条 +- 时间处理:mention time(说的时间)vs event time(发生时间) +- 输出 JSON:`{"facts": [...], "entities": [...], "decisions": [...], "conclusions": [...], "is": 0.8, "su": 0.7, "pa": 0.6, "vd": 0.9, "ru": 0.7}` + +### 2. LLMResponse 结构 + +- `Facts` 字段含义升级:从「1条整段摘要」→「多条独立事实」 +- 保持 `Decisions/Conclusions/ActionsTaken/OpenQuestions` 兼容(下游使用) + +### 3. extractFacts fallback 保留 + +LLM 失败时仍走关键词+命名实体启发式(原逻辑不动)。 + +## 测试命令(Test commands) + +```bash +# 1. 编译 +cd /tmp/memoryweave/go && go build -o zhiyid-new ./cmd/zhiyid + +# 2. 单元测试(若有) +cd /tmp/memoryweave/go && go test ./internal/distill/ -v 2>&1 | tail -20 + +# 3. 部署 +systemctl --user stop zhiyid +cp /tmp/memoryweave/go/zhiyid-new /home/muc/bin/zhiyid-new +systemctl --user start zhiyid +sleep 2 + +# 4. 功能测试 — 提交一条含多事实的对话 +curl -s -X POST -H "X-API-Key: zhiyi-dev-key-2026" -H "Content-Type: application/json" \ + -d '{"agent_id":"a06","content":"牧尘说小唯今天安装了ffmpeg用于语音转码,昨天研究了LightMem的架构,上周买了新显卡RTX 5080","metadata":{"source":"test"}}' \ + http://localhost:7821/api/v1/commit + +# 5. 验证蒸馏质量 — 日志出现 facts ≥ 3 条 + entities 含具体实体 +journalctl --user -u zhiyid --no-pager -n 20 | grep distill + +# 6. recall 命中验证 +curl -s -X POST -H "X-API-Key: zhiyi-dev-key-2026" -H "Content-Type: application/json" \ + -d '{"query":"ffmpeg 语音转码","top_k":3}' \ + http://localhost:7821/api/v1/recall +``` + +## 验收标准 + +- [ ] go build 通过 +- [ ] 部署后 7821 健康 +- [ ] 提交多事实内容后,日志显示 `LLM facts: N` 且 N ≥ 3(旧版只有 1) +- [ ] recall 能命中具体实体(ffmpeg/RTX 5080/LightMem) +- [ ] 蒸馏无 fallback(日志无 `LLMEndpoint empty` / `JSON parse error`) diff --git a/go/internal/distill/engine.go b/go/internal/distill/engine.go index b867092..4965c80 100644 --- a/go/internal/distill/engine.go +++ b/go/internal/distill/engine.go @@ -262,26 +262,48 @@ func (e *Engine) distillOne(input DistillInput) DistillResult { // callLLM5D 调用 LLM 进行 5维评估 + 实体/事实提取 func (e *Engine) callLLM5D(content string) (LLMResponse, error) { - prompt := fmt.Sprintf(`你是一个对话蒸馏器。从以下内容中提取结构化的记忆片段,以JSON格式返回。 + // LightMem 式逐条事实提取 prompt(2026-08-11 移植) + // 精华:逐条判断含事实 → 轻量补全独立句 → 保留全部实体细节 → 推断隐含信息 → 时间区分 + prompt := fmt.Sprintf(`你是一个个人信息提取器。从以下对话内容中提取所有可能的用户事实信息,以JSON格式返回。 -重点提取: -1. **decisions**:明确的决策结论(做了什么决定、选了什么方案、拒绝了什么) -2. **conclusions**:最终结论或答案(问题如何解决、最终结论是什么) -3. **actions_taken**:采取的具体行动(执行了什么命令、创建了什么文件、修改了什么配置) -4. **open_questions**:悬而未决的问题(需要进一步确认的事项) -5. **entities**:提到的关键实体(系统名、工具名、人名、技术名词) +输入格式: +[时间戳, 星期] 说话者: 消息 +... -注意: -- 对话类内容(用户: xxx / 助手: yyy):提取对话的核心结论和决定,不要逐条总结 -- 决策类内容:重点记录"做了什么决定、为什么、选择了哪个方案" -- 纯对话无结论:提取"讨论了X话题,未得出结论" -- 每个decisions/conclusions最多3条,简洁一句话 +重要指令: +1. 必须按顺序逐条处理每条消息。对每条消息,判断是否包含事实信息。 + - 如果包含 → 提取并改写为独立的完整句子 + - 如果不包含(纯问候、填充语、无关评论)→ 跳过 + - 不要因为信息看起来微小、琐碎或不重要就跳过。即使是小细节(如"用户今早喝了咖啡")也必须保留。只有完全无意义的(如"你好"、"哈哈"、"谢谢")才跳过。 +2. 进行轻量上下文补全,使每个事实成为清晰的独立陈述: + - "user: 昨天买了苹果" → "用户昨天买了苹果。" + - "user: 我的朋友John在学医" → "用户的朋友John在学医。" +3. 保留所有具体实体和细节: + - 完整名称: "The Name of the Wind by Patrick Rothfuss"(不是"一本书") + - 完整地点: Galway, Ireland; 北京海淀区 + - 具体事件名: 慈善篮球赛、留学项目 + - 数字和数量: 4年前、下个月、上周 + - 公司/组织名: 某饮料公司 +4. 推断隐含信息:如果多个相关条目提到 → 可以推断一般模式(保留具体事实和推断结论为独立条目) +5. 时间处理:区分提及时间(何时说的)和事件时间(何时发生的) + - 相对时间(昨天、上周、X前、下个月)→ 保留相对时间并引用消息时间戳 + - 持续/永久事实 → 无需时间标注 +6. 额外提取: + - decisions: 明确的决策结论(做了什么决定、选了什么方案、拒绝了什么) + - conclusions: 最终结论或答案 + - actions_taken: 采取的具体行动 + - open_questions: 悬而未决的问题 + - entities: 提到的关键实体(系统名、工具名、人名、技术名词) + +输出格式(严格JSON): +{"facts": ["独立事实1", "独立事实2"], "decisions": ["决定1"], "conclusions": ["结论1"], "actions_taken": ["行动1"], "open_questions": ["问题1"], "entities": ["entity1", "entity2"], "is": 0.8, "su": 0.7, "pa": 0.6, "vd": 0.9, "ru": 0.7} + +评分说明:is/su/pa/vd/ru 是 0.0 到 1.0 之间的浮点数(越高越好),不要用 0-10 整数。 +要求:除非消息完全无意义,否则提取并输出为事实。facts 要详尽,不要只给1条摘要。 内容: %s - -只返回JSON: {"decisions": ["决定1", "决定2"], "conclusions": ["结论1"], "actions_taken": ["行动1"], "open_questions": ["问题1"], "entities": ["entity1", "entity2"], "is": 0.8, "su": 0.7, "pa": 0.6, "vd": 0.9, "ru": 0.7} -评分说明:is/su/pa/vd/ru 是 0.0 到 1.0 之间的浮点数(越高越好),不要用 0-10 整数。`, truncate(content, 1000)) +`, truncate(content, 1000)) body := map[string]interface{}{ "model": e.LLMModel,