一、整体架构
┌─────────────────────────────────────────────────────────────────────┐
│ Agent 记忆系统总体架构 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ 用户请求 │ │
│ └────────────────────────┬────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ MemoryManager (入口) │ │
│ │ • 统一读写接口 Save/Search/Forget │ │
│ │ • 自动路由到对应层级 │ │
│ │ • 读写策略(同步/异步/批量) │ │
│ └──────┬──────────────┬──────────────┬───────────────────────┘ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌──────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Working │ │ Episodic │ │ Semantic │ │
│ │ Memory │ │ Memory │ │ Memory │ │
│ └────┬─────┘ └──────┬───────┘ └──────┬───────┘ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌──────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ 内存 │ │ 向量数据库 │ │ 知识图谱 │ │
│ │ RingBuf │ │ Chroma/Pine │ │ Neo4j/Nebula │ │
│ └──────────┘ └──────────────┘ └──────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ 辅助组件 │ │
│ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │
│ │ │Embedding │ │ 压缩 │ │ 遗忘 │ │ 重要性 │ │ │
│ │ │ 服务 │ │ Compress │ │ Forget │ │ Scorer │ │ │
│ │ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────┘
二、核心数据结构
2.1 统一记忆条目
// memory/types.go
package memory
import (
"time"
"encoding/json"
)
// MemoryEntry 是所有记忆层的统一数据单元
type MemoryEntry struct {
ID string `json:"id"` // 唯一标识 UUID
UserID string `json:"user_id"` // 所属用户
SessionID string `json:"session_id"` // 所属会话
Type MemoryType `json:"type"` // 记忆类型
Layer MemoryLayer `json:"layer"` // 所属层级
Content string `json:"content"` // 记忆内容(文本)
Metadata map[string]interface{} `json:"metadata"` // 元数据
Importance float64 `json:"importance"` // 重要性 0-1
Embedding []float64 `json:"-"` // 向量(不序列化到 JSON)
CreatedAt time.Time `json:"created_at"`
AccessedAt time.Time `json:"accessed_at"` // 最后访问时间
ExpiresAt *time.Time `json:"expires_at"` // 过期时间(nil 表示永不过期)
Version int `json:"version"` // 版本号,用于冲突处理
}
// MemoryType 记忆类型枚举
type MemoryType string
const (
MemoryTypeConversation MemoryType = "conversation" // 对话记录
MemoryTypePreference MemoryType = "preference" // 用户偏好
MemoryTypeFact MemoryType = "fact" // 事实性知识
MemoryTypeDecision MemoryType = "decision" // 决策记录
MemoryTypeFeedback MemoryType = "feedback" // 反馈结果
MemoryTypeTask MemoryType = "task" // 任务状态
MemoryTypeSummary MemoryType = "summary" // 摘要
)
// MemoryLayer 记忆层级
type MemoryLayer string
const (
LayerWorking MemoryLayer = "working"
LayerEpisodic MemoryLayer = "episodic"
LayerSemantic MemoryLayer = "semantic"
)
// MemoryEntryOption 创建条目的可选参数
type MemoryEntryOption func(*MemoryEntry)
func WithImportance(score float64) MemoryEntryOption {
return func(e *MemoryEntry) {
e.Importance = score
}
}
func WithMetadata(key string, value interface{}) MemoryEntryOption {
return func(e *MemoryEntry) {
if e.Metadata == nil {
e.Metadata = make(map[string]interface{})
}
e.Metadata[key] = value
}
}
func WithTTL(d time.Duration) MemoryEntryOption {
return func(e *MemoryEntry) {
t := time.Now().Add(d)
e.ExpiresAt = &t
}
}
func NewMemoryEntry(userID, sessionID string, mtype MemoryType,
layer MemoryLayer, content string, opts ...MemoryEntryOption) *MemoryEntry {
entry := &MemoryEntry{
ID: generateUUID(),
UserID: userID,
SessionID: sessionID,
Type: mtype,
Layer: layer,
Content: content,
Metadata: make(map[string]interface{}),
Importance: 0.5, // 默认中等重要性
CreatedAt: time.Now(),
AccessedAt: time.Now(),
Version: 1,
}
for _, opt := range opts {
opt(entry)
}
return entry
}
2.2 检索请求与响应
// memory/types_search.go
package memory
import "time"
// SearchRequest 统一的检索请求
type SearchRequest struct {
Query string // 查询文本
UserID string // 限定用户(空=不限定)
SessionID string // 限定会话(空=不限定)
Types []MemoryType // 限定记忆类型(空=不限)
Layers []MemoryLayer // 限定层级(空=全部)
TopK int // 返回条数上限
MinScore float64 // 最低相似度阈值
TimeRange *TimeRange // 时间范围过滤
Importance *float64 // 最低重要性阈值
IncludeMeta bool // 是否返回元数据
}
type TimeRange struct {
Start time.Time
End time.Time
}
// SearchResult 检索结果
type SearchResult struct {
Entry *MemoryEntry
Score float64 // 综合得分(相似度 + 重要性 + 时间衰减)
Similarity float64 // 语义相似度
Relevancy float64 // 相关性得分
}
// SearchResponse 检索响应
type SearchResponse struct {
Results []*SearchResult
TotalCount int
QueryTime time.Duration
SourceLayers map[MemoryLayer]int // 各层级命中的数量
}
三、核心接口设计
3.1 MemoryStore 接口(底层存储抽象)
// memory/store.go
package memory
import (
"context"
"time"
)
// MemoryStore 底层存储接口
// 不同的记忆层级可以实现各自的 Store
type MemoryStore interface {
// 写入
Save(ctx context.Context, entry *MemoryEntry) error
BatchSave(ctx context.Context, entries []*MemoryEntry) error
// 读取
Get(ctx context.Context, id string) (*MemoryEntry, error)
GetByUser(ctx context.Context, userID string, limit, offset int) ([]*MemoryEntry, error)
// 检索
Search(ctx context.Context, req *SearchRequest) (*SearchResponse, error)
// 更新
Update(ctx context.Context, entry *MemoryEntry) error
// 删除
Delete(ctx context.Context, id string) error
DeleteByUser(ctx context.Context, userID string) error
DeleteExpired(ctx context.Context) (int, error) // 返回删除条数
// 维护
Count(ctx context.Context, userID string) (int, error)
Close() error
}
// MemoryStoreFactory 工厂方法
type MemoryStoreFactory func(config map[string]interface{}) (MemoryStore, error)
3.2 MemoryManager 接口(统一入口)
// memory/manager.go
package memory
import (
"context"
"fmt"
"sync"
"time"
"log"
)
// MemoryManager 记忆管理器------对外统一入口
type MemoryManager struct {
working *WorkingMemory
episodic *EpisodicMemory
semantic *SemanticMemory
embedding EmbeddingService
scorer ImportanceScorer
compressor MemoryCompressor
forgetter ForgetStrategy
config ManagerConfig
stats ManagerStats
mu sync.RWMutex
}
type ManagerConfig struct {
EnableWorking bool `yaml:"enable_working"`
EnableEpisodic bool `yaml:"enable_episodic"`
EnableSemantic bool `yaml:"enable_semantic"`
// 工作记忆配置
WorkingMemorySize int `yaml:"working_memory_size"` // 保留最近 N 轮
// 写入策略
WriteMode WriteMode `yaml:"write_mode"` // sync/async/batch
AsyncBufferSize int `yaml:"async_buffer_size"`
FlushInterval time.Duration `yaml:"flush_interval"`
// 检索策略
DefaultTopK int `yaml:"default_top_k"`
MinSimilarity float64 `yaml:"min_similarity"`
TimeDecayFactor float64 `yaml:"time_decay_factor"` // 时间衰减系数
}
type WriteMode string
const (
WriteModeSync WriteMode = "sync"
WriteModeAsync WriteMode = "async"
WriteModeBatch WriteMode = "batch"
)
type ManagerStats struct {
TotalSaves int64
TotalSearches int64
TotalForgets int64
AvgSaveLatency time.Duration
AvgSearchLatency time.Duration
CacheHitRate float64
}
// NewMemoryManager 创建记忆管理器
func NewMemoryManager(
working *WorkingMemory,
episodic *EpisodicMemory,
semantic *SemanticMemory,
embedding EmbeddingService,
scorer ImportanceScorer,
compressor MemoryCompressor,
forgetter ForgetStrategy,
config ManagerConfig,
) *MemoryManager {
mm := &MemoryManager{
working: working,
episodic: episodic,
semantic: semantic,
embedding: embedding,
scorer: scorer,
compressor: compressor,
forgetter: forgetter,
config: config,
}
// 如果启用异步写入,启动后台协程
if config.WriteMode == WriteModeAsync || config.WriteMode == WriteModeBatch {
go mm.asyncFlushLoop()
}
// 定期清理过期记忆
go mm.expirationCleanupLoop()
return mm
}
// Save 保存一条记忆(自动路由到对应层级)
func (mm *MemoryManager) Save(ctx context.Context, entry *MemoryEntry) error {
start := time.Now()
defer func() {
mm.mu.Lock()
mm.stats.TotalSaves++
mm.stats.AvgSaveLatency = (mm.stats.AvgSaveLatency + time.Since(start)) / 2
mm.mu.Unlock()
}()
// 1. 计算重要性
importance, err := mm.scorer.Score(ctx, entry)
if err != nil {
log.Printf("[MEMORY] Importance scoring failed: %v", err)
importance = 0.5 // fallback
}
entry.Importance = importance
// 2. 根据重要性和配置决定写入哪些层级
switch {
case importance > 0.8:
// 高重要性 → 写入所有层级
return mm.saveToAllLayers(ctx, entry)
case importance > 0.4:
// 中等重要性 → 写入情景记忆
return mm.saveToEpisodic(ctx, entry)
default:
// 低重要性 → 仅工作记忆
return mm.saveToWorking(ctx, entry)
}
}
// Search 检索记忆(多层级融合)
func (mm *MemoryManager) Search(ctx context.Context, req *SearchRequest) (*SearchResponse, error) {
start := time.Now()
defer func() {
mm.mu.Lock()
mm.stats.TotalSearches++
mm.stats.AvgSearchLatency = (mm.stats.AvgSearchLatency + time.Since(start)) / 2
mm.mu.Unlock()
}()
response := &SearchResponse{
Results: make([]*SearchResult, 0),
SourceLayers: make(map[MemoryLayer]int),
}
// 1. 并行检索各层级
type layerResult struct {
layer MemoryLayer
results []*SearchResult
err error
}
resultCh := make(chan layerResult, 3)
var wg sync.WaitGroup
// 检查是否需要检索工作记忆
if mm.config.EnableWorking && containsLayer(req.Layers, LayerWorking) {
wg.Add(1)
go func() {
defer wg.Done()
results, err := mm.working.Search(ctx, req)
resultCh <- layerResult{LayerWorking, results, err}
}()
}
if mm.config.EnableEpisodic && containsLayer(req.Layers, LayerEpisodic) {
wg.Add(1)
go func() {
defer wg.Done()
results, err := mm.episodic.Search(ctx, req)
resultCh <- layerResult{LayerEpisodic, results, err}
}()
}
if mm.config.EnableSemantic && containsLayer(req.Layers, LayerSemantic) {
wg.Add(1)
go func() {
defer wg.Done()
results, err := mm.semantic.Search(ctx, req)
resultCh <- layerResult{LayerSemantic, results, err}
}()
}
// 等待所有检索完成
go func() {
wg.Wait()
close(resultCh)
}()
// 2. 收集并融合结果
seen := make(map[string]bool) // 去重
for res := range resultCh {
if res.err != nil {
log.Printf("[MEMORY] Search error in %s: %v", res.layer, res.err)
continue
}
for _, r := range res.results {
if r.Entry == nil || seen[r.Entry.ID] {
continue
}
seen[r.Entry.ID] = true
// 应用时间衰减
r.Score = mm.applyTimeDecay(r)
response.Results = append(response.Results, r)
response.SourceLayers[res.layer]++
}
}
// 3. 按综合得分排序
sortSearchResults(response.Results)
// 4. 截断到 TopK
if len(response.Results) > req.TopK {
response.Results = response.Results[:req.TopK]
}
response.TotalCount = len(response.Results)
response.QueryTime = time.Since(start)
return response, nil
}
// Remember 便捷方法:保存一条记忆
func (mm *MemoryManager) Remember(ctx context.Context, userID, sessionID string,
content string, mtype MemoryType, opts ...MemoryEntryOption) error {
entry := NewMemoryEntry(userID, sessionID, mtype, "", content, opts...)
return mm.Save(ctx, entry)
}
// Recall 便捷方法:检索记忆
func (mm *MemoryManager) Recall(ctx context.Context, query string, userID string,
topK int) ([]*MemoryEntry, error) {
req := &SearchRequest{
Query: query,
UserID: userID,
TopK: topK,
Layers: []MemoryLayer{LayerWorking, LayerEpisodic, LayerSemantic},
}
resp, err := mm.Search(ctx, req)
if err != nil {
return nil, err
}
entries := make([]*MemoryEntry, len(resp.Results))
for i, r := range resp.Results {
entries[i] = r.Entry
}
return entries, nil
}
// Forget 遗忘记忆
func (mm *MemoryManager) Forget(ctx context.Context, id string) error {
mm.mu.Lock()
mm.stats.TotalForgets++
mm.mu.Unlock()
var lastErr error
if mm.config.EnableWorking {
if err := mm.working.Delete(ctx, id); err != nil {
lastErr = err
}
}
if mm.config.EnableEpisodic {
if err := mm.episodic.Delete(ctx, id); err != nil {
lastErr = err
}
}
if mm.config.EnableSemantic {
if err := mm.semantic.Delete(ctx, id); err != nil {
lastErr = err
}
}
return lastErr
}
// Stats 获取统计信息
func (mm *MemoryManager) Stats() ManagerStats {
mm.mu.RLock()
defer mm.mu.RUnlock()
return mm.stats
}
// 内部方法
func (mm *MemoryManager) saveToAllLayers(ctx context.Context, entry *MemoryEntry) error {
// 先计算 embedding
emb, err := mm.embedding.Embed(ctx, entry.Content)
if err != nil {
return fmt.Errorf("embedding: %w", err)
}
entry.Embedding = emb
// 并行写入各层级
var wg sync.WaitGroup
errCh := make(chan error, 3)
if mm.config.EnableWorking {
wg.Add(1)
go func() {
defer wg.Done()
if err := mm.working.Save(ctx, entry); err != nil {
errCh <- fmt.Errorf("working: %w", err)
}
}()
}
if mm.config.EnableEpisodic {
wg.Add(1)
go func() {
defer wg.Done()
if err := mm.episodic.Save(ctx, entry); err != nil {
errCh <- fmt.Errorf("episodic: %w", err)
}
}()
}
if mm.config.EnableSemantic {
wg.Add(1)
go func() {
defer wg.Done()
if err := mm.semantic.Save(ctx, entry); err != nil {
errCh <- fmt.Errorf("semantic: %w", err)
}
}()
}
wg.Wait()
close(errCh)
// 收集第一个错误
for err := range errCh {
return err // 返回第一个错误
}
return nil
}
func (mm *MemoryManager) saveToEpisodic(ctx context.Context, entry *MemoryEntry) error {
emb, err := mm.embedding.Embed(ctx, entry.Content)
if err != nil {
return fmt.Errorf("embedding: %w", err)
}
entry.Embedding = emb
return mm.episodic.Save(ctx, entry)
}
func (mm *MemoryManager) saveToWorking(ctx context.Context, entry *MemoryEntry) error {
return mm.working.Save(ctx, entry)
}
func (mm *MemoryManager) applyTimeDecay(result *SearchResult) float64 {
if mm.config.TimeDecayFactor <= 0 {
return result.Score
}
hoursSinceAccess := time.Since(result.Entry.AccessedAt).Hours()
decay := 1.0 / (1.0 + mm.config.TimeDecayFactor*hoursSinceAccess)
return result.Score * decay
}
func (mm *MemoryManager) asyncFlushLoop() {
// 异步/批量写入的后台循环
ticker := time.NewTicker(mm.config.FlushInterval)
defer ticker.Stop()
buffer := make([]*MemoryEntry, 0, mm.config.AsyncBufferSize)
for range ticker.C {
if len(buffer) == 0 {
continue
}
// 批量写入情景记忆
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
if err := mm.episodic.BatchSave(ctx, buffer); err != nil {
log.Printf("[MEMORY] Async flush failed: %v", err)
}
cancel()
buffer = buffer[:0]
}
}
func (mm *MemoryManager) expirationCleanupLoop() {
ticker := time.NewTicker(1 * time.Hour)
defer ticker.Stop()
for range ticker.C {
ctx := context.Background()
if mm.config.EnableEpisodic {
deleted, err := mm.episodic.DeleteExpired(ctx)
if err != nil {
log.Printf("[MEMORY] Episodic cleanup: %v", err)
} else if deleted > 0 {
log.Printf("[MEMORY] Cleaned %d expired episodic memories", deleted)
}
}
if mm.config.EnableSemantic {
deleted, err := mm.semantic.DeleteExpired(ctx)
if err != nil {
log.Printf("[MEMORY] Semantic cleanup: %v", err)
} else if deleted > 0 {
log.Printf("[MEMORY] Cleaned %d expired semantic memories", deleted)
}
}
}
}
// 辅助函数
func containsLayer(layers []MemoryLayer, target MemoryLayer) bool {
if len(layers) == 0 {
return true // 空列表表示全部
}
for _, l := range layers {
if l == target {
return true
}
}
return false
}
func sortSearchResults(results []*SearchResult) {
// 按 Score 降序排列
for i := 0; i < len(results); i++ {
for j := i + 1; j < len(results); j++ {
if results[j].Score > results[i].Score {
results[i], results[j] = results[j], results[i]
}
}
}
}
四、辅助服务接口
// memory/services.go
package memory
import "context"
// EmbeddingService 向量化服务
type EmbeddingService interface {
Embed(ctx context.Context, text string) ([]float64, error)
EmbedBatch(ctx context.Context, texts []string) ([][]float64, error)
Dimensions() int
}
// ImportanceScorer 重要性评分器
type ImportanceScorer interface {
Score(ctx context.Context, entry *MemoryEntry) (float64, error)
}
// MemoryCompressor 记忆压缩器
type MemoryCompressor interface {
// Compress 压缩多条记忆为一条摘要
Compress(ctx context.Context, entries []*MemoryEntry) (*MemoryEntry, error)
// ShouldCompress 判断是否需要压缩
ShouldCompress(entries []*MemoryEntry) bool
}
// ForgetStrategy 遗忘策略
type ForgetStrategy interface {
// SelectCandidates 选择需要遗忘的记忆
SelectCandidates(ctx context.Context, store MemoryStore, count int) ([]*MemoryEntry, error)
// Forget 执行遗忘
Forget(ctx context.Context, store MemoryStore, entries []*MemoryEntry) error
}
五、工作记忆实现(示例)
// memory/working.go
package memory
import (
"container/ring"
"context"
"sync"
)
// WorkingMemory 工作记忆------基于环形缓冲区的短期记忆
type WorkingMemory struct {
ring *ring.Ring // 环形缓冲区
index map[string]*ring.Ring // ID 到节点的快速索引
mu sync.RWMutex
config WorkingConfig
}
type WorkingConfig struct {
Size int `yaml:"size"` // 最大条目数
}
func NewWorkingMemory(config WorkingConfig) *WorkingMemory {
return &WorkingMemory{
ring: ring.New(config.Size),
index: make(map[string]*ring.Ring),
config: config,
}
}
func (wm *WorkingMemory) Save(ctx context.Context, entry *MemoryEntry) error {
wm.mu.Lock()
defer wm.mu.Unlock()
// 如果存在旧条目,覆盖
if node, exists := wm.index[entry.ID]; exists {
node.Value = entry
return nil
}
// 如果当前节点已有值,从索引中移除旧值
if old, ok := wm.ring.Value.(*MemoryEntry); ok {
delete(wm.index, old.ID)
}
// 写入新值
wm.ring.Value = entry
wm.index[entry.ID] = wm.ring
wm.ring = wm.ring.Next()
return nil
}
func (wm *WorkingMemory) Search(ctx context.Context, req *SearchRequest) ([]*SearchResult, error) {
wm.mu.RLock()
defer wm.mu.RUnlock()
var results []*SearchResult
wm.ring.Do(func(v interface{}) {
if v == nil {
return
}
entry, ok := v.(*MemoryEntry)
if !ok {
return
}
// 过滤条件
if req.UserID != "" && entry.UserID != req.UserID {
return
}
if req.SessionID != "" && entry.SessionID != req.SessionID {
return
}
if len(req.Types) > 0 && !containsType(req.Types, entry.Type) {
return
}
// 简单的关键词匹配(工作记忆不需要语义检索)
score := simpleMatchScore(req.Query, entry.Content)
if score < req.MinScore {
return
}
results = append(results, &SearchResult{
Entry: entry,
Score: score,
})
})
return results, nil
}
func (wm *WorkingMemory) Get(ctx context.Context, id string) (*MemoryEntry, error) {
wm.mu.RLock()
defer wm.mu.RUnlock()
if node, exists := wm.index[id]; exists {
if entry, ok := node.Value.(*MemoryEntry); ok {
return entry, nil
}
}
return nil, nil
}
func (wm *WorkingMemory) Delete(ctx context.Context, id string) error {
wm.mu.Lock()
defer wm.mu.Unlock()
if node, exists := wm.index[id]; exists {
node.Value = nil
delete(wm.index, id)
}
return nil
}
func (wm *WorkingMemory) DeleteExpired(ctx context.Context) (int, error) {
// 工作记忆不过期,由环形缓冲区自动覆盖
return 0, nil
}
func (wm *WorkingMemory) Count(ctx context.Context, userID string) (int, error) {
wm.mu.RLock()
defer wm.mu.RUnlock()
return len(wm.index), nil
}
func (wm *WorkingMemory) Close() error {
wm.mu.Lock()
defer wm.mu.Unlock()
wm.index = nil
wm.ring = nil
return nil
}
// 辅助函数
func containsType(types []MemoryType, target MemoryType) bool {
for _, t := range types {
if t == target {
return true
}
}
return false
}
func simpleMatchScore(query, content string) float64 {
if query == "" {
return 0.5 // 无查询时返回中等分数
}
// 简单的子串匹配
// 生产环境中应使用 TF-IDF 或 BM25
if len(query) > len(content) {
return 0
}
// 计算重叠字符比例
matchCount := 0
for _, c := range query {
for _, d := range content {
if c == d {
matchCount++
break
}
}
}
return float64(matchCount) / float64(len(query))
}
六、生产坑位
┌─────────────────────────────────────────────────────────────┐
│ 第1讲 生产坑位汇总 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 坑位 1:写入放大问题 │
│ • 现象:一条记忆写入触发多次 embedding + 多层存储 │
│ • 后果:高并发下 embedding 服务成为瓶颈 │
│ • 解决: │
│ - embedding 结果本地缓存(相同内容不重复计算) │
│ - 写入采用批量 + 异步模式 │
│ - 重要性 < 0.4 的记忆跳过 embedding │
│ │
│ 坑位 2:检索结果过多导致 Prompt 超长 │
│ • 现象:一次检索返回 50 条记忆,全部塞入 Prompt │
│ • 后果:Token 消耗暴增,LLM 注意力分散 │
│ • 解决: │
│ - 严格控制 TopK(建议 3-5 条) │
│ - 结果重排序,只保留最相关的 │
│ - 设置单条内容最大长度(如 500 chars) │
│ │
│ 坑位 3:记忆冲突与一致性 │
│ • 现象:用户在不同时间给出矛盾信息 │
│ • 后果:Agent 产生混乱的回答 │
│ • 解决: │
│ - 版本号递增,保留历史版本 │
│ - 冲突检测:同类型同主题的记忆超过 2 条标记为冲突 │
│ - 引入置信度字段,新信息的置信度初始较低 │
│ │
│ 坑位 4:冷启动问题 │
│ • 现象:新用户没有任何历史记忆 │
│ • 后果:检索为空,记忆系统形同虚设 │
│ • 解决: │
│ - 预设通用知识库作为种子记忆 │
│ - 前几轮对话强制记录,快速积累 │
│ - 利用用户画像(地区/行业/角色)生成初始偏好 │
│ │
│ 坑位 5:遗忘策略过于激进 │
│ • 现象:用户几天没来,记忆被清理 │
│ • 后果:用户回来发现 Agent 不认识自己了 │
│ • 解决: │
│ - 区分短期遗忘和长期遗忘 │
│ - 高重要性(>0.8)的记忆永不自动删除 │
│ - 用户活跃度影响遗忘阈值 │
│ │
└─────────────────────────────────────────────────────────────┘
七、评测指标
┌─────────────────────────────────────────────────────────────┐
│ 第1讲 评测指标体系 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 1. 功能正确性 │
│ ├─ Save/Search/Forget 基本流程通过率:≥ 99% │
│ ├─ 多层级检索结果融合正确率:≥ 95% │
│ └─ 重要性路由准确率:≥ 90% │
│ │
│ 2. 性能指标 │
│ ├─ Save P99 延迟:≤ 50ms(同步)/ ≤ 200ms(含 embedding) │
│ ├─ Search P99 延迟:≤ 100ms │
│ ├─ 吞吐量:≥ 1000 ops/s(单节点) │
│ └─ 内存占用:工作记忆 10K 条目 ≤ 50MB │
│ │
│ 3. 检索质量 │
│ ├─ 命中率(用户期望的记忆能被找到):≥ 85% │
│ ├─ 精确率(检索结果的相关性):≥ 80% │
│ ├─ 召回率(相关记忆被找到的比例):≥ 75% │
│ └─ MRR(Mean Reciprocal Rank):≥ 0.7 │
│ │
│ 4. 稳定性 │
│ ├─ 连续运行 72h 无内存泄漏 │
│ ├─ 并发 100 请求下无死锁 │
│ ├─ 异常输入(空内容/超长文本)不崩溃 │
│ └─ embedding 服务中断时有降级策略 │
│ │
└─────────────────────────────────────────────────────────────┘
八、目录结构
memory/
├── types.go # 核心数据类型(MemoryEntry, SearchRequest 等)
├── types_search.go # 检索相关类型
├── manager.go # MemoryManager 统一入口
├── store.go # MemoryStore 接口定义
├── services.go # 辅助服务接口(Embedding, Scorer 等)
├── working.go # 工作记忆实现
├── episodic.go # 情景记忆接口(具体实现在后续讲)
├── semantic.go # 语义记忆接口(具体实现在后续讲)
├── errors.go # 错误定义
├── utils.go # 工具函数(UUID, 排序等)
│
├── impl/
│ ├── memory/
│ │ ├── chroma.go # Chroma 向量数据库适配
│ │ ├── pinecone.go # Pinecone 适配
│ │ └── memory.go # 内存版(测试用)
│ ├── embedding/
│ │ ├── openai.go # OpenAI Embedding
│ │ ├── local.go # 本地模型(BGE)
│ │ └── cache.go # 嵌入缓存
│ ├── scorer/
│ │ ├── basic.go # 基于规则的评分
│ │ └── ml.go # 基于模型的评分
│ └── compress/
│ └── summarizer.go # LLM 摘要压缩
│
├── config/
│ └── defaults.go # 默认配置
│
└── examples/
└── basic_test.go # 基本使用示例
九、本讲总结
┌─────────────────────────────────────────────────────────────┐
│ 第1讲 核心收获 │
│ │
│ ✅ 设计了记忆系统三层架构: │
│ - 工作记忆(短期、内存、自动淘汰) │
│ - 情景记忆(中期、向量数据库、语义检索) │
│ - 语义记忆(长期、知识图谱、关系推理) │
│ │
│ ✅ 定义了统一的数据模型: │
│ - MemoryEntry 作为所有层级的通用数据单元 │
│ - 包含 ID/用户/内容/重要性/向量/时间戳等字段 │
│ - 支持扩展元数据和版本控制 │
│ │
│ ✅ 实现了 MemoryManager 统一入口: │
│ - Save 自动根据重要性路由到合适层级 │
│ - Search 并行检索各层级并融合结果 │
│ - 支持同步/异步/批量三种写入模式 │
│ - 内置时间衰减和过期清理 │
│ │
│ ✅ 实现了工作记忆(WorkingMemory): │
│ - 基于环形缓冲区,O(1) 写入 │
│ - 自动淘汰旧条目 │
│ - 支持快速 ID 索引 │
│ │
│ ✅ 识别了 5 个生产坑位及解决方案 │
│ │
└─────────────────────────────────────────────────────────────┘
十、下一讲预告
第2讲:《情景记忆------向量数据库集成与语义检索》
你将学到:
- Chroma/Pinecone 向量数据库适配
- 文本向量化与索引构建
- 多路召回(向量 + 关键词 + 时间)
- 结果重排序与融合
- 缓存策略降低延迟
🧰 开发之余的小工具推荐
在调试记忆系统时,经常需要查看向量数据库的内容、测试 embedding 效果或可视化记忆之间的关系。zz365.top 提供在线 JSON 格式化工具、Base64 编解码器和文本对比工具,方便你在开发过程中快速处理数据格式问题。所有处理都在浏览器本地完成,无需担心数据泄露。