feat(memory): P1 长期记忆升级 —— 异步攒批 Consolidate + 软删 + importance/last_seen
把"逐轮盲写抽取"升级为 Mem0 式对账(方案见 memory_industry_analysis.md 落地节):
mcp-go:
- Profile 加 Importance(1~10, poignancy) + LastSeenAt(为 Generative Agents 读路径
Score=w1·Relevance+w2·Recency+w3·Importance 铺路)。
- Upsert 收 importance + 每次置 last_seen(印证);新增 Delete(软删,BaseModel.DeletedAt
已具备,失效不物删可审计)+ Touch;memory_upsert 透传 importance、新增 memory_delete 工具。
dispatcher:
- extractMemory → consolidateMemory:一次 LLM 调用同时做 抽取+对账,输出
[{op:ADD|UPDATE|DELETE|NOOP,key,value,importance}];ADD/UPDATE→upsert、DELETE→软删;
sanitizeOps 防幻删(DELETE 须命中已有)/夹 importance[1,10]/同key保末个/丢 NOOP。
- 攒批:每 3 轮(per-session 计数)才 consolidate 一次,省成本,对齐 ChatGPT 周期整理。
从根上解决 exact-key 盲写的记忆腐烂。
验证:parseOps/sanitizeOps/parseProfile 纯逻辑单测;store 集成测试(真 PG)覆盖
importance/last_seen 写入 + 软删(live 0 / 物理 1);dispatcher -race 全过。
(注:完整多轮 LLM consolidate 未做实跑,属构造性验证 + 沿用已证 pool.Chat 模式。)
P2 待做:读路径按 Score(Recency+Importance) 排序/衰减/截断 + 桌面端记忆面板。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -6,6 +6,7 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/cloudwego/eino/schema"
|
||||
@@ -46,6 +47,9 @@ type Orchestrator struct {
|
||||
sink TokenSink
|
||||
tools ToolCaller
|
||||
exec ExecSink
|
||||
|
||||
turnMu sync.Mutex // 保护 turns(攒批计数,多任务 goroutine 共享)
|
||||
turns map[string]int // sessionID → 累计轮次,用于每 N 轮触发 consolidate
|
||||
}
|
||||
|
||||
// NewOrchestrator 持有依赖;图按任务的 DSL 在 Handle 内动态编译。
|
||||
@@ -169,8 +173,10 @@ func (o *Orchestrator) fetchHistory(ctx context.Context, sessionID string) []*sc
|
||||
return msgs
|
||||
}
|
||||
|
||||
// memorize 写回阶段:把本轮对话落进短期历史,并(TODO)抽取长期偏好记忆。
|
||||
// 异步执行,离开热路径。
|
||||
// consolidateEveryTurns 控制记忆对账的攒批节奏:每 N 轮 consolidate 一次(不逐轮,省成本)。
|
||||
const consolidateEveryTurns = 3
|
||||
|
||||
// memorize 写回阶段(异步、离热路径):落短期历史;每 N 轮做一次记忆对账(consolidate)。
|
||||
func (o *Orchestrator) memorize(t *contract.Task, answer string) {
|
||||
uid, _ := t.Meta[contract.MetaUserID].(string)
|
||||
sid, _ := t.Meta[contract.MetaSessionID].(string)
|
||||
@@ -179,9 +185,20 @@ func (o *Orchestrator) memorize(t *contract.Task, answer string) {
|
||||
o.appendHistory(sid, "assistant", answer)
|
||||
log.Printf("[eino] (writeback) task %s 已落会话历史 session=%s", t.ID, sid)
|
||||
}
|
||||
if uid != "" {
|
||||
// 从本轮对话抽取长期偏好 → 去重 → memory_upsert(离开热路径,已在 goroutine 内)。
|
||||
o.extractMemory(context.Background(), uid, dsl.Compile(t.Graph).Query, answer)
|
||||
if uid == "" || sid == "" || o.tools == nil {
|
||||
return
|
||||
}
|
||||
// 攒批:累计轮次,每 N 轮才把近期对话与已有画像交给 LLM 对账一次。
|
||||
o.turnMu.Lock()
|
||||
if o.turns == nil {
|
||||
o.turns = map[string]int{}
|
||||
}
|
||||
o.turns[sid]++
|
||||
n := o.turns[sid]
|
||||
o.turnMu.Unlock()
|
||||
if n%consolidateEveryTurns == 0 {
|
||||
ctx := context.Background()
|
||||
o.consolidateMemory(ctx, uid, o.fetchHistory(ctx, sid))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user