如果AI角色拥有像人类一样的记忆?—— 一套可复用的通用AI记忆系统架构蓝图剖析

150 阅读20分钟

参考 Steam 神作 RimWorld T0模组的AI记忆系统深度技术分析

目录

  1. 系统概述
  2. 核心架构设计
  3. 四层记忆系统详解
  4. 记忆类型和数据结构
  5. 智能注入系统
  6. 记忆检索和匹配算法
  7. 高级评分系统
  8. 常识知识库
  9. 设计思路和架构模式
  10. 技术亮点和创新点
  11. 可借鉴的设计理念
  12. HTTP请求和对话发送机制
  13. 记忆筛选和发送逻辑
  14. 避免重复对话的机制
  15. 对话历史管理

系统概述

下面我将参考一个为RimWorld游戏模组设计的先进AI记忆系统,模拟人类记忆的工作机制,为游戏中的角色(Pawn)提供持久化、上下文感知的记忆管理能力。该系统通过多层级记忆结构、智能检索算法和动态评分机制,实现了接近人类记忆特性的AI行为。

核心特性

  • 四层记忆架构:模拟人类记忆的超短期、短期、中期、长期记忆
  • 上下文感知检索:基于场景分析的智能记忆注入
  • 多因子评分算法:综合考虑相关性、时效性、重要性等多个维度
  • 向量数据库集成:支持语义搜索和相似度匹配
  • 用户可编辑性:支持用户对记忆进行编辑、标注和固定
  • 常识知识库:支持全局和角色特定的知识管理

核心架构设计

系统架构图

┌─────────────────────────────────────────────────────────────┐
│                     RimWorld AI记忆系统                      │
├─────────────────────────────────────────────────────────────┤
│                                                               │
│  ┌──────────────┐    ┌──────────────┐    ┌──────────────┐   │
│  │  记忆类型层  │    │  记忆层级    │    │  标签系统    │   │
│  │ MemoryTypes  │    │ MemoryLayer  │    │  TagSystem   │   │
│  └──────────────┘    └──────────────┘    └──────────────┘   │
│           ↓                  ↓                  ↓            │
│  ┌──────────────────────────────────────────────────────┐  │
│  │              四层记忆系统 (Four-Layer Memory)          │  │
│  │  ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌────────┐  │  │
│  │  │  Active  │ │Situational│ │EventLog  │ │Archive │  │  │
│  │  │超短期记忆│ │  短期记忆  │ │ 中期记忆 │ │长期记忆│  │  │
│  │  └──────────┘ └──────────┘ └──────────┘ └────────┘  │  │
│  └──────────────────────────────────────────────────────┘  │
│           ↓                                                  │
│  ┌──────────────────────────────────────────────────────┐  │
│  │              高级评分系统 (AdvancedScoring)          │  │
│  │  - 场景分析 (SceneAnalyzer)                           │  │
│  │  - 多因子评分 (Multi-factor Scoring)                  │  │
│  │  - 动态权重调整 (Dynamic Weight Adjustment)           │  │
│  └──────────────────────────────────────────────────────┘  │
│           ↓                                                  │
│  ┌──────────────────────────────────────────────────────┐  │
│  │           智能注入管理器 (SmartInjectionManager)      │  │
│  │  1. 指令/规则 (Current Guidelines)                    │  │
│  │  2. 常识/背景 (World Knowledge)                       │  │
│  │  3. 角色记忆 (Character Memories)                      │  │
│  └──────────────────────────────────────────────────────┘  │
│           ↓                                                  │
│  ┌──────────────────────────────────────────────────────┐  │
│  │           常识知识库 (CommonKnowledgeLibrary)          │  │
│  │  - 全局知识 (Global Knowledge)                        │  │
│  │  - 角色特定知识 (Character-specific Knowledge)        │  │
│  │  - 向量检索 (Vector Search)                           │  │
│  └──────────────────────────────────────────────────────┘  │
│                                                               │
└─────────────────────────────────────────────────────────────┘

设计原则

  1. 分层存储:按照记忆的重要性和时效性,将记忆分布在不同层级
  2. 上下文感知:根据当前场景动态调整记忆检索策略
  3. 多维度评分:综合多个因子计算记忆的相关性得分
  4. 用户可控:允许用户编辑、标注和管理记忆内容
  5. 性能优化:使用向量数据库和缓存机制提升检索效率

四层记忆系统详解

记忆层级定义

/// <summary>
/// 记忆层级
/// </summary>
public enum MemoryLayer
{
    Active,         // 超短期记忆 (Active Buffer Memory)
    Situational,    // 短期记忆 (Situational Context Memory)
    EventLog,       // 中期记忆 (Event Log Summary)
    Archive         // 长期记忆 (Colony Lore & Persona Archive)
}

各层级特性

1. Active(超短期记忆)
  • 容量限制:极小(通常5-10条)
  • 保留时间:数秒到数分钟
  • 内容类型:当前正在进行的对话、即时感知
  • 用途:维持对话连贯性,处理即时上下文
  • 更新频率:极高,实时更新

设计理念:模拟人类的工作记忆(Working Memory),用于临时存储当前正在处理的信息。

2. Situational(短期记忆)
  • 容量限制:中等(通常20-50条)
  • 保留时间:数小时到数天
  • 内容类型:近期事件、当前任务、短期目标
  • 用途:维持短期上下文,支持连贯的行为决策
  • 更新频率:中等,定期更新

设计理念:模拟人类的短期记忆(Short-term Memory),存储当前情境下的相关信息。

3. EventLog(中期记忆)
  • 容量限制:较大(通常100-200条)
  • 保留时间:数天到数周
  • 内容类型:重要事件、关键对话、里程碑事件
  • 用途:提供中期上下文,支持回忆和反思
  • 更新频率:较低,事件驱动更新

设计理念:模拟人类的情景记忆(Episodic Memory),存储具体的事件和经历。

4. Archive(长期记忆)
  • 容量限制:极大(通常1000+条)
  • 保留时间:永久或游戏周期
  • 内容类型:角色背景、重要关系、长期知识
  • 用途:维持角色一致性,支持长期行为模式
  • 更新频率:极低,仅在重要事件时更新

设计理念:模拟人类的语义记忆(Semantic Memory)和自传体记忆(Autobiographical Memory),存储抽象知识和重要人生经历。

记忆流转机制

新记忆创建
    ↓
┌─────────┐
│ Active  │ ←──┐
└─────────┘    │ 衰减/重要性评估
    ↓          │
┌─────────────┐│
│Situational  ││
└─────────────┘│
    ↓          │
┌─────────────┐│
│  EventLog   ││
└─────────────┘│
    ↓          │
┌─────────────┐│
│   Archive   ││
└─────────────┘│
    ↓          │
  删除/归档  ──┘

流转规则

  1. 新记忆首先进入Active层
  2. 根据重要性和活跃度,记忆会向更深层级迁移
  3. 低重要性记忆会逐渐衰减并被删除
  4. 高重要性记忆会保留在Archive层
  5. 用户可以手动固定记忆,防止被删除

记忆类型和数据结构

记忆类型定义

/// <summary>
/// 记忆类型
/// </summary>
public enum MemoryType
{
    Conversation,   // 对话(RimTalk生成的完整对话内容)
    Action,         // 行动(工作、战斗等)
    Observation,    // 观察(未实现)
    Event,          // 事件
    Emotion,        // 情绪
    Relationship,   // 关系
    Internal        // 内部上下文(数据库查询结果,不显示给用户)
}

各类型说明

1. Conversation(对话记忆)
  • 内容:完整的对话记录,包括说话者、内容、时间
  • 重要性:中等,根据对话内容动态评估
  • 用途:维持对话连贯性,支持角色关系发展
  • 示例:"昨天和John讨论了种植计划"
2. Action(行动记忆)
  • 内容:角色执行的重要行动,如工作、战斗、建造
  • 重要性:根据行动的影响范围和结果评估
  • 用途:支持行为决策,维持角色一致性
  • 示例:"建造了新的防御塔"
3. Observation(观察记忆)
  • 内容:角色观察到的环境变化和其他角色的行为
  • 重要性:根据观察的新颖性和相关性评估
  • 用途:支持环境感知,影响行为决策
  • 示例:"注意到敌人从北方接近"
4. Event(事件记忆)
  • 内容:游戏中的重要事件,如袭击、贸易、灾难
  • 重要性:高,根据事件的影响范围评估
  • 用途:影响角色情绪和行为,支持剧情发展
  • 示例:"基地遭受了袭击"
5. Emotion(情绪记忆)
  • 内容:角色的情绪状态和情绪变化
  • 重要性:中等,根据情绪强度评估
  • 用途:影响角色行为和对话内容
  • 示例:"因为朋友的死亡而感到悲伤"
6. Relationship(关系记忆)
  • 内容:角色之间的关系状态和互动历史
  • 重要性:高,关系是游戏的核心机制
  • 用途:影响对话内容、行为决策和社交互动
  • 示例:"和Mary是好朋友"
7. Internal(内部上下文)
  • 内容:数据库查询结果、系统生成的上下文信息
  • 重要性:不显示给用户,仅用于系统内部处理
  • 用途:支持AI决策,提供额外的上下文信息
  • 示例:"查询到的角色属性信息"

记忆条目数据结构

/// <summary>
/// 新的记忆条目 - 支持标签化和编辑
/// </summary>
public class MemoryEntry : IExposable
{
    // 基础信息
    public string id;                   // 唯一ID
    public string content;              // 内容
    public MemoryType type;             // 类型
    public MemoryLayer layer;           // 层级
    public int timestamp;               // 时间戳
    
    // 重要性和活跃度
    public float importance;            // 重要性 (0-1)
    public float activity;              // 活跃度 (随时间衰减)
    
    // 关联信息
    public string relatedPawnId;        // 相关小人ID
    public string relatedPawnName;      // 相关小人名字
    public string location;             // 地点
    public List<string> tags;           // 标签(中文)
    public List<string> keywords;       // 关键词
    
    // 元数据
    public bool isUserEdited;           // 是否被用户编辑过
    public bool isPinned;               // 是否固定(不会被删除)
    public string notes;                // 用户备注
    public string aiCacheKey;           // AI总结的缓存键
}

数据结构设计亮点

  1. 唯一标识:每个记忆都有唯一的ID,支持精确引用
  2. 类型化存储:通过MemoryType区分不同类型的记忆
  3. 层级管理:通过MemoryLayer实现记忆的分层存储
  4. 重要性评分:importance字段支持记忆的重要性评估
  5. 活跃度衰减:activity字段模拟记忆的自然遗忘
  6. 标签系统:tags字段支持灵活的记忆分类和检索
  7. 关键词提取:keywords字段支持基于关键词的快速检索
  8. 用户编辑:isUserEdited字段保护用户修改的内容
  9. 固定机制:isPinned字段防止重要记忆被删除
  10. 缓存优化:aiCacheKey字段支持AI生成的总结缓存

智能注入系统

系统概述

智能注入系统负责在对话生成时,根据当前上下文智能选择和注入相关的记忆、知识和指令。该系统通过分层注入策略,确保AI角色能够生成连贯、相关、符合角色设定的对话内容。

注入架构

/// <summary>
/// 智能注入上下文
/// ? v3.3.20: 重写知识注入逻辑以支持指令分区
/// ? 注入顺序
///   1. Current Guidelines(指令/规则) - 系统提示
///   2. World Knowledge(常识/背景) - 共享知识
///   3. Character Memories(角色记忆) - 个人经历
/// </summary>
public static string InjectSmartContext(
    Pawn speaker,
    Pawn listener,
    string context,
    int maxMemories = 10,
    int maxKnowledge = 5)

三层注入策略

1. Current Guidelines(指令/规则层)
  • 内容:系统级指令、角色设定、行为规则
  • 优先级:最高,确保角色行为符合设定
  • 注入时机:每次对话生成时
  • 示例
    你是一个名叫Alice的殖民者,性格开朗,喜欢种植作物。
    在对话中要体现你的性格特点,使用友好、热情的语气。
    
2. World Knowledge(常识/背景层)
  • 内容:游戏世界的常识、背景设定、共享知识
  • 优先级:中等,提供上下文信息
  • 注入时机:根据相关性动态选择
  • 示例
    当前季节:春季
    基地位置:温带森林
    主要作物:土豆、玉米
    
3. Character Memories(角色记忆层)
  • 优先级:动态,根据场景和相关性调整
  • 注入时机:根据评分系统选择最相关的记忆
  • 示例
    昨天和John讨论了种植计划
    上周建造了新的防御塔
    因为朋友的死亡而感到悲伤
    

注入流程

开始对话生成
    ↓
┌─────────────────────────────────┐
│  1. 分析当前场景                │
│     - 识别对话类型               │
│     - 确定参与者                 │
│     - 提取上下文关键词           │
└─────────────────────────────────┘
    ↓
┌─────────────────────────────────┐
│  2. 注入指令/规则               │
│     - 角色设定                   │
│     - 行为规则                   │
│     - 系统提示                   │
└─────────────────────────────────┘
    ↓
┌─────────────────────────────────┐
│  3. 注入常识/背景               │
│     - 游戏世界知识               │
│     - 背景设定                   │
│     - 共享知识                   │
└─────────────────────────────────┘
    ↓
┌─────────────────────────────────┐
│  4. 注入角色记忆                │
│     - 评分排序                   │
│     - 多样性选择                 │
│     - 上下文相关性               │
└─────────────────────────────────┘
    ↓
┌─────────────────────────────────┐
│  5. 生成对话内容                │
│     - 基于注入的上下文           │
│     - 符合角色设定               │
│     - 保持连贯性                 │
└─────────────────────────────────┘

注入优化策略

  1. 相关性过滤:只注入与当前上下文相关的记忆和知识
  2. 数量控制:通过maxMemories和maxKnowledge参数控制注入数量
  3. 多样性保证:避免重复注入相同类型的记忆
  4. 时效性考虑:优先注入近期的记忆
  5. 重要性加权:高重要性记忆优先注入
  6. 场景适配:根据不同场景调整注入策略

记忆检索和匹配算法

检索系统概述

记忆检索系统是整个记忆系统的核心,负责根据当前上下文从海量记忆中快速、准确地找到最相关的记忆。该系统结合了传统关键词匹配、向量语义搜索和多因子评分算法,实现了高效、精准的记忆检索。

检索流程

检索请求
    ↓
┌─────────────────────────────────┐
│  1. 场景分析                    │
│     - 识别对话场景类型           │
│     - 提取上下文关键词           │
│     - 确定检索权重               │
└─────────────────────────────────┘
    ↓
┌─────────────────────────────────┐
│  2. 多路检索                    │
│     ├─ 关键词匹配检索           │
│     ├─ 向量语义检索             │
│     └─ 标签过滤检索             │
└─────────────────────────────────┘
    ↓
┌─────────────────────────────────┐
│  3. 结果融合                    │
│     - 合并多路检索结果           │
│     - 去重和排序                 │
│     - 应用多样性补偿             │
└─────────────────────────────────┘
    ↓
┌─────────────────────────────────┐
│  4. 多因子评分                  │
│     - 上下文相关性               │
│     - 时间新近度                 │
│     - 重要性                     │
│     - 多样性补偿                 │
│     - 层级优先级                 │
└─────────────────────────────────┘
    ↓
┌─────────────────────────────────┐
│  5. 结果输出                    │
│     - 返回Top-N记忆              │
│     - 提供评分详情               │
│     - 支持调试和分析             │
└─────────────────────────────────┘

关键词匹配算法

/// <summary>
/// 关键词匹配得分
/// </summary>
public static float CalculateKeywordMatchScore(
    string context,
    List<string> keywords)
{
    if (keywords == null || keywords.Count == 0)
        return 0f;
    
    float score = 0f;
    int matchedCount = 0;
    
    foreach (var keyword in keywords)
    {
        if (context.Contains(keyword, StringComparison.OrdinalIgnoreCase))
        {
            matchedCount++;
            score += 1f / keywords.Count;  // 每个匹配的关键词贡献相等
        }
    }
    
    return score;
}

算法特点

  • 简单高效,适合快速过滤
  • 支持大小写不敏感匹配
  • 每个关键词贡献相等权重
  • 可扩展为加权关键词匹配

向量语义检索

/// <summary>
/// 向量相似度计算
/// </summary>
public static float CalculateVectorSimilarity(
    float[] vector1,
    float[] vector2)
{
    if (vector1 == null || vector2 == null || 
        vector1.Length != vector2.Length)
        return 0f;
    
    float dotProduct = 0f;
    float norm1 = 0f;
    float norm2 = 0f;
    
    for (int i = 0; i < vector1.Length; i++)
    {
        dotProduct += vector1[i] * vector2[i];
        norm1 += vector1[i] * vector1[i];
        norm2 += vector2[i] * vector2[i];
    }
    
    if (norm1 == 0 || norm2 == 0)
        return 0f;
    
    return dotProduct / (float)Math.Sqrt(norm1 * norm2);
}

算法特点

  • 使用余弦相似度计算语义相似性
  • 支持高维向量表示
  • 适合处理语义相关性
  • 需要预训练的词向量或句子向量模型

多因子评分算法

/// <summary>
/// 计算记忆综合得分
/// </summary>
public static float CalculateMemoryScore(
    MemoryEntry memory,
    string context,
    SceneType scene,
    ScoringWeights weights,
    List<MemoryEntry> selectedMemories)
{
    // 1. 上下文相关性
    float contextScore = CalculateContextRelevance(memory, context);
    
    // 2. 时间新近度
    float recencyScore = CalculateRecencyScore(memory);
    
    // 3. 重要性
    float importanceScore = memory.importance;
    
    // 4. 多样性补偿
    float diversityScore = CalculateDiversityScore(memory, selectedMemories);
    
    // 5. 层级优先级
    float layerScore = CalculateLayerPriorityScore(memory.layer);
    
    // 6. 综合得分
    float totalScore = 
        contextScore * weights.ContextRelevance +
        recencyScore * weights.Recency +
        importanceScore * weights.Importance +
        diversityScore * weights.Diversity +
        layerScore * weights.LayerPriority;
    
    return totalScore;
}

各评分因子详解

1. 上下文相关性(Context Relevance)
  • 权重:0.40(最高)
  • 计算方法:结合关键词匹配和向量相似度
  • 目的:确保检索的记忆与当前上下文高度相关
  • 实现
    private static float CalculateContextRelevance(
        MemoryEntry memory, 
        string context)
    {
        float keywordScore = CalculateKeywordMatchScore(
            context, memory.keywords);
        
        float vectorScore = CalculateVectorSimilarity(
            memory.vector, contextVector);
        
        return keywordScore * 0.6f + vectorScore * 0.4f;
    }
    
2. 时间新近度(Recency)
  • 权重:0.20
  • 计算方法:基于时间戳的指数衰减
  • 目的:优先选择近期的记忆
  • 实现
    private static float CalculateRecencyScore(MemoryEntry memory)
    {
        int currentTick = Find.TickManager.TicksGame;
        int age = currentTick - memory.timestamp;
        
        // 指数衰减:越近的记忆得分越高
        float decayRate = 0.0001f;  // 衰减率
        return (float)Math.Exp(-decayRate * age);
    }
    
3. 重要性(Importance)
  • 权重:0.20
  • 计算方法:使用记忆的importance字段
  • 目的:优先选择重要的记忆
  • 实现
    private static float CalculateImportanceScore(MemoryEntry memory)
    {
        return memory.importance;
    }
    
4. 多样性补偿(Diversity)
  • 权重:0.10
  • 计算方法:基于已选记忆的相似度
  • 目的:避免选择过于相似的记忆
  • 实现
    private static float CalculateDiversityScore(
        MemoryEntry memory,
        List<MemoryEntry> selectedMemories)
    {
        if (selectedMemories == null || selectedMemories.Count == 0)
            return 1f;
        
        float minSimilarity = 1f;
        foreach (var selected in selectedMemories)
        {
            float similarity = CalculateVectorSimilarity(
                memory.vector, selected.vector);
            minSimilarity = Math.Min(minSimilarity, similarity);
        }
        
        // 相似度越低,多样性得分越高
        return 1f - minSimilarity;
    }
    
5. 层级优先级(Layer Priority)
  • 权重:0.10
  • 计算方法:基于记忆层级的固定权重
  • 目的:优先选择更近层级的记忆
  • 实现
    private static float CalculateLayerPriorityScore(MemoryLayer layer)
    {
        switch (layer)
        {
            case MemoryLayer.Active:
                return 1.0f;
            case MemoryLayer.Situational:
                return 0.8f;
            case MemoryLayer.EventLog:
                return 0.6f;
            case MemoryLayer.Archive:
                return 0.4f;
            default:
                return 0.5f;
        }
    }
    

高级评分系统

场景分析器

场景分析器是高级评分系统的核心组件,负责自动识别当前对话的场景类型,并根据场景动态调整评分权重。

场景类型定义

/// <summary>
/// 场景类型,自动识别对话场景
/// </summary>
public enum SceneType
{
    Casual,         // 日常闲聊
    EmotionalTalk,  // 情感对话
    WorkDiscussion, // 工作讨论
    HistoryRecall,  // 回忆过去
    Emergency,      // 紧急情况
    Introduction    // 介绍认识
}

各场景特征

1. Casual(日常闲聊)
  • 特征:轻松、随意的话题
  • 关键词:天气、食物、娱乐、日常活动
  • 权重配置
    • ContextRelevance: 0.35
    • Recency: 0.25
    • Importance: 0.15
    • Diversity: 0.15
    • LayerPriority: 0.10
2. EmotionalTalk(情感对话)
  • 特征:涉及情感、情绪、关系
  • 关键词:爱、恨、悲伤、快乐、担心
  • 权重配置
    • ContextRelevance: 0.30
    • Recency: 0.15
    • Importance: 0.30 // 重要性提高
    • Diversity: 0.10
    • LayerPriority: 0.15
3. WorkDiscussion(工作讨论)
  • 特征:涉及任务、工作、计划
  • 关键词:工作、任务、计划、建造、种植
  • 权重配置
    • ContextRelevance: 0.45 // 相关性提高
    • Recency: 0.20
    • Importance: 0.20
    • Diversity: 0.10
    • LayerPriority: 0.05
4. HistoryRecall(回忆过去)
  • 特征:涉及过去的事件、经历
  • 关键词:记得、以前、那时候、过去
  • 权重配置
    • ContextRelevance: 0.40
    • Recency: 0.10 // 时效性降低
    • Importance: 0.25
    • Diversity: 0.15
    • LayerPriority: 0.10
5. Emergency(紧急情况)
  • 特征:涉及危险、紧急、危机
  • 关键词:危险、紧急、帮助、逃跑
  • 权重配置
    • ContextRelevance: 0.50 // 相关性最高
    • Recency: 0.30 // 时效性提高
    • Importance: 0.15
    • Diversity: 0.05
    • LayerPriority: 0.00
6. Introduction(介绍认识)
  • 特征:涉及自我介绍、相互认识
  • 关键词:你好、我是、认识
  • 权重配置
    • ContextRelevance: 0.35
    • Recency: 0.20
    • Importance: 0.20
    • Diversity: 0.15
    • LayerPriority: 0.10

场景识别算法

/// <summary>
/// 识别对话场景
/// </summary>
public static SceneType IdentifyScene(string context)
{
    // 定义各场景的关键词
    Dictionary<SceneType, List<string>> sceneKeywords = new Dictionary<SceneType, List<string>>
    {
        { SceneType.Casual, new List<string> { "天气", "食物", "娱乐", "聊天" } },
        { SceneType.EmotionalTalk, new List<string> { "爱", "恨", "悲伤", "快乐", "担心" } },
        { SceneType.WorkDiscussion, new List<string> { "工作", "任务", "计划", "建造", "种植" } },
        { SceneType.HistoryRecall, new List<string> { "记得", "以前", "那时候", "过去" } },
        { SceneType.Emergency, new List<string> { "危险", "紧急", "帮助", "逃跑" } },
        { SceneType.Introduction, new List<string> { "你好", "我是", "认识" } }
    };
    
    // 计算各场景的匹配得分
    Dictionary<SceneType, float> sceneScores = new Dictionary<SceneType, float>();
    foreach (var kvp in sceneKeywords)
    {
        float score = 0f;
        foreach (var keyword in kvp.Value)
        {
            if (context.Contains(keyword))
            {
                score += 1f;
            }
        }
        sceneScores[kvp.Key] = score;
    }
    
    // 返回得分最高的场景
    return sceneScores.OrderByDescending(x => x.Value).First().Key;
}

动态权重调整

/// <summary>
/// 根据场景获取评分权重
/// </summary>
public static ScoringWeights GetWeightsForScene(SceneType scene)
{
    switch (scene)
    {
        case SceneType.Casual:
            return new ScoringWeights
            {
                ContextRelevance = 0.35f,
                Recency = 0.25f,
                Importance = 0.15f,
                Diversity = 0.15f,
                LayerPriority = 0.10f
            };
        case SceneType.EmotionalTalk:
            return new ScoringWeights
            {
                ContextRelevance = 0.30f,
                Recency = 0.15f,
                Importance = 0.30f,
                Diversity = 0.10f,
                LayerPriority = 0.15f
            };
        case SceneType.WorkDiscussion:
            return new ScoringWeights
            {
                ContextRelevance = 0.45f,
                Recency = 0.20f,
                Importance = 0.20f,
                Diversity = 0.10f,
                LayerPriority = 0.05f
            };
        case SceneType.HistoryRecall:
            return new ScoringWeights
            {
                ContextRelevance = 0.40f,
                Recency = 0.10f,
                Importance = 0.25f,
                Diversity = 0.15f,
                LayerPriority = 0.10f
            };
        case SceneType.Emergency:
            return new ScoringWeights
            {
                ContextRelevance = 0.50f,
                Recency = 0.30f,
                Importance = 0.15f,
                Diversity = 0.05f,
                LayerPriority = 0.00f
            };
        case SceneType.Introduction:
            return new ScoringWeights
            {
                ContextRelevance = 0.35f,
                Recency = 0.20f,
                Importance = 0.20f,
                Diversity = 0.15f,
                LayerPriority = 0.10f
            };
        default:
            return new ScoringWeights();  // 使用默认权重
    }
}

常识知识库

系统概述

常识知识库用于存储和管理游戏世界的共享知识,包括全局知识和角色特定知识。该系统支持知识的分类、检索和注入,为AI角色提供丰富的背景信息。

知识条目结构

/// <summary>
/// 常识条目
/// </summary>
public class CommonKnowledgeEntry : IExposable
{
    public string id;
    public string tag;          // 标签(支持多个,用逗号分隔)
    public string content;      // 内容(用于注入)
    public float importance;    // 重要性
    public List<string> keywords; // 关键词(可选,用户手动设置,不导出导入)
    public bool isEnabled;      // 是否启用
    public bool isUserEdited;   // 是否被用户编辑过(用于保护手动修改)
    
    // 目标Pawn限制(用于角色专属常识)
    public int targetPawnId = -1;  // -1表示全局,否则只对特定Pawn有效
    
    // 创建时间戳和原始事件文本(用于动态更新时间前缀)
    public int creationTick = -1;       // -1表示永久,>=0表示创建时的游戏tick
    public string originalEventText = "";  // 保存不带时间前缀的原始事件文本
}

知识分类

1. 全局知识(Global Knowledge)
  • targetPawnId:-1
  • 适用范围:所有角色
  • 内容类型:游戏世界设定、通用规则、环境信息
  • 示例
    标签: 世界设定
    内容: 这是一个位于温带森林的殖民地,气候温和,四季分明。
    
2. 角色特定知识(Character-specific Knowledge)
  • targetPawnId:特定角色的ID
  • 适用范围:仅对特定角色有效
  • 内容类型:角色背景、个人经历、特殊关系
  • 示例
    标签: 角色背景
    内容: Alice是一名来自城市的殖民者,擅长种植作物。
    目标角色: Alice
    

知识检索算法

/// <summary>
/// 检索相关知识
/// </summary>
public static List<CommonKnowledgeEntry> RetrieveKnowledge(
    Pawn pawn,
    string context,
    int maxKnowledge = 5)
{
    // 1. 获取所有启用的知识
    var allKnowledge = GetAllEnabledKnowledge();
    
    // 2. 过滤出适用于当前角色的知识
    var applicableKnowledge = allKnowledge.Where(k => 
        k.targetPawnId == -1 || k.targetPawnId == pawn.thingIDNumber).ToList();
    
    // 3. 计算每条知识的相关性得分
    var scoredKnowledge = applicableKnowledge.Select(k => new
    {
        Knowledge = k,
        Score = CalculateKnowledgeRelevance(k, context)
    });
    
    // 4. 按得分排序并返回Top-N
    return scoredKnowledge
        .OrderByDescending(x => x.Score)
        .Take(maxKnowledge)
        .Select(x => x.Knowledge)
        .ToList();
}

/// <summary>
/// 计算知识相关性得分
/// </summary>
private static float CalculateKnowledgeRelevance(
    CommonKnowledgeEntry knowledge,
    string context)
{
    float score = 0f;
    
    // 1. 标签匹配
    if (knowledge.keywords != null && knowledge.keywords.Count > 0)
    {
        foreach (var keyword in knowledge.keywords)
        {
            if (context.Contains(keyword))
            {
                score += 0.5f;
            }
        }
    }
    
    // 2. 重要性加权
    score += knowledge.importance * 0.5f;
    
    return score;
}

知识注入策略

/// <summary>
/// 注入知识到上下文
/// </summary>
public static string InjectKnowledge(
    List<CommonKnowledgeEntry> knowledgeList)
{
    if (knowledgeList == null || knowledgeList.Count == 0)
        return "";
    
    StringBuilder sb = new StringBuilder();
    sb.AppendLine("=== 世界知识 ===");
    
    foreach (var knowledge in knowledgeList)
    {
        sb.AppendLine($"[{knowledge.tag}] {knowledge.content}");
    }
    
    return sb.ToString();
}

知识管理功能

  1. 添加知识:支持手动添加新的知识条目
  2. 编辑知识:允许用户修改现有知识
  3. 删除知识:支持删除不需要的知识
  4. 导入导出:支持知识的导入和导出
  5. 标签管理:支持知识的分类和标签化
  6. 启用/禁用:支持知识的启用和禁用
  7. 重要性设置:支持设置知识的重要性权重

设计思路和架构模式

核心设计理念

1. 模拟人类记忆

该系统的核心设计理念是模拟人类记忆的工作机制,通过分层存储、自然衰减、重要性评估等机制,实现接近人类记忆特性的AI行为。

人类记忆模型对照

  • 工作记忆(Working Memory) → Active层
  • 短期记忆(Short-term Memory) → Situational层
  • 情景记忆(Episodic Memory) → EventLog层
  • 语义记忆(Semantic Memory) → Archive层
2. 上下文感知

系统通过场景分析和动态权重调整,实现了上下文感知的记忆检索。这意味着AI角色能够根据当前情境,智能地选择最相关的记忆和知识。

实现方式

  • 场景识别:自动识别对话场景类型
  • 动态权重:根据场景调整评分权重
  • 上下文相关性:计算记忆与当前上下文的相关性
3. 多维度评估

系统通过多因子评分算法,综合考虑相关性、时效性、重要性、多样性等多个维度,确保检索结果的准确性和多样性。

评分因子

  • 上下文相关性(Context Relevance)
  • 时间新近度(Recency)
  • 重要性(Importance)
  • 多样性补偿(Diversity)
  • 层级优先级(Layer Priority)

架构模式

1. 分层架构(Layered Architecture)

系统采用分层架构,将记忆按照重要性和时效性分布在不同层级,每层有不同的容量限制和保留时间。

优点

  • 清晰的职责分离
  • 易于维护和扩展
  • 符合人类记忆模型

实现

Active → Situational → EventLog → Archive
2. 策略模式(Strategy Pattern)

系统使用策略模式实现不同的评分策略,根据场景类型动态选择评分权重。

优点

  • 灵活的评分策略
  • 易于添加新的场景类型
  • 符合开闭原则

实现

public static ScoringWeights GetWeightsForScene(SceneType scene)
{
    switch (scene)
    {
        case SceneType.Casual:
            return new ScoringWeights { ... };
        case SceneType.EmotionalTalk:
            return new ScoringWeights { ... };
        // ...
    }
}
3. 工厂模式(Factory Pattern)

系统使用工厂模式创建不同类型的记忆条目和知识条目。

优点

  • 统一的对象创建接口
  • 易于扩展新的记忆类型
  • 降低耦合度

实现

public static MemoryEntry CreateMemoryEntry(
    MemoryType type,
    string content,
    MemoryLayer layer)
{
    return new MemoryEntry
    {
        id = GenerateId(),
        type = type,
        content = content,
        layer = layer,
        timestamp = Find.TickManager.TicksGame,
        importance = CalculateInitialImportance(type, content),
        activity = 1.0f
    };
}
4. 观察者模式(Observer Pattern)

系统使用观察者模式监听游戏事件,自动创建和更新记忆。

优点

  • 自动化的记忆创建
  • 实时的事件响应
  • 松耦合的事件处理

实现

public class MemoryObserver : IEventObserver
{
    public void OnEventOccurred(GameEvent evt)
    {
        if (evt.IsImportant())
        {
            CreateMemoryFromEvent(evt);
        }
    }
}
5. 装饰器模式(Decorator Pattern)

系统使用装饰器模式为记忆添加额外的功能,如标签、备注、固定等。

优点

  • 灵活的功能扩展
  • 不修改原有代码
  • 组合多个装饰器

实现

public class PinnedMemoryDecorator : MemoryEntry
{
    private MemoryEntry _memory;
    
    public PinnedMemoryDecorator(MemoryEntry memory)
    {
        _memory = memory;
        _memory.isPinned = true;
    }
    
    // 委托原有方法
    public override void ExposeData()
    {
        _memory.ExposeData();
    }
}

数据流设计

记忆创建流程
游戏事件发生
    ↓
事件监听器捕获
    ↓
判断是否需要创建记忆
    ↓
创建记忆条目
    ├─ 设置基础信息(ID、内容、类型、时间戳)
    ├─ 计算初始重要性
    ├─ 提取关键词
    └─ 分配到Active层
    ↓
记忆活跃度衰减
    ↓
根据重要性迁移到更深层级
    ↓
低重要性记忆被删除
记忆检索流程
检索请求
    ↓
场景分析
    ↓
多路检索
    ├─ 关键词匹配
    ├─ 向量检索
    └─ 标签过滤
    ↓
结果融合
    ↓
多因子评分
    ↓
多样性补偿
    ↓
返回Top-N结果

性能优化策略

