Loading

记忆与持久化:智能体的长期记忆

记忆与持久化:智能体的长期记忆

前言

在前几篇文章中,我们已经掌握了如何创建能说会道的智能体、如何让智能体使用工具、如何管理多轮对话的状态。但是有一个关键问题还没有解决:每次对话结束后,智能体就像失去了记忆一样,下次用户再来,它什么都不记得了。

想象一下这样的场景:

第一次对话:
用户:"我的邮箱是 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 };
    }
}

九、总结与展望

通过本文的学习,我们已经掌握了智能体长期记忆的核心技术:

  1. 三层记忆架构:短期记忆、长期记忆、向量记忆
  2. 多种存储方案:文件存储、数据库存储、向量数据库
  3. 用户偏好管理:自动学习和更新用户偏好
  4. 对话摘要:自动压缩长对话为摘要
  5. 向量检索:基于语义相似度的记忆检索
  6. 隐私安全:访问控制和数据脱敏
  7. 性能优化:缓存策略提升响应速度

关键收获:

记忆系统是实现真正智能化服务的基础。通过合理设计记忆系统,智能体可以记住用户的偏好、历史交互、重要信息,从而提供更加个性化和连贯的服务体验。在实际应用中,需要根据具体场景选择合适的存储方案,并注意保护用户隐私。

下一篇文章预告:

在第六篇文章中,我们将探索工作流编排。我们将学习如何使用Agent Framework构建复杂的业务工作流,实现多步骤的任务处理和自动化流程。


实践建议:

  1. 从简单的文件存储开始,逐步升级到数据库和向量存储
  2. 重视用户隐私,实现完善的数据保护机制
  3. 定期清理无用记忆,避免存储空间膨胀
  4. 监控记忆检索性能,优化查询效率

相关资源:


"最好的智能体不是知道最多,而是记住最准的。"

posted @ 2026-03-13 10:26  饭勺oO  阅读(479)  评论(0)    收藏  举报