尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

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

从认知科学到工程实践:构建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 记忆的流程编码、存储与检索记忆不是一个静态的仓库而是一个动态的过程。认知科学将其描述为三个阶段编码将外部信息转化为大脑可以存储和处理的内部表征。对于AI Agent这就是将自然语言对话、工具调用结果、环境状态等原始数据转化为结构化的、可索引的记忆对象。例如将一段用户对话解析并标记上时间戳、对话者、关键实体人名、地点、任务名、情感倾向、摘要等元数据。存储将编码后的信息放入记忆仓库。这里涉及存储介质的选择内存、数据库、向量数据库、存储格式JSON、向量嵌入以及组织方式如何建立索引。检索在需要时根据当前线索从存储中找出相关信息。这是记忆系统最核心、也最复杂的环节。糟糕的检索等于没有记忆。我们需要设计检索策略是基于关键词基于语义相似度还是基于时间、重要性等元数据这个“编码-存储-检索”的流程就是我们实现记忆系统的核心逻辑闭环。接下来我们将用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、情感值等 }; }为什么这样设计id和timestamp是任何数据记录的基石便于唯一标识和按时间排序。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 存储策略分层与混合我建议采用一种分层混合存储策略这在实际项目中非常有效高速缓存层工作记忆使用内存存储如Map或LRU Cache。存储当前会话的活跃记忆读写极快。需要设置容量上限和过期策略防止内存泄漏。import { LRUCache } from lru-cache; export class WorkingMemoryStore { private cache: LRUCachestring, IWorkingMemoryItem; constructor(maxSize: number) { this.cache new LRUCache({ max: maxSize }); } // ... 增删改查方法 }持久化层长时记忆元数据与结构化存储使用关系型数据库如 PostgreSQL或文档数据库如 MongoDB。它们擅长存储和查询结构化的metadata比如按时间范围、类型、关键词进行筛选。向量存储用于语义检索使用专门的向量数据库如ChromaDB,Pinecone, 或Weaviate。它们专门为高维向量即embedding的相似性搜索而优化。这是实现“根据意思查找”而非“根据关键字匹配”的关键。实操心得不要试图用一个数据库解决所有问题。用PostgreSQL存元数据和关联关系用ChromaDB存向量两者通过id关联。这种“双写”或“异步同步”的模式虽然增加了一些复杂度但让每种查询都做到了最优。4.2 检索策略从关键词到语义再到混合搜索检索是记忆系统的“大脑”。单一的检索方式往往不够用。关键词检索最简单直接。根据metadata.keywords或对content进行全文搜索数据库的LIKE或全文索引。适合精确匹配已知术语。语义检索这是AI Agent记忆的“灵魂”。流程如下 a.向量化当一条记忆被存储时使用嵌入模型如OpenAI的text-embedding-3-small或开源的BGE、SentenceTransformers将content文本转化为embedding向量存入向量数据库。 b.查询向量化当需要检索时将当前的查询语句例如用户的问题或Agent的当前目标也转化为向量。 c.相似度计算在向量数据库中计算查询向量与所有记忆向量的余弦相似度或点积返回最相似的Top-K条记忆。// 伪代码示例语义检索核心流程 async function semanticSearch(query: string, vectorStore: VectorStore, topK: number): PromiseIMemory[] { const queryEmbedding await embeddingModel.encode(query); // 生成查询向量 const similarMemories await vectorStore.similaritySearch(queryEmbedding, topK); return similarMemories; }混合检索在实际应用中结合多种检索方式的结果往往效果最好。例如可以并行执行关键词检索和语义检索然后对结果进行去重、排序和融合。排序策略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: OmitIMemory, id | embedding): Promisestring { // 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): PromiseIMemory[] { 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): Promisevoid { /* ... */ } async refreshWorkingMemory(sessionId: string): Promisevoid { /* ... */ } }为什么这样设计APIremember方法封装了完整的编码和存储逻辑Agent只需传递原始内容。内部的向量化、关键词提取、重要性计算都是自动化的。recall方法提供了灵活的检索选项。recencyBias参数是一个实用技巧让更近的记忆有更高权重这符合人类的记忆规律近因效应。将向量存储 (vectorStore.save) 设计为异步操作可以显著提升remember方法的响应速度因为向量化通常是耗时操作。你需要确保有机制处理异步失败的情况如重试队列。5.2 与Agent框架的集成记忆管理器本身是独立的。要集成到如 LangChain、LlamaIndex 或自定义的Agent框架中你需要创建适配层。作为工具Tool可以将remember和recall封装成Agent可以调用的工具。当Agent认为某条信息需要长期保存时就调用“记住”工具。作为中间件Middleware更优雅的方式是在Agent的推理循环中插入记忆中间件。在Agent处理用户输入前自动调用recall检索相关记忆并将它们注入到本次对话的上下文工作记忆中。在Agent生成回复后自动分析回复内容将有价值的信息调用remember保存。// 伪代码记忆中间件 class MemoryMiddleware { constructor(private memoryManager: MemoryManager) {} async beforeInvoke(agentState: AgentState): Promisevoid { const userQuery agentState.currentInput; const relevantMemories await this.memoryManager.recall(userQuery, { limit: 5 }); // 将检索到的记忆格式化加入到agentState的上下文提示词中 agentState.context.append(this.formatMemoriesForPrompt(relevantMemories)); } async afterInvoke(agentState: AgentState): Promisevoid { 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 测试与评估如何验证记忆系统有效记忆系统的好坏不能凭感觉必须建立评估体系。单元测试测试MemoryManager的每个方法特别是remember和recall。模拟数据验证存储和检索功能是否正确。集成测试将记忆系统与一个简单的Agent连接设计多轮对话测试。验证Agent在后续对话中是否能正确引用之前提到过的信息。评估指标检索召回率Recall对于给定的测试查询系统能否找回所有相关的已知记忆检索精确率Precision返回的记忆中有多少是真正相关的任务完成度在一个需要记忆的多步骤任务中例如“记住我最喜欢的颜色是蓝色然后在我下次问你推荐衣服时提及”配备记忆系统的Agent任务成功率是否显著高于没有记忆的基线Agent踩坑实录向量嵌入的“语义漂移”早期我们直接使用通用的嵌入模型发现对于领域特定的术语比如我们产品内部的模块名称检索效果很差。例如“同步引擎”和“数据管道”在通用语义上不相似但在我们的业务语境下紧密相关。解决方案是进行领域适配收集一批领域内的文本对相似/不相似对开源的嵌入模型如BGE进行轻量级的继续预训练Contrastive Learning让模型学会我们领域的语义空间。这一步带来的检索精度提升是巨大的。7. 总结与展望迈向更自主的Agent构建一个记忆系统是将AI Agent从“一次性的问答机”升级为“长期的数字伴侣”的关键一步。我们从认知科学中获得设计灵感用分层的存储架构和混合检索策略将其工程化实现并通过清晰的API和中间件模式与Agent框架无缝集成。这个系统目前还处于“显式记忆”阶段——即由我们设计规则来决定什么该记、如何记、如何找。未来的方向是让记忆系统更加自主化记忆的自动评估与压缩让Agent自己判断信息的价值自动生成摘要决定存储还是遗忘。记忆间的关联与推理不仅存储孤立的记忆片段还能建立记忆之间的关联图例如“事件A导致了事件B”支持更复杂的推理查询。情感与个性化记忆为记忆附加情感色彩或个性化标签让Agent的回应不仅能基于事实还能贴合用户的情绪和风格。实现这些需要我们更深入地融合机器学习模型与符号化的知识表示。但无论如何今天用TypeScript搭建的这个坚实、可扩展的记忆系统底座将是通向未来更智能Agent的必经之路。
返回列表