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

594 lines
15 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Orca 记忆系统实施计划
**基于设计文档**: `thoughts/shared/designs/2025-05-11-orca-memory-final-design.md`
**实施方式**: 增量升级(在已有 v1 基础上扩展)
**预估工期**: 5-7 天
---
## Phase 1: 数据库 Schema 扩展(第 1 天)
### 1.1 新增 `memory_usage_log` 表
**文件**: `pkg/session/sqlite_store.go``initSchema()` 方法
```go
// 在 initSchema() 中添加
schema += `
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,
was_referenced INTEGER NOT NULL DEFAULT 0,
used_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (memory_id) REFERENCES long_term_memories(id) ON DELETE CASCADE
);
CREATE INDEX IF NOT EXISTS idx_usage_memory ON memory_usage_log(memory_id);
CREATE INDEX IF NOT EXISTS idx_usage_session ON memory_usage_log(session_id);
`
```
**依赖**: 无
**测试**: 验证表创建成功,外键约束生效
---
### 1.2 新增 `dialogue_buffer` 表
**文件**: `pkg/session/sqlite_store.go``initSchema()` 方法
```go
// 在 initSchema() 中添加
schema += `
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
);
CREATE INDEX IF NOT EXISTS idx_buffer_session ON dialogue_buffer(session_id, created_at);
`
```
**依赖**: 无
**测试**: 验证表创建成功
---
### 1.3 扩展 `long_term_memories` 表
**文件**: `pkg/session/sqlite_store.go`
当前表缺少 `weight`, `tags`, `archived` 字段。由于是 ALTER TABLE需要小心处理
```go
// 在 initSchema() 中修改 long_term_memories 定义
// 或添加迁移逻辑
migrations := []string{
`ALTER TABLE long_term_memories ADD COLUMN weight REAL NOT NULL DEFAULT 1.0`,
`ALTER TABLE long_term_memories ADD COLUMN tags TEXT`,
`ALTER TABLE long_term_memories ADD COLUMN archived INTEGER NOT NULL DEFAULT 0`,
}
```
**注意**: SQLite 支持 ADD COLUMN但有限制不能加 UNIQUE / PRIMARY KEY
**依赖**: 无
**测试**: 验证迁移后数据完整性
---
## Phase 2: 配置扩展(第 1-2 天)
### 2.1 扩展 Config 结构体
**文件**: `internal/config/config.go`
```go
type Config struct {
// ... 现有字段 ...
Memory MemoryConfig // 新增
MemoryAgent MemoryAgentConfig // 新增
}
type MemoryConfig struct {
Enabled bool `toml:"enabled"`
MaxHistory int `toml:"max_history"`
ShortTerm ShortTermConfig `toml:"short_term"`
LongTerm LongTermConfig `toml:"long_term"`
Inject InjectConfig `toml:"inject"`
}
type ShortTermConfig struct {
MaxItems int `toml:"max_items"`
CompressionThreshold int `toml:"compression_threshold"`
}
type LongTermConfig struct {
Enabled bool `toml:"enabled"`
VectorIndex bool `toml:"vector_index"`
MaxReturn int `toml:"max_return"`
ArchiveThreshold float64 `toml:"archive_threshold"`
}
type InjectConfig struct {
FirstRoundEmpty bool `toml:"first_round_empty"`
ShortTermCount int `toml:"short_term_count"`
LongTermTrigger string `toml:"long_term_trigger"`
MinQueryLength int `toml:"min_query_length"`
}
type MemoryAgentConfig struct {
Enabled bool `toml:"enabled"`
Provider string `toml:"provider"`
Model string `toml:"model"`
APIKey string `toml:"api_key"`
BaseURL string `toml:"base_url"`
Timeout string `toml:"timeout"`
Extract ExtractConfig `toml:"extract"`
Summarize SummarizeConfig `toml:"summarize"`
}
type ExtractConfig struct {
BatchSize int `toml:"batch_size"`
MaxFacts int `toml:"max_facts"`
MinConfidence float64 `toml:"min_confidence"`
AutoTag bool `toml:"auto_tag"`
}
type SummarizeConfig struct {
Enabled bool `toml:"enabled"`
TriggerTokens int `toml:"trigger_tokens"`
}
```
**依赖**: Phase 1 完成
**测试**: 验证 TOML 解析正确
---
### 2.2 更新 config.toml.example
**文件**: `config.toml.example`
添加完整的 `[memory]``[memory_agent]` 示例配置。
**依赖**: 2.1 完成
---
## Phase 3: MemoryManager 扩展(第 2-3 天)
### 3.1 添加权重管理方法
**文件**: `pkg/session/memory_manager.go`
```go
// RecordMemoryUsage 记录记忆使用并更新权重
func (mm *MemoryManager) RecordMemoryUsage(memoryID int64, sessionID, query string, referenced bool) error
// UpdateMemoryWeight 更新单条记忆权重
func (mm *MemoryManager) UpdateMemoryWeight(memoryID int64, delta float64) error
// ArchiveLowWeightMemories 归档低权重记忆
func (mm *MemoryManager) ArchiveLowWeightMemories(threshold float64) (int, error)
// GetCoreMemories 获取核心记忆(高权重)
func (mm *MemoryManager) GetCoreMemories(minWeight float64) ([]LongTermMemory, error)
// GetArchivedMemories 获取已归档记忆
func (mm *MemoryManager) GetArchivedMemories() ([]LongTermMemory, error)
```
**依赖**: Phase 1, Phase 2
**测试**: 验证权重计算正确,归档逻辑生效
---
### 3.2 添加对话缓冲管理
**文件**: `pkg/session/memory_manager.go`
```go
// BufferDialogue 缓冲对话
func (mm *MemoryManager) BufferDialogue(sessionID, userQuery, assistantResponse string) error
// GetBufferedDialogues 获取缓冲的对话
func (mm *MemoryManager) GetBufferedDialogues(sessionID string, limit int) ([]Dialogue, error)
// ClearBuffer 清空缓冲
func (mm *MemoryManager) ClearBuffer(sessionID string) error
// ShouldExtract 检查是否触发提取
func (mm *MemoryManager) ShouldExtract(sessionID string) (bool, error)
```
**依赖**: Phase 1
**测试**: 验证缓冲写入、读取、清空
---
### 3.3 增强记忆注入逻辑
**文件**: `pkg/session/memory_manager.go`
```go
// ShouldInjectMemory 判断是否注入记忆
func (mm *MemoryManager) ShouldInjectMemory(query string, msgCount int) bool
// BuildMemoryContextWithStats 构建记忆上下文(增强版)
// 现有方法需要增强:支持 first_round_empty, min_query_length
```
修改 `BuildMemoryContextWithStats` 支持配置参数:
- `first_round_empty`: msgCount == 0 时返回空
- `min_query_length`: len(query) < threshold 时返回空
- `long_term_trigger`: 检测技术关键词
**依赖**: Phase 2
**测试**: 验证各种注入条件
---
### 3.4 添加长期记忆写入
**文件**: `pkg/session/memory_manager.go`
```go
// AddLongTermMemoryWithEmbedding 添加长期记忆并生成向量
func (mm *MemoryManager) AddLongTermMemoryWithEmbedding(content, memoryType string, tags []string, confidence float64) error
// UpdateLongTermMemory 更新长期记忆
func (mm *MemoryManager) UpdateLongTermMemory(id int64, updates map[string]interface{}) error
```
**依赖**: Phase 1
**测试**: 验证写入 + 向量生成
---
## Phase 4: MemoryExtractorAgent第 3-4 天)
### 4.1 创建 Agent Prompt 文件
**文件**: `~/.orca/agents/memory_extractor.md`
内容来自设计文档需要安装到用户目录
**文件**: `pkg/actor/memory_extractor_prompt.go`内嵌默认 prompt作为 fallback
```go
package actor
const DefaultMemoryExtractorPrompt = `...`
```
**依赖**:
**测试**: 验证 prompt 加载
---
### 4.2 实现 MemoryExtractorAgent
**文件**: `pkg/actor/memory_extractor_agent.go`
```go
package actor
type MemoryExtractorAgent struct {
*SubAgent
config MemoryAgentConfig
}
// NewMemoryExtractorAgent 创建提取 Agent
func NewMemoryExtractorAgent(id string, llmBackend llm.LLM, cfg MemoryAgentConfig) (*MemoryExtractorAgent, error)
// ExtractFacts 从对话中提取事实
func (mea *MemoryExtractorAgent) ExtractFacts(dialogues []Dialogue) ([]Fact, error)
// parseFactJSON 解析提取结果
func parseFactJSON(content string) ([]Fact, error)
```
**类型定义**:
```go
type Dialogue struct {
UserQuery string
AssistantResponse string
}
type Fact struct {
Content string
Type string // fact/preference/project
Tags []string
Confidence float64
Replace *string // 被替换的旧事实
}
```
**依赖**: Phase 2, Phase 3
**测试**: 验证提取逻辑JSON 解析
---
### 4.3 集成到 Kernel
**文件**: `pkg/kernel/kernel.go`
Kernel 初始化时创建 MemoryExtractorAgent
```go
// 在 NewWithConfig() 中
if cfg.MemoryAgent.Enabled {
kernel.memoryExtractor, err = actor.NewMemoryExtractorAgent(
"memory_extractor",
kernel.llmBackend,
cfg.MemoryAgent,
)
}
```
**依赖**: 4.2
**测试**: 验证 Kernel 启动
---
## Phase 5: LLMAgent 集成(第 4-5 天)
### 5.1 修改 buildLLMMessages
**文件**: `pkg/actor/llm_agent.go`
增强 `buildLLMMessages()` 方法
```go
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 a.memoryManager != nil {
msgCount := a.sessionMgr.GetMessageCount(a.sessionID)
if a.memoryManager.ShouldInjectMemory(query, msgCount) {
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
}
```
**依赖**: Phase 3
**测试**: 验证注入条件Token 预算
---
### 5.2 修改消息处理流程
**文件**: `pkg/actor/llm_agent.go`
在消息保存后添加记忆维护
```go
func (a *LLMAgent) handleUserMessage(ctx context.Context, msg Message) error {
// ... 原有逻辑 ...
// 保存消息(原有)
a.sessionMgr.SaveMessage(...)
// 记忆维护(新增)
if a.memoryManager != nil {
a.memoryManager.MaintainSessionMemory(a.sessionID, query, response)
}
return nil
}
```
**依赖**: Phase 3
**测试**: 验证 STM 生成缓冲写入
---
### 5.3 添加异步提取触发
**文件**: `pkg/actor/llm_agent.go` `pkg/session/memory_manager.go`
```go
// triggerExtraction 异步触发事实提取
func (mm *MemoryManager) triggerExtraction(sessionID string) {
dialogues, _ := mm.GetBufferedDialogues(sessionID, mm.config.Extract.BatchSize)
go func() {
// 调用 MemoryExtractorAgent
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.Extract.MinConfidence {
mm.AddLongTermMemoryWithEmbedding(...)
}
}
mm.ClearBuffer(sessionID)
}()
}
```
**依赖**: Phase 4
**测试**: 验证异步执行错误处理
---
## Phase 6: 权重反馈系统(第 5 天)
### 6.1 记忆引用检测
**文件**: `pkg/session/memory_manager.go`
```go
// DetectMemoryReference 检测 Agent 回复是否引用了某条记忆
func (mm *MemoryManager) DetectMemoryReference(response string, memories []LongTermMemory) []int64 {
// 简单实现:检查记忆中关键词是否出现在回复中
// 高级实现:使用 Embedding 相似度
}
```
**依赖**: Phase 3
**测试**: 验证检测准确率
---
### 6.2 自动归档任务
**文件**: `pkg/session/memory_manager.go`
```go
// RunMaintenance 运行记忆维护任务
func (mm *MemoryManager) RunMaintenance() error {
// 1. 归档低权重记忆
mm.ArchiveLowWeightMemories(mm.config.LongTerm.ArchiveThreshold)
// 2. 清理过期 STM
mm.Cleanup()
// 3. 其他维护...
}
```
**依赖**: Phase 3
**测试**: 验证归档逻辑
---
## Phase 7: CLI 命令(第 5-6 天)
### 7.1 添加 CLI 命令
**文件**: `cmd/orca/main.go` 或新建 `cmd/orca/memory.go`
```go
// orca memory list
// orca memory search "query"
// orca memory delete <id>
// orca memory clean
// orca memory stats
// orca memory export > backup.json
// orca memory import < backup.json
```
**依赖**: Phase 3
**测试**: 验证命令执行
---
## Phase 8: 测试与优化(第 6-7 天)
### 8.1 单元测试
| 模块 | 测试项 | 文件 |
|------|--------|------|
| Schema | 表创建迁移 | `pkg/session/sqlite_store_test.go` |
| Config | TOML 解析 | `internal/config/config_test.go` |
| Weight | 权重计算归档 | `pkg/session/memory_manager_test.go` |
| Buffer | 缓冲写入清空 | `pkg/session/memory_manager_test.go` |
| Extractor | 事实提取JSON 解析 | `pkg/actor/memory_extractor_test.go` |
| Injection | 注入条件Token 预算 | `pkg/actor/llm_agent_test.go` |
### 8.2 集成测试
```go
// test_memory_system_v2.go
func TestMemorySystemV2(t *testing.T) {
// 1. 创建会话
// 2. 发送消息(验证 STM 生成)
// 3. 发送 5 条消息(验证 LTM 提取触发)
// 4. 查询长期记忆(验证向量检索)
// 5. 验证权重变化
// 6. 验证归档逻辑
}
```
### 8.3 性能测试
| 指标 | 目标 | 测试方法 |
|------|------|---------|
| 向量检索 | < 100ms | BenchmarkVectorSearch |
| Embedding 缓存 | > 95% 命中 | BenchmarkEmbeddingCache |
| 权重更新 | < 10ms | BenchmarkWeightUpdate |
| 整体延迟 | < 200ms | BenchmarkMemoryInjection |
---
## 依赖图
```
Phase 1 (Schema)
├──→ Phase 2 (Config)
│ │
│ ├──→ Phase 3 (MemoryManager)
│ │ │
│ │ ├──→ Phase 4 (ExtractorAgent)
│ │ │ │
│ │ │ └──→ Phase 5 (LLMAgent)
│ │ │
│ │ └──→ Phase 6 (Weight System)
│ │
│ └──→ Phase 7 (CLI)
└──→ Phase 8 (Test)
```
---
## 风险与缓解
| 风险 | 可能性 | 影响 | 缓解 |
|------|--------|------|------|
| Schema 迁移失败 | | | 备份数据库测试迁移脚本 |
| MemoryExtractor 幻觉 | | | 高置信度阈值人工抽查 |
| 权重系统不准确 | | | 提供手动调整 CLI |
| 性能退化 | | | 监控及时优化索引 |
| 向后兼容 | | | 保留旧配置解析逻辑 |
---
## 验收标准
- [ ] 所有新增表创建成功
- [ ] 配置解析正确默认值合理
- [ ] STM 自动生成每轮对话
- [ ] LTM 批量提取 5
- [ ] 向量检索正常语义相似度
- [ ] 权重自动更新引用检测
- [ ] 低权重记忆自动归档
- [ ] CLI 命令可用
- [ ] 单元测试覆盖率 > 80%
- [ ] 集成测试通过
- [ ] 性能指标达标