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:
parent
1ef1747769
commit
a6403dd088
|
|
@ -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-dup,3c 检查语义矛盾)
|
||||
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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
// ─── 缺口
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue