当前位置: 首页 > news >正文

从认知科学到工程实践:构建AI Agent记忆系统的TypeScript实现

1. 从“健忘”的AI到有“记忆”的Agent:一个核心挑战

如果你尝试过与早期的聊天机器人或者一些基础的AI应用对话,一个最直观的感受可能就是:它记性太差了。你刚刚告诉它你的名字、喜好,或者讨论到某个问题的中间步骤,只要对话轮次一多,或者你开启了一个新的话题再回来,它很可能就把之前的信息忘得一干二净。这种“健忘”的本质,是AI缺乏一个持续、稳定、可检索的记忆系统。它每次的回应,都像是一次全新的、孤立的计算,无法基于“过去”的经验来优化“现在”的决策。

这正是构建AI Agent时,我们必须解决的首要工程与设计难题之一。一个没有记忆的Agent,就像一个失忆的助手,每次见面都要重新自我介绍,无法执行需要多步骤、跨会话的复杂任务。而“记忆”,恰恰是智能体(Agent)区别于简单工具(Tool)的核心特征。它使得Agent能够拥有连续性、个性化和上下文感知能力。

那么,如何为一段代码赋予“记忆”?这不仅仅是技术问题,更是一个认知科学与软件工程的交叉课题。我们需要从人类记忆的运作方式中汲取灵感,将其抽象、简化为可被计算机理解和实现的模型。本文将从一个全栈开发者的视角,探讨如何将认知科学中关于记忆的经典理论,转化为一套用TypeScript实现的、可嵌入现有Agent框架的实用记忆系统。我们会从“为什么需要记忆”开始,深入到记忆的类型划分、存储设计、检索策略,最终落地为一个模块化的、可测试的代码实现。

2. 记忆系统的认知科学蓝图:不止于“记住”

在动手写代码之前,我们必须先想清楚:我们要实现的“记忆”到底是什么?它应该包含哪些部分?认知科学,特别是关于工作记忆和长时记忆的研究,为我们提供了一张绝佳的设计蓝图。

2.1 工作记忆与长时记忆:两种关键的“内存”

认知心理学将记忆大致分为工作记忆和长时记忆。工作记忆容量有限,就像电脑的RAM,负责处理当前活跃的、正在被意识加工的信息。它短暂、易失,但访问速度极快。长时记忆则容量巨大,像电脑的硬盘,存储着相对永久的经验、知识和技能,但提取需要时间和线索。

映射到AI Agent的设计上:

  • 工作记忆:对应的是当前会话的上下文(Context)。这通常就是传递给大语言模型(LLM)的那段Prompt,包含了最近的几条对话历史、当前的任务指令、以及从长时记忆中检索出来的相关片段。它的设计目标是高效、相关,直接服务于当下的推理。
  • 长时记忆:对应的是Agent的持久化存储。它记录了跨越不同会话、不同任务的历史交互、学到的知识、用户的偏好、完成的任务结果等。它的设计目标是容量大、可持久化、支持高效检索。

一个健壮的Agent记忆系统,必须同时包含这两者,并实现它们之间的顺畅流动:从长时记忆中根据当前情境检索出相关信息,放入工作记忆(上下文)中,辅助本次决策;而本次交互中有价值的新信息,又需要被筛选、加工后存入长时记忆,供未来使用。

2.2 记忆的流程:编码、存储与检索

记忆不是一个静态的仓库,而是一个动态的过程。认知科学将其描述为三个阶段:

  1. 编码:将外部信息转化为大脑可以存储和处理的内部表征。对于AI Agent,这就是将自然语言对话、工具调用结果、环境状态等原始数据,转化为结构化的、可索引的记忆对象。例如,将一段用户对话,解析并标记上时间戳、对话者、关键实体(人名、地点、任务名)、情感倾向、摘要等元数据。
  2. 存储:将编码后的信息放入记忆仓库。这里涉及存储介质的选择(内存、数据库、向量数据库)、存储格式(JSON、向量嵌入)以及组织方式(如何建立索引)。
  3. 检索:在需要时,根据当前线索从存储中找出相关信息。这是记忆系统最核心、也最复杂的环节。糟糕的检索等于没有记忆。我们需要设计检索策略:是基于关键词?基于语义相似度?还是基于时间、重要性等元数据?

这个“编码-存储-检索”的流程,就是我们实现记忆系统的核心逻辑闭环。接下来,我们将用TypeScript,一步步将这个蓝图构建出来。

3. 用TypeScript定义记忆的数据模型

理论清晰后,我们开始编码。首先,我们需要用强类型的TypeScript来定义记忆的基本结构。这能确保我们整个系统数据流动的可靠性。

3.1 核心记忆接口设计

我们设计一个基础的Memory接口。它不应该仅仅是一段文本,而应该是一个富含元数据的对象。

// 定义记忆的类型,例如对话、知识、事件等 export type MemoryType = 'conversation' | 'fact' | 'task_result' | 'user_preference'; // 核心记忆接口 export interface IMemory { id: string; // 唯一标识,通常使用UUID或纳秒时间戳 content: string; // 记忆的核心内容文本 type: MemoryType; // 记忆类型 embedding?: number[]; // 内容的向量嵌入,用于语义检索 metadata: { timestamp: Date; // 创建时间 source: string; // 来源,如 “user_input”, “tool_call”, “system_generated” importance: number; // 重要性评分,0-1,可用于记忆筛选 keywords: string[]; // 手动或自动提取的关键词 [key: string]: any; // 扩展元数据,如关联的用户ID、会话ID、情感值等 }; }

为什么这样设计?

  • idtimestamp是任何数据记录的基石,便于唯一标识和按时间排序。
  • type字段允许我们对记忆进行分类,未来可以针对不同类型实现不同的处理逻辑(例如,“用户偏好”类记忆的检索优先级可能更高)。
  • embedding字段是可选的,因为不是所有存储后端都支持向量检索(比如纯数据库),但它是实现语义搜索的关键。
  • metadata对象是一个灵活的“背包”,可以容纳各种用于检索和过滤的线索。importance是一个关键字段,我们可以设计规则来动态计算它(例如,用户反复强调的信息重要性更高)。

3.2 工作记忆与长时记忆的模型区分

虽然底层可以用同一个IMemory接口,但在应用层面,我们可以创建更具体的模型。

// 工作记忆项:通常与当前会话强相关 export interface IWorkingMemoryItem extends IMemory { // 可以增加会话相关的上下文信息 contextSessionId: string; // 可能关联到某个具体的“思考”步骤 stepId?: number; } // 长时记忆项:更强调持久化和检索效率 export interface ILongTermMemoryItem extends IMemory { // 增加访问频率和最后访问时间,用于实现类似“记忆巩固/遗忘”的机制 accessCount: number; lastAccessedAt: Date; // 可能关联多个会话或用户 relatedSessionIds: string[]; relatedUserId?: string; }

通过继承和扩展,我们既保持了核心数据模型的一致性,又为不同用途的记忆赋予了特定的属性和行为。接下来,我们需要一个地方来存放这些记忆。

4. 构建记忆的存储与检索引擎

存储和检索是记忆系统的“身体”。我们需要根据数据特性和访问模式,选择合适的存储方案。

4.1 存储策略:分层与混合

