feat(memory-quality): 方案B写时质量门槛+方案C provenance 标注

方案B: distill 结果写 memories 前 IsQualityFact 过滤(碎片<8字拒/8-20字需用户信号/噪声前缀拦)+完全重复查重跳过
  - quality.go: 与 LightMem 保留细节哲学兼容(咖啡偏好保留), 拦进程噪声/桥接/reflection
  - 单测 8/8 通过
方案C: distill MemoryRecord 标 Source=llm_distill(provenance); volatile_flag 降权已确认生效(×0.5)
  - ContentMD5/VersionHistory 字段已在, bi-temporal valid_to 加列缓行(风险高)
This commit is contained in:
xiaowei 2026-09-07 19:29:54 +08:00 committed by 小唯
parent a5b5e217f3
commit 4130e6b668
2 changed files with 62 additions and 0 deletions

View File

@ -139,7 +139,18 @@ func NewServer() http.Handler {
})
// 回写蒸馏产物到记忆库LanceDB
// 2026-09-07 方案B 质量门槛: 碎片(<20字)/噪声句(状态汇报/桥接/reflection)不入库,
// 与已有记忆完全重复跳过(蒸馏直写不走 commit exact_dup, 需本层查重)。
for _, fact := range result.Facts {
if !distill.IsQualityFact(fact) {
continue // 碎片/噪声过滤
}
// 查重: 同 namespace 已存在完全相同 content → 跳过
if qvec, eErr := emb.EncodeSingle(fact); eErr == nil {
if sim, _ := ldb.Search("memories", qvec, 1, input.Namespace); len(sim) > 0 && sim[0].Content == fact {
continue
}
}
mem := models.MemoryRecord{
ID: fmt.Sprintf("mem_%d", time.Now().UnixNano()),
AgentID: input.AgentID,
@ -153,6 +164,7 @@ func NewServer() http.Handler {
UpdatedAt: time.Now(),
DerivedFrom: input.EpisodeID,
Freshness: "fresh",
Source: "llm_distill", // 2026-09-07 方案C provenance: LLM蒸馏推断来源
}
if err := ldb.InsertMemory(mem); err != nil {
log.Printf("[distill] commit fact failed: %v", err)

View File

@ -0,0 +1,50 @@
package distill
import "strings"
// IsQualityFact 判断蒸馏事实是否有入库价值2026-09-07 方案B 质量门槛)
//
// 背景: 审计发现 memories 74% <50 字符碎片("重启系统"/"健康检查完成"/daemon reflection 等)
// = 蒸馏/直接 commit 无质量门槛产物。这里拦碎片 + 噪声句。
// 与 LightMem "保留小细节"哲学兼容: 咖啡偏好(18字+信息)仍保留, 纯噪声/碎片拒绝。
func IsQualityFact(fact string) bool {
s := strings.TrimSpace(fact)
if s == "" {
return false
}
// 碎片判断: 绝对碎片(<8字)拒绝; 8-20字需含用户相关词才保留(短偏好如"用户喜欢喝咖啡"有价值)
runeLen := len([]rune(s))
if runeLen < 8 {
return false
}
if runeLen < 20 {
userSignals := []string{"用户", "牧尘", "小唯", "偏好", "喜欢", "决定", "使用", "需要", "认为", "选择", "计划", "希望", "正在做", "完成了"}
hasSignal := false
for _, sg := range userSignals {
if strings.Contains(s, sg) {
hasSignal = true
break
}
}
if !hasSignal {
return false // 8-20字且无用户信号 = 碎片(如"重启系统"已<8; "所有进程运行中"无信号)
}
}
// 噪声: 状态汇报/桥接/daemon reflection 类前缀(这些是进程噪声不是用户事实)
noisePrefixes := []string{
"健康检查完成", "状态同步", "所有进程运行中", "当前系统状态完全正常",
"系统健康检查", "正在评估上次决策", "用户在深度思考中", "上次修复了",
"小唯a06主profile", "小唯a06使用的模型", "小唯a06的织忆数据", "小唯a06的cron", "小唯a06的daemon",
}
lower := strings.ToLower(s)
for _, p := range noisePrefixes {
if strings.HasPrefix(lower, strings.ToLower(p)) {
return false
}
}
// 含桥接关键词且短(通信日志非事实)
if strings.Contains(s, "桥接") && len([]rune(s)) < 40 {
return false
}
return true
}