feat(distill): LightMem式逐条事实提取 prompt(P1)— 从整段摘要升级为逐条独立事实,保留全部实体细节+时间区分+推断隐含信息

This commit is contained in:
小唯 2026-08-11 17:51:08 +08:00
parent 11162e2f60
commit 0734ffaa5a
2 changed files with 111 additions and 15 deletions

View File

@ -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`

View File

@ -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 式逐条事实提取 prompt2026-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,