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:
parent
a5b5e217f3
commit
4130e6b668
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
Loading…
Reference in New Issue