找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

1741

积分

0

好友

231

主题
发表于 20 小时前 | 查看: 2| 回复: 0

如何让你的 AI 助手拥有“长期记忆”?本文将从架构设计到代码实现,带你深入理解一个生产级的 Agent 记忆系统。

你是否遇到过这样的困扰:与AI聊了很久,它却像初次见面一样,对你之前提到的喜好或约定毫无印象?这并非AI的“健忘”,而是源于大语言模型(LLM)的一个根本特性——无状态。每次对话对LLM来说,都是一次全新的开始。要让AI真正成为一个可靠的智能助手,构建一套记忆系统是必不可少的工程实践。

本文将分享一套基于Rust实现的记忆系统架构,深入探讨其设计哲学、核心组件与工作流程,并附上可直接运行的代码示例。

为什么AI Agent需要记忆系统?

LLM的局限性

大语言模型固然强大,但存在一个核心瓶颈:有限的上下文窗口。即便最新的模型支持百万级别的tokens,也不可能无限制地记住所有历史对话。这背后是几个现实问题:

  • 成本激增:每次交互都携带完整的对话历史,API调用成本会迅速累积。
  • 注意力稀释:过长的上下文会导致模型注意力分散,反而影响当前回复的质量。
  • 信息孤岛:不同会话之间的信息无法互通,每次对话都从零开始。

记忆系统的价值

一个设计精良的记忆系统,能有效弥补上述缺陷,为AI Agent赋予:

  • 跨会话记忆:记住用户的身份、偏好和历史交互细节。
  • 智能检索:根据当前对话的语义,精准召回最相关的历史信息。
  • 知识沉淀:将交互中产生的有价值信息(如用户提供的资料)长期保存。
  • 成本优化:用高效的检索替代冗长的上下文传递,降低token消耗。

系统架构概览

agent-io的记忆系统采用了分层架构设计,职责清晰,易于扩展。

AI Agent记忆系统架构图

核心模块职责

模块 职责 文件
MemoryManager 统一入口,协调各组件 manager.rs
MemoryEntry 记忆条目的核心数据结构 entry.rs
MemoryStore 存储抽象trait store.rs
RingBuffer 实现短期记忆的环形缓冲区 buffer.rs
EmbeddingProvider 文本向量化接口 embeddings.rs
MemoryRanker 记忆相关性排序器 ranker.rs

核心组件详解

记忆条目(MemoryEntry)

每条记忆都被封装为一个结构化的数据条目,这是系统的基础单元。

pub struct MemoryEntry {
    pub id: String,                    // 唯一标识
    pub content: String,               // 记忆内容
    pub embedding: Option<Vec<f32>>,   // 向量表示(用于相似度搜索)
    pub memory_type: MemoryType,       // 记忆类型
    pub metadata: HashMap<String, Value>, // 元数据
    pub created_at: DateTime<Utc>,     // 创建时间
    pub last_accessed: Option<DateTime<Utc>>, // 最后访问时间
    pub importance: f32,               // 重要性评分 (0.0 - 1.0)
    pub access_count: u32,             // 访问次数
}

记忆类型分类

系统借鉴了认知心理学模型,对记忆进行了分类:

pub enum MemoryType {
    ShortTerm,   // 短期记忆:最近的对话
    LongTerm,    // 长期记忆:持久化知识
    Episodic,    // 情景记忆:特定事件/经历
    Semantic,    // 语义记忆:事实和概念
}
  • 短期记忆:容量有限,存取迅速,类似于我们的“工作记忆”,存放最近几轮对话。
  • 长期记忆:容量理论上无限,需要检索才能访问,用于存放需要持久化的信息。
  • 情景记忆:记录个人经历,例如“昨天用户告诉我他养了一只猫”。
  • 语义记忆:存储通用事实和概念,例如“巴黎是法国的首都”。

相关性评分算法

系统会根据多重因素动态计算每条记忆与当前查询的相关性分数。

