orca.ai/thoughts/shared/designs/2025-05-11-orca-memory-final-design.md
2026-05-12 00:09:01 +08:00

17 KiB
Raw Blame History

date, topic, status
date topic status
2025-05-11 Orca 完整记忆系统 + Agent 进化方案 validated

Orca 完整记忆系统 + Agent 进化方案

一、系统架构总览

┌─────────────────────────────────────────────────────────────┐
│                        用户消息                              │
└──────────────────────┬──────────────────────────────────────┘
                       ▼
┌─────────────────────────────────────────────────────────────┐
│  智能注入层(规则驱动,零 LLM 开销)                            │
│  • 首轮对话 → 不带记忆                                        │
│  • 第 2+ 轮 → 自动注入短期记忆3条                          │
│  • 技术讨论 → 额外注入长期记忆2条                          │
└──────────────────────┬──────────────────────────────────────┘
                       ▼
┌─────────────────────────────────────────────────────────────┐
│  记忆查询层(按需加载)                                        │
│  • 短期记忆SQLite + 语义检索(缓存命中 130x 加速)           │
│  • 长期记忆SQLite + 独立向量索引vec_long_term_memories   │
└──────────────────────┬──────────────────────────────────────┘
                       ▼
┌─────────────────────────────────────────────────────────────┐
│  LLM Agent 处理消息                                          │
└──────────────────────┬──────────────────────────────────────┘
                       ▼
┌─────────────────────────────────────────────────────────────┐
│  记忆维护层(后台异步)                                        │
│  • 短期记忆:即时摘要保存(现有逻辑)                           │
│  • 长期记忆MemoryExtractorAgent 批量提取(每 5 轮一次)      │
└──────────────────────┬──────────────────────────────────────┘
                       ▼
┌─────────────────────────────────────────────────────────────┐
│  进化层(权重系统)                                           │
│  • 记忆被引用 → 权重 +1                                       │
│  • 记忆未引用 → 权重 -0.5                                     │
│  • 权重 < 0.3 → 归档                                         │
│  • 权重 > 5.0 → 核心记忆                                     │
└─────────────────────────────────────────────────────────────┘

二、配置架构config.toml

# ========== 主 LLM 配置 ==========
provider = "deepseek"

[deepseek]
base_url = "https://api.deepseek.com/v1"
model = "deepseek-v4-flash"
api_key = "sk-xxx"
timeout = "120s"

# ========== Embedding 配置 ==========
[embedding]
provider = "siliconflow"
model = "Pro/BAAI/bge-m3"
dimensions = 1024
max_context = 8192

[siliconflow]
api_key = "sk-xxx"
base_url = "https://api.siliconflow.cn/v1"

# ========== 记忆系统配置(新增) ==========
[memory]
enabled = true                    # 记忆系统总开关
max_history = 100                 # 工作记忆最大轮数

[memory.short_term]
max_items = 10                    # 最多保留 10 条短期记忆
compression_threshold = 200       # 超过 200 字自动压缩

[memory.long_term]
enabled = true                    # 长期记忆开关
vector_index = true               # 启用向量索引
max_return = 2                    # 每次最多返回 2 条
archive_threshold = 0.3           # 权重低于此值归档

[memory.inject]
first_round_empty = true          # 首轮不带记忆
short_term_count = 3              # 默认注入 3 条短期记忆
long_term_trigger = "technical"   # 技术讨论触发长期记忆
min_query_length = 10             # query 最短长度才查长期记忆

# ========== MemoryExtractorAgent 配置(新增) ==========
[memory_agent]
enabled = true
provider = "deepseek"             # 可独立配置,默认继承主 LLM
model = "deepseek-v4-flash"       # 可用便宜模型,如 deepseek-chat
api_key = ""                      # 留空继承 deepseek.api_key
base_url = ""                     # 留空继承 deepseek.base_url
timeout = "60s"

[memory_agent.extract]
batch_size = 5                    # 每 5 轮提取一次
max_facts = 10                    # 每次最多提取 10 个事实
min_confidence = 0.6              # 置信度阈值
auto_tag = true                   # 自动打标签

[memory_agent.summarize]
enabled = true                    # 启用对话总结
trigger_tokens = 4000             # 超过此 token 触发总结

三、Agent 描述文件

文件位置 ~/.orca/agents/memory_extractor.md

# Memory Extractor Agent

你是一个专门从对话中提取用户信息的 Agent。你的工作是将非结构化的对话转化为结构化的长期记忆。

## 任务

分析给定的对话记录,提取以下类型的信息:

1. **事实 (fact)**:客观信息
   - 工作:公司、职位、技术栈、行业
   - 技术:擅长语言、框架偏好、架构经验
   - 个人:教育背景、所在城市(仅用户明确提及)

2. **偏好 (preference)**:主观倾向
   - 回答风格:简洁/详细/代码示例/架构图
   - 技术偏好:语言、数据库、部署方式
   - 沟通偏好:正式/ casual

3. **项目 (project)**:当前工作
   - 项目名称、技术方案、当前阶段、遇到的挑战

## 输出格式

只输出 JSON不要任何解释

```json
{
  "facts": [
    {
      "content": "用户在电商公司担任后端工程师",
      "type": "fact",
      "tags": ["工作", "后端", "电商"],
      "confidence": 0.95,
      "replace": null
    },
    {
      "content": "用户偏好简洁的技术回答,不要过多解释",
      "type": "preference",
      "tags": ["沟通风格", "偏好"],
      "confidence": 0.85,
      "replace": "用户喜欢详细的回答"
    }
  ]
}

规则

  • confidence < 0.6 的事实不输出
  • 如果新事实与旧事实冲突:
    • 在 replace 字段填入被替换的旧事实 content
    • 只替换同一 type + 同一 tags 的事实
  • 不猜测、不推断,只提取用户明确表达的信息
  • 标签从预设列表选择:工作、技术、偏好、项目、沟通风格、行业

---

## 四、数据表设计

### 现有表(已验证)

- `main_messages` — 工作记忆
- `short_term_memories` — 短期记忆
- `subagent_messages` — 子 Agent 对话

### 新增表

```sql
-- 长期记忆向量索引(核心新增)
CREATE VIRTUAL TABLE IF NOT EXISTS vec_long_term_memories USING vec0(
    memory_id INTEGER PRIMARY KEY,
    embedding FLOAT[1024]
);

-- 长期记忆主表(扩展)
CREATE TABLE IF NOT EXISTS long_term_memories (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    content TEXT NOT NULL UNIQUE,
    memory_type TEXT NOT NULL DEFAULT 'fact',  -- fact/preference/project
    tags TEXT,                                   -- JSON 数组 ["工作", "技术"]
    confidence REAL NOT NULL DEFAULT 0.8,
    weight REAL NOT NULL DEFAULT 1.0,            -- 动态权重
    access_count INTEGER NOT NULL DEFAULT 0,
    last_accessed DATETIME,
    created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    archived INTEGER NOT NULL DEFAULT 0          -- 0=活跃, 1=归档
);

-- 记忆使用日志(用于权重计算)
CREATE TABLE IF NOT EXISTS memory_usage_log (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    memory_id INTEGER NOT NULL,
    session_id TEXT NOT NULL,
    query TEXT NOT NULL,                         -- 用户原始 query
    was_referenced INTEGER NOT NULL DEFAULT 0,   -- 是否被 Agent 引用
    used_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
);

-- 对话缓冲(批量提取用)
CREATE TABLE IF NOT EXISTS dialogue_buffer (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    session_id TEXT NOT NULL,
    user_query TEXT NOT NULL,
    assistant_response TEXT NOT NULL,
    created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
);

五、核心流程设计

5.1 消息处理流程(动态注入)

func (a *LLMAgent) buildLLMMessages(query string) []llm.Message {
    messages := make([]llm.Message, 0)
    
    // 1. System prompt原有
    if a.systemPrompt != "" {
        messages = append(messages, llm.Message{Role: "system", Content: a.systemPrompt})
    }
    
    // 2. Tool prompt原有
    if toolPrompt := a.buildToolPrompt(); toolPrompt != "" {
        messages = append(messages, llm.Message{Role: "system", Content: toolPrompt})
    }
    
    // 3. 记忆注入(新增:智能判断)
    if shouldInjectMemory(query, a.sessionMgr.GetMessageCount(a.sessionID)) {
        ctx, stats := a.memoryManager.BuildMemoryContextWithStats(a.sessionID, query)
        if ctx != "" {
            messages = append(messages, llm.Message{Role: "system", Content: ctx})
            log.Printf("[memory] Injected: short=%d, long=%d, tokens=%d", 
                stats.ShortTermCount, stats.LongTermCount, stats.TotalTokens)
        }
    }
    
    // 4. 对话历史(原有)
    // ...
    
    return messages
}

func shouldInjectMemory(query string, msgCount int) bool {
    if msgCount == 0 {
        return false  // 首轮不带记忆
    }
    if len(query) < 10 {
        return false  // 太短不查
    }
    return true
}

5.2 记忆维护流程(批量提取)

func (mm *MemoryManager) MaintainSessionMemory(sessionID, userQuery, assistantResponse string) {
    // 1. 保存短期记忆(即时)
    summary := fmt.Sprintf("用户问:%s\n回答%s", 
        userQuery, truncateString(assistantResponse, 100))
    mm.AddShortTermMemory(sessionID, summary)
    
    // 2. 缓冲对话(用于批量提取)
    mm.bufferDialogue(sessionID, userQuery, assistantResponse)
    
    // 3. 检查是否触发批量提取
    if mm.shouldExtract() {
        mm.triggerExtraction(sessionID)
    }
}

func (mm *MemoryManager) triggerExtraction(sessionID string) {
    dialogues := mm.flushBuffer(sessionID)
    
    // 异步调用 MemoryExtractorAgent
    go func() {
        facts, err := mm.extractor.ExtractFacts(dialogues)
        if err != nil {
            log.Printf("[memory] Extraction failed: %v", err)
            return
        }
        
        for _, fact := range facts {
            if fact.Confidence >= mm.config.MinConfidence {
                mm.AddLongTermMemory(fact)
            }
        }
    }()
}

5.3 权重反馈流程(自动进化)

func (mm *MemoryManager) recordMemoryUsage(memoryID int64, sessionID, query string, referenced bool) {
    // 1. 记录使用日志
    mm.store.Exec(
        "INSERT INTO memory_usage_log (memory_id, session_id, query, was_referenced) VALUES (?, ?, ?, ?)",
        memoryID, sessionID, query, referenced,
    )
    
    // 2. 更新权重
    delta := 0.5
    if !referenced {
        delta = -0.3
    }
    
    mm.store.Exec(
        "UPDATE long_term_memories SET weight = weight + ?, access_count = access_count + 1, last_accessed = ? WHERE id = ?",
        delta, time.Now(), memoryID,
    )
    
    // 3. 检查归档
    mm.archiveLowWeightMemories()
}

六、MemoryExtractorAgent 实现

package actor

type MemoryExtractorAgent struct {
    *SubAgent
    config ExtractConfig
}

func NewMemoryExtractorAgent(id string, llmBackend llm.LLM, cfg ExtractConfig) *MemoryExtractorAgent {
    prompt := loadAgentPrompt("memory_extractor")  // 读取 ~/.orca/agents/memory_extractor.md
    
    sa := NewSubAgent(id, llmBackend,
        WithSubAgentRole("memory_extractor"),
        WithSubAgentSystemPrompt(prompt),
    )
    
    return &MemoryExtractorAgent{SubAgent: sa, config: cfg}
}

func (mea *MemoryExtractorAgent) ExtractFacts(dialogues []Dialogue) ([]Fact, error) {
    // 格式化对话为 prompt
    var sb strings.Builder
    sb.WriteString("请分析以下对话记录,提取用户的关键信息:\n\n")
    for i, d := range dialogues {
        sb.WriteString(fmt.Sprintf("--- 对话 %d ---\n", i+1))
        sb.WriteString(fmt.Sprintf("用户:%s\n", d.UserQuery))
        sb.WriteString(fmt.Sprintf("助手:%s\n\n", d.AssistantResponse))
    }
    
    msg := bus.Message{Type: bus.MsgTypeTaskRequest, Content: sb.String()}
    resp, err := mea.Process(context.Background(), msg)
    if err != nil {
        return nil, err
    }
    
    return parseFactJSON(resp.Content)
}

