智能体的长期记忆如何实现有效持久化?

摘要:记忆与持久化:智能体的长期记忆 前言 在前几篇文章中,我们已经掌握了如何创建能说会道的智能体、如何让智能体使用工具、如何管理多轮对话的状态。但是有一个关键问题还没有解决:每次对话结束后,智能体就像失去了记忆一样,下次用户再来,它什么都不记得
记忆与持久化:智能体的长期记忆 前言 在前几篇文章中,我们已经掌握了如何创建能说会道的智能体、如何让智能体使用工具、如何管理多轮对话的状态。但是有一个关键问题还没有解决:每次对话结束后,智能体就像失去了记忆一样,下次用户再来,它什么都不记得了。 想象一下这样的场景: 第一次对话: 用户:"我的邮箱是 john@example.com" 助手:"好的,我已经记录下来了。" 第二次对话(第二天): 用户:"我的邮箱是什么?" 助手:"抱歉,我不知道。" 这种"失忆症"让智能体无法提供真正个性化的服务。本文将深入探讨如何在Agent Framework中实现智能的长期记忆系统,让智能体能够"记住"用户的历史交互,提供更加贴心的服务。 一、记忆系统的核心概念 1.1 记忆的三层架构 一个完整的记忆系统通常包含三层: 第一层:短期记忆(Short-term Memory) 也称为工作记忆,存储当前对话的上下文信息。这种记忆的生命周期与对话相同,对话结束后通常会被清除。在Agent Framework中,这对应于ConversationContext中的消息历史。 第二层:长期记忆(Long-term Memory) 存储跨会话的用户信息,包括用户偏好、历史交互记录、重要事项等。这种记忆会被持久化存储,可以在多个会话间共享。 第三层:向量记忆(Vector Memory) 存储非结构化的语义信息,通过向量相似度检索实现语义匹配。这种记忆特别适合存储对话摘要、文档内容等需要语义检索的数据。 1.2 记忆存储的选择 根据不同的场景和需求,可以选择不同的存储方案: 内存存储:适合开发测试、小规模应用,优点是速度快、成本低,缺点是重启后数据丢失。 文件存储:适合单机应用、简单场景,使用JSON或SQLite存储,优点是部署简单,缺点是扩展性差。 数据库存储:适合生产环境,使用关系型数据库(SQL Server、PostgreSQL)或NoSQL数据库(MongoDB、Cassandra),优点是可靠、可扩展,缺点是需要额外的基础设施。 向量数据库:适合需要语义检索的场景,使用Pinecone、Milvus、Qdrant等向量数据库,优点是支持语义相似度搜索,缺点是成本较高。 二、Agent Framework记忆实现 2.1 基础记忆接口定义 首先,我们定义记忆系统的基础接口: // IMemoryStore.cs public interface IMemoryStore { // 保存记忆 Task SaveMemoryAsync(MemoryItem memory); // 检索记忆 Task<IEnumerable<MemoryItem>> SearchAsync( string userId, string query, int limit = 5); // 获取用户所有记忆 Task<IEnumerable<MemoryItem>> GetUserMemoriesAsync( string userId, MemoryType? type = null); // 删除记忆 Task DeleteMemoryAsync(string memoryId); // 更新记忆 Task UpdateMemoryAsync(MemoryItem memory); } // 记忆类型 public enum MemoryType { UserPreference, // 用户偏好 ConversationSummary, // 对话摘要 ImportantInfo, // 重要信息 UserProfile, // 用户档案 InteractionHistory // 交互历史 } // 记忆项 public class MemoryItem { public string Id { get; set; } = Guid.NewGuid().ToString(); public string UserId { get; set; } = string.Empty; public string Content { get; set; } = string.Empty; public MemoryType Type { get; set; } public DateTime CreatedAt { get; set; } = DateTime.UtcNow; public DateTime LastAccessedAt { get; set; } = DateTime.UtcNow; public Dictionary<string, object> Metadata { get; set; } = new(); public List<string> Tags { get; set; } = new(); public float Importance { get; set; } = 1.0f; // 重要性评分 public int AccessCount { get; set; } } 2.2 文件存储实现 对于简单的应用,可以使用文件存储: // FileMemoryStore.cs public class FileMemoryStore : IMemoryStore { private readonly string _storagePath; private readonly ILogger<FileMemoryStore> _logger; private readonly ConcurrentDictionary<string, List<MemoryItem>> _cache; private readonly SemaphoreSlim _lock = new(1, 1); public FileMemoryStore(string storagePath, ILogger<FileMemoryStore> logger) { _storagePath = storagePath; _logger = logger; _cache = new ConcurrentDictionary<string, List<MemoryItem>>(); // 确保存储目录存在 Directory.CreateDirectory(_storagePath); } public async Task SaveMemoryAsync(MemoryItem memory) { await _lock.WaitAsync(); try { var memories = await LoadUserMemoriesAsync(memory.UserId); memories.Add(memory); await SaveUserMemoriesAsync(memory.UserId, memories); // 更新缓存 _cache.AddOrUpdate(memory.UserId, memories, (_, _) => memories); _logger.LogInformation("保存记忆: UserId={UserId}, Type={Type}", memory.UserId, memory.Type); } finally { _lock.Release(); } } public async Task<IEnumerable<MemoryItem>> SearchAsync( string userId, string query, int limit = 5) { var memories = await LoadUserMemoriesAsync(userId); // 简单的关键词匹配(生产环境应使用向量检索) var results = memories .Where(m => m.Content.Contains(query, StringComparison.OrdinalIgnoreCase)) .OrderByDescending(m => m.Importance) .ThenByDescending(m => m.LastAccessedAt) .Take(limit); // 更新访问时间 foreach (var memory in results) { memory.AccessCount++; memory.LastAccessedAt = DateTime.UtcNow; } return results; } public async Task<IEnumerable<MemoryItem>> GetUserMemoriesAsync( string userId, MemoryType? type = null) { var memories = await LoadUserMemoriesAsync(userId); if (type.HasValue) { return memories.Where(m => m.Type == type.Value) .OrderByDescending(m => m.LastAccessedAt); } return memories.OrderByDescending(m => m.LastAccessedAt); } public async Task DeleteMemoryAsync(string memoryId) { await _lock.WaitAsync(); try { // 从缓存中查找并删除 foreach (var kvp in _cache) { var item = kvp.Value.FirstOrDefault(m => m.Id == memoryId); if (item != null) { kvp.Value.Remove(item); await SaveUserMemoriesAsync(kvp.Key, kvp.Value); break; } } } finally { _lock.Release(); } } public async Task UpdateMemoryAsync(MemoryItem memory) { await _lock.WaitAsync(); try { var memories = await LoadUserMemoriesAsync(memory.UserId); var index = memories.FindIndex(m => m.Id == memory.Id); if (index >= 0) { memories[index] = memory; await SaveUserMemoriesAsync(memory.UserId, memory.UserId, memories); // 更新缓存 if (_cache.TryGetValue(memory.UserId, out var cached)) { cached[index] = memory; } } } finally { _lock.Release(); } } private async Task<List<MemoryItem>> LoadUserMemoriesAsync(string userId) { if (_cache.TryGetValue(userId, out var cached)) { return cached; } var filePath = GetUserMemoryFilePath(userId); if (!File.Exists(filePath)) { return new List<MemoryItem>(); } try { var json = await File.ReadAllTextAsync(filePath); var memories = JsonSerializer.Deserialize<List<MemoryItem>>(json) ?? new List<MemoryItem>(); _cache.AddOrUpdate(userId, memories, (_, _) => memories); return memories; } catch (Exception ex) { _logger.LogError(ex, "加载用户记忆失败: UserId={UserId}", userId); return new List<MemoryItem>(); } } private async Task SaveUserMemoriesAsync(string userId, List<MemoryItem> memories) { var filePath = GetUserMemoryFilePath(userId); var json = JsonSerializer.Serialize(memories, new JsonSerializerOptions { WriteIndented = true }); await File.WriteAllTextAsync(filePath, json); } private string GetUserMemoryFilePath(string userId) { return Path.Combine(_storagePath, $"{userId}_memories.json"); } } 2.3 数据库存储实现 对于生产环境,推荐使用数据库存储: // SqlMemoryStore.cs public class SqlMemoryStore : IMemoryStore { private readonly AppDbContext _context; private readonly ILogger<SqlMemoryStore> _logger; public SqlMemoryStore(AppDbContext context, ILogger<SqlMemoryStore> logger) { _context = context; _logger = logger; } public async Task SaveMemoryAsync(MemoryItem memory) { var entity = new MemoryEntity { Id = memory.Id, UserId = memory.UserId, Content = memory.Content, Type = (int)memory.Type, CreatedAt = memory.CreatedAt, LastAccessedAt = memory.LastAccessedAt, Metadata = JsonSerializer.Serialize(memory.Metadata), Tags = JsonSerializer.Serialize(memory.Tags), Importance = memory.Importance, AccessCount = memory.AccessCount }; _context.Memories.Add(entity); await _context.SaveChangesAsync(); _logger.LogInformation("保存记忆到数据库: Id={Id}, UserId={UserId}", memory.Id, memory.UserId); } public async Task<IEnumerable<MemoryItem>> SearchAsync( string userId, string query, int limit = 5) { // 简化实现:使用LIKE搜索 // 生产环境应使用全文检索或向量搜索 var entities = await _context.Memories .Where(m => m.UserId == userId && m.Content.Contains(query)) .OrderByDescending(m => m.Importance) .ThenByDescending(m => m.LastAccessedAt) .Take(limit) .ToListAsync(); // 更新访问信息 foreach (var entity in entities) { entity.AccessCount++; entity.LastAccessedAt = DateTime.UtcNow; } await _context.SaveChangesAsync(); return entities.Select(MapToMemoryItem); } public async Task<IEnumerable<MemoryItem>> GetUserMemoriesAsync( string userId, MemoryType? type = null) { var query = _context.Memories.Where(m => m.UserId == userId); if (type.HasValue) { query = query.Where(m => m.Type == (int)type.Value); } var entities = await query .OrderByDescending(m => m.LastAccessedAt) .ToListAsync(); return entities.Select(MapToMemoryItem); } public async Task DeleteMemoryAsync(string memoryId) { var entity = await _context.Memories.FindAsync(memoryId); if (entity != null) { _context.Memories.Remove(entity); await _context.SaveChangesAsync(); } } public async Task UpdateMemoryAsync(MemoryItem memory) { var entity = await _context.Memories.FindAsync(memory.Id); if (entity != null) { entity.Content = memory.Content; entity.Metadata = JsonSerializer.Serialize(memory.Metadata); entity.Tags = JsonSerializer.Serialize(memory.Tags); entity.Importance = memory.Importance; entity.LastAccessedAt = DateTime.UtcNow; await _context.SaveChangesAsync(); } } private MemoryItem MapToMemoryItem(MemoryEntity entity) { return new MemoryItem { Id = entity.Id, UserId = entity.UserId, Content = entity.Content, Type = (MemoryType)entity.Type, CreatedAt = entity.CreatedAt, LastAccessedAt = entity.LastAccessedAt, Metadata = JsonSerializer.Deserialize<Dictionary<string, object>>(entity.Metadata) ?? new Dictionary<string, object>(), Tags = JsonSerializer.Deserialize<List<string>>(entity.Tags) ?? new List<string>(), Importance = entity.Importance, AccessCount = entity.AccessCount }; } } // 数据库实体 public class MemoryEntity { [Key] public string Id { get; set; } = string.Empty; [Required] [Index] public string UserId { get; set; } = string.Empty; [Required] public string Content { get; set; } = string.Empty; public int Type { get; set; } public DateTime CreatedAt { get; set; } public DateTime LastAccessedAt { get; set; } public string Metadata { get; set; } = "{}"; public string Tags { get; set; } = "[]"; public float Importance { get; set; } public int AccessCount { get; set; } } 三、用户偏好记忆 3.1 用户偏好模型 用户偏好是最重要的记忆类型之一: // UserPreference.cs public class UserPreference { public string UserId { get; set; } = string.Empty; // 语言偏好 public string PreferredLanguage { get; set; } = "zh-CN"; public string PreferredLanguageCode { get; set; } = "zh"; // 地区偏好 public string TimeZone { get; set; } = "Asia/Shanghai"; public string PreferredCity { get; set; } = string.Empty; // 沟通偏好 public bool PreferDetailedResponse { get; set; } = true; public bool PreferEmoji { get; set; } = false; public string ResponseTone { get; set; } = "professional"; // professional, casual, friendly // 功能偏好 public List<string> EnabledFeatures { get; set; } = new(); public Dictionary<string, object> CustomSettings { get; set; } = new(); // 历史交互 public int TotalConversations { get; set; } public DateTime LastConversationAt { get; set; } public List<string> FrequentlyUsedIntents { get; set; } = new(); // 更新时间 public DateTime UpdatedAt { get; set; } = DateTime.UtcNow; } 3.2 偏好记忆管理器 // UserPreferenceManager.cs public class UserPreferenceManager { private readonly IMemoryStore _memoryStore; private readonly ILogger<UserPreferenceManager> _logger; private readonly ConcurrentDictionary<string, UserPreference> _cache; public UserPreferenceManager( IMemoryStore memoryStore, ILogger<UserPreferenceManager> logger) { _memoryStore = memoryStore; _logger = logger; _cache = new ConcurrentDictionary<string, UserPreference>(); } public async Task<UserPreference> GetPreferenceAsync(string userId) { // 先从缓存获取 if (_cache.TryGetValue(userId, out var cached)) { return cached; } // 从存储加载 var memories = await _memoryStore.GetUserMemoriesAsync( userId, MemoryType.UserPreference); var preferenceMemory = memories.FirstOrDefault(); if (preferenceMemory != null) { var preference = DeserializePreference(preferenceMemory.Content); _cache.AddOrUpdate(userId, preference, (_, _) => preference); return preference; } // 创建默认偏好 var defaultPreference = new UserPreference { UserId = userId }; _cache.AddOrUpdate(userId, defaultPreference, (_, _) => defaultPreference); return defaultPreference; } public async Task SavePreferenceAsync(UserPreference preference) { preference.UpdatedAt = DateTime.UtcNow; var memory = new MemoryItem { UserId = preference.UserId, Content = SerializePreference(preference), Type = MemoryType.UserPreference, Importance = 5.0f, // 用户偏好非常重要 Metadata = new Dictionary<string, object> { { "language", preference.PreferredLanguage }, { "tone", preference.ResponseTone } } }; await _memoryStore.SaveMemoryAsync(memory); // 更新缓存 _cache.AddOrUpdate(preference.UserId, preference, (_, _) => preference); _logger.LogInformation("保存用户偏好: UserId={UserId}", preference.UserId); } public async Task UpdatePreferenceAsync( string userId, Action<UserPreference> updateAction) { var preference = await GetPreferenceAsync(userId); updateAction(preference); await SavePreferenceAsync(preference); } // 从交互中学习偏好 public async Task LearnFromInteractionAsync( string userId, string userMessage, string assistantResponse) { // 分析用户消息,推断偏好 var inferredPreferences = AnalyzePreferences(userMessage); if (inferredPreferences.Any()) { var preference = await GetPreferenceAsync(userId); foreach (var (key, value) in inferredPreferences) { ApplyPreference(preference, key, value); } await SavePreferenceAsync(preference); } } private Dictionary<string, object> AnalyzePreferences(string message) { var preferences = new Dictionary<string, object>(); // 检测语言 if (message.Contains("用英文") || message.Contains("please")) { preferences["PreferredLanguage"] = "en-US"; } else if (message.Contains("用中文")) { preferences["PreferredLanguage"] = "zh-CN"; } // 检测语气 if (message.Contains("幽默") || message.Contains("俏皮")) { preferences["ResponseTone"] = "friendly"; } else if (message.Contains("正式")) { preferences["ResponseTone"] = "professional"; } return preferences; } private void ApplyPreference(UserPreference preference, string key, object value) { var property = typeof(UserPreference).GetProperty(key); if (property != null && property.CanWrite) { property.SetValue(preference, value); } } private string SerializePreference(UserPreference preference) { return JsonSerializer.Serialize(preference, new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }); } private UserPreference DeserializePreference(string json) { return JsonSerializer.Deserialize<UserPreference>( json, new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }) ?? new UserPreference(); } } 四、对话摘要记忆 4.1 自动摘要生成 为了在有限的上下文中存储更多信息,我们需要对对话进行摘要: // ConversationSummarizer.cs public class ConversationSummarizer { private readonly IAIAgent _agent; private readonly ILogger<ConversationSummarizer> _logger; private const string SummaryPrompt = @" 请将以下对话内容压缩成简洁的摘要,包含: 1. 用户的主要需求和意图 2. 已收集的关键信息 3. 未完成的事项 4. 用户偏好(如果有) 对话内容: {conversation_history} 请用中文输出摘要,控制在100字以内。"; public ConversationSummarizer(IAIAgent agent, ILogger<ConversationSummarizer> logger) { _agent = agent; _logger = logger; } public async Task<string> SummarizeAsync(List<ConversationTurn> turns) { if (turns.Count < 4) { return string.Empty; // 对话太短,不需要摘要 } var historyText = string.Join("\n", turns.Select(t => $"{(t.Role == "user" ? "用户" : "助手")}: {t.Content}")); var prompt = SummaryPrompt.Replace("{conversation_history}", historyText); try { var summary = await _agent.CompleteAsync(prompt); _logger.LogInformation("生成对话摘要成功,长度: {Length}", summary.Length); return summary.Trim(); } catch (Exception ex) { _logger.LogError(ex, "生成对话摘要失败"); return GenerateSimpleSummary(turns); } } // 降级方案:简单规则生成摘要 private string GenerateSimpleSummary(List<ConversationTurn> turns) { var userMessages = turns.Where(t => t.Role == "user").ToList(); var firstMessage = userMessages.FirstOrDefault()?.Content ?? ""; var lastMessage = userMessages.LastOrDefault()?.Content ?? ""; return $"用户咨询主题:{ExtractTopic(firstMessage)}。" + $"最后交互:{ExtractTopic(lastMessage)}。" + $"共{userMessages.Count}轮对话。"; } private string ExtractTopic(string message) { // 简单的主题提取 var keywords = new[] { "机票", "酒店", "天气", "订单", "查询", "预订" }; foreach (var keyword in keywords) { if (message.Contains(keyword)) { return keyword; } } return message.Length > 10 ? message.Substring(0, 10) + "..." : message; } } 4.2 智能体记忆集成 将记忆系统集成到智能体中: // AgentWithMemory.cs public class AgentWithMemory { private readonly IAIAgent _agent; private readonly IMemoryStore _memoryStore; private readonly UserPreferenceManager _preferenceManager; private readonly ConversationSummarizer _summarizer; private readonly ILogger<AgentWithMemory> _logger; // 对话历史缓冲区 private readonly Dictionary<string, List<ConversationTurn>> _conversationBuffers; private readonly int _summaryThreshold = 10; // 多少轮对话后生成摘要 public AgentWithMemory( IAIAgent agent, IMemoryStore memoryStore, UserPreferenceManager preferenceManager, ConversationSummarizer summarizer, ILogger<AgentWithMemory> logger) { _agent = agent; _memoryStore = memoryStore; _preferenceManager = preferenceManager; _summarizer = summarizer; _logger = logger; _conversationBuffers = new Dictionary<string, List<ConversationTurn>>(); } public async Task<string> ProcessMessageAsync( string userId, string conversationId, string message) { // 1. 获取用户偏好 var preference = await _preferenceManager.GetPreferenceAsync(userId); // 2. 检索相关记忆 var relevantMemories = await _memoryStore.SearchAsync(userId, message, limit: 3); // 3. 构建上下文 var context = await BuildContextAsync(userId, conversationId, message, relevantMemories, preference); // 4. 调用智能体 var response = await _agent.ProcessAsync(context, message); // 5. 保存对话到缓冲区 await AddToBufferAsync(conversationId, userId, message, response); // 6. 检查是否需要生成摘要 await CheckAndGenerateSummaryAsync(conversationId, userId); // 7. 从交互中学习偏好 await _preferenceManager.LearnFromInteractionAsync(userId, message, response); // 8. 根据偏好格式化响应 response = FormatResponseByPreference(response, preference); return response; } private async Task<ConversationContext> BuildContextAsync( string userId, string conversationId, string message, IEnumerable<MemoryItem> relevantMemories, UserPreference preference) { var context = new ConversationContext { UserId = userId, ConversationId = conversationId }; // 添加记忆到系统提示 var memoryContext = new List<string>(); if (relevantMemories.Any()) { memoryContext.Add("以下是与此用户相关的历史信息:"); foreach (var memory in relevantMemories) { memoryContext.Add($"- {memory.Content}"); } } // 添加用户偏好 memoryContext.Add($"\n用户偏好:语言={preference.PreferredLanguage}," + $"语气={preference.ResponseTone}"); context.SystemMessage = string.Join("\n", memoryContext); // 添加历史对话(从缓冲区) if (_conversationBuffers.TryGetValue(conversationId, out var buffer)) { context.History = buffer.TakeLast(5).ToList(); } return context; } private async Task AddToBufferAsync( string conversationId, string userId, string userMessage, string assistantResponse) { if (!_conversationBuffers.ContainsKey(conversationId)) { _conversationBuffers[conversationId] = new List<ConversationTurn>(); } var buffer = _conversationBuffers[conversationId]; buffer.Add(new ConversationTurn { Role = "user", Content = userMessage, Timestamp = DateTime.UtcNow }); buffer.Add(new ConversationTurn { Role = "assistant", Content = assistantResponse, Timestamp = DateTime.UtcNow }); } private async Task CheckAndGenerateSummaryAsync( string conversationId, string userId) { if (!_conversationBuffers.TryGetValue(conversationId, out var buffer)) { return; } if (buffer.Count >= _summaryThreshold) { // 生成摘要 var summary = await _summarizer.SummarizeAsync(buffer); if (!string.IsNullOrEmpty(summary)) { // 保存摘要记忆 var summaryMemory = new MemoryItem { UserId = userId, Content = summary, Type = MemoryType.ConversationSummary, Metadata = new Dictionary<string, object> { { "conversationId", conversationId }, { "turnCount", buffer.Count / 2 } }, Tags = new List<string> { "conversation_summary" } }; await _memoryStore.SaveMemoryAsync(summaryMemory); _logger.LogInformation("保存对话摘要: UserId={UserId}, Turns={TurnCount}", userId, buffer.Count / 2); } // 清空缓冲区(保留最近的对话) var keepCount = 4; // 保留最近2轮对话 if (buffer.Count > keepCount) { _conversationBuffers[conversationId] = buffer.TakeLast(keepCount).ToList(); } } } private string FormatResponseByPreference( string response, UserPreference preference) { // 根据偏好格式化响应 if (preference.PreferDetailedResponse) { // 确保响应足够详细 if (response.Split('。').Length < 2) { response += " 如果您需要更多信息,请告诉我。"; } } return response; } } 五、向量记忆与语义检索 5.1 向量嵌入服务 对于更智能的记忆检索,我们需要向量嵌入: // EmbeddingService.cs public interface IEmbeddingService { Task<float[]> GetEmbeddingAsync(string text); Task<List<float[]>> GetEmbeddingsAsync(List<string> texts); } public class OpenAIEmbeddingService : IEmbeddingService { private readonly HttpClient _httpClient; private readonly string _apiKey; private readonly string _model; public OpenAIEmbeddingService( HttpClient httpClient, string apiKey, string model = "text-embedding-3-small") { _httpClient = httpClient; _apiKey = apiKey; _model = model; } public async Task<float[]> GetEmbeddingAsync(string text) { var request = new { model = _model, input = text }; var json = JsonSerializer.Serialize(request); var content = new StringContent(json, Encoding.UTF8, "application/json"); _httpClient.DefaultRequestHeaders.Clear(); _httpClient.DefaultRequestHeaders.Add("Authorization", $"Bearer {_apiKey}"); var response = await _httpClient.PostAsync( "https://api.openai.com/v1/embeddings", content); var responseJson = await response.Content.ReadAsStringAsync(); var result = JsonSerializer.Deserialize<EmbeddingResponse>(responseJson); return result?.Data?.FirstOrDefault()?.Embedding ?? throw new Exception("获取嵌入失败"); } public async Task<List<float[]>> GetEmbeddingsAsync(List<string> texts) { var request = new { model = _model, input = texts }; var json = JsonSerializer.Serialize(request); var content = new StringContent(json, Encoding.UTF8, "application/json"); _httpClient.DefaultRequestHeaders.Clear(); _httpClient.DefaultRequestHeaders.Add("Authorization", $"Bearer {_apiKey}"); var response = await _httpClient.PostAsync( "https://api.openai.com/v1/embeddings", content); var responseJson = await response.Content.ReadAsStringAsync(); var result = JsonSerializer.Deserialize<EmbeddingResponse>(responseJson); return result?.Data?.Select(d => d.Embedding).ToList() ?? new List<float[]>(); } } 5.2 向量记忆存储 使用向量数据库实现语义检索: // VectorMemoryStore.cs public class VectorMemoryStore : IMemoryStore { private readonly IEmbeddingService _embeddingService; private readonly IMemoryStore _baseStore; // 底层存储 private readonly ILogger<VectorMemoryStore> _logger; public VectorMemoryStore( IEmbeddingService embeddingService, IMemoryStore baseStore, ILogger<VectorMemoryStore> logger) { _embeddingService = embeddingService; _baseStore = baseStore; _logger = logger; } public async Task SaveMemoryAsync(MemoryItem memory) { // 生成向量嵌入 var embedding = await _embeddingService.GetEmbeddingAsync(memory.Content); memory.Metadata["embedding"] = embedding; // 保存到基础存储 await _baseStore.SaveMemoryAsync(memory); _logger.LogInformation("保存向量记忆: Id={Id}, UserId={UserId}", memory.Id, memory.UserId); } public async Task<IEnumerable<MemoryItem>> SearchAsync( string userId, string query, int limit = 5) { // 生成查询的向量 var queryEmbedding = await _embeddingService.GetEmbeddingAsync(query); // 获取用户所有记忆 var allMemories = await _baseStore.GetUserMemoriesAsync(userId); // 计算相似度并排序 var results = allMemories .Select(m => new { Memory = m, Similarity = CalculateCosineSimilarity( queryEmbedding, m.Metadata.GetValueOrDefault("embedding") as float[] ?? Array.Empty<float>()) }) .Where(x => x.Similarity > 0.7f) // 相似度阈值 .OrderByDescending(x => x.Similarity) .Take(limit) .Select(x => x.Memory); return results; } public async Task<IEnumerable<MemoryItem>> GetUserMemoriesAsync( string userId, MemoryType? type = null) { return await _baseStore.GetUserMemoriesAsync(userId, type); } public async Task DeleteMemoryAsync(string memoryId) { await _baseStore.DeleteMemoryAsync(memoryId); } public async Task UpdateMemoryAsync(MemoryItem memory) { await _baseStore.UpdateMemoryAsync(memory); } private float CalculateCosineSimilarity(float[] a, float[] b) { if (a.Length != b.Length || a.Length == 0) return 0; var dotProduct = 0.0; var normA = 0.0; var normB = 0.0; for (int i = 0; i < a.Length; i++) { dotProduct += a[i] * b[i]; normA += a[i] * a[i]; normB += b[i] * b[i]; } return (float)(dotProduct / (Math.Sqrt(normA) * Math.Sqrt(normB))); } } 六、隐私与安全 6.1 记忆访问控制 // MemoryAccessControl.cs public class MemoryAccessControl { public bool CanAccessMemory(string userId, string memoryUserId, string action) { // 用户只能访问自己的记忆 if (userId != memoryUserId) { return false; } // 管理员可以查看所有记忆 if (IsAdmin(userId)) { return action == "read"; } return true; } public bool CanDeleteMemory(string userId, string memoryUserId) { return userId == memoryUserId; } public bool CanExportMemory(string userId, string targetUserId) { return userId == targetUserId; } private bool IsAdmin(string userId) { // 实现管理员检查逻辑 var adminIds = new[] { "admin@example.com" }; return adminIds.Contains(userId); } } 6.2 数据脱敏 // DataSanitizer.cs public class DataSanitizer { private static readonly Regex EmailRegex = new( @"[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}", RegexOptions.Compiled); private static readonly Regex PhoneRegex = new( @"1[3-9]\d{9}", RegexOptions.Compiled); public string Sanitize(string content) { var sanitized = content; // 脱敏邮箱 sanitized = EmailRegex.Replace(sanitized, "***@***.***"); // 脱敏手机号 sanitized = PhoneRegex.Replace(sanitized, "***********"); return sanitized; } public bool ContainsSensitiveData(string content) { return EmailRegex.IsMatch(content) || PhoneRegex.IsMatch(content); } } 七、性能优化 7.1 记忆缓存策略 // MemoryCacheManager.cs public class MemoryCacheManager { private readonly IMemoryStore _memoryStore; private readonly MemoryCache _cache; private readonly ILogger<MemoryCacheManager> _logger; public MemoryCacheManager(IMemoryStore memoryStore, ILogger<MemoryCacheManager> logger) { _memoryStore = memoryStore; _logger = logger; _cache = MemoryCache.Create(new CacheOptions { SizeLimit = 1000, ExpirationScanFrequency = TimeSpan.FromMinutes(5) }); } public async Task<IEnumerable<MemoryItem>> GetCachedMemoriesAsync( string userId, MemoryType type) { var cacheKey = $"{userId}:{type}"; if (_cache.TryGetValue(cacheKey, out IEnumerable<MemoryItem> cached)) { _logger.LogDebug("命中缓存: {CacheKey}", cacheKey); return cached; } // 从存储加载 var memories = await _memoryStore.GetUserMemoriesAsync(userId, type); var memoryList = memories.ToList(); // 存入缓存 _cache.Set(cacheKey, memoryList, new CacheEntryOptions { Size = memoryList.Count, AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(30) }); return memoryList; } public void InvalidateCache(string userId) { // 清除用户相关的所有缓存 // 注意:这里简化实现,实际需要更复杂的缓存管理 _logger.LogInformation("清除缓存: UserId={UserId}", userId); } } 八、完整示例:个性化助手 整合所有组件,创建完整的个性化助手: // PersonalizedAssistant.cs public class PersonalizedAssistant { private readonly AgentWithMemory _agentWithMemory; private readonly UserPreferenceManager _preferenceManager; private readonly MemoryAccessControl _accessControl; private readonly ILogger<PersonalizedAssistant> _logger; public PersonalizedAssistant( AgentWithMemory agentWithMemory, UserPreferenceManager preferenceManager, MemoryAccessControl accessControl, ILogger<PersonalizedAssistant> logger) { _agentWithMemory = agentWithMemory; _preferenceManager = preferenceManager; _accessControl = accessControl; _logger = logger; } public async Task<AssistantResponse> ProcessAsync(Request request) { // 验证访问权限 if (!_accessControl.CanAccessMemory(request.UserId, request.UserId, "read")) { return new AssistantResponse { Success = false, Message = "您没有权限访问此功能。" }; } try { var response = await _agentWithMemory.ProcessMessageAsync( request.UserId, request.ConversationId, request.Message); return new AssistantResponse { Success = true, Message = response }; } catch (Exception ex) { _logger.LogError(ex, "处理请求失败: UserId={UserId}", request.UserId); return new AssistantResponse { Success = false, Message = "处理您的请求时发生错误,请稍后重试。" }; } } // 用户管理自己的记忆 public async Task<MemoryManagementResult> ManageMemoriesAsync( string userId, MemoryManagementRequest request) { if (!_accessControl.CanAccessMemory(userId, userId, request.Action)) { return new MemoryManagementResult { Success = false, Message = "权限不足" }; } // 实现记忆管理逻辑 return new MemoryManagementResult { Success = true }; } } 九、总结与展望 通过本文的学习,我们已经掌握了智能体长期记忆的核心技术: ✅ 三层记忆架构:短期记忆、长期记忆、向量记忆 ✅ 多种存储方案:文件存储、数据库存储、向量数据库 ✅ 用户偏好管理:自动学习和更新用户偏好 ✅ 对话摘要:自动压缩长对话为摘要 ✅ 向量检索:基于语义相似度的记忆检索 ✅ 隐私安全:访问控制和数据脱敏 ✅ 性能优化:缓存策略提升响应速度 关键收获: 记忆系统是实现真正智能化服务的基础。通过合理设计记忆系统,智能体可以记住用户的偏好、历史交互、重要信息,从而提供更加个性化和连贯的服务体验。在实际应用中,需要根据具体场景选择合适的存储方案,并注意保护用户隐私。 下一篇文章预告: 在第六篇文章中,我们将探索工作流编排。我们将学习如何使用Agent Framework构建复杂的业务工作流,实现多步骤的任务处理和自动化流程。 实践建议: 从简单的文件存储开始,逐步升级到数据库和向量存储 重视用户隐私,实现完善的数据保护机制 定期清理无用记忆,避免存储空间膨胀 监控记忆检索性能,优化查询效率 相关资源: Agent Framework记忆文档 向量数据库选择指南 数据隐私最佳实践 "最好的智能体不是知道最多,而是记住最准的。"