207 lines
5.0 KiB
Go
207 lines
5.0 KiB
Go
// 织忆 MemoryWeave — V 值反向传播引擎
|
|
package selfoptimize
|
|
|
|
import (
|
|
"encoding/json"
|
|
"math"
|
|
"strconv"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/xiaoxue/memoryweave/internal/storage"
|
|
)
|
|
|
|
// ─── V 值反向传播 ──────────────────────────────────────
|
|
|
|
// VDecision V 值决策记录
|
|
type VDecision struct {
|
|
ID string `json:"id"`
|
|
MemoryIDs []string `json:"memory_ids"` // 引用的记忆 ID
|
|
Action string `json:"action"` // 牧尘的决策行动
|
|
Outcome string `json:"outcome"` // success / failure / partial
|
|
VValue float64 `json:"v_value"` // 决策 V 值 (0-1)
|
|
ParentID string `json:"parent_id"` // 上级决策(级联链路)
|
|
Timestamp time.Time `json:"timestamp"`
|
|
}
|
|
|
|
// VPropagator V 值传播器
|
|
type VPropagator struct {
|
|
mu sync.RWMutex
|
|
decisions map[string]*VDecision
|
|
memVValues map[string]float64 // memory_id → 累积 V 值
|
|
decayRate float64 // V 值衰减率 (default 0.01/day)
|
|
|
|
// Redis 持久化
|
|
redisClient *storage.RedisClient
|
|
}
|
|
|
|
const vpropRedisKey = "zhiyi:vprop_decisions"
|
|
const vpropValsRedisKey = "zhiyi:vprop_values"
|
|
|
|
func (vp *VPropagator) EnableVPropRedisPersistence() {
|
|
rc := storage.GetRedisClient()
|
|
if rc == nil {
|
|
return
|
|
}
|
|
vp.redisClient = rc
|
|
vp.loadVPropFromRedis()
|
|
}
|
|
|
|
func (vp *VPropagator) loadVPropFromRedis() {
|
|
if vp.redisClient == nil {
|
|
return
|
|
}
|
|
// 加载决策
|
|
data, err := vp.redisClient.HGetAll(vpropRedisKey)
|
|
if err == nil && len(data) > 0 {
|
|
for id, jsonStr := range data {
|
|
var d VDecision
|
|
if json.Unmarshal([]byte(jsonStr), &d) == nil {
|
|
vp.decisions[id] = &d
|
|
}
|
|
}
|
|
}
|
|
// 加载 V 值
|
|
valData, err := vp.redisClient.HGetAll(vpropValsRedisKey)
|
|
if err == nil && len(valData) > 0 {
|
|
for memID, valStr := range valData {
|
|
if v, err := strconv.ParseFloat(valStr, 64); err == nil {
|
|
vp.memVValues[memID] = v
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func (vp *VPropagator) persistVProp() {
|
|
if vp.redisClient == nil {
|
|
return
|
|
}
|
|
for id, d := range vp.decisions {
|
|
data, _ := json.Marshal(d)
|
|
vp.redisClient.HSet(vpropRedisKey, id, string(data))
|
|
}
|
|
vp.redisClient.Expire(vpropRedisKey, 90*24*time.Hour)
|
|
for memID, v := range vp.memVValues {
|
|
vp.redisClient.HSet(vpropValsRedisKey, memID, strconv.FormatFloat(v, 'f', 4, 64))
|
|
}
|
|
vp.redisClient.Expire(vpropValsRedisKey, 90*24*time.Hour)
|
|
}
|
|
|
|
var VProp = &VPropagator{
|
|
decisions: make(map[string]*VDecision),
|
|
memVValues: make(map[string]float64),
|
|
decayRate: 0.01,
|
|
}
|
|
|
|
// RecordDecision 记录一条决策及其引用的记忆
|
|
func (vp *VPropagator) RecordDecision(id string, memoryIDs []string, action, outcome string, parentID string) *VDecision {
|
|
vp.mu.Lock()
|
|
defer vp.mu.Unlock()
|
|
|
|
vValue := 0.0
|
|
switch outcome {
|
|
case "success": vValue = 1.0
|
|
case "partial": vValue = 0.5
|
|
case "failure": vValue = 0.0
|
|
}
|
|
|
|
d := &VDecision{
|
|
ID: id,
|
|
MemoryIDs: memoryIDs,
|
|
Action: action,
|
|
Outcome: outcome,
|
|
VValue: vValue,
|
|
ParentID: parentID,
|
|
Timestamp: time.Now(),
|
|
}
|
|
vp.decisions[id] = d
|
|
|
|
// 反向传播:引用的记忆获得 V 值
|
|
boostPerMem := vValue / float64(maxInt(1, len(memoryIDs)))
|
|
for _, memID := range memoryIDs {
|
|
vp.memVValues[memID] += boostPerMem
|
|
}
|
|
|
|
// 级联传播:如果父决策存在,修改父决策的 outcome
|
|
if parentID != "" {
|
|
if parent, ok := vp.decisions[parentID]; ok {
|
|
if vValue > 0.8 {
|
|
parent.Outcome = "success"
|
|
parent.VValue = math.Min(1.0, parent.VValue+0.1)
|
|
}
|
|
}
|
|
}
|
|
|
|
vp.persistVProp()
|
|
return d
|
|
}
|
|
|
|
// GetMemVValue 获取某条记忆的累积 V 值
|
|
func (vp *VPropagator) GetMemVValue(memoryID string) float64 {
|
|
vp.mu.RLock()
|
|
defer vp.mu.RUnlock()
|
|
|
|
v := vp.memVValues[memoryID]
|
|
// 应用时间衰减
|
|
if v > 0 {
|
|
// 检查最后引用时间
|
|
lastRef := time.Time{}
|
|
for _, d := range vp.decisions {
|
|
for _, mid := range d.MemoryIDs {
|
|
if mid == memoryID && d.Timestamp.After(lastRef) {
|
|
lastRef = d.Timestamp
|
|
}
|
|
}
|
|
}
|
|
if !lastRef.IsZero() {
|
|
days := time.Since(lastRef).Hours() / 24
|
|
v *= math.Exp(-vp.decayRate * days)
|
|
}
|
|
}
|
|
return math.Round(v*100) / 100
|
|
}
|
|
|
|
// GetDecisionChain 获取某条决策的因果链路
|
|
func (vp *VPropagator) GetDecisionChain(decisionID string) []*VDecision {
|
|
vp.mu.RLock()
|
|
defer vp.mu.RUnlock()
|
|
|
|
var chain []*VDecision
|
|
current := vp.decisions[decisionID]
|
|
for current != nil {
|
|
chain = append(chain, current)
|
|
if current.ParentID == "" {
|
|
break
|
|
}
|
|
current = vp.decisions[current.ParentID]
|
|
}
|
|
return chain
|
|
}
|
|
|
|
// ListRecentDecisions 列出最近 N 条决策
|
|
func (vp *VPropagator) ListRecentDecisions(n int) []*VDecision {
|
|
vp.mu.RLock()
|
|
defer vp.mu.RUnlock()
|
|
|
|
var all []*VDecision
|
|
for _, d := range vp.decisions {
|
|
all = append(all, d)
|
|
}
|
|
// 按时间降序简单排列
|
|
sortByTime(all)
|
|
if len(all) > n {
|
|
all = all[:n]
|
|
}
|
|
return all
|
|
}
|
|
|
|
func sortByTime(decisions []*VDecision) {
|
|
for i := 0; i < len(decisions); i++ {
|
|
for j := i + 1; j < len(decisions); j++ {
|
|
if decisions[j].Timestamp.After(decisions[i].Timestamp) {
|
|
decisions[i], decisions[j] = decisions[j], decisions[i]
|
|
}
|
|
}
|
|
}
|
|
}
|