E4 图谱推理:E4.1 矛盾检测+E4.2 跨agent recall+E4.3 遗忘参考图谱度

E4.1: ConflictDetector 注入 core.go:API, Commit 时对相似记忆运行矛盾检测(IsContradiction),有冲突时在响应中返回 conflicts 字段
E4.2: Recall 结果 < 3 时自动补充搜索 shared namespace,合并跨 agent 结果
E4.3: Forgetter.ShouldForget 加 graphDegree 可选参数,节点度 > 5 时每度 +0.03 保留分
This commit is contained in:
xiaowei 2026-05-31 16:03:21 +08:00
parent 1ef1747769
commit a6403dd088
3 changed files with 78 additions and 16 deletions

View File

@ -9,6 +9,7 @@ import (
"strings"
"time"
"github.com/xiaoxue/memoryweave/internal/governance"
"github.com/xiaoxue/memoryweave/internal/metrics"
"github.com/xiaoxue/memoryweave/internal/models"
"github.com/xiaoxue/memoryweave/internal/selfoptimize"
@ -17,20 +18,22 @@ import (
// API 持有所有依赖
type API struct {
LanceDB storage.LanceDB
Embedder *storage.Embedder
Reranker *storage.Reranker
Pipeline *storage.RecallPipeline
LanceDB storage.LanceDB
Embedder *storage.Embedder
Reranker *storage.Reranker
Pipeline *storage.RecallPipeline
ConflictDetector *governance.ConflictDetector
}
func NewAPI(ldb storage.LanceDB, emb *storage.Embedder, rerank *storage.Reranker) *API {
func NewAPI(ldb storage.LanceDB, emb *storage.Embedder, rerank *storage.Reranker, cd *governance.ConflictDetector) *API {
pipeline := storage.NewRecallPipeline(emb, ldb, rerank)
pipeline.SetPrefetchPusher(&WSPrefetchAdapter{})
return &API{
LanceDB: ldb,
Embedder: emb,
Reranker: rerank,
Pipeline: pipeline,
LanceDB: ldb,
Embedder: emb,
Reranker: rerank,
Pipeline: pipeline,
ConflictDetector: cd,
}
}
@ -112,6 +115,16 @@ func (a *API) Commit(w http.ResponseWriter, r *http.Request) {
}
}
// 3c. 矛盾检测:对所有相似记忆检查内容矛盾
// 3a/3b 已处理 exact/near-dup3c 检查语义矛盾)
var conflictSources []string
for _, existing := range similar {
if existing.Content != req.Content {
conflictSources = append(conflictSources, existing.Content)
}
}
conflicts := a.ConflictDetector.DetectContradiction(req.Content, conflictSources)
// 4. 新增
memID := fmt.Sprintf("mem_%d", time.Now().UnixNano())
mem := models.MemoryRecord{
@ -156,9 +169,13 @@ func (a *API) Commit(w http.ResponseWriter, r *http.Request) {
}
}()
respond(w, 201, map[string]string{
respData := map[string]interface{}{
"episode_id": epID, "memory_id": memID, "status": "ok",
})
}
if len(conflicts) > 0 {
respData["conflicts"] = conflicts
}
respond(w, 201, respData)
go AutoDistillTrigger(epID, req.Content, req.Category, req.Namespace, req.AgentID)
}
@ -237,6 +254,33 @@ func (a *API) Recall(w http.ResponseWriter, r *http.Request) {
respondError(w, 500, "recall failed: "+err.Error())
return
}
// E4.2: 跨 agent 知识共享 — recall 结果 < 3 时,补充搜索 "shared" namespace
if len(results) < 3 && req.Namespace != "shared" {
vec, encErr := a.Embedder.EncodeSingle(req.Query)
if encErr == nil {
shared, _ := a.LanceDB.Search("memories", vec, 5, "shared")
for _, m := range shared {
// 去重:跳过已在 own namespace 结果中的记忆
alreadyHave := false
for _, r := range results {
if r.ID == m.ID {
alreadyHave = true
break
}
}
if !alreadyHave {
results = append(results, models.RecallResult{
ID: m.ID,
Content: m.Content,
Category: m.Category,
Score: 0.5, // shared 结果降权,使用默认分
})
}
}
}
}
// Record recall hit rate (has results = hit, empty = miss)
selfoptimize.Dash.RecordRecall(len(results) > 0)
// VProp 自动记录:命中 → success空结果 → failure

View File

@ -75,7 +75,9 @@ func NewServer() http.Handler {
ldb = storage.NewMemLanceClient(emb)
log.Printf("[zhiyid] 存储后端: 内存(零依赖)")
}
api := routes.NewAPI(ldb, emb, rerank)
// 冲突检测器E4.1: Commit 时矛盾检测依赖此实例)
conflictDetector := governance.NewConflictDetector()
api := routes.NewAPI(ldb, emb, rerank, conflictDetector)
// 启动时初始化 Prometheus 指标
go func() {
@ -115,7 +117,6 @@ func NewServer() http.Handler {
routes.SetGraphUpdater(graphUpdater)
// 冲突
conflictDetector := governance.NewConflictDetector()
conflictAPI := routes.NewConflictAPI(conflictDetector)
// ─── 缺口

View File

@ -50,7 +50,7 @@ func (cd *ConflictDetector) Scan(newContent string, newEntities []string, existi
for _, e2 := range existingEntities {
if e1 == e2 {
// 检测事实冲突:内容语义矛盾
if isContradiction(newContent, existingContent) {
if IsContradiction(newContent, existingContent) {
conflicts = append(conflicts, &Conflict{
Type: ConflictFact,
Entity: e1,
@ -95,7 +95,7 @@ func toStringSlice(v interface{}) []string {
return nil
}
func isContradiction(a, b string) bool {
func IsContradiction(a, b string) bool {
// 简单启发式:重叠词 > 50% 但存在否定词差异
wordsA := strings.Fields(strings.ToLower(a))
wordsB := strings.Fields(strings.ToLower(b))
@ -118,6 +118,18 @@ func isContradiction(a, b string) bool {
return totalOverlap > 0.5 && negInA != negInB
}
// DetectContradiction 检查 newContent 是否与 existingContents 中任意一条矛盾
// 返回矛盾的记忆内容列表
func (cd *ConflictDetector) DetectContradiction(newContent string, existingContents []string) []string {
var conflicting []string
for _, ec := range existingContents {
if IsContradiction(newContent, ec) {
conflicting = append(conflicting, ec)
}
}
return conflicting
}
func containsNeg(words []string) bool {
negs := []string{"not", "no", "don't", "doesn't", "false", "错误", "不是", "没有", "禁止", "不允许"}
for _, w := range words {
@ -179,7 +191,8 @@ func (f *Forgetter) AgentType() string {
}
// ShouldForget 判断记忆是否该被遗忘
func (f *Forgetter) ShouldForget(lastAccessed time.Time, recallCount int, tier string) bool {
// graphDegree: 该记忆关联实体的图谱节点度(连接数),度越高越优先保留
func (f *Forgetter) ShouldForget(lastAccessed time.Time, recallCount int, tier string, graphDegree ...int) bool {
if tier == "core" {
return false // 核心记忆永不遗忘
}
@ -190,6 +203,10 @@ func (f *Forgetter) ShouldForget(lastAccessed time.Time, recallCount int, tier s
}
// recallCount > 0 减缓衰减
score += float64(recallCount) * 0.05
// 图谱节点度 > 5 时,每超过 1 度 + 0.03 保留分E4.3: 图谱推理参与遗忘决策)
if len(graphDegree) > 0 && graphDegree[0] > 5 {
score += float64(graphDegree[0]-5) * 0.03
}
return score < 0.2
}