317 lines
7.8 KiB
Go
317 lines
7.8 KiB
Go
// 织忆 MemoryWeave — 自优化引擎
|
|
package selfoptimize
|
|
|
|
import (
|
|
"math"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
// ─── 自优化仪表盘 ────────────────────────────────────────
|
|
|
|
type Dashboard struct {
|
|
mu sync.RWMutex
|
|
UsefulCount int `json:"useful_count"`
|
|
NotUsefulCount int `json:"not_useful_count"`
|
|
TotalRecalls int `json:"total_recalls"`
|
|
HitCount int `json:"hit_count"`
|
|
ClosedGaps int `json:"closed_gaps"`
|
|
TotalGaps int `json:"total_gaps"`
|
|
CascadeFixedTotal int `json:"cascade_fixed_total"`
|
|
TotalFixes int `json:"total_fixes"`
|
|
DeprecatedToday int `json:"deprecated_today"`
|
|
DistillLossSum float64 `json:"distill_loss_sum"`
|
|
DistillLossCount int `json:"distill_loss_count"`
|
|
AutoResolvedConflicts int `json:"auto_resolved_conflicts"`
|
|
TotalConflicts int `json:"total_conflicts"`
|
|
}
|
|
|
|
var Dash = &Dashboard{}
|
|
|
|
// Metrics 返回 7 项核心指标
|
|
func (d *Dashboard) Metrics() map[string]float64 {
|
|
d.mu.RLock()
|
|
defer d.mu.RUnlock()
|
|
|
|
usefulRate := 0.0
|
|
if d.UsefulCount+d.NotUsefulCount > 0 {
|
|
usefulRate = float64(d.UsefulCount) / float64(d.UsefulCount+d.NotUsefulCount)
|
|
}
|
|
|
|
hitRate := 0.0
|
|
if d.TotalRecalls > 0 {
|
|
hitRate = float64(d.HitCount) / float64(d.TotalRecalls)
|
|
}
|
|
|
|
gapRate := 0.0
|
|
if d.TotalGaps > 0 {
|
|
gapRate = float64(d.ClosedGaps) / float64(d.TotalGaps)
|
|
}
|
|
|
|
cascadeRate := 0.0
|
|
if d.TotalFixes > 0 {
|
|
cascadeRate = float64(d.CascadeFixedTotal) / float64(d.TotalFixes)
|
|
}
|
|
|
|
avgLoss := 0.0
|
|
if d.DistillLossCount > 0 {
|
|
avgLoss = d.DistillLossSum / float64(d.DistillLossCount)
|
|
}
|
|
|
|
autoRate := 0.0
|
|
if d.TotalConflicts > 0 {
|
|
autoRate = float64(d.AutoResolvedConflicts) / float64(d.TotalConflicts)
|
|
}
|
|
|
|
return map[string]float64{
|
|
"recall_usefulness_rate": math.Round(usefulRate*100) / 100,
|
|
"recall_hit_rate": math.Round(hitRate*100) / 100,
|
|
"gap_closure_rate": math.Round(gapRate*100) / 100,
|
|
"cascade_fix_rate": math.Round(cascadeRate*100) / 100,
|
|
"deprecated_per_day": float64(d.DeprecatedToday),
|
|
"avg_distill_loss": math.Round(avgLoss*100) / 100,
|
|
"auto_resolve_rate": math.Round(autoRate*100) / 100,
|
|
}
|
|
}
|
|
|
|
func (d *Dashboard) RecordRecall(hit bool) {
|
|
d.mu.Lock()
|
|
defer d.mu.Unlock()
|
|
d.TotalRecalls++
|
|
if hit {
|
|
d.HitCount++
|
|
}
|
|
}
|
|
|
|
func (d *Dashboard) RecordFeedback(useful bool) {
|
|
d.mu.Lock()
|
|
defer d.mu.Unlock()
|
|
if useful {
|
|
d.UsefulCount++
|
|
} else {
|
|
d.NotUsefulCount++
|
|
}
|
|
}
|
|
|
|
// ─── 知识缺口检测 ────────────────────────────────────────
|
|
|
|
type GapType string
|
|
|
|
const (
|
|
GapUnknown GapType = "A" // 真不知道
|
|
GapSynonym GapType = "B" // 同义词不匹配
|
|
GapRecallFailed GapType = "C" // 召回失败
|
|
GapFragmented GapType = "D" // 碎片化
|
|
)
|
|
|
|
type Gap struct {
|
|
Topic string `json:"topic"`
|
|
Type GapType `json:"type"`
|
|
MissCount int `json:"miss_count"`
|
|
CreatedAt time.Time `json:"created_at"`
|
|
Closed bool `json:"closed"`
|
|
}
|
|
|
|
type GapDetector struct {
|
|
mu sync.RWMutex
|
|
gaps map[string]*Gap
|
|
misses map[string]int
|
|
}
|
|
|
|
func NewGapDetector() *GapDetector {
|
|
return &GapDetector{
|
|
gaps: make(map[string]*Gap),
|
|
misses: make(map[string]int),
|
|
}
|
|
}
|
|
|
|
// RecordMiss 记录一次召回失败
|
|
func (gd *GapDetector) RecordMiss(topic string) *Gap {
|
|
gd.mu.Lock()
|
|
defer gd.mu.Unlock()
|
|
|
|
gd.misses[topic]++
|
|
if gd.misses[topic] >= 3 {
|
|
if _, exists := gd.gaps[topic]; !exists {
|
|
gap := &Gap{
|
|
Topic: topic,
|
|
Type: gd.classifyGap(topic),
|
|
MissCount: gd.misses[topic],
|
|
CreatedAt: time.Now(),
|
|
}
|
|
gd.gaps[topic] = gap
|
|
return gap
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (gd *GapDetector) classifyGap(topic string) GapType {
|
|
// 简单启发式:大写缩写 → 同义词;中文 → 可能是真的不知道
|
|
for _, r := range topic {
|
|
if r >= 'A' && r <= 'Z' {
|
|
return GapSynonym
|
|
}
|
|
}
|
|
return GapUnknown
|
|
}
|
|
|
|
func (gd *GapDetector) List() []*Gap {
|
|
gd.mu.RLock()
|
|
defer gd.mu.RUnlock()
|
|
var result []*Gap
|
|
for _, g := range gd.gaps {
|
|
result = append(result, g)
|
|
}
|
|
return result
|
|
}
|
|
|
|
func (gd *GapDetector) Close(topic string) {
|
|
gd.mu.Lock()
|
|
defer gd.mu.Unlock()
|
|
if g, ok := gd.gaps[topic]; ok {
|
|
g.Closed = true
|
|
}
|
|
}
|
|
|
|
// ─── 因果追踪 ────────────────────────────────────────────
|
|
|
|
type TraceEntry struct {
|
|
MemoryID string `json:"memory_id"`
|
|
Version int `json:"version"`
|
|
Content string `json:"content"`
|
|
Source string `json:"source"` // muchen_oral / config_parse / agent_infer / llm_distill
|
|
Trigger string `json:"trigger"` // 什么触发了这次修改
|
|
UpdatedAt time.Time `json:"updated_at"`
|
|
}
|
|
|
|
type CausalTracker struct {
|
|
mu sync.RWMutex
|
|
entries map[string][]*TraceEntry // memory_id → version history
|
|
deps map[string][]string // memory_id → depends_on[]
|
|
}
|
|
|
|
func NewCausalTracker() *CausalTracker {
|
|
return &CausalTracker{
|
|
entries: make(map[string][]*TraceEntry),
|
|
deps: make(map[string][]string),
|
|
}
|
|
}
|
|
|
|
// RecordVersion 记录版本变更
|
|
func (ct *CausalTracker) RecordVersion(memoryID, content, source, trigger string) {
|
|
ct.mu.Lock()
|
|
defer ct.mu.Unlock()
|
|
|
|
entry := &TraceEntry{
|
|
MemoryID: memoryID,
|
|
Version: len(ct.entries[memoryID]) + 1,
|
|
Content: content,
|
|
Source: source,
|
|
Trigger: trigger,
|
|
UpdatedAt: time.Now(),
|
|
}
|
|
ct.entries[memoryID] = append(ct.entries[memoryID], entry)
|
|
}
|
|
|
|
// AddDependency A depends_on B
|
|
func (ct *CausalTracker) AddDependency(a, b string) {
|
|
ct.mu.Lock()
|
|
defer ct.mu.Unlock()
|
|
ct.deps[a] = append(ct.deps[a], b)
|
|
}
|
|
|
|
// GetAffected 当 memoryID 被修正时,返回所有依赖它的记忆
|
|
func (ct *CausalTracker) GetAffected(memoryID string, visited map[string]bool) []string {
|
|
ct.mu.RLock()
|
|
defer ct.mu.RUnlock()
|
|
|
|
if visited == nil {
|
|
visited = make(map[string]bool)
|
|
}
|
|
if visited[memoryID] {
|
|
return nil
|
|
}
|
|
visited[memoryID] = true
|
|
|
|
var affected []string
|
|
for dependent, deps := range ct.deps {
|
|
for _, d := range deps {
|
|
if d == memoryID && !visited[dependent] {
|
|
affected = append(affected, dependent)
|
|
affected = append(affected, ct.GetAffected(dependent, visited)...)
|
|
}
|
|
}
|
|
}
|
|
return affected
|
|
}
|
|
|
|
// SourceTrust 来源信任度
|
|
func SourceTrust(source string) float64 {
|
|
switch source {
|
|
case "muchen_oral":
|
|
return 1.0
|
|
case "muchen_feishu":
|
|
return 0.95
|
|
case "config_parse":
|
|
return 0.7
|
|
case "agent_infer":
|
|
return 0.5
|
|
case "llm_distill":
|
|
return 0.4
|
|
default:
|
|
return 0.3
|
|
}
|
|
}
|
|
|
|
// IsVolatile 判断一条记忆是否易变(频繁修正)
|
|
func (ct *CausalTracker) IsVolatile(memoryID string) bool {
|
|
ct.mu.RLock()
|
|
defer ct.mu.RUnlock()
|
|
return len(ct.entries[memoryID]) >= 3
|
|
}
|
|
|
|
// ─── 记忆预取 ────────────────────────────────────────────
|
|
|
|
type PrefetchGraph struct {
|
|
mu sync.RWMutex
|
|
coOccurs map[string]map[string]int // A → {B: count}
|
|
}
|
|
|
|
func NewPrefetchGraph() *PrefetchGraph {
|
|
return &PrefetchGraph{coOccurs: make(map[string]map[string]int)}
|
|
}
|
|
|
|
func (pg *PrefetchGraph) RecordCoAccess(a, b string) {
|
|
pg.mu.Lock()
|
|
defer pg.mu.Unlock()
|
|
if pg.coOccurs[a] == nil {
|
|
pg.coOccurs[a] = make(map[string]int)
|
|
}
|
|
pg.coOccurs[a][b]++
|
|
}
|
|
|
|
// GetPrefetch 获取某个 query 的预取候选项
|
|
func (pg *PrefetchGraph) GetPrefetch(query string) []string {
|
|
pg.mu.RLock()
|
|
defer pg.mu.RUnlock()
|
|
|
|
related := pg.coOccurs[query]
|
|
if related == nil {
|
|
return nil
|
|
}
|
|
|
|
var result []string
|
|
for topic, count := range related {
|
|
total := 0
|
|
for _, c := range pg.coOccurs[query] {
|
|
total += c
|
|
}
|
|
prob := float64(count) / float64(total)
|
|
if prob > 0.6 {
|
|
result = append(result, topic)
|
|
}
|
|
}
|
|
return result
|
|
}
|