七、实施路线图

Phase 1: 基础记忆层1-2 天)

  • 扩展 long_term_memories 表(添加 weight, tags, archived 字段)
  • 创建 vec_long_term_memories 向量索引表
  • 创建 memory_usage_log 使用日志表
  • 创建 dialogue_buffer 对话缓冲表
  • 修改 config.toml 解析,支持 [memory][memory_agent]

Phase 2: 记忆注入层1-2 天)

  • 实现 shouldInjectMemory() 智能判断逻辑
  • 修改 buildLLMMessages() 注入记忆上下文
  • 实现短期记忆检索(现有逻辑优化)
  • 实现长期记忆向量检索(语义搜索)
  • 添加 Embedding 缓存层130x 加速)

Phase 3: MemoryExtractorAgent2-3 天)

  • 创建 ~/.orca/agents/memory_extractor.md 提示词文件
  • 实现 MemoryExtractorAgent 结构体
  • 实现 ExtractFacts() 批量提取逻辑
  • 实现对话缓冲和批量触发机制
  • 集成到 MaintainSessionMemory() 流程

Phase 4: 权重进化系统1-2 天)

  • 实现 recordMemoryUsage() 权重反馈
  • 实现记忆引用检测(判断 Agent 是否使用了某条记忆)
  • 实现自动归档逻辑(权重 < 0.3
  • 实现核心记忆标记(权重 > 5.0
  • 添加 orca memory CLI 命令list, stats, clean

Phase 5: 测试与优化1-2 天)

  • 单元测试:记忆检索、权重计算、事实提取
  • 集成测试:端到端对话流程
  • 性能测试Embedding 缓存命中率、向量检索延迟
  • 调优:权重阈值、提取频率、注入策略

八、预期效果

指标 目标值
记忆命中率 > 80%(相关查询能命中有效记忆)
Token 消耗 增加 < 15%(记忆注入的额外开销)
长期记忆检索 < 100ms向量搜索 + 缓存)
自动学习 每 5 轮对话自动提取 2-5 个事实
记忆质量 人工抽查 > 90% 准确
成本增加 Embedding API 调用 ≈ ¥0.01/千次

九、关键决策总结

决策 方案 理由
提取频率 每 5 轮批量提取 平衡实时性与 API 成本
提取模型 复用主 LLM可独立配置 降低复杂度,留优化空间
意图分类 规则驱动(首轮/长度/技术词) 零 LLM 开销,可预测
预加载 无,按需查询 避免无关记忆污染上下文
聚类 标签 + 类型,无自动聚类 人工可理解,可控
反馈机制 引用检测 + 权重调整 简单有效,可解释

评审意见处理

评审意见 处理方式
vec0 兼容性 已确认可用,保留原方案
Agent 演化系统 用户明确要求保留权重系统
异步处理 使用 goroutine 异步调用 MemoryExtractorAgent
记忆衰减 weight 字段 + archive_threshold 实现
Token 预算 通过 max_return 和 short_term_count 控制
降级策略 配置项 enabled = true/false 可完全关闭
混合检索 ⚠️ 当前纯向量检索,后续可添加 FTS5
嵌入模型版本化 ⚠️ 当前固定 bge-m3后续可扩展

附录

A. 嵌入模型配置

固定使用:硅基流动 Pro/BAAI/bge-m3

  • 维度1024
  • 最大上下文8192
  • 优势:中文优化,质量高

B. 权重计算示例

初始weight = 1.0
被引用 5 次1.0 + 5*0.5 = 3.5
未被引用 3 次3.5 - 3*0.3 = 2.6
超过 5.0 → 标记为核心记忆
低于 0.3 → 归档(不删除,可恢复)

C. 降级路径

Level 1: 向量检索 + 权重排序(正常)
Level 2: 纯 SQL 检索(向量服务故障)
Level 3: 仅短期记忆(长期记忆关闭)
Level 4: 无记忆memory.enabled = false