-持久化记忆-基于Redis向量搜索的AI长期记忆

📅 2026/7/11 20:08:04
-持久化记忆-基于Redis向量搜索的AI长期记忆
内存存储量有限不适合大规模应用解决方案把向量存到支持向量搜索的数据库中。本文以Redis为例演示整个流程实际生产中不一定要选Redis可以根据你的场景选择合适的向量数据库如Qdrant、Milvus、pgvector等。环境准备安装Redis Stack向量搜索需要Redis Stack包含RediSearch模块。最简单的方式是用Dockerdocker run -d --name redis-stack -p 6379:6379 redis/redis-stack:latest数据模型设计首先定义向量存储的数据模型public class VectorModel{public string Id { get; set; } Guid.NewGuid().ToString(“N”);public string Data { get; set; } string.Empty; // 原始文本内容public float[] Embedding { get; set; } Array.Empty(); // 向量数据public Dictionarystring, object Metadata { get; set; } new(); // 元数据如rolepublic string? UserId { get; set; } // 用户标识public string? Hash { get; set; }public string? AgentId { get; set; } // 生成向量的模型标识public DateTime CreatedAt { get; set; }public DateTime? UpdatedAt { get; set; }}查询结果需要附带相似度得分public class QueryVectorItemDto{public VectorModel Vector { get; set; } new();public float Score { get; set; } // 余弦相似度得分}Metadata字段用于存储额外信息比如这条记录是user消息还是assistant消息——这在构建上下文时非常重要。从零实现RedisVectorStore连接Redis并创建向量索引var connectionMultiplexer await ConnectionMultiplexer.ConnectAsync(“127.0.0.1:6379”);var redisVectorStore new RedisVectorStore(connectionMultiplexer, “chat_memory”);// 确保向量索引存在2048维对应embedding-3模型await redisVectorStore.EnsureCollectionExistsAsync(2048, allowRecreation: false);索引创建的核心逻辑Redis的向量搜索需要先创建索引。这是最关键的一步private Task CreateIndexAsync(ISearchCommands ft, int vectorSize){var schema new Schema().AddTextField(“id”).AddTextField(“data”).AddTextField(“user_id”).AddTextField(“metadata”).AddNumericField(“created_at”).AddNumericField(“updated_at”).AddVectorField(“embedding”,Schema.VectorField.VectorAlgo.HNSW,new Dictionarystring, object{[“TYPE”] “FLOAT32”, // 向量数据类型[“DIM”] vectorSize, // 向量维度2048[“DISTANCE_METRIC”] “COSINE” // 余弦距离});bool success ft.Create(_indexName, new FTCreateParams() .On(IndexDataType.HASH) .Prefix(_keyPrefix), schema); if (!success) throw new Exception(Failed to create Redis vector index.); return Task.CompletedTask;}关键参数解析HNSW算法Hierarchical Navigable Small World目前最主流的近似最近邻搜索算法兼顾速度和精度FLOAT32单精度浮点每个向量占2048 × 4 8KBCOSINE余弦距离度量与第二篇的余弦相似度对应向量序列化Redis存储的是字节数组所以需要在float[]和byte[]之间转换private byte[] SerializeVector(float[] vector){var bytes new byte[vector.Length * sizeof(float)];Buffer.BlockCopy(vector, 0, bytes, 0, bytes.Length);return bytes;}private float[] DeserializeVector(byte[] bytes){var floats new float[bytes.Length / sizeof(float)];Buffer.BlockCopy(bytes, 0, floats, 0, bytes.Length);return floats;}Buffer.BlockCopy是最快的数组拷贝方式比逐元素转换快几个数量级。插入向量数据public async Task InsertAsync(List vectors, CancellationToken ct default){foreach (var vector in vectors){var key $“{_keyPrefix}{vector.Id}”;var hashEntries new HashEntry[]{new(“id”, vector.Id),new(“data”, vector.Data),new(“user_id”, vector.UserId ?? string.Empty),new(“metadata”, JsonSerializer.Serialize(vector.Metadata)),new(“created_at”, vector.CreatedAt.Ticks),new(“updated_at”, vector.UpdatedAt?.Ticks),new(“embedding”, SerializeVector(vector.Embedding)) // 向量序列化为bytes};await _db.HashSetAsync(key, hashEntries);}}每条记录以Redis Hash的形式存储key格式为chat_memory:{id}。KNN向量搜索这是整个系统最核心的部分——在Redis中执行KNNK-Nearest Neighbors向量搜索public TaskList SearchAsync(float[] queryVector, string? userId null, int limit 100){var ft _db.FT();// 构建KNN搜索查询 var queryStr userId ! null ? $user_id:{EscapeRedisQuery(userId)} : *; var searchQuery ${queryStr}[KNN {limit} embedding $query_vector AS __embedding_score]; var fullQuery new Query(searchQuery) .SetSortBy(__embedding_score) // 按距离排序 .Limit(0, limit) .ReturnFields(id, data, user_id, metadata, created_at, __embedding_score) .Dialect(2); // 必须使用Dialect 2 fullQuery.AddParam(query_vector, SerializeVector(queryVector)); var results ft.Search(_indexName, fullQuery); var searchResults new ListQueryVectorItemDto(); foreach (var doc in results.Documents) { var vectorItem ParseVectorModel(doc); var scoreValue doc[__embedding_score]; // Redis返回的是距离转换为相似度1 - distance var score float.TryParse(scoreValue.ToString(), out var s) ? 1.0f - s : 0.0f; searchResults.Add(new QueryVectorItemDto { Vector vectorItem, Score score }); } return Task.FromResult(searchResults);}关键细节查询语法*[KNN 6 embedding $query_vector AS __embedding_score]*表示不过滤user_id:xxx可按用户过滤KNN 6表示找最近的6个邻居embedding是向量字段名$query_vector是参数化的查询向量距离转相似度Redis返回余弦距离0-21 - distance转换为相似度-1到1Dialect 2向量搜索必须使用Dialect 2查询引擎持久化记忆的完整对话流程将Redis向量存储接入对话系统async Task RedisVectorMemoryAsync(){// 初始化AI客户端和Embedding生成器同前两篇IChatClient chatClient …;IEmbeddingGeneratorstring, Embedding embeddingGenerator …;const int topK 6; const float threshold 0.5f; // 比内存版更高的阈值Redis搜索更精准 while (true) { var userInput Console.ReadLine(); // 1. 生成查询向量 var userEmbedding await embeddingGenerator.GenerateAsync(userInput); float[] userVector userEmbedding.Vector.ToArray(); // 2. 从Redis检索相关历史 var relevantItems await redisVectorStore.SearchAsync(userVector, limit: topK); var filteredItems relevantItems .Where(item item.Score threshold) .OrderByDescending(item item.Score) .ToList(); // 3. 构建上下文 ListChatMessage chatMessages [ new ChatMessage(ChatRole.System, 你是一个有用的AI助手。下面是相关的历史对话记录请参考这些上下文来回答。 如果历史记录与当前问题无关请忽略历史记录直接回答。) ]; foreach (var item in filteredItems) { var role item.Vector.Metadata.TryGetValue(role, out var r) ? r.ToString() : user; var chatRole role assistant ? ChatRole.Assistant : ChatRole.User; chatMessages.Add(new ChatMessage(chatRole, item.Vector.Data)); } chatMessages.Add(new ChatMessage(ChatRole.User, userInput)); // 4. 调用AI流式输出 // ... 同前几篇 ... // 5. 持久化到Redis var vectorsToInsert new ListVectorModel { new() { Id Guid.NewGuid().ToString(N), Data userInput, Embedding userVector, UserId default, Metadata new Dictionarystring, object { [role] user }, CreatedAt DateTime.UtcNow } }; if (sb.Length 0) { var aiEmbedding await embeddingGenerator.GenerateAsync(sb.ToString()); vectorsToInsert.Add(new() { Id Guid.NewGuid().ToString(N), Data sb.ToString(), Embedding aiEmbedding.Vector.ToArray(), UserId default, Metadata new Dictionarystring, object { [role] assistant }, CreatedAt DateTime.UtcNow }); } await redisVectorStore.InsertAsync(vectorsToInsert); }}清除记忆功能持久化存储还带来了新的需求——清除记忆if (userInput.Equals(“clear”, StringComparison.OrdinalIgnoreCase)){var existingItems await redisVectorStore.ListAsync(limit: 10000);foreach (var item in existingItems){await redisVectorStore.DeleteAsync(item.Id);}Console.WriteLine(“[系统] 已清除所有历史记忆。”);continue;}内存版 vs Redis版对比维度 内存版第二篇 Redis版本篇持久化 ❌ 重启丢失 ✅ 持久存储跨会话 ❌ 不支持 ✅ 支持搜索方式 手动计算余弦相似度 Redis原生KNN搜索相似度计算 TensorPrimitives.CosineSimilarity Redis引擎内部计算多用户 ❌ 不支持 ✅ 通过UserId过滤性能 小数据量快 大数据量更优部署成本 零 需要Redis Stack小结这篇文章我们学习了Redis向量搜索的完整实现索引创建、向量序列化、KNN搜索HNSW算法和COSINE距离的配置NRedisStack的RediSearch API使用从内存记忆到持久化记忆的升级