一、整体架构
┌─────────────────────────────────────────────────────────────────────┐ │ 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 编解码器和文本对比工具,方便你在开发过程中快速处理数据格式问题。所有处理都在浏览器本地完成,无需担心数据泄露。