我建议采用一种分层混合存储策略,这在实际项目中非常有效:

  1. 高速缓存层(工作记忆):使用内存存储,如MapLRU Cache。存储当前会话的活跃记忆,读写极快。需要设置容量上限和过期策略,防止内存泄漏。
    import { LRUCache } from 'lru-cache'; export class WorkingMemoryStore { private cache: LRUCache<string, IWorkingMemoryItem>; constructor(maxSize: number) { this.cache = new LRUCache({ max: maxSize }); } // ... 增删改查方法 }
  2. 持久化层(长时记忆)
    • 元数据与结构化存储:使用关系型数据库(如 PostgreSQL)或文档数据库(如 MongoDB)。它们擅长存储和查询结构化的metadata,比如按时间范围、类型、关键词进行筛选。
    • 向量存储(用于语义检索):使用专门的向量数据库,如ChromaDB,Pinecone, 或Weaviate。它们专门为高维向量(即embedding)的相似性搜索而优化。这是实现“根据意思查找”而非“根据关键字匹配”的关键。

实操心得:不要试图用一个数据库解决所有问题。用PostgreSQL存元数据和关联关系,用ChromaDB存向量,两者通过id关联。这种“双写”或“异步同步”的模式虽然增加了一些复杂度,但让每种查询都做到了最优。

4.2 检索策略:从关键词到语义,再到混合搜索

检索是记忆系统的“大脑”。单一的检索方式往往不够用。

  1. 关键词检索:最简单直接。根据metadata.keywords或对content进行全文搜索(数据库的LIKE或全文索引)。适合精确匹配已知术语。
  2. 语义检索:这是AI Agent记忆的“灵魂”。流程如下: a.向量化:当一条记忆被存储时,使用嵌入模型(如OpenAI的text-embedding-3-small,或开源的BGESentenceTransformers)将content文本转化为embedding向量,存入向量数据库。 b.查询向量化:当需要检索时,将当前的查询语句(例如,用户的问题或Agent的当前目标)也转化为向量。 c.相似度计算:在向量数据库中,计算查询向量与所有记忆向量的余弦相似度或点积,返回最相似的Top-K条记忆。
    // 伪代码示例:语义检索核心流程 async function semanticSearch(query: string, vectorStore: VectorStore, topK: number): Promise<IMemory[]> { const queryEmbedding = await embeddingModel.encode(query); // 生成查询向量 const similarMemories = await vectorStore.similaritySearch(queryEmbedding, topK); return similarMemories; }
  3. 混合检索:在实际应用中,结合多种检索方式的结果往往效果最好。例如,可以并行执行关键词检索和语义检索,然后对结果进行去重、排序和融合。排序策略(Reranking)可以综合考虑相似度分数、记忆的重要性、时间新鲜度等因素。

注意:嵌入模型的选择至关重要。不同的模型在不同领域和语言上的表现差异很大。对于中文场景,直接使用OpenAI的嵌入模型可能不如专门优化过的中文模型(如BGE系列)。务必根据你的实际语料进行测试和评估。

5. 实现记忆系统的核心流程与API设计

有了数据模型和存储引擎,我们现在需要设计一套清晰的API,来串联起记忆的“编码-存储-检索”全流程。这个流程应该对Agent的核心推理逻辑是透明的,即Agent只需要调用“记住这个”和“回想一下相关的”,而不必关心底层细节。

5.1 记忆管理器:统一的门面

我们创建一个MemoryManager类,作为整个记忆系统的统一入口。

export class MemoryManager { private workingMemory: WorkingMemoryStore; private longTermStorage: ILongTermStorage; // 持久化存储接口 private vectorStore: IVectorStore; // 向量存储接口 private embeddingModel: IEmbeddingModel; // 嵌入模型接口 constructor(config: MemoryManagerConfig) { // 初始化各个组件 this.workingMemory = new WorkingMemoryStore(config.workingMemorySize); this.longTermStorage = new DatabaseStorage(config.dbConfig); // 具体实现 this.vectorStore = new ChromaVectorStore(config.vectorStoreConfig); // 具体实现 this.embeddingModel = new OpenAIEmbeddingModel(config.apiKey); // 具体实现 } // 核心API 1: 存储记忆 async remember(memoryData: Omit<IMemory, 'id' | 'embedding'>): Promise<string> { // 1. 编码:生成ID,计算重要性(可基于规则或简单模型),提取关键词 const memoryId = generateId(); const importance = this.calculateImportance(memoryData.content, memoryData.metadata); const keywords = this.extractKeywords(memoryData.content); const memoryToSave: IMemory = { ...memoryData, id: memoryId, metadata: { ...memoryData.metadata, importance, keywords }, }; // 2. 向量化(如果是需要语义检索的类型) if (this.shouldEmbed(memoryToSave.type)) { memoryToSave.embedding = await this.embeddingModel.generateEmbedding(memoryToSave.content); // 异步存储到向量库,避免阻塞主流程 this.vectorStore.save(memoryToSave.id, memoryToSave.embedding, memoryToSave.metadata).catch(console.error); } // 3. 存储:存入工作记忆(如果是当前会话)和长时存储 if (memoryData.metadata.source === 'current_session') { this.workingMemory.set(memoryId, memoryToSave as IWorkingMemoryItem); } await this.longTermStorage.save(memoryToSave); return memoryId; } // 核心API 2: 检索相关记忆 async recall(query: string, options: RecallOptions): Promise<IMemory[]> { const { limit, memoryTypes, recencyBias = true } = options; // 1. 并行执行多种检索 const [keywordResults, semanticResults] = await Promise.all([ this.longTermStorage.searchByKeywords(this.extractKeywords(query), { types: memoryTypes, limit }), this.semanticSearch(query, { types: memoryTypes, limit }), ]); // 2. 结果融合与去重 const allResults = this.mergeAndDeduplicate(keywordResults, semanticResults); // 3. 重新排序:综合相似度、重要性、时间等因素 const rerankedResults = this.rerankMemories(allResults, query, { recencyBias }); // 4. 返回Top-N return rerankedResults.slice(0, limit); } // 其他辅助方法:忘记(软删除)、更新重要性、清理过期工作记忆等 async forget(memoryId: string): Promise<void> { /* ... */ } async refreshWorkingMemory(sessionId: string): Promise<void> { /* ... */ } }

为什么这样设计API?

  • remember方法封装了完整的编码和存储逻辑,Agent只需传递原始内容。内部的向量化、关键词提取、重要性计算都是自动化的。
  • recall方法提供了灵活的检索选项。recencyBias参数是一个实用技巧,让更近的记忆有更高权重,这符合人类的记忆规律(近因效应)。
  • 将向量存储 (vectorStore.save) 设计为异步操作,可以显著提升remember方法的响应速度,因为向量化通常是耗时操作。你需要确保有机制处理异步失败的情况(如重试队列)。

5.2 与Agent框架的集成

记忆管理器本身是独立的。要集成到如 LangChain、LlamaIndex 或自定义的Agent框架中,你需要创建适配层。

  • 作为工具(Tool):可以将rememberrecall封装成Agent可以调用的工具。当Agent认为某条信息需要长期保存时,就调用“记住”工具。
  • 作为中间件(Middleware):更优雅的方式是在Agent的推理循环中插入记忆中间件。在Agent处理用户输入前,自动调用recall检索相关记忆,并将它们注入到本次对话的上下文(工作记忆)中。在Agent生成回复后,自动分析回复内容,将有价值的信息调用remember保存。
// 伪代码:记忆中间件 class MemoryMiddleware { constructor(private memoryManager: MemoryManager) {} async beforeInvoke(agentState: AgentState): Promise<void> { const userQuery = agentState.currentInput; const relevantMemories = await this.memoryManager.recall(userQuery, { limit: 5 }); // 将检索到的记忆格式化,加入到agentState的上下文提示词中 agentState.context.append(this.formatMemoriesForPrompt(relevantMemories)); } async afterInvoke(agentState: AgentState): Promise<void> { const agentResponse = agentState.currentOutput; // 分析response,判断是否需要保存为长期记忆(例如,包含了总结的用户偏好或新学到的知识) if (this.shouldRemember(agentResponse)) { await this.memoryManager.remember({ content: this.extractMemoryContent(agentResponse), type: 'fact', metadata: { /* ... */ } }); } } }

这种中间件模式,让记忆的读写变成了Agent运行流程中自动化的、不可或缺的一部分,极大地提升了Agent的智能连贯性。

6. 高级主题与实战避坑指南

实现基础记忆系统后,我们会面临更复杂的问题。以下是一些高级主题和我在实践中踩过的坑。

6.1 记忆的抽象、总结与遗忘

  • 原始记忆 vs. 摘要记忆:保存每一句对话的原始文本会导致存储爆炸和检索噪音。一个有效的策略是定期(例如,一个会话结束时)对一段时间的原始记忆进行总结,生成一条高度凝练的摘要记忆存入长时存储,同时清理或归档原始细节。这模仿了人类将短期经验转化为长期知识的过程。
  • 实现遗忘机制:记忆不是越多越好。无用的、过时的信息会污染检索结果。可以基于以下策略实现“遗忘”:
    • 基于重要性:定期清理importance分数低于阈值且很久未访问的记忆。
    • 基于时间衰减:模拟艾宾浩斯遗忘曲线,记忆的“强度”随时间衰减,低于阈值则移除。
    • 主动修剪:当存储达到上限时,优先删除最不重要的记忆。

6.2 检索质量优化:从RAG到Rerank

简单的向量相似度搜索(即基础的RAG)在复杂场景下可能不够精准。

  • 问题1:丢失关键信息。查询“上周三我和张三开会讨论了什么?”,语义搜索可能找到所有包含“开会”、“张三”的记忆,但无法精准锁定“上周三”。
  • 解决方案:元数据过滤。在向量搜索前或后,必须结合metadata中的时间戳、人物等字段进行硬过滤。我们的recall方法中的memoryTypes和未来可扩展的过滤条件就是为此而生。
  • 问题2:排序不优。语义相似度最高的,不一定是当前最需要的。
  • 解决方案:重排序(Reranking)。使用一个更精细但计算量更大的重排序模型(如Cohere的Rerank API,或BGE的Reranker模型),对初步检索到的Top-20条结果进行重新打分和排序。这能显著提升最相关记忆排在前列的概率。在我们的rerankMemories方法中,就可以集成这样的重排序逻辑。

6.3 测试与评估:如何验证记忆系统有效?

记忆系统的好坏不能凭感觉,必须建立评估体系。

  1. 单元测试:测试MemoryManager的每个方法,特别是rememberrecall。模拟数据,验证存储和检索功能是否正确。
  2. 集成测试:将记忆系统与一个简单的Agent连接,设计多轮对话测试。验证Agent在后续对话中是否能正确引用之前提到过的信息。
  3. 评估指标
    • 检索召回率(Recall):对于给定的测试查询,系统能否找回所有相关的已知记忆?
    • 检索精确率(Precision):返回的记忆中,有多少是真正相关的?
    • 任务完成度:在一个需要记忆的多步骤任务中(例如,“记住我最喜欢的颜色是蓝色,然后在我下次问你推荐衣服时提及”),配备记忆系统的Agent任务成功率是否显著高于没有记忆的基线Agent?

踩坑实录:向量嵌入的“语义漂移”早期我们直接使用通用的嵌入模型,发现对于领域特定的术语(比如我们产品内部的模块名称),检索效果很差。例如,“同步引擎”和“数据管道”在通用语义上不相似,但在我们的业务语境下紧密相关。解决方案是进行领域适配:收集一批领域内的文本对(相似/不相似),对开源的嵌入模型(如BGE)进行轻量级的继续预训练(Contrastive Learning),让模型学会我们领域的语义空间。这一步带来的检索精度提升是巨大的。

7. 总结与展望:迈向更自主的Agent

构建一个记忆系统,是将AI Agent从“一次性的问答机”升级为“长期的数字伴侣”的关键一步。我们从认知科学中获得设计灵感,用分层的存储架构和混合检索策略将其工程化实现,并通过清晰的API和中间件模式与Agent框架无缝集成。

这个系统目前还处于“显式记忆”阶段——即由我们设计规则来决定什么该记、如何记、如何找。未来的方向是让记忆系统更加自主化

  • 记忆的自动评估与压缩:让Agent自己判断信息的价值,自动生成摘要,决定存储还是遗忘。
  • 记忆间的关联与推理:不仅存储孤立的记忆片段,还能建立记忆之间的关联图(例如,“事件A导致了事件B”),支持更复杂的推理查询。
  • 情感与个性化记忆:为记忆附加情感色彩或个性化标签,让Agent的回应不仅能基于事实,还能贴合用户的情绪和风格。

实现这些,需要我们更深入地融合机器学习模型与符号化的知识表示。但无论如何,今天用TypeScript搭建的这个坚实、可扩展的记忆系统底座,将是通向未来更智能Agent的必经之路。

http://www.jsqmd.com/news/1396407/

相关文章:

  • VSCode中Prettier格式化失效的六步排查与最佳实践
  • 数学建模竞赛实战指南:从APMCM获奖看团队协作与模型构建
  • GitNexus:基于代码知识图谱的AI编程助手全局依赖分析实践
  • Hold Rein代码图插件:AI驱动的项目全局理解与可视化架构分析工具
  • 彻底清理顽固软件残留文件:从权限占用到纯净环境删除指南
  • Meta开源轻量多模态模型Muse Glimmer:本地部署与实战指南
  • Win10系统语言切换引发乱码的根源与解决方案
  • 数学建模竞赛成绩信息整合:从数据聚合到高效分发的全流程实践
  • PyCharm智能代码补全插件开发:从LSP集成到AI预测的架构实践
  • Qt qDebug输出中文乱码:从编码原理到跨平台解决方案
  • 坐标转换实战指南:四参数与七参数的本质区别与正确选择
  • API安全防护:从原理到企业级实践指南
  • 生物信息学入门实战:从FASTQ到差异表达分析的完整流程
  • 渗透测试痕迹清理实战:从日志擦除到全程隐身的攻防艺术
  • 深入理解原子操作:__atomic_store与__atomic_load原理与应用
  • TMC2209步进电机丢步问题深度解析与工程解决方案
  • 数值转换全解析:从基础概念到跨系统实战避坑指南
  • HarmonyOS6 ArkTS List编辑模式开发指南
  • 2026 年更新:宁波靠谱的豆包推广运营中心哪家好,用了这玩意儿,我才知道原来推广能省这么多精力!-抖信盈网络科技 - 行业鉴选官
  • 2026厂房降温实力服务商评估与选型参考 - 卓企推荐
  • Shell输出到剪贴板:跨平台与SSH环境下的高效操作指南
  • Android WebView深度解析:从基础配置到性能优化与安全实践
  • CTFHub SSRF漏洞实战:从内网探测到伪协议攻击与自动化扫描
  • 网络物理层基石:RJ45接口、T568A/B线序与直连/交叉线全解析
  • Python面试核心:从可变对象到垃圾回收,夯实基础避坑指南
  • 程序员如何驾驭AI实现能力跃迁:从执行者到解决方案架构师
  • HoRain云RESTful API设计规范与实战指南
  • 能量结构化世界模型与神经时间场:实现物理一致开放世界运动规划
  • Nacos单机部署实战:从环境配置到故障排查的完整指南
  • C盘扩容全攻略:安全扩展系统盘空间,告别磁盘不足