244 lines
6.2 KiB
Go
244 lines
6.2 KiB
Go
// 织忆 MemoryWeave — 治理引擎:冲突检测 + 遗忘 + 知识图谱
|
||
package governance
|
||
|
||
import (
|
||
"math"
|
||
"strings"
|
||
"sync"
|
||
"time"
|
||
)
|
||
|
||
// ─── 冲突检测 ────────────────────────────────────────────
|
||
|
||
type ConflictType string
|
||
|
||
const (
|
||
ConflictEntityRelation ConflictType = "entity_relation"
|
||
ConflictFact ConflictType = "fact_conflict"
|
||
ConflictDecision ConflictType = "decision_conflict"
|
||
)
|
||
|
||
type Conflict struct {
|
||
ID string `json:"id"`
|
||
Type ConflictType `json:"type"`
|
||
Entity string `json:"entity"`
|
||
Description string `json:"description"`
|
||
Strategy string `json:"strategy"`
|
||
Status string `json:"status"`
|
||
CreatedAt time.Time `json:"created_at"`
|
||
}
|
||
|
||
type ConflictDetector struct {
|
||
mu sync.RWMutex
|
||
active map[string]*Conflict
|
||
}
|
||
|
||
func NewConflictDetector() *ConflictDetector {
|
||
return &ConflictDetector{active: make(map[string]*Conflict)}
|
||
}
|
||
|
||
// Scan 扫描新记忆与已有记忆的冲突
|
||
func (cd *ConflictDetector) Scan(newContent string, newEntities []string, existing []map[string]interface{}) []*Conflict {
|
||
var conflicts []*Conflict
|
||
|
||
for _, existing := range existing {
|
||
existingContent := existing["content"].(string)
|
||
existingEntities := toStringSlice(existing["entities"])
|
||
|
||
// 检查相同实体但不同关系
|
||
for _, e1 := range newEntities {
|
||
for _, e2 := range existingEntities {
|
||
if e1 == e2 {
|
||
// 检测事实冲突:内容语义矛盾
|
||
if isContradiction(newContent, existingContent) {
|
||
conflicts = append(conflicts, &Conflict{
|
||
Type: ConflictFact,
|
||
Entity: e1,
|
||
Description: "事实冲突:新内容与已有记录矛盾",
|
||
Strategy: "ask_user",
|
||
Status: "pending",
|
||
CreatedAt: time.Now(),
|
||
})
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
return conflicts
|
||
}
|
||
|
||
// AutoResolve 自动裁决冲突(latest_wins / primary_wins)
|
||
func (cd *ConflictDetector) AutoResolve(conflict *Conflict) string {
|
||
if conflict.Strategy == "latest_wins" {
|
||
return "latest"
|
||
}
|
||
if conflict.Strategy == "primary_wins" {
|
||
return "primary"
|
||
}
|
||
return "pending"
|
||
}
|
||
|
||
func toStringSlice(v interface{}) []string {
|
||
if arr, ok := v.([]string); ok {
|
||
return arr
|
||
}
|
||
if arr, ok := v.([]interface{}); ok {
|
||
var result []string
|
||
for _, item := range arr {
|
||
if s, ok := item.(string); ok {
|
||
result = append(result, s)
|
||
}
|
||
}
|
||
return result
|
||
}
|
||
return nil
|
||
}
|
||
|
||
func isContradiction(a, b string) bool {
|
||
// 简单启发式:重叠词 > 50% 但存在否定词差异
|
||
wordsA := strings.Fields(strings.ToLower(a))
|
||
wordsB := strings.Fields(strings.ToLower(b))
|
||
setA := make(map[string]bool)
|
||
for _, w := range wordsA {
|
||
setA[w] = true
|
||
}
|
||
|
||
overlap := 0
|
||
negInA := containsNeg(wordsA)
|
||
negInB := containsNeg(wordsB)
|
||
|
||
for _, w := range wordsB {
|
||
if setA[w] {
|
||
overlap++
|
||
}
|
||
}
|
||
|
||
totalOverlap := float64(overlap) / math.Max(float64(len(wordsA)), float64(len(wordsB)))
|
||
return totalOverlap > 0.5 && negInA != negInB
|
||
}
|
||
|
||
func containsNeg(words []string) bool {
|
||
negs := []string{"not", "no", "don't", "doesn't", "false", "错误", "不是", "没有", "禁止", "不允许"}
|
||
for _, w := range words {
|
||
for _, n := range negs {
|
||
if strings.Contains(w, n) {
|
||
return true
|
||
}
|
||
}
|
||
}
|
||
return false
|
||
}
|
||
|
||
// ─── 遗忘策略 ────────────────────────────────────────────
|
||
|
||
// AgentTypeDecay 不同 Agent 类型的衰减率
|
||
// 短期 Agent(如一次性的任务 agent)衰减快,长期 Agent(如主 agent)衰减慢
|
||
var AgentTypeDecay = map[string]float64{
|
||
"default": 0.015, // 通用
|
||
"longterm": 0.005, // 长期记忆型
|
||
"shortterm": 0.050, // 短期任务型
|
||
"ephemeral": 0.200, // 会话级别
|
||
"hermes": 0.008, // Hermes 主 agent
|
||
"researcher": 0.030, // 研究型 agent
|
||
"executor": 0.040, // 执行型 agent
|
||
"watcher": 0.025, // 监控型 agent
|
||
}
|
||
|
||
type Forgetter struct {
|
||
agentType string
|
||
decayRate float64
|
||
}
|
||
|
||
func NewForgetter() *Forgetter {
|
||
return &Forgetter{agentType: "default", decayRate: 0.015}
|
||
}
|
||
|
||
// NewForgetterWithType 按 Agent 类型创建遗忘器
|
||
func NewForgetterWithType(agentType string) *Forgetter {
|
||
rate, ok := AgentTypeDecay[agentType]
|
||
if !ok {
|
||
rate = 0.015
|
||
}
|
||
return &Forgetter{agentType: agentType, decayRate: rate}
|
||
}
|
||
|
||
// SetAgentType 动态调整遗忘器类型
|
||
func (f *Forgetter) SetAgentType(agentType string) {
|
||
rate, ok := AgentTypeDecay[agentType]
|
||
if !ok {
|
||
rate = 0.015
|
||
}
|
||
f.agentType = agentType
|
||
f.decayRate = rate
|
||
}
|
||
|
||
// AgentType 返回当前类型
|
||
func (f *Forgetter) AgentType() string {
|
||
return f.agentType
|
||
}
|
||
|
||
// ShouldForget 判断记忆是否该被遗忘
|
||
func (f *Forgetter) ShouldForget(lastAccessed time.Time, recallCount int, tier string) bool {
|
||
if tier == "core" {
|
||
return false // 核心记忆永不遗忘
|
||
}
|
||
days := time.Since(lastAccessed).Hours() / 24
|
||
score := 1.0 - days*f.decayRate
|
||
if score < 0.1 {
|
||
score = 0.1
|
||
}
|
||
// recallCount > 0 减缓衰减
|
||
score += float64(recallCount) * 0.05
|
||
return score < 0.2
|
||
}
|
||
|
||
// DecayScore 计算衰减分数
|
||
func (f *Forgetter) DecayScore(lastAccessed time.Time, recallCount int) float64 {
|
||
days := time.Since(lastAccessed).Hours() / 24
|
||
score := 1.0 - days*f.decayRate
|
||
if score < 0.1 {
|
||
score = 0.1
|
||
}
|
||
score += float64(recallCount) * 0.05
|
||
if score > 1.0 {
|
||
score = 1.0
|
||
}
|
||
return math.Round(score*100) / 100
|
||
}
|
||
|
||
// ScanAndForget 检查单条记忆是否需要衰减(供触发器 decay 使用)
|
||
// 实际遗忘操作:降低 importance 到 0.1,标记为 stale
|
||
func (f *Forgetter) ScanAndForget(memoryID string) bool {
|
||
// 生产版从存储读取并真正执行软删除/降权
|
||
// 当前版本返回 true 确认建议遗忘
|
||
_ = memoryID
|
||
return true
|
||
}
|
||
|
||
|
||
|
||
// ListActive 返回所有活跃冲突
|
||
func (cd *ConflictDetector) ListActive() []*Conflict {
|
||
cd.mu.RLock()
|
||
defer cd.mu.RUnlock()
|
||
var list []*Conflict
|
||
for _, c := range cd.active {
|
||
if c.Status == "pending" {
|
||
list = append(list, c)
|
||
}
|
||
}
|
||
return list
|
||
}
|
||
|
||
// Resolve 解决冲突
|
||
func (cd *ConflictDetector) Resolve(id, resolution, winner string) error {
|
||
cd.mu.Lock()
|
||
defer cd.mu.Unlock()
|
||
if c, ok := cd.active[id]; ok {
|
||
c.Status = "resolved"
|
||
c.Strategy = resolution
|
||
}
|
||
return nil
|
||
}
|