参考 Steam 神作 RimWorld T0模组的AI记忆系统深度技术分析
目录
- 系统概述
- 核心架构设计
- 四层记忆系统详解
- 记忆类型和数据结构
- 智能注入系统
- 记忆检索和匹配算法
- 高级评分系统
- 常识知识库
- 设计思路和架构模式
- 技术亮点和创新点
- 可借鉴的设计理念
- HTTP请求和对话发送机制
- 记忆筛选和发送逻辑
- 避免重复对话的机制
- 对话历史管理
系统概述
下面我将参考一个为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) │ │
│ └──────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
设计原则
- 分层存储:按照记忆的重要性和时效性,将记忆分布在不同层级
- 上下文感知:根据当前场景动态调整记忆检索策略
- 多维度评分:综合多个因子计算记忆的相关性得分
- 用户可控:允许用户编辑、标注和管理记忆内容
- 性能优化:使用向量数据库和缓存机制提升检索效率
四层记忆系统详解
记忆层级定义
/// <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 ││
└─────────────┘│
↓ │
删除/归档 ──┘
流转规则:
- 新记忆首先进入Active层
- 根据重要性和活跃度,记忆会向更深层级迁移
- 低重要性记忆会逐渐衰减并被删除
- 高重要性记忆会保留在Archive层
- 用户可以手动固定记忆,防止被删除
记忆类型和数据结构
记忆类型定义
/// <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总结的缓存键
}
数据结构设计亮点
- 唯一标识:每个记忆都有唯一的ID,支持精确引用
- 类型化存储:通过MemoryType区分不同类型的记忆
- 层级管理:通过MemoryLayer实现记忆的分层存储
- 重要性评分:importance字段支持记忆的重要性评估
- 活跃度衰减:activity字段模拟记忆的自然遗忘
- 标签系统:tags字段支持灵活的记忆分类和检索
- 关键词提取:keywords字段支持基于关键词的快速检索
- 用户编辑:isUserEdited字段保护用户修改的内容
- 固定机制:isPinned字段防止重要记忆被删除
- 缓存优化: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. 生成对话内容 │
│ - 基于注入的上下文 │
│ - 符合角色设定 │
│ - 保持连贯性 │
└─────────────────────────────────┘
注入优化策略
- 相关性过滤:只注入与当前上下文相关的记忆和知识
- 数量控制:通过maxMemories和maxKnowledge参数控制注入数量
- 多样性保证:避免重复注入相同类型的记忆
- 时效性考虑:优先注入近期的记忆
- 重要性加权:高重要性记忆优先注入
- 场景适配:根据不同场景调整注入策略
记忆检索和匹配算法
检索系统概述
记忆检索系统是整个记忆系统的核心,负责根据当前上下文从海量记忆中快速、准确地找到最相关的记忆。该系统结合了传统关键词匹配、向量语义搜索和多因子评分算法,实现了高效、精准的记忆检索。
检索流程
检索请求
↓
┌─────────────────────────────────┐
│ 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. 模拟人类记忆
该系统的核心设计理念是模拟人类记忆的工作机制,通过分层存储、自然衰减、重要性评估等机制,实现接近人类记忆特性的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行为。
核心优势
- 科学的理论基础:基于认知科学的人类记忆模型
- 先进的技术实现:结合传统算法和现代AI技术
- 灵活的架构设计:采用多种设计模式,易于扩展
- 优秀的用户体验:支持用户编辑和管理记忆
- 强大的性能优化:使用缓存、索引、批量处理等优化策略
应用价值
该系统的设计理念和实现方法对于开发其他AI记忆系统具有重要的参考价值,特别是在以下领域:
- 游戏AI:为游戏角色提供持久化记忆
- 对话系统:为对话AI提供上下文记忆
- 推荐系统:为推荐算法提供用户历史数据
- 智能助手:为智能助手提供记忆能力
- 虚拟角色:为虚拟角色提供个性化记忆
通过学习和借鉴这个系统的设计思路和架构模式,开发者可以构建出更加智能、更加自然的AI记忆系统。
附录
关键代码文件
- MemoryTypes.cs - 记忆类型和数据结构定义
- SmartInjectionManager.cs - 智能注入管理器
- CommonKnowledgeLibrary.cs - 常识知识库
- DynamicMemoryInjection.cs - 动态记忆注入
- AdvancedScoringSystem.cs - 高级评分系统
相关技术
- 向量数据库:用于语义检索和相似度计算
- 自然语言处理:用于关键词提取和文本分析
- 认知科学:人类记忆模型的理论基础
- 设计模式:分层架构、策略模式、工厂模式等
参考资源
- 认知心理学:人类记忆模型
- 信息检索:多因子评分算法
- 机器学习:向量表示和相似度计算
- 软件工程:设计模式和架构模式
HTTP请求和对话发送机制
核心问题解析
在实现AI对话系统时,开发者经常面临以下关键问题:
- AI如何知道之前说过的话?
- 如何将记忆和知识发送给AI?
- 如何避免发送过多数据导致请求过大?
- 如何避免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
}
记忆筛选和发送逻辑
核心挑战
当本地存储了成百上千条记忆时,直接发送所有记忆会导致:
- 请求过大:超过API的token限制(通常4096或8192 tokens)
- 成本过高:按token计费,发送大量数据成本昂贵
- 性能下降:处理大量上下文会影响响应速度
- 质量下降:过多无关信息会干扰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重复说同一件事是常见问题,主要原因包括:
- 记忆重复注入:相同的记忆在多次对话中被重复发送
- 历史消息重复:历史对话消息被重复发送
- 缺乏去重机制:系统没有识别和过滤重复内容
- 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对话中的核心挑战:
关键解决方案
- 智能记忆筛选:通过多因子评分算法,从上千条记忆中筛选出最相关的10-20条
- 分层注入策略:将记忆、知识和规则分层注入到系统提示中
- 去重机制:通过文本相似度计算和历史消息管理,避免重复内容
- 时间窗口策略:优先使用近期记忆,必要时从更早的记忆中补充
- 标记机制:标记已提及的记忆,降低其在后续对话中的权重
技术优势
- 高效性:只发送最相关的信息,避免请求过大
- 经济性:减少token使用,降低API调用成本
- 准确性:通过多维度评分,确保注入的信息高度相关
- 自然性:避免重复对话,保持对话的自然流畅
- 可扩展性:模块化设计,易于扩展和优化
实践建议
在构建自己的AI记忆系统时,建议:
- 从简单开始:先实现基本的记忆存储和检索
- 逐步优化:根据实际效果调整评分权重和筛选策略
- 监控性能:记录API调用次数、token使用量和响应时间
- 用户反馈:收集用户反馈,持续优化记忆筛选逻辑
- 测试验证:通过大量测试验证去重和避免重复的效果
通过借鉴RimWorld模组的设计思路,开发者可以构建出高效、智能、自然的AI记忆系统。