第1讲:记忆系统总体架构与核心接口设计

一、整体架构

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

相关推荐
海宇服务3 小时前
零信任架构实战:基于海宇网上踪迹欺诈行为识别构建自动化电商大促营销补贴风控网关
运维·人工智能·架构·自动化
时空节拍AI数字人3 小时前
AI 数字人技术架构拆解:从形象克隆到实时驱动的 5 层链路
人工智能·microsoft·3d·架构·语音识别
Lethehong4 小时前
启元 Q1 的控制中枢:输入源优先级仲裁机制拆解
架构·机器人·启元机器人·启元·启元ai
技术原点4 小时前
03 · 战略设计(下):限界上下文与上下文映射
架构
Thomas.Sir4 小时前
第35课:Spring Cloud 2025架构复盘、性能调优 & 面试核心总结
spring cloud·面试·架构
努力实干4 小时前
C++ 面向对象与工程架构
开发语言·c++·架构
aichitang20245 小时前
Meta Muse Spark Open:816B 巨型开放权重多模态 Agent 模型架构、部署实战与社区首测全解
大数据·人工智能·python·算法·ai·架构·spark
阡陌数智5 小时前
Anthropic Claude Fable 5.1 长周期 Agent 原生架构解析:工具调用链路稳定性与 API 聚合平台落地实战
人工智能·语言模型·自然语言处理·架构·推荐算法
艾莉丝努力练剑5 小时前
【AI大模型接入SDK】ChatServer接口实现
c++·人工智能·学习·ai·架构·大模型·sdk