1. 缓存机制
  • AI总结缓存:使用aiCacheKey缓存AI生成的总结
  • 向量缓存:缓存记忆的向量表示
  • 评分缓存:缓存记忆的评分结果
2. 索引优化
  • 关键词索引:为关键词建立倒排索引
  • 向量索引:使用向量数据库进行高效检索
  • 标签索引:为标签建立快速查询索引
3. 批量处理
  • 批量创建记忆:一次事件创建多个相关记忆
  • 批量检索:一次性检索多个相关记忆
  • 批量更新:定期批量更新记忆状态
4. 异步处理
  • 异步向量计算:使用异步任务计算向量表示
  • 异步评分:使用异步任务计算记忆评分
  • 异步持久化:使用异步任务保存记忆到数据库

技术亮点和创新点

1. 四层记忆架构

创新点

  • 首次在游戏AI中实现完整的四层记忆系统
  • 模拟人类记忆的超短期、短期、中期、长期记忆
  • 自然的记忆流转和衰减机制

技术优势

  • 符合认知科学理论
  • 提供逼真的AI行为
  • 易于理解和维护

2. 场景感知的动态评分

创新点

  • 自动识别对话场景类型
  • 根据场景动态调整评分权重
  • 实现上下文感知的记忆检索

技术优势

  • 提高检索准确性
  • 适应不同对话场景
  • 提供更自然的AI行为

3. 多因子评分算法

创新点

  • 综合考虑相关性、时效性、重要性、多样性等多个维度
  • 动态权重调整机制
  • 多样性补偿算法

技术优势

  • 提供全面的评估
  • 避免结果过于集中
  • 保证结果的多样性

4. 向量数据库集成

创新点

  • 集成向量数据库进行语义检索
  • 支持高维向量表示
  • 实现语义相似度计算

技术优势

  • 提高检索准确性
  • 支持语义理解
  • 扩展性强

5. 用户可编辑性

创新点

  • 支持用户编辑记忆内容
  • 支持用户添加标签和备注
  • 支持用户固定重要记忆

技术优势

  • 提高用户参与度
  • 允许个性化定制
  • 增强系统灵活性

6. 角色特定知识库

创新点

  • 支持全局知识和角色特定知识
  • 灵活的知识管理机制
  • 动态的知识注入策略

技术优势

  • 提供丰富的背景信息
  • 支持角色个性化
  • 易于扩展和维护

可借鉴的设计理念

1. 分层存储策略

适用场景

  • 需要管理大量历史数据的系统
  • 需要根据重要性进行数据分级存储的系统
  • 需要模拟自然衰减的系统

实现建议

  • 定义清晰的层级划分标准
  • 为每个层级设置不同的容量限制和保留时间
  • 实现自动化的数据流转机制

2. 上下文感知检索

适用场景

  • 需要根据当前情境动态调整检索策略的系统
  • 需要提供个性化推荐的系统
  • 需要实现智能对话的系统

实现建议

  • 设计场景识别机制
  • 实现动态权重调整
  • 综合多个相关性因子

3. 多维度评分

适用场景

  • 需要对结果进行排序的系统
  • 需要平衡多个评估维度的系统
  • 需要保证结果多样性的系统

实现建议

  • 定义清晰的评分因子
  • 为每个因子设置合理的权重
  • 实现多样性补偿机制

4. 向量语义检索

适用场景

  • 需要理解语义相似性的系统
  • 需要处理自然语言的系统
  • 需要提供智能推荐的系统

实现建议

  • 选择合适的向量表示方法
  • 使用高效的向量数据库
  • 实现相似度计算算法

5. 用户可控性

适用场景

  • 需要用户参与数据管理的系统
  • 需要支持个性化定制的系统
  • 需要提高用户参与度的系统

实现建议

  • 提供友好的用户界面
  • 支持数据的编辑和管理
  • 保护用户修改的内容

6. 模块化设计

适用场景

  • 需要易于维护和扩展的系统
  • 需要支持多种功能的系统
  • 需要降低耦合度的系统

实现建议

  • 使用分层架构
  • 应用设计模式
  • 实现清晰的接口定义

总结

这个RimWorld AI记忆系统是一个设计精良、功能完善的AI记忆管理解决方案。它通过四层记忆架构、场景感知的动态评分、多因子评分算法、向量数据库集成等技术,实现了接近人类记忆特性的AI行为。

核心优势

  1. 科学的理论基础:基于认知科学的人类记忆模型
  2. 先进的技术实现:结合传统算法和现代AI技术
  3. 灵活的架构设计:采用多种设计模式,易于扩展
  4. 优秀的用户体验:支持用户编辑和管理记忆
  5. 强大的性能优化:使用缓存、索引、批量处理等优化策略

应用价值

该系统的设计理念和实现方法对于开发其他AI记忆系统具有重要的参考价值,特别是在以下领域:

  • 游戏AI:为游戏角色提供持久化记忆
  • 对话系统:为对话AI提供上下文记忆
  • 推荐系统:为推荐算法提供用户历史数据
  • 智能助手:为智能助手提供记忆能力
  • 虚拟角色:为虚拟角色提供个性化记忆

通过学习和借鉴这个系统的设计思路和架构模式,开发者可以构建出更加智能、更加自然的AI记忆系统。


附录

关键代码文件

  1. MemoryTypes.cs - 记忆类型和数据结构定义
  2. SmartInjectionManager.cs - 智能注入管理器
  3. CommonKnowledgeLibrary.cs - 常识知识库
  4. DynamicMemoryInjection.cs - 动态记忆注入
  5. AdvancedScoringSystem.cs - 高级评分系统

相关技术

  • 向量数据库:用于语义检索和相似度计算
  • 自然语言处理:用于关键词提取和文本分析
  • 认知科学:人类记忆模型的理论基础
  • 设计模式:分层架构、策略模式、工厂模式等

参考资源

  • 认知心理学:人类记忆模型
  • 信息检索:多因子评分算法
  • 机器学习:向量表示和相似度计算
  • 软件工程:设计模式和架构模式

HTTP请求和对话发送机制

核心问题解析

在实现AI对话系统时,开发者经常面临以下关键问题:

  1. AI如何知道之前说过的话?
  2. 如何将记忆和知识发送给AI?
  3. 如何避免发送过多数据导致请求过大?
  4. 如何避免AI重复说同一件事?

RimWorld模组通过智能的记忆筛选和注入机制,巧妙地解决了这些问题。

HTTP请求架构

对话请求流程
用户发起对话
    ↓
┌─────────────────────────────────┐
│  1. 保存用户消息到历史          │
│     - 存储到本地数据库          │
│     - 记录时间戳和发送者        │
└─────────────────────────────────┘
    ↓
┌─────────────────────────────────┐
│  2. 构建请求上下文              │
│     - 添加系统提示              │
│     - 添加历史消息(最近N条)   │
│     - 添加当前用户消息          │
└─────────────────────────────────┘
    ↓
┌─────────────────────────────────┐
│  3. 智能注入记忆和知识          │
│     - 筛选相关记忆              │
│     - 筛选相关知识              │
│     - 注入到系统提示中          │
└─────────────────────────────────┘
    ↓
┌─────────────────────────────────┐
│  4. 发送HTTP请求                │
│     - 构建请求体                │
│     - 调用AI API                │
│     - 等待响应                  │
└─────────────────────────────────┘
    ↓
┌─────────────────────────────────┐
│  5. 处理AI回复                  │
│     - 解析响应内容              │
│     - 保存AI回复到历史          │
│     - 返回给用户                │
└─────────────────────────────────┘

请求上下文构建

标准对话上下文结构
/// <summary>
/// 构建对话请求上下文
/// </summary>
public static List<ChatMessage> BuildConversationContext(
    Pawn speaker,
    Pawn listener,
    string userMessage,
    int maxHistoryMessages = 10,
    int maxMemories = 10,
    int maxKnowledge = 5)
{
    var context = new List<ChatMessage>();
    
    // 1. 添加系统提示(包含角色设定、规则、记忆和知识)
    string systemPrompt = BuildSystemPrompt(speaker, listener, maxMemories, maxKnowledge);
    context.Add(new ChatMessage { role = "system", content = systemPrompt });
    
    // 2. 添加历史对话消息(最近N条)
    var recentHistory = GetRecentConversationHistory(speaker, listener, maxHistoryMessages);
    foreach (var message in recentHistory)
    {
        context.Add(message);
    }
    
    // 3. 添加当前用户消息
    context.Add(new ChatMessage { role = "user", content = userMessage });
    
    return context;
}
系统提示构建
/// <summary>
/// 构建系统提示(包含记忆和知识)
/// </summary>
private static string BuildSystemPrompt(
    Pawn speaker,
    Pawn listener,
    int maxMemories,
    int maxKnowledge)
{
    StringBuilder sb = new StringBuilder();
    
    // 1. 角色设定和规则
    sb.AppendLine("=== 角色设定 ===");
    sb.AppendLine($"你是{speaker.Name}{speaker.Personality}");
    sb.AppendLine($"性格特点:{speaker.Traits}");
    sb.AppendLine($"背景故事:{speaker.Backstory}");
    sb.AppendLine();
    
    // 2. 智能注入记忆
    sb.AppendLine("=== 相关记忆 ===");
    string memories = InjectSmartContext(speaker, listener, "", maxMemories, maxKnowledge);
    sb.AppendLine(memories);
    sb.AppendLine();
    
    // 3. 行为规则
    sb.AppendLine("=== 行为规则 ===");
    sb.AppendLine("- 保持角色性格一致性");
    sb.AppendLine("- 根据记忆内容调整对话");
    sb.AppendLine("- 避免重复已说过的事情");
    sb.AppendLine("- 使用自然的对话语言");
    sb.AppendLine();
    
    return sb.ToString();
}

HTTP请求实现

请求格式
/// <summary>
/// 发送对话请求到AI API
/// </summary>
public static async Task<string> SendConversationRequest(
    List<ChatMessage> context,
    string apiKey,
    string model)
{
    // 1. 构建请求URL
    string url = "https://api.openai.com/v1/chat/completions";
    
    // 2. 构建请求头
    var headers = new Dictionary<string, string>
    {
        { "Content-Type", "application/json" },
        { "Authorization", $"Bearer {apiKey}" }
    };
    
    // 3. 构建请求体
    var requestBody = new
    {
        model = model,
        messages = context.Select(m => new
        {
            role = m.role,
            content = m.content
        }).ToList(),
        max_tokens = 1000,
        temperature = 0.7,
        top_p = 1.0,
        frequency_penalty = 0.0,
        presence_penalty = 0.0
    };
    
    // 4. 发送HTTP请求
    using (var client = new HttpClient())
    {
        var json = JsonConvert.SerializeObject(requestBody);
        var content = new StringContent(json, Encoding.UTF8, "application/json");
        
        var response = await client.PostAsync(url, content);
        response.EnsureSuccessStatusCode();
        
        var responseBody = await response.Content.ReadAsStringAsync();
        var result = JsonConvert.DeserializeObject<ChatCompletionResponse>(responseBody);
        
        return result.choices[0].message.content;
    }
}
请求体示例
{
  "model": "gpt-4",
  "messages": [
    {
      "role": "system",
      "content": "=== 角色设定 ===\n你是Alice,性格开朗,喜欢种植作物。\n\n=== 相关记忆 ===\n昨天和John讨论了种植计划\n上周建造了新的防御塔\n因为朋友的死亡而感到悲伤\n\n=== 行为规则 ===\n- 保持角色性格一致性\n- 根据记忆内容调整对话\n- 避免重复已说过的事情\n- 使用自然的对话语言"
    },
    {
      "role": "user",
      "content": "你好,最近怎么样?"
    }
  ],
  "max_tokens": 1000,
  "temperature": 0.7
}

记忆筛选和发送逻辑

核心挑战

当本地存储了成百上千条记忆时,直接发送所有记忆会导致:

  1. 请求过大:超过API的token限制(通常4096或8192 tokens)
  2. 成本过高:按token计费,发送大量数据成本昂贵
  3. 性能下降:处理大量上下文会影响响应速度
  4. 质量下降:过多无关信息会干扰AI的理解

智能筛选策略

筛选流程
所有记忆(1000+条)
    ↓
┌─────────────────────────────────┐
│  1. 场景分析                    │
│     - 识别当前对话场景           │
│     - 提取上下文关键词           │
│     - 确定场景类型               │
└─────────────────────────────────┘
    ↓
┌─────────────────────────────────┐
│  2. 多维度筛选                  │
│     ├─ 层级筛选                  │
│     ├─ 类型筛选                  │
│     ├─ 时间筛选                  │
│     └─ 关键词筛选                │
└─────────────────────────────────┘
    ↓
┌─────────────────────────────────┐
│  3. 多因子评分                  │
│     - 上下文相关性 (40%)         │
│     - 时间新近度 (20%)           │
│     - 重要性 (20%)               │
│     - 多样性 (10%)               │
│     - 层级优先级 (10%)           │
└─────────────────────────────────┘
    ↓
┌─────────────────────────────────┐
│  4. Top-N选择                   │
│     - 按得分排序                 │
│     - 选择前N条(通常10-20条)   │
│     - 应用多样性补偿             │
└─────────────────────────────────┘
    ↓
相关记忆(10-20条)
筛选算法实现
/// <summary>
/// 智能筛选相关记忆
/// </summary>
public static List<MemoryEntry> FilterRelevantMemories(
    FourLayerMemoryComp memoryComp,
    string context,
    int maxMemories = 10)
{
    // 1. 获取所有记忆
    var allMemories = memoryComp.GetAllMemories();
    
    // 2. 场景分析
    SceneType scene = SceneAnalyzer.IdentifyScene(context);
    ScoringWeights weights = AdvancedScoringSystem.GetWeightsForScene(scene);
    
    // 3. 多因子评分
    var scoredMemories = new List<MemoryScore>();
    var selectedMemories = new List<MemoryEntry>();
    
    foreach (var memory in allMemories)
    {
        float score = AdvancedScoringSystem.CalculateMemoryScore(
            memory,
            context,
            scene,
            weights,
            selectedMemories);
        
        scoredMemories.Add(new MemoryScore
        {
            memory = memory,
            score = score,
            breakdown = new ScoreBreakdown
            {
                contextRelevance = CalculateContextRelevance(memory, context),
                recency = CalculateRecencyScore(memory),
                importance = memory.importance,
                diversity = CalculateDiversityScore(memory, selectedMemories),
                layerPriority = CalculateLayerPriorityScore(memory.layer)
            }
        });
    }
    
    // 4. 排序并选择Top-N
    var sortedMemories = scoredMemories
        .OrderByDescending(m => m.score)
        .Take(maxMemories)
        .Select(m => m.memory)
        .ToList();
    
    return sortedMemories;
}

知识筛选逻辑

/// <summary>
/// 筛选相关知识
/// </summary>
public static List<CommonKnowledgeEntry> FilterRelevantKnowledge(
    Pawn pawn,
    string context,
    int maxKnowledge = 5)
{
    // 1. 获取所有启用的知识
    var allKnowledge = CommonKnowledgeLibrary.GetAllEnabledKnowledge();
    
    // 2. 过滤出适用于当前角色的知识
    var applicableKnowledge = allKnowledge.Where(k => 
        k.targetPawnId == -1 || k.targetPawnId == pawn.thingIDNumber).ToList();
    
    // 3. 计算相关性得分
    var scoredKnowledge = applicableKnowledge.Select(k => new
    {
        Knowledge = k,
        Score = CalculateKnowledgeRelevance(k, context)
    });
    
    // 4. 排序并选择Top-N
    return scoredKnowledge
        .OrderByDescending(x => x.Score)
        .Take(maxKnowledge)
        .Select(x => x.Knowledge)
        .ToList();
}

/// <summary>
/// 计算知识相关性得分
/// </summary>
private static float CalculateKnowledgeRelevance(
    CommonKnowledgeEntry knowledge,
    string context)
{
    float score = 0f;
    
    // 1. 关键词匹配
    if (knowledge.keywords != null && knowledge.keywords.Count > 0)
   的系统
    {
        foreach (var keyword in knowledge.keywords)
        {
            if (context.Contains(keyword))
            {
                score += 0.5f;
            }
        }
    }
    
    // 2. 重要性加权
    score += knowledge.importance * 0.5f;
    
    return score;
}

注入格式化

/// <summary>
/// 格式化记忆用于注入
/// </summary>
public static string FormatMemoriesForInjection(
    List<MemoryEntry> memories)
{
    if (memories == null || memories.Count == 0)
        return "(无相关记忆)";
    
    StringBuilder sb = new StringBuilder();
    
    foreach (var memory in memories)
    {
        sb.AppendLine($"- {memory.content}");
        
        // 添加时间信息(可选)
        if (memory.timestamp > 0)
        {
            var timeAgo = FormatTimeAgo(memory.timestamp);
            sb.AppendLine($"  ({timeAgo})");
        }
        
        // 添加标签(可选)
        if (memory.tags != null && memory.tags.Count > 0)
        {
            sb.AppendLine($"  标签:{string.Join(", ", memory.tags)}");
        }
    }
    
    return sb.ToString();
}

/// <summary>
/// 格式化知识用于注入
/// </summary>
public static string FormatKnowledgeForInjection(
    List<CommonKnowledgeEntry> knowledge)
{
    if (knowledge == null || knowledge.Count == 0)
        return "(无相关知识)";
    
    StringBuilder sb = new StringBuilder();
    
    foreach (var k in knowledge)
    {
        sb.AppendLine($"[{k.tag}] {k.content}");
    }
    
    return sb.ToString();
}