pub fn relevance_score(&self) -> f32 {
    let age_hours = (Utc::now() - self.created_at).num_hours() as f32;
    let recency_factor = (-age_hours / 24.0 / 7.0).exp(); // 一周内指数衰减

    let access_factor = 1.0 + (self.access_count as f32).ln().max(0.0) * 0.1;

    self.importance * recency_factor * access_factor
}

评分公式为:score = 重要性 × 时间衰减因子 × 访问频率因子。该算法确保了越重要、越新鲜、被访问越频繁的记忆,在检索时排名越靠前。

存储抽象(MemoryStore)

为了支持多种存储后端,系统使用Trait定义了统一的存储接口,这大大提升了灵活性和可测试性。

#[async_trait]
pub trait MemoryStore: Send + Sync {
    async fn add(&self, entry: MemoryEntry) -> Result<String>;
    async fn search(&self, query: &str, limit: usize) -> Result<Vec<MemoryEntry>>;
    async fn search_by_embedding(&self, embedding: &[f32], limit: usize, threshold: f32)
        -> Result<Vec<MemoryEntry>>;
    async fn get(&self, id: &str) -> Result<Option<MemoryEntry>>;
    async fn update(&self, entry: MemoryEntry) -> Result<()>;
    async fn delete(&self, id: &str) -> Result<()>;
    async fn clear(&self) -> Result<()>;
    async fn count(&self) -> Result<usize>;
}

内存存储实现

InMemoryStore适合开发和测试场景,所有数据保存在内存的HashMap中。

pub struct InMemoryStore {
    memories: RwLock<HashMap<String, MemoryEntry>>,
}

它支持基于向量余弦相似度的语义搜索。向量相似度是人工智能领域实现语义理解的关键技术。

fn cosine_similarity(a: &[f32], b: &[f32]) -> f32 {
    let dot: f32 = a.iter().zip(b.iter()).map(|(x, y)| x * y).sum();
    let norm_a: f32 = a.iter().map(|x| x * x).sum::<f32>().sqrt();
    let norm_b: f32 = b.iter().map(|x| x * x).sum::<f32>().sqrt();
    dot / (norm_a * norm_b)
}

SQLite存储实现

SqliteStore适用于生产环境,提供持久化存储能力,并巧妙利用SQLite的特性实现高效检索。

pub struct SqliteStore {
    conn: Arc<Mutex<Connection>>,
}

数据库设计亮点

  1. FTS5全文搜索:利用SQLite内置的FTS5扩展实现快速关键词匹配。
  2. 向量存储:将高维向量序列化为BLOB字段存储。
  3. 自动同步:通过触发器,在数据表更新时自动维护全文索引。
CREATE VIRTUAL TABLE memories_fts USING fts5(
    id UNINDEXED,
    content,
    content='memories',
    content_rowid='rowid'
);

-- 自动同步触发器
CREATE TRIGGER memories_ai AFTER INSERT ON memories BEGIN
    INSERT INTO memories_fts(rowid, id, content)
    VALUES (new.rowid, new.id, new.content);
END;

短期记忆缓冲区(RingBuffer)

短期记忆采用环形缓冲区实现,这是一种经典数据结构,固定容量,新数据满时会自动覆盖最旧的数据。

pub struct RingBuffer<T> {
    buffer: VecDeque<T>,
    capacity: usize,
}

impl<T> RingBuffer<T> {
    pub fn push(&mut self, item: T) {
        if self.buffer.len() == self.capacity {
            self.buffer.pop_front(); // 淘汰最旧的
        }
        self.buffer.push_back(item);
    }
}

设计优势

  • O(1)时间复杂度的插入和删除操作,性能高效。
  • 内存占用可控,不会无限增长。
  • 完美模拟了人类“最近对话”的记忆特性。

向量嵌入(Embeddings)

记忆系统的语义搜索能力,核心依赖于将文本转换为向量表示的嵌入模型。

