diff --git a/plugins/golang-filter/mcp-server/servers/rag/memory/README.md b/plugins/golang-filter/mcp-server/servers/rag/memory/README.md new file mode 100644 index 00000000..bd34ac42 --- /dev/null +++ b/plugins/golang-filter/mcp-server/servers/rag/memory/README.md @@ -0,0 +1,273 @@ +# Memory - 对话历史管理模块 + +Memory 模块是一个通用的对话历史和会话上下文管理组件,提供了会话存储的抽象接口和多种实现。 + +## 📦 功能特性 + +- **对话历史管理**: 存储和检索多轮对话历史 +- **文档ID管理**: 关联会话与相关文档 +- **多种存储后端**: 内存存储(开发/测试)和 Redis 存储(生产环境) +- **线程安全**: 所有操作都是并发安全的 +- **自动过期**: Redis 存储支持 TTL 自动过期 +- **轮次限制**: 自动保持最近 N 轮对话 + +## 🏗️ 核心接口 + +### ConversationStore + +对话存储的核心接口: + +```go +type ConversationStore interface { + // 获取最近 N 轮对话 + GetLastNRounds(ctx context.Context, sessionID string, n int) ([]ConversationRound, error) + + // 获取会话相关的文档ID列表 + GetDocIDs(ctx context.Context, sessionID string) ([]string, error) + + // 保存一轮对话 + SaveRound(ctx context.Context, sessionID string, round ConversationRound) error + + // 保存会话相关的文档ID列表 + SaveDocIDs(ctx context.Context, sessionID string, docIDs []string) error + + // 清除指定会话的所有数据 + Clear(ctx context.Context, sessionID string) error +} +``` + +### 数据结构 + +```go +// 对话轮次 +type ConversationRound struct { + Question string `json:"question"` + Answer string `json:"answer"` + Timestamp time.Time `json:"timestamp,omitempty"` +} + +// 查询上下文 +type QueryContext struct { + Query string `json:"query"` + LastNRounds []ConversationRound `json:"last_n_rounds,omitempty"` + DocIDs []string `json:"doc_ids,omitempty"` + SessionID string `json:"session_id,omitempty"` + Timestamp time.Time `json:"timestamp"` +} +``` + +## 🚀 使用示例 + +### 1. 内存存储(开发/测试) + +```go +import "github.com/alibaba/higress/plugins/golang-filter/mcp-server/servers/rag/memory" + +// 创建内存存储,最多保留 10 轮对话 +store := memory.NewInMemoryConversationStore(10) + +// 保存对话 +round := memory.ConversationRound{ + Question: "什么是 RAG?", + Answer: "RAG 是检索增强生成技术...", + Timestamp: time.Now(), +} +err := store.SaveRound(context.Background(), "session-123", round) + +// 获取历史对话 +rounds, err := store.GetLastNRounds(context.Background(), "session-123", 5) +for _, r := range rounds { + fmt.Printf("Q: %s\nA: %s\n\n", r.Question, r.Answer) +} +``` + +### 2. Redis 存储(生产环境) + +```go +import "github.com/alibaba/higress/plugins/golang-filter/mcp-server/servers/rag/memory" + +// 配置 Redis 存储 +cfg := &memory.RedisConversationStoreConfig{ + RedisClient: redisClient, + KeyPrefix: "rag:conversation:", + SessionExpiry: 24 * time.Hour, + MaxHistoryRounds: 20, +} + +store := memory.NewRedisConversationStore(cfg) + +// 使用方式与内存存储相同 +err := store.SaveRound(ctx, sessionID, round) +rounds, err := store.GetLastNRounds(ctx, sessionID, 10) +``` + +### 3. 管理文档关联 + +```go +// 保存会话相关的文档ID +docIDs := []string{"doc-001", "doc-002", "doc-003"} +err := store.SaveDocIDs(ctx, "session-123", docIDs) + +// 获取相关文档 +docIDs, err := store.GetDocIDs(ctx, "session-123") +fmt.Printf("相关文档: %v\n", docIDs) +``` + +### 4. 清理会话数据 + +```go +// 清除指定会话的所有数据 +err := store.Clear(ctx, "session-123") +``` + +## 🔄 与其他模块的关系 + +### Pre-Retrieve 模块 + +Pre-Retrieve 模块使用 Memory 进行对话历史采集: + +```go +// Pre-Retrieve 使用 memory.ConversationStore +sessionStore := memory.NewInMemoryConversationStore(10) +processor := pre_retrieve.NewMemoryIntakeProcessor( + cfg, + sessionStore, + nil, // externalStore +) +``` + +### 类型别名 + +为了保持向后兼容,Memory 模块提供了以下别名: + +```go +// SessionStore 是 ConversationStore 的别名 +type SessionStore = ConversationStore + +// 推荐使用 ConversationStore,以避免与 rag 包中的 SessionStore 混淆 +``` + +## ⚠️ 注意事项 + +### 与 RAG SessionStore 的区别 + +Memory 模块的 `ConversationStore` 与 RAG 包中的 `SessionStore` 是**两个不同的概念**: + +| 特性 | memory.ConversationStore | rag.SessionStore | +|------|-------------------------|------------------| +| 用途 | 对话历史管理 | 聊天会话管理 | +| 数据结构 | ConversationRound | ChatMessage | +| 主要功能 | 存储问答轮次 | 管理完整会话 | +| 使用场景 | Pre-Retrieve, 上下文采集 | 聊天接口 | + +### 线程安全 + +- `InMemoryConversationStore`: 使用 RWMutex 保证并发安全 +- `RedisConversationStore`: 利用 Redis 的原子操作保证安全 + +### 性能考虑 + +1. **内存存储** + - 优点:速度快,无网络延迟 + - 缺点:不持久化,重启丢失 + - 适用:开发、测试、单机部署 + +2. **Redis 存储** + - 优点:持久化,支持分布式 + - 缺点:网络延迟,依赖 Redis + - 适用:生产环境,多实例部署 + +## 📊 存储格式 + +### Redis Key 格式 + +``` +{keyPrefix}{sessionID}:rounds - 存储对话轮次数组(JSON) +{keyPrefix}{sessionID}:docs - 存储文档ID数组(JSON) +``` + +示例: +``` +rag:conversation:session-123:rounds +rag:conversation:session-123:docs +``` + +### JSON 格式示例 + +```json +// rounds +[ + { + "question": "什么是RAG?", + "answer": "RAG是检索增强生成技术...", + "timestamp": "2025-01-01T12:00:00Z" + } +] + +// docs +["doc-001", "doc-002", "doc-003"] +``` + +## 🔧 扩展性 + +要实现自定义存储后端,只需实现 `ConversationStore` 接口: + +```go +type MyCustomStore struct { + // your fields +} + +func (s *MyCustomStore) GetLastNRounds(ctx context.Context, sessionID string, n int) ([]ConversationRound, error) { + // your implementation +} + +// ... 实现其他方法 +``` + +## 📚 相关模块 + +- `pre-retrieve`: 使用 Memory 进行上下文采集 +- `rag`: RAG 主流程 +- `cache`: 缓存模块 +- `session`: 会话管理模块(不同用途) + +## 🎯 最佳实践 + +1. **开发环境**: 使用 `InMemoryConversationStore` +2. **生产环境**: 使用 `RedisConversationStore` +3. **设置合理的轮次限制**: 避免内存/存储占用过大 +4. **定期清理**: 对不活跃的会话调用 `Clear()` +5. **错误处理**: 妥善处理存储失败的情况 +6. **监控**: 监控 Redis 连接状态和存储大小 + +## 🐛 故障排查 + +### 内存存储问题 + +```go +// 检查并发访问 +store := memory.NewInMemoryConversationStore(10) +// 内部使用 RWMutex,并发安全 +``` + +### Redis 存储问题 + +```go +// 检查 Redis 连接 +err := redisClient.checkConnection() + +// 检查 TTL 设置 +// 确保 SessionExpiry > 0 + +// 查看 Redis 键 +// redis-cli KEYS "rag:conversation:*" +``` + +## 📝 TODO + +- [ ] 支持更多存储后端(MongoDB, PostgreSQL) +- [ ] 添加批量操作接口 +- [ ] 支持会话搜索和过滤 +- [ ] 添加指标收集(存储大小、访问频率) +- [ ] 支持会话导出和备份 + diff --git a/plugins/golang-filter/mcp-server/servers/rag/memory/store.go b/plugins/golang-filter/mcp-server/servers/rag/memory/store.go new file mode 100644 index 00000000..59d4aab6 --- /dev/null +++ b/plugins/golang-filter/mcp-server/servers/rag/memory/store.go @@ -0,0 +1,270 @@ +package memory + +import ( + "context" + "encoding/json" + "fmt" + "sync" + "time" + + "github.com/alibaba/higress/plugins/golang-filter/mcp-session/common" +) + +// ConversationStore 对话历史存储接口 +// 提供对话历史和文档ID的存储和检索功能 +// 注意:此接口与 rag 包中的 SessionStore(聊天会话管理)不同 +type ConversationStore interface { + // GetLastNRounds 获取最近 N 轮对话 + GetLastNRounds(ctx context.Context, sessionID string, n int) ([]ConversationRound, error) + + // GetDocIDs 获取会话相关的文档ID列表 + GetDocIDs(ctx context.Context, sessionID string) ([]string, error) + + // SaveRound 保存一轮对话 + SaveRound(ctx context.Context, sessionID string, round ConversationRound) error + + // SaveDocIDs 保存会话相关的文档ID列表 + SaveDocIDs(ctx context.Context, sessionID string, docIDs []string) error + + // Clear 清除指定会话的所有数据 + Clear(ctx context.Context, sessionID string) error +} + +// ============================================================================= +// InMemoryConversationStore - 内存实现 +// ============================================================================= + +// InMemoryConversationStore 内存对话存储实现 +// 适用于开发测试或单机部署 +type InMemoryConversationStore struct { + mu sync.RWMutex + sessions map[string][]ConversationRound + docIDs map[string][]string + maxRounds int +} + +// NewInMemoryConversationStore 创建内存对话存储 +func NewInMemoryConversationStore(maxRounds int) ConversationStore { + if maxRounds <= 0 { + maxRounds = 10 + } + return &InMemoryConversationStore{ + sessions: make(map[string][]ConversationRound), + docIDs: make(map[string][]string), + maxRounds: maxRounds, + } +} + +// NewInMemorySessionStore 是 NewInMemoryConversationStore 的别名,为了向后兼容 +func NewInMemorySessionStore(maxRounds int) ConversationStore { + return NewInMemoryConversationStore(maxRounds) +} + +func (s *InMemoryConversationStore) GetLastNRounds(ctx context.Context, sessionID string, n int) ([]ConversationRound, error) { + s.mu.RLock() + defer s.mu.RUnlock() + + rounds := s.sessions[sessionID] + if len(rounds) == 0 { + return []ConversationRound{}, nil + } + + if n <= 0 || n >= len(rounds) { + // 返回所有轮次的副本 + result := make([]ConversationRound, len(rounds)) + copy(result, rounds) + return result, nil + } + + // 返回最后 n 轮的副本 + result := make([]ConversationRound, n) + copy(result, rounds[len(rounds)-n:]) + return result, nil +} + +func (s *InMemoryConversationStore) GetDocIDs(ctx context.Context, sessionID string) ([]string, error) { + s.mu.RLock() + defer s.mu.RUnlock() + + docIDs := s.docIDs[sessionID] + if len(docIDs) == 0 { + return []string{}, nil + } + + // 返回副本 + result := make([]string, len(docIDs)) + copy(result, docIDs) + return result, nil +} + +func (s *InMemoryConversationStore) SaveRound(ctx context.Context, sessionID string, round ConversationRound) error { + s.mu.Lock() + defer s.mu.Unlock() + + rounds := s.sessions[sessionID] + rounds = append(rounds, round) + + // 保持最大轮数限制 + if len(rounds) > s.maxRounds { + rounds = rounds[len(rounds)-s.maxRounds:] + } + + s.sessions[sessionID] = rounds + return nil +} + +func (s *InMemoryConversationStore) SaveDocIDs(ctx context.Context, sessionID string, docIDs []string) error { + s.mu.Lock() + defer s.mu.Unlock() + + // 保存副本 + newDocIDs := make([]string, len(docIDs)) + copy(newDocIDs, docIDs) + s.docIDs[sessionID] = newDocIDs + return nil +} + +func (s *InMemoryConversationStore) Clear(ctx context.Context, sessionID string) error { + s.mu.Lock() + defer s.mu.Unlock() + + delete(s.sessions, sessionID) + delete(s.docIDs, sessionID) + return nil +} + +// ============================================================================= +// RedisConversationStore - Redis 实现 +// ============================================================================= + +// RedisConversationStore Redis 对话存储实现 +// 适用于生产环境,支持分布式部署 +type RedisConversationStore struct { + redisClient *common.RedisClient + keyPrefix string + sessionExpiry time.Duration + maxHistoryRounds int +} + +// RedisConversationStoreConfig Redis 对话存储配置 +type RedisConversationStoreConfig struct { + RedisClient *common.RedisClient + KeyPrefix string + SessionExpiry time.Duration + MaxHistoryRounds int +} + +// NewRedisConversationStore 创建 Redis 对话存储 +func NewRedisConversationStore(cfg *RedisConversationStoreConfig) ConversationStore { + if cfg.KeyPrefix == "" { + cfg.KeyPrefix = "rag:conversation:" + } + if cfg.SessionExpiry == 0 { + cfg.SessionExpiry = 24 * time.Hour + } + if cfg.MaxHistoryRounds == 0 { + cfg.MaxHistoryRounds = 10 + } + + return &RedisConversationStore{ + redisClient: cfg.RedisClient, + keyPrefix: cfg.KeyPrefix, + sessionExpiry: cfg.SessionExpiry, + maxHistoryRounds: cfg.MaxHistoryRounds, + } +} + +// NewRedisSessionStore 是 NewRedisConversationStore 的别名,为了向后兼容 +func NewRedisSessionStore(cfg *RedisConversationStoreConfig) ConversationStore { + return NewRedisConversationStore(cfg) +} + +func (s *RedisConversationStore) GetLastNRounds(ctx context.Context, sessionID string, n int) ([]ConversationRound, error) { + key := s.keyPrefix + sessionID + ":rounds" + value, err := s.redisClient.Get(key) + if err != nil || value == "" { + return []ConversationRound{}, nil + } + + var rounds []ConversationRound + if err := json.Unmarshal([]byte(value), &rounds); err != nil { + return nil, fmt.Errorf("failed to unmarshal session rounds: %w", err) + } + + if n <= 0 || n >= len(rounds) { + return rounds, nil + } + + return rounds[len(rounds)-n:], nil +} + +func (s *RedisConversationStore) GetDocIDs(ctx context.Context, sessionID string) ([]string, error) { + key := s.keyPrefix + sessionID + ":docs" + value, err := s.redisClient.Get(key) + if err != nil || value == "" { + return []string{}, nil + } + + var docIDs []string + if err := json.Unmarshal([]byte(value), &docIDs); err != nil { + return nil, fmt.Errorf("failed to unmarshal doc IDs: %w", err) + } + + return docIDs, nil +} + +func (s *RedisConversationStore) SaveRound(ctx context.Context, sessionID string, round ConversationRound) error { + key := s.keyPrefix + sessionID + ":rounds" + + // 获取现有轮次 + rounds, err := s.GetLastNRounds(ctx, sessionID, s.maxHistoryRounds) + if err != nil { + return err + } + + // 添加新轮次 + rounds = append(rounds, round) + + // 保持最大轮数限制 + if len(rounds) > s.maxHistoryRounds { + rounds = rounds[len(rounds)-s.maxHistoryRounds:] + } + + // 序列化并保存 + data, err := json.Marshal(rounds) + if err != nil { + return fmt.Errorf("failed to marshal rounds: %w", err) + } + + return s.redisClient.Set(key, string(data), s.sessionExpiry) +} + +func (s *RedisConversationStore) SaveDocIDs(ctx context.Context, sessionID string, docIDs []string) error { + key := s.keyPrefix + sessionID + ":docs" + + data, err := json.Marshal(docIDs) + if err != nil { + return fmt.Errorf("failed to marshal doc IDs: %w", err) + } + + return s.redisClient.Set(key, string(data), s.sessionExpiry) +} + +func (s *RedisConversationStore) Clear(ctx context.Context, sessionID string) error { + roundsKey := s.keyPrefix + sessionID + ":rounds" + docsKey := s.keyPrefix + sessionID + ":docs" + + // 使用 Lua 脚本删除键 + script := ` + redis.call('DEL', KEYS[1]) + redis.call('DEL', KEYS[2]) + return 1 + ` + _, err := s.redisClient.Eval(script, 2, []string{roundsKey, docsKey}, nil) + if err != nil { + // 忽略错误,因为键可能不存在 + return nil + } + + return nil +} diff --git a/plugins/golang-filter/mcp-server/servers/rag/memory/types.go b/plugins/golang-filter/mcp-server/servers/rag/memory/types.go new file mode 100644 index 00000000..b0e64940 --- /dev/null +++ b/plugins/golang-filter/mcp-server/servers/rag/memory/types.go @@ -0,0 +1,24 @@ +package memory + +import "time" + +// ConversationRound 对话轮次 +type ConversationRound struct { + Question string `json:"question"` + Answer string `json:"answer"` + Timestamp time.Time `json:"timestamp,omitempty"` +} + +// QueryContext 查询上下文,包含原始查询和会话信息 +type QueryContext struct { + // 原始用户查询 + Query string `json:"query"` + // 最近 N 轮对话历史 + LastNRounds []ConversationRound `json:"last_n_rounds,omitempty"` + // 相关文档 ID + DocIDs []string `json:"doc_ids,omitempty"` + // 会话 ID + SessionID string `json:"session_id,omitempty"` + // 时间戳 + Timestamp time.Time `json:"timestamp"` +} diff --git a/plugins/golang-filter/mcp-server/servers/rag/pre-retrieve/processors.go b/plugins/golang-filter/mcp-server/servers/rag/pre-retrieve/processors.go index a0a76134..802f5a3b 100644 --- a/plugins/golang-filter/mcp-server/servers/rag/pre-retrieve/processors.go +++ b/plugins/golang-filter/mcp-server/servers/rag/pre-retrieve/processors.go @@ -2,15 +2,13 @@ package pre_retrieve import ( "context" - "encoding/json" "fmt" "strings" - "time" "github.com/alibaba/higress/plugins/golang-filter/mcp-server/servers/rag/config" "github.com/alibaba/higress/plugins/golang-filter/mcp-server/servers/rag/embedding" "github.com/alibaba/higress/plugins/golang-filter/mcp-server/servers/rag/llm" - "github.com/alibaba/higress/plugins/golang-filter/mcp-session/common" + "github.com/alibaba/higress/plugins/golang-filter/mcp-server/servers/rag/memory" ) // ============================================================================= @@ -19,14 +17,7 @@ import ( // MemoryIntakeProcessor 记忆采集处理器接口 type MemoryIntakeProcessor interface { - Process(ctx context.Context, rawQuery string, sessionID string) (*QueryContext, error) -} - -// SessionStore 会话存储接口 -type SessionStore interface { - GetLastNRounds(ctx context.Context, sessionID string, n int) ([]ConversationRound, error) - GetDocIDs(ctx context.Context, sessionID string) ([]string, error) - SaveRound(ctx context.Context, sessionID string, round ConversationRound) error + Process(ctx context.Context, rawQuery string, sessionID string) (*memory.QueryContext, error) } // ExternalMemoryStore 外部记忆存储接口 @@ -37,11 +28,11 @@ type ExternalMemoryStore interface { // DefaultMemoryIntakeProcessor 默认记忆采集处理器 type DefaultMemoryIntakeProcessor struct { config *config.MemoryConfig - sessionStore SessionStore + sessionStore memory.ConversationStore externalStore ExternalMemoryStore } -func NewMemoryIntakeProcessor(cfg *config.MemoryConfig, sessionStore SessionStore, externalStore ExternalMemoryStore) MemoryIntakeProcessor { +func NewMemoryIntakeProcessor(cfg *config.MemoryConfig, sessionStore memory.ConversationStore, externalStore ExternalMemoryStore) MemoryIntakeProcessor { return &DefaultMemoryIntakeProcessor{ config: cfg, sessionStore: sessionStore, @@ -49,8 +40,8 @@ func NewMemoryIntakeProcessor(cfg *config.MemoryConfig, sessionStore SessionStor } } -func (p *DefaultMemoryIntakeProcessor) Process(ctx context.Context, rawQuery string, sessionID string) (*QueryContext, error) { - queryCtx := &QueryContext{ +func (p *DefaultMemoryIntakeProcessor) Process(ctx context.Context, rawQuery string, sessionID string) (*memory.QueryContext, error) { + queryCtx := &memory.QueryContext{ Query: rawQuery, SessionID: sessionID, } @@ -76,131 +67,18 @@ func (p *DefaultMemoryIntakeProcessor) Process(ctx context.Context, rawQuery str return queryCtx, nil } -// RedisSessionStore Redis 会话存储实现 -type RedisSessionStore struct { - redisClient *common.RedisClient - keyPrefix string - sessionExpiry time.Duration - maxHistoryRounds int -} - -func NewRedisSessionStore(redisClient *common.RedisClient, keyPrefix string, sessionExpiry time.Duration, maxHistoryRounds int) SessionStore { - if keyPrefix == "" { - keyPrefix = "pre-retrieve:session:" - } - if sessionExpiry == 0 { - sessionExpiry = 24 * time.Hour - } - if maxHistoryRounds == 0 { - maxHistoryRounds = 10 - } - return &RedisSessionStore{ - redisClient: redisClient, - keyPrefix: keyPrefix, - sessionExpiry: sessionExpiry, - maxHistoryRounds: maxHistoryRounds, - } -} - -func (s *RedisSessionStore) GetLastNRounds(ctx context.Context, sessionID string, n int) ([]ConversationRound, error) { - key := s.keyPrefix + sessionID - value, err := s.redisClient.Get(key) - if err != nil { - return []ConversationRound{}, nil - } - - var rounds []ConversationRound - if err := json.Unmarshal([]byte(value), &rounds); err != nil { - return nil, fmt.Errorf("failed to unmarshal session data: %w", err) - } - - if len(rounds) <= n { - return rounds, nil - } - return rounds[len(rounds)-n:], nil -} - -func (s *RedisSessionStore) GetDocIDs(ctx context.Context, sessionID string) ([]string, error) { - key := s.keyPrefix + sessionID + ":docs" - value, err := s.redisClient.Get(key) - if err != nil { - return []string{}, nil - } - - var docIDs []string - if err := json.Unmarshal([]byte(value), &docIDs); err != nil { - return nil, fmt.Errorf("failed to unmarshal doc IDs: %w", err) - } - return docIDs, nil -} - -func (s *RedisSessionStore) SaveRound(ctx context.Context, sessionID string, round ConversationRound) error { - key := s.keyPrefix + sessionID - rounds, _ := s.GetLastNRounds(ctx, sessionID, s.maxHistoryRounds) - rounds = append(rounds, round) - if len(rounds) > s.maxHistoryRounds { - rounds = rounds[len(rounds)-s.maxHistoryRounds:] - } - - data, err := json.Marshal(rounds) - if err != nil { - return fmt.Errorf("failed to marshal rounds: %w", err) - } - return s.redisClient.Set(key, string(data), s.sessionExpiry) -} - -// InMemorySessionStore 内存会话存储(测试用) -type InMemorySessionStore struct { - sessions map[string][]ConversationRound - docIDs map[string][]string - maxRounds int -} - -func NewInMemorySessionStore(maxRounds int) SessionStore { - if maxRounds == 0 { - maxRounds = 10 - } - return &InMemorySessionStore{ - sessions: make(map[string][]ConversationRound), - docIDs: make(map[string][]string), - maxRounds: maxRounds, - } -} - -func (s *InMemorySessionStore) GetLastNRounds(ctx context.Context, sessionID string, n int) ([]ConversationRound, error) { - rounds := s.sessions[sessionID] - if len(rounds) <= n { - return rounds, nil - } - return rounds[len(rounds)-n:], nil -} - -func (s *InMemorySessionStore) GetDocIDs(ctx context.Context, sessionID string) ([]string, error) { - return s.docIDs[sessionID], nil -} - -func (s *InMemorySessionStore) SaveRound(ctx context.Context, sessionID string, round ConversationRound) error { - rounds := s.sessions[sessionID] - rounds = append(rounds, round) - if len(rounds) > s.maxRounds { - rounds = rounds[len(rounds)-s.maxRounds:] - } - s.sessions[sessionID] = rounds - return nil -} - // ============================================================================= // Context Alignment Processor - 上下文对齐 // ============================================================================= // ContextAlignmentProcessor 上下文对齐处理器接口 type ContextAlignmentProcessor interface { - Process(ctx context.Context, queryCtx *QueryContext) (*AlignedQuery, error) + Process(ctx context.Context, queryCtx *memory.QueryContext) (*AlignedQuery, error) } // AnchorCandidateRetriever 锚点候选检索器接口 type AnchorCandidateRetriever interface { - RetrieveCandidates(ctx context.Context, queryCtx *QueryContext) ([]Anchor, error) + RetrieveCandidates(ctx context.Context, queryCtx *memory.QueryContext) ([]Anchor, error) } // DefaultContextAlignmentProcessor 默认上下文对齐处理器 @@ -218,7 +96,7 @@ func NewContextAlignmentProcessor(cfg *config.ContextAlignmentConfig, llmProvide } } -func (p *DefaultContextAlignmentProcessor) Process(ctx context.Context, queryCtx *QueryContext) (*AlignedQuery, error) { +func (p *DefaultContextAlignmentProcessor) Process(ctx context.Context, queryCtx *memory.QueryContext) (*AlignedQuery, error) { if !p.config.Enabled { return &AlignedQuery{Query: queryCtx.Query}, nil } @@ -244,7 +122,7 @@ func (p *DefaultContextAlignmentProcessor) Process(ctx context.Context, queryCtx return alignedQuery, nil } -func (p *DefaultContextAlignmentProcessor) integrateContext(ctx context.Context, queryCtx *QueryContext) (string, []string, error) { +func (p *DefaultContextAlignmentProcessor) integrateContext(ctx context.Context, queryCtx *memory.QueryContext) (string, []string, error) { ops := []string{} query := queryCtx.Query @@ -271,7 +149,7 @@ func (p *DefaultContextAlignmentProcessor) integrateContext(ctx context.Context, return query, ops, nil } -func (p *DefaultContextAlignmentProcessor) resolvePronounsWithLLM(ctx context.Context, queryCtx *QueryContext) (string, error) { +func (p *DefaultContextAlignmentProcessor) resolvePronounsWithLLM(ctx context.Context, queryCtx *memory.QueryContext) (string, error) { history := strings.Builder{} for i, round := range queryCtx.LastNRounds { history.WriteString(fmt.Sprintf("Q%d: %s\nA%d: %s\n", i+1, round.Question, i+1, round.Answer)) @@ -313,7 +191,7 @@ Normalized Query:`, query) return strings.TrimSpace(normalized), nil } -func (p *DefaultContextAlignmentProcessor) retrieveAndDecideAnchors(ctx context.Context, queryCtx *QueryContext, alignedQuery string) ([]Anchor, error) { +func (p *DefaultContextAlignmentProcessor) retrieveAndDecideAnchors(ctx context.Context, queryCtx *memory.QueryContext, alignedQuery string) ([]Anchor, error) { candidates, err := p.anchorCandidateRetriever.RetrieveCandidates(ctx, queryCtx) if err != nil { return []Anchor{}, err @@ -344,7 +222,7 @@ func NewDefaultAnchorCandidateRetriever() AnchorCandidateRetriever { return &DefaultAnchorCandidateRetriever{} } -func (r *DefaultAnchorCandidateRetriever) RetrieveCandidates(ctx context.Context, queryCtx *QueryContext) ([]Anchor, error) { +func (r *DefaultAnchorCandidateRetriever) RetrieveCandidates(ctx context.Context, queryCtx *memory.QueryContext) ([]Anchor, error) { anchors := []Anchor{} for _, docID := range queryCtx.DocIDs { anchors = append(anchors, Anchor{ @@ -911,10 +789,7 @@ func (p *DefaultHyDEProcessor) shouldGenerateHyDE(node QueryNode) bool { return true } words := strings.Fields(node.Query) - if len(words) < 5 { - return true - } - return false + return len(words) < 5 } func (p *DefaultHyDEProcessor) generateHypotheticalDocument(ctx context.Context, node QueryNode) (string, error) { diff --git a/plugins/golang-filter/mcp-server/servers/rag/pre-retrieve/provider.go b/plugins/golang-filter/mcp-server/servers/rag/pre-retrieve/provider.go index 8054d52a..c3ba8bc3 100644 --- a/plugins/golang-filter/mcp-server/servers/rag/pre-retrieve/provider.go +++ b/plugins/golang-filter/mcp-server/servers/rag/pre-retrieve/provider.go @@ -8,6 +8,7 @@ import ( "github.com/alibaba/higress/plugins/golang-filter/mcp-server/servers/rag/config" "github.com/alibaba/higress/plugins/golang-filter/mcp-server/servers/rag/embedding" "github.com/alibaba/higress/plugins/golang-filter/mcp-server/servers/rag/llm" + "github.com/alibaba/higress/plugins/golang-filter/mcp-server/servers/rag/memory" ) const ( @@ -152,7 +153,7 @@ func (i *PreRetrieveInitializer) CreateProvider(cfg *config.PreRetrieveConfig) ( } // 1. Memory Intake Processor - sessionStore := NewInMemorySessionStore(cfg.Memory.LastNRounds) + sessionStore := memory.NewInMemorySessionStore(cfg.Memory.LastNRounds) provider.memoryProcessor = NewMemoryIntakeProcessor(&cfg.Memory, sessionStore, nil) // 2. Context Alignment Processor diff --git a/plugins/golang-filter/mcp-server/servers/rag/pre-retrieve/schema.go b/plugins/golang-filter/mcp-server/servers/rag/pre-retrieve/schema.go index 073651a7..54c72c73 100644 --- a/plugins/golang-filter/mcp-server/servers/rag/pre-retrieve/schema.go +++ b/plugins/golang-filter/mcp-server/servers/rag/pre-retrieve/schema.go @@ -1,26 +1,6 @@ package pre_retrieve -import "time" - -// QueryContext 查询上下文,包含原始查询和会话信息 -type QueryContext struct { - // 原始用户查询 - Query string `json:"query"` - // 最近 N 轮对话历史 - LastNRounds []ConversationRound `json:"last_n_rounds,omitempty"` - // 相关文档 ID - DocIDs []string `json:"doc_ids,omitempty"` - // 会话 ID - SessionID string `json:"session_id,omitempty"` - // 时间戳 - Timestamp time.Time `json:"timestamp"` -} - -// ConversationRound 对话轮次 -type ConversationRound struct { - Question string `json:"question"` - Answer string `json:"answer"` -} +import "github.com/alibaba/higress/plugins/golang-filter/mcp-server/servers/rag/memory" // Anchor 锚点信息 type Anchor struct { @@ -128,7 +108,7 @@ type HyDEVector struct { // PreRetrieveResult Pre-Retrieve 完整结果 type PreRetrieveResult struct { // 原始上下文 - Context QueryContext `json:"context"` + Context memory.QueryContext `json:"context"` // 对齐后的查询 AlignedQuery AlignedQuery `json:"aligned_query"` // 查询计划