避免重复对话的机制

问题分析

AI重复说同一件事是常见问题,主要原因包括:

  1. 记忆重复注入:相同的记忆在多次对话中被重复发送
  2. 历史消息重复:历史对话消息被重复发送
  3. 缺乏去重机制:系统没有识别和过滤重复内容
  4. AI理解偏差:AI没有意识到已经说过这件事

避免重复的策略

1. 记忆去重机制
/// <summary>
/// 记忆去重
/// </summary>
public static List<MemoryEntry> DeduplicateMemories(
    List<MemoryEntry> memories,
    List<ChatMessage> recentHistory)
{
    var deduplicated = new List<MemoryEntry>();
    var seenContent = new HashSet<string>();
    
    // 1. 从历史消息中提取已说过的内容
    foreach (var message in recentHistory)
    {
        if (message.role == "assistant")
        {
            seenContent.Add(message.content);
        }
    }
    
    // 2. 过滤掉与历史消息内容相似的记忆
    foreach (var memory in memories)
    {
        bool isDuplicate = false;
        
        // 检查是否与历史消息相似
        foreach (var content in seenContent)
        {
            float similarity = CalculateTextSimilarity(memory.content, content);
            if (similarity > 0.8)  // 相似度阈值
            {
                isDuplicate = true;
                break;
            }
        }
        
        if (!isDuplicate)
        {
            deduplicated.Add(memory);
            seenContent.Add(memory.content);
        }
    }
    
    return deduplicated;
}

/// <summary>
/// 计算文本相似度
/// </summary>
private static float CalculateTextSimilarity(string text1, string text2)
{
    // 使用简单的词重叠度计算相似度
    var words1 = Tokenize(text1);
    var words2 = Tokenize(text2);
    
    var intersection = words1.Intersect(words2).ToList();
    var union = words1.Union(words2).ToList();
    
    return union.Count > 0 ? (float)intersection.Count / union.Count : 0f;
}

/// <summary>
/// 文本分词
/// </summary>
private static List<string> Tokenize(string text)
{
    return text.Split(new[] { ' ', ',', '.', '!', '?', ',', '。', '!', '?' }, 
        StringSplitOptions.RemoveEmptyEntries)
        .Select(w => w.ToLower())
        .ToList();
}
2. 历史消息管理
/// <summary>
/// 获取历史对话消息(智能去重)
/// </summary>
public static List<ChatMessage> GetRecentConversationHistory(
    Pawn speaker,
    Pawn listener,
    int maxMessages = 10)
{
    // 1. 获取所有历史消息
    var allHistory = ConversationHistory.GetHistory(speaker, listener);
    
    // 2. 按时间排序(最新的在前)
    var sortedHistory = allHistory
        .OrderByDescending(m => m.timestamp)
        .ToList();
    
    // 3. 去重:移除内容相似的消息
    var deduplicatedHistory = new List<ChatMessage>();
    var seenContent = new HashSet<string>();
    
    foreach (var message in sortedHistory)
    {
        bool isDuplicate = false;
        
        foreach (var content in seenContent)
        {
            float similarity = CalculateTextSimilarity(message.content, content);
            if (similarity > 0.85)  // 更严格的阈值
            {
                isDuplicate = true;
                break;
            }
        }
        
        if (!isDuplicate)
        {
            deduplicatedHistory.Add(message);
            seenContent.Add(message.content);
            
            if (deduplicatedHistory.Count >= maxMessages)
                break;
        }
    }
    
    // 4. 反转回时间顺序(最早的在前)
    deduplicatedHistory.Reverse();
    
    return deduplicatedHistory;
}
3. 系统提示优化
/// <summary>
/// 构建优化的系统提示(包含避免重复的指令)
/// </summary>
private static string BuildOptimizedSystemPrompt(
    Pawn speaker,
    Pawn listener,
    List<MemoryEntry> memories,
    List<CommonKnowledgeEntry> knowledge,
    List<ChatMessage> recentHistory)
{
    StringBuilder sb = new StringBuilder();
    
    // 1. 角色设定
    sb.AppendLine("=== 角色设定 ===");
    sb.AppendLine($"你是{speaker.Name}{speaker.Personality}");
    sb.AppendLine();
    
    // 2. 已知信息(避免重复)
    sb.AppendLine("=== 已知信息(请勿重复) ===");
    sb.AppendLine("以下是你已经知道或已经说过的事情,请不要重复提及:");
    sb.AppendLine();
    
    // 添加历史AI回复
    foreach (var message in recentHistory.Where(m => m.role == "assistant"))
    {
        sb.AppendLine($"- {message.content}");
    }
    sb.AppendLine();
    
    // 3. 相关记忆(新信息)
    sb.AppendLine("=== 相关记忆(新信息) ===");
    sb.AppendLine("以下是你应该参考的新记忆:");
    sb.AppendLine();
    
    foreach (var memory in memories)
    {
        sb.AppendLine($"- {memory.content}");
    }
    sb.AppendLine();
    
    // 4. 相关知识
    sb.AppendLine("=== 相关知识 ===");
    foreach (var k in knowledge)
    {
        sb.AppendLine($"[{k.tag}] {k.content}");
    }
    sb.AppendLine();
    
    // 5. 明确的避免重复指令
    sb.AppendLine("=== 重要指令 ===");
    sb.AppendLine("1. 请勿重复提及'已知信息'部分的内容");
    sb.AppendLine("2. 重点关注'相关记忆(新信息)'部分的内容");
    sb.AppendLine("3. 如果用户询问你已知的事情,可以简短确认,但不要详细重复");
    sb.AppendLine("4. 保持对话的自然流畅,避免机械重复");
    sb.AppendLine();
    
    return sb.ToString();
}
4. 记忆标记机制
/// <summary>
/// 记忆条目扩展(添加已提及标记)
/// </summary>
public class MemoryEntry : IExposable
{
    // ... 原有字段 ...
    
    // 新增字段
    public bool hasBeenMentioned;        // 是否已经在对话中被提及
    public int lastMentionedTimestamp;   // 最后一次被提及的时间戳
    public int mentionCount;             // 被提及的次数
}

/// <summary>
/// 标记记忆为已提及
/// </summary>
public static void MarkMemoryAsMentioned(MemoryEntry memory)
{
    memory.hasBeenMentioned = true;
    memory.lastMentionedTimestamp = Find.TickManager.TicksGame;
    memory.mentionCount++;
}

/// <summary>
/// 筛选时降低已提及记忆的权重
/// </summary>
public static float AdjustScoreForMentionedMemory(
    float originalScore,
    MemoryEntry memory)
{
    if (!memory.hasBeenMentioned)
        return originalScore;
    
    // 根据被提及的次数降低权重
    float penalty = 1.0f - (memory.mentionCount * 0.2f);
    return originalScore * Math.Max(0.2f, penalty);
}
5. 时间窗口策略
/// <summary>
/// 基于时间窗口的记忆筛选
/// </summary>
public static List<MemoryEntry> FilterMemoriesByTimeWindow(
    List<MemoryEntry> memories,
    int timeWindowTicks = 60000)  // 默认1天(60000 ticks)
{
    int currentTick = Find.TickManager.TicksGame;
    int windowStart = currentTick - timeWindowTicks;
    
    return memories
        .Where(m => m.timestamp >= windowStart)
        .ToList();
}

/// <summary>
/// 混合策略:结合时间窗口和相关性
/// </summary>
public static List<MemoryEntry> HybridMemoryFilter(
    FourLayerMemoryComp memoryComp,
    string context,
    int maxMemories = 10)
{
    // 1. 获取时间窗口内的记忆
    var recentMemories = FilterMemoriesByTimeWindow(
        memoryComp.GetAllMemories(),
        60000  // 最近1天
    );
    
    // 2. 如果时间窗口内记忆不足,从更早的记忆中补充
    if (recentMemories.Count < maxMemories)
    {
        var olderMemories = memoryComp.GetAllMemories()
            .Where(m => m.timestamp < (Find.TickManager.TicksGame - 60000))
            .ToList();
        
        recentMemories.AddRange(olderMemories);
    }
    
    // 3. 多因子评分和Top-N选择
    return FilterRelevantMemories(
        memoryComp,
        context,
        maxMemories
    );
}

对话历史管理

历史存储架构

存储结构
/// <summary>
/// 对话历史条目
/// </summary>
public class ConversationEntry : IExposable
{
    public string id;                   // 唯一ID
    public string speakerId;            // 说话者ID
    public string speakerName;          // 说话者名字
    public string listenerId;           // 听者ID
    public string listenerName;         // 听者名字
    public string role;                 // role: "user" 或 "assistant"
    public string content;              // 对话内容
    public int timestamp;               // 时间戳
    public List<string> relatedMemoryIds; // 相关记忆ID列表
    public List<string> injectedKnowledgeIds; // 注入的知识ID列表
}

/// <summary>
/// 对话历史管理器
/// </summary>
public class ConversationHistoryManager
{
    private static Dictionary<string, List<ConversationEntry>> _historyCache;
    private static int _maxHistoryPerConversation = 100;
    
    /// <summary>
    /// 保存对话条目
    /// </summary>
    public static void SaveConversationEntry(ConversationEntry entry)
    {
        string key = GetConversationKey(entry.speakerId, entry.listenerId);
        
        if (!_historyCache.ContainsKey(key))
        {
            _historyCache[key] = new List<ConversationEntry>();
        }
        
        _historyCache[key].Add(entry);
        
        // 限制历史记录数量
        if (_historyCache[key].Count > _maxHistoryPerConversation)
        {
            _historyCache[key].RemoveAt(0);
        }
        
        // 持久化到数据库
        SaveToDatabase(entry);
    }
    
    /// <summary>
    /// 获取对话历史
    /// </summary>
    public static List<ConversationEntry> GetConversationHistory(
        string speakerId,
        string listenerId,
        int maxEntries = 10)
    {
        string key = GetConversationKey(speakerId, listenerId);
        
        if (!_historyCache.ContainsKey(key))
        {
            return new List<ConversationEntry>();
        }
        
        var history = _historyCache[key];
        
        // 返回最近的N条记录
        return history.Count > maxEntries
            ? history.GetRange(history.Count - maxEntries, maxEntries)
            : history;
    }
    
    /// <summary>
    /// 生成对话键
    /// </summary>
    private static string GetConversationKey(string speakerId, string listenerId)
    {
        // 确保键的顺序一致(A-B 和 B-A 是同一个对话)
        var ids = new[] { speakerId, listenerId }.OrderBy(id => id).ToArray();
        return $"{ids[0]}_{ids[1]}";
    }
}

历史消息格式化

/// <summary>
/// 格式化历史消息用于API请求
/// </summary>
public static List<ChatMessage> FormatHistoryForAPI(
    List<ConversationEntry> history)
{
    return history.Select(entry => new ChatMessage
    {
        role = entry.role,
        content = entry.content
    }).ToList();
}

完整的对话流程

/// <summary>
/// 完整的对话流程
/// </summary>
public static async Task<string> ConductConversation(
    Pawn speaker,
    Pawn listener,
    string userMessage)
{
    try
    {
        // 1. 保存用户消息到历史
        var userEntry = new ConversationEntry
        {
            id = GenerateId(),
            speakerId = speaker.thingIDNumber.ToString(),
            speakerName = speaker.Name,
            listenerId = listener.thingIDNumber.ToString(),
            listenerName = listener.Name,
            role = "user",
            content = userMessage,
            timestamp = Find.TickManager.TicksGame
        };
        ConversationHistoryManager.SaveConversationEntry(userEntry);
        
        // 2. 获取历史消息
        var recentHistory = ConversationHistoryManager.GetConversationHistory(
            speaker.thingIDNumber.ToString(),
            listener.thingIDNumber.ToString(),
            10  // 最近10条
        );
        
        // 3. 筛选相关记忆(去重)
        var allMemories = speaker.GetComp<FourLayerMemoryComp>().GetAllMemories();
        var relevantMemories = FilterRelevantMemories(
            speaker.GetComp<FourLayerMemoryComp>(),
            userMessage,
            10
        );
        relevantMemories = DeduplicateMemories(relevantMemories, FormatHistoryForAPI(recentHistory));
        
        // 4. 筛选相关知识
        var relevantKnowledge = FilterRelevantKnowledge(speaker, userMessage, 5);
        
        // 5. 构建请求上下文
        var context = BuildConversationContext(
            speaker,
            listener,
            userMessage,
            relevantMemories,
            relevantKnowledge,
            FormatHistoryForAPI(recentHistory)
        );
        
        // 6. 发送HTTP请求
        var aiResponse = await SendConversationRequest(
            context,
            Settings.APIKey,
            Settings.Model
        );
        
        // 7. 保存AI回复到历史
        var aiEntry = new ConversationEntry
        {
            id = GenerateId(),
            speakerId = speaker.thingIDNumber.ToString(),
            speakerName = speaker.Name,
            listenerId = listener.thingIDNumber.ToString(),
            listenerName = listener.Name,
            role = "assistant",
            content = aiResponse,
            timestamp = Find.TickManager.TicksGame,
            relatedMemoryIds = relevantMemories.Select(m => m.id).ToList(),
            injectedKnowledgeIds = relevantKnowledge.Select(k => k.id).ToList()
        };
        ConversationHistoryManager.SaveConversationEntry(aiEntry);
        
        // 8. 标记已提及的记忆
        foreach (var memory in relevantMemories)
        {
            if (aiResponse.Contains(memory.content))
            {
                MarkMemoryAsMentioned(memory);
            }
        }
        
        return aiResponse;
    }
    catch (Exception ex)
    {
        Log.Error($"对话失败: {ex.Message}");
        return "抱歉,我遇到了一些问题。";
    }
}

性能优化策略

1. 缓存机制
/// <summary>
/// 记忆筛选结果缓存
/// </summary>
public class MemoryFilterCache
{
    private static Dictionary<string, CacheEntry> _cache;
    private static int _cacheSize = 100;
    private static int _cacheTTL = 300;  // 5分钟
    
    public static List<MemoryEntry> GetOrCompute(
        string cacheKey,
        Func<List<MemoryEntry>> compute)
    {
        // 检查缓存
        if (_cache.ContainsKey(cacheKey))
        {
            var entry = _cache[cacheKey];
            if (DateTime.Now - entry.timestamp < TimeSpan.FromSeconds(_cacheTTL))
            {
                return entry.memories;
            }
        }
        
        // 计算并缓存
        var memories = compute();
        
        // 限制缓存大小
        if (_cache.Count >= _cacheSize)
        {
            var oldestKey = _cache.OrderBy(kvp => kvp.Value.timestamp).First().Key;
            _cache.Remove(oldestKey);
        }
        
        _cache[cacheKey] = new CacheEntry
        {
            memories = memories,
            timestamp = DateTime.Now
        };
        
        return memories;
    }
}
2. 批量处理
/// <summary>
/// 批量计算记忆得分
/// </summary>
public static List<MemoryScore> BatchCalculateScores(
    List<MemoryEntry> memories,
    string context,
    SceneType scene,
    ScoringWeights weights)
{
    // 并行计算得分
    var scores = memories.AsParallel()
        .Select(memory => new MemoryScore
        {
            memory = memory,
            score = AdvancedScoringSystem.CalculateMemoryScore(
                memory,
                context,
                scene,
                weights,
                new List<MemoryEntry>()
            )
        })
        .ToList();
    
    return scores;
}
3. 增量更新
/// <summary>
/// 增量更新记忆得分
/// </summary>
public static void IncrementalUpdateScores(
    List<MemoryScore> existingScores,
    List<MemoryEntry> newMemories,
    string context)
{
    foreach (var newMemory in newMemories)
    {
        float score = AdvancedScoringSystem.CalculateMemoryScore(
            newMemory,
            context,
            SceneType.Casual,
            new ScoringWeights(),
            existingScores.Select(s => s.memory).ToList()
        );
        
        existingScores.Add(new MemoryScore
        {
            memory = newMemory,
            score = score
        });
    }
    
    // 重新排序
    existingScores.Sort((a, b) => b.score.CompareTo(a.score));
}

总结

通过深入分析RimWorld模组的记忆系统,我们揭示了如何解决AI对话中的核心挑战:

关键解决方案

  1. 智能记忆筛选:通过多因子评分算法,从上千条记忆中筛选出最相关的10-20条
  2. 分层注入策略:将记忆、知识和规则分层注入到系统提示中
  3. 去重机制:通过文本相似度计算和历史消息管理,避免重复内容
  4. 时间窗口策略:优先使用近期记忆,必要时从更早的记忆中补充
  5. 标记机制:标记已提及的记忆,降低其在后续对话中的权重

技术优势

  • 高效性:只发送最相关的信息,避免请求过大
  • 经济性:减少token使用,降低API调用成本
  • 准确性:通过多维度评分,确保注入的信息高度相关
  • 自然性:避免重复对话,保持对话的自然流畅
  • 可扩展性:模块化设计,易于扩展和优化

实践建议

在构建自己的AI记忆系统时,建议:

  1. 从简单开始:先实现基本的记忆存储和检索
  2. 逐步优化:根据实际效果调整评分权重和筛选策略
  3. 监控性能:记录API调用次数、token使用量和响应时间
  4. 用户反馈:收集用户反馈,持续优化记忆筛选逻辑
  5. 测试验证:通过大量测试验证去重和避免重复的效果

通过借鉴RimWorld模组的设计思路,开发者可以构建出高效、智能、自然的AI记忆系统。