#[async_trait]
pub trait EmbeddingProvider: Send + Sync {
    async fn embed(&self, text: &str) -> Result<Vec<f32>>;
    async fn embed_batch(&self, texts: &[&str]) -> Result<Vec<Vec<f32>>>;
    fn dimension(&self) -> usize;
}

系统内置了多种嵌入模型供选择:

Provider 模型 维度
OpenAIEmbedding text-embedding-3-small 1536
OpenAIEmbedding text-embedding-3-large 3072
MockEmbedding 测试用Mock 可配置

记忆排序器(Ranker)

当从不同存储中检索到多条候选记忆时,Ranker负责对它们进行综合排序,返回最相关的Top-K结果。

pub struct RankingWeights {
    pub similarity: f32,   // 向量相似度权重 (默认0.4)
    pub importance: f32,   // 重要性权重 (默认0.25)
    pub recency: f32,      // 时效性权重 (默认0.2)
    pub frequency: f32,    // 访问频率权重 (默认0.15)
}

综合评分公式为:
最终分数 = 0.4 × 语义相似度 + 0.25 × 重要性 + 0.2 × 时效性 + 0.15 × 访问频率

时间衰减机制

系统模拟了记忆随时间淡忘的过程,重要性会按配置进行衰减。

pub struct DecayConfig {
    pub daily_rate: f32,         // 每日衰减率 (默认 1%)
    pub min_threshold: f32,      // 最小阈值 (低于此值的记忆可被清理)
    pub grace_period_days: u32,  // 宽限期 (新记忆在此期间不衰减)
}

衰减公式当前重要性 = 原始重要性 × (1 - 每日衰减率)^已过天数

核心工作流程

记忆存储流程(Remember)

当需要记住一段新信息时,系统会执行以下流程:

pub async fn remember(&mut self, content: &str, memory_type: MemoryType) -> Result<String> {
    // 1. 生成向量嵌入
    let embedding = self.embedder.embed(content).await?;

    // 2. 创建记忆条目
    let entry = MemoryEntry::new(content)
        .with_type(memory_type)
        .with_embedding(embedding);

    // 3. 根据类型选择存储位置
    match memory_type {
        MemoryType::ShortTerm => {
            self.short_term.push(entry.clone());
            Ok(entry.id)
        }
        _ => {
            if self.config.enable_long_term {
                self.store.add(entry).await  // 持久化存储
            } else {
                self.short_term.push(entry.clone());
                Ok(entry.id)
            }
        }
    }
}

记忆存储流程图

记忆检索流程(Recall)

当Agent需要回想与当前对话相关的信息时,触发检索流程。

pub async fn recall(&self, query: &str) -> Result<Vec<MemoryEntry>> {
    // 1. 将查询文本向量化
    let query_embedding = self.embedder.embed(query).await?;

    // 2. 并行从长期记忆存储中检索
    let mut memories = self.store
        .search_by_embedding(&query_embedding, limit, threshold)
        .await?;

    // 3. 从短期记忆缓冲区中检索
    for entry in self.short_term.iter_recent() {
        if let Some(ref embedding) = entry.embedding {
            let similarity = cosine_similarity(&query_embedding, embedding);
            if similarity >= threshold {
                memories.push(entry.clone());
            }
        }
    }

    // 4. 对所有候选记忆按综合相关性排序
    memories.sort_by(|a, b| b.relevance_score().partial_cmp(&a.relevance_score()).unwrap());

    // 5. 限制返回数量
    memories.truncate(self.config.retrieval_limit);

    Ok(memories)
}

记忆检索流程图

实战示例

基本使用

下面是一个完整的示例,展示如何初始化记忆管理器并执行记忆的存储与检索。

use agent_io::memory::{
    MemoryManager, MemoryConfig, MemoryType,
    InMemoryStore, MockEmbedding
};

#[tokio::main]
async fn main() -> Result<()> {
    // 1. 创建存储和向量化服务
    let store = Arc::new(InMemoryStore::new());
    let embedder = Arc::new(MockEmbedding::new(384));

    // 2. 创建记忆管理器
    let config = MemoryConfig {
        short_term_size: 20,
        enable_long_term: true,
        retrieval_limit: 5,
        relevance_threshold: 0.7,
        ..Default::default()
    };
    let mut manager = MemoryManager::new(config, store, embedder);

    // 3. 存储记忆
    manager.remember("用户喜欢 Rust 编程语言", MemoryType::LongTerm).await?;
    manager.remember("用户是软件工程师", MemoryType::Semantic).await?;
    manager.remember("上次讨论了异步编程", MemoryType::Episodic).await?;

    // 4. 检索相关记忆
    let memories = manager.recall("编程").await?;
    for memory in memories {
        println!("相关记忆: {}", memory.content);
    }

    // 5. 构建上下文
    let context = manager.recall_context("用户的技术背景").await?;
    println!("上下文: {}", context);

    Ok(())
}

使用SQLite持久化

切换到持久化存储非常简单。

use agent_io::memory::SqliteStore;

// 创建文件数据库
let store = Arc::new(SqliteStore::open("./data/memories.db")?);

// 或使用内存数据库(测试用)
let store = Arc::new(SqliteStore::new()?);

自定义排序权重

你可以根据应用场景调整排序策略的侧重点。

use agent_io::memory::{MemoryRanker, RankingWeights};

let weights = RankingWeights {
    similarity: 0.5,   // 更重视语义相似度
    importance: 0.3,
    recency: 0.1,
    frequency: 0.1,
};

let ranker = MemoryRanker::with_weights(weights)
    .with_recency_half_life(24.0 * 3.0); // 设置半衰期为3天

设计亮点与可扩展性

架构设计总结

特性 实现方式
存储抽象 Trait + 多后端实现,轻松切换
短期/长期分离 RingBuffer + Store 双层架构,符合认知模型
语义搜索 Embedding 向量化 + 余弦相似度计算
混合检索 FTS全文搜索 + 向量语义搜索,双管齐下
相关性排序 多因子(相似度、重要性、时效性、频率)加权评分

性能与扩展

  • 异步设计:所有I/O操作为async,支持高并发场景。
  • 批量处理embed_batch支持批量文本向量化,显著减少API调用次数。
  • 易于扩展:实现MemoryStoreEmbeddingProvider trait即可接入自定义的存储后端或嵌入服务。
// 自定义存储后端
impl MemoryStore for MyCustomStore { ... }

// 自定义嵌入服务
impl EmbeddingProvider for MyEmbeddingService { ... }

未来展望

当前系统已具备生产可用性,但仍有进化空间:

  1. 集成专业向量数据库:对于超大规模记忆库,可接入Qdrant、Milvus等,获得更优的检索性能与管理功能。
  2. 记忆压缩与摘要:使用LLM对陈旧或冗余的记忆进行总结压缩,释放存储空间。
  3. 自动化遗忘机制:基于DecayConfig,自动清理重要性低于阈值的历史记忆。
  4. 支持多模态记忆:扩展系统以支持图像、音频等非文本记忆的存储与检索。
  5. 记忆推理与联想:不局限于检索,还能基于已有记忆进行推理,生成新的洞察或知识。

结语

这套记忆系统的设计,是认知科学原理现代软件工程的一次有趣结合。它通过分层的架构、清晰的抽象、以及多因子权衡的算法,为AI Agent赋予了既灵活又高效的记忆能力。希望本文的解析和代码实践,能为你在构建更智能的AI应用时提供扎实的参考。

完整代码请访问GitHub仓库:https://github.com/lispking/agent-io

对AI Agent开发、Rust系统编程或相关架构设计感兴趣的朋友,欢迎在技术社区进行更深入的交流与探讨,例如在云栈社区的相关板块分享你的见解与实践。




上一篇:腾讯高瓴投资,出海品牌TENWAYS冲刺香港E-Bike第一股
下一篇:AI每周热点 | 0224-0228:英伟达、谷歌、阿里等巨头动态Top50关键词盘点
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-3-2 22:40 , Processed in 0.398062 second(s), 42 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表