尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

基于认知科学的AI Agent记忆系统设计与TypeScript实现

基于认知科学的AI Agent记忆系统设计与TypeScript实现 1. 项目概述为什么AI Agent需要一个记忆系统如果你正在开发一个AI Agent无论是聊天机器人、自动化助手还是更复杂的决策系统你肯定遇到过这样的场景用户问“我昨天提到的那个项目进展如何了”或者Agent在处理一个多步骤任务时完全忘记了第一步自己做了什么决定。这种“健忘症”让Agent显得非常愚蠢用户体验大打折扣。这背后的核心问题就是大多数基于大语言模型的Agent缺乏一个持久化、结构化的记忆系统。这个项目就是从认知科学的视角出发探讨如何为AI Agent设计和实现一个真正有用的记忆系统并用TypeScript代码将其落地。这不仅仅是简单地存储聊天记录而是借鉴人类记忆的工作机制——比如工作记忆、情景记忆、语义记忆——来构建一个能让Agent“记住”关键信息、“回忆”相关上下文、“遗忘”无用噪音的智能架构。一个强大的记忆系统是Agent实现长期对话、个性化服务、复杂任务规划和持续学习的基础。无论你是想构建一个能进行深度对话的陪伴型Agent还是一个能处理复杂工作流的自动化工具理解并实现记忆系统都是绕不开的关键一步。2. 记忆系统的认知科学基础与设计思路2.1 人类记忆模型对AI的启示在动手写代码之前我们必须先想清楚要构建什么。盲目地存储所有交互历史只会得到一个臃肿且低效的“垃圾堆”。认知科学为我们提供了清晰的蓝图。人类的记忆并非单一仓库而是一个多层次的系统感觉记忆瞬时存储大量感官信息。对应到Agent可以理解为对单次输入用户消息、API返回、传感器数据的原始缓存留存时间极短主要用于初步过滤。工作记忆容量有限用于处理当前任务中的信息。这是Agent的“思考白板”存放正在被推理、组合和操作的上下文。LLM本身的上下文窗口Context Window在某种程度上扮演了这个角色但其容量和持久性远远不够。长期记忆容量近乎无限用于存储需要长期保留的知识和经验。这正是我们要为Agent构建的核心。它又可以细分为情景记忆对特定事件、经历的记忆包括时间、地点、人物、情感。例如“用户张三在2023年10月26日下午3点抱怨过登录缓慢”。这对实现个性化对话和历史回溯至关重要。语义记忆对概念、事实和知识的记忆与具体经历无关。例如“巴黎是法国的首都”“登录流程需要验证用户名和密码”。这构成了Agent的“常识库”或“知识图谱”。程序性记忆关于“如何做”的记忆比如骑自行车、打字。对应到Agent就是它学会的技能Skills和执行特定任务的流程Workflows。注意我们并非要完全复刻人脑而是借鉴其分层、分类、有选择性地存储与提取的核心思想。这直接决定了我们代码中的数据结构设计。2.2 AI Agent记忆系统的核心设计目标基于以上认知模型一个实用的AI Agent记忆系统应满足以下几个设计目标持久化与可检索性记忆必须能跨越会话Session存在并能通过高效的查询被快速召回。这要求我们将记忆存储在外部数据库如向量数据库、关系型数据库中而非仅仅依赖LLM的临时上下文。结构化与语义化原始文本存储不利于精确检索。我们需要将记忆结构化例如提取实体用户、项目、产品、动作询问、确认、执行、情感正面、负面和关键事实并转换为语义向量Embeddings。这样Agent不仅能通过关键词更能通过“意思”来查找相关记忆。动态更新与整合记忆不是一次写入就永不改变。新的交互可能会修正旧记忆“用户更正了手机号”或与旧记忆整合形成新的认知“用户每次周一早上都会询问周报模板”。选择性遗忘与记忆强化并非所有信息都值得长期记住。系统需要机制来衰减不重要的记忆如琐碎的寒暄或定期清理过时数据同时强化高频访问或标记为重要的记忆。与推理循环集成记忆系统不能是孤立的。它需要无缝嵌入Agent的“感知-思考-行动”循环中。在“思考”阶段Agent应能自动查询相关记忆作为上下文在“行动”后重要结果应能自动存储为新的记忆。3. 核心模块拆解与TypeScript接口设计接下来我们将设计目标转化为具体的TypeScript模块和接口。我们将系统分为几个核心层每一层职责明确。3.1 记忆表示层定义记忆的数据结构这是记忆系统的基石。我们首先定义Memory这个核心接口。// 记忆的元数据用于管理和检索 interface MemoryMetadata { id: string; // 唯一标识符通常使用UUID timestamp: Date; // 记忆创建或发生的时间 source: string; // 记忆来源如 “user_message”, “agent_action”, “system” agentId?: string; // 产生此记忆的Agent ID多Agent场景 tags: string[]; // 标签用于分类如 [“project-alpha”, “complaint”, “preference”] importance: number; // 重要性评分0-1可由LLM或规则生成 lastAccessed?: Date; // 最后访问时间用于实现基于访问频率的强化 } // 记忆的内容本体采用联合类型支持多种记忆形式 type MemoryContent | { type: ‘observational’; text: string } // 观察性记忆用户说了什么系统看到了什么 | { type: ‘factual’; subject: string; predicate: string; object: string } // 事实性记忆三元组便于结构化查询 | { type: ‘procedural’; skillName: string; steps: string[] } // 程序性记忆 | { type: ‘summary’; ofMemoryIds: string[]; text: string }; // 摘要性记忆用于压缩长对话 // 核心记忆接口 interface Memory { metadata: MemoryMetadata; content: MemoryContent; embedding?: number[]; // 语义向量由文本内容生成用于向量检索 }设计理由将元数据与内容分离便于独立索引和管理。MemoryContent的联合类型设计允许系统灵活支持从简单文本到复杂知识图谱的不同粒度记忆。embedding字段是可选的因为并非所有存储后端都支持向量检索。3.2 记忆存储层持久化与检索抽象存储层负责记忆的增删改查。我们定义一个抽象接口MemoryStore以便未来可以轻松切换不同的存储后端如内存、SQLite、PostgreSQL、向量数据库Pinecone/Weaviate。interface MemoryStore { // 增 create(memory: Memory): Promisestring; // 返回创建的记忆ID createBatch(memories: Memory[]): Promisestring[]; // 删 delete(memoryId: string): Promiseboolean; deleteByFilter(filter: MemoryFilter): Promisenumber; // 根据条件批量删除 // 改 update(memoryId: string, updates: PartialMemory): Promiseboolean; // 特别重要的更新访问时间或重要性 touch(memoryId: string): Promiseboolean; // 更新lastAccessed时间 reinforce(memoryId: string, delta: number): Promiseboolean; // 调整重要性分数 // 查 - 这是核心中的核心 get(memoryId: string): PromiseMemory | null; // 基于元数据的过滤查询精确匹配 findByMetadata(filter: MemoryFilter): PromiseMemory[]; // 基于语义的相似性搜索向量检索 searchByEmbedding( queryEmbedding: number[], options: { limit: number; similarityThreshold?: number } ): PromiseArray{ memory: Memory; similarity: number }; // 混合搜索结合元数据过滤和语义搜索 hybridSearch( filter: MemoryFilter, queryText?: string, options: { limit: number } ): PromiseMemory[]; } // 用于过滤记忆的条件 interface MemoryFilter { startTime?: Date; endTime?: Date; source?: string; tags?: string[]; minImportance?: number; // ... 其他可索引的字段 }实操心得hybridSearch方法非常关键。在实际应用中用户可能既想查找“上周”时间过滤关于“项目A”标签过滤的“所有讨论”语义搜索。纯向量搜索可能把“项目B”的相似讨论也找出来而混合搜索能更精准地定位目标。3.3 记忆处理层记忆的生成、提取与压缩这一层是系统的“大脑”负责将原始信息加工成记忆以及从记忆中提取有用的上下文。interface MemoryProcessor { // 1. 记忆生成从原始交互中提取结构化记忆 extractMemoriesFromInteraction(interaction: { role: ‘user’ | ‘agent’ | ‘system’; content: string; timestamp: Date; // 可能包含更丰富的原始数据 }): PromiseMemory[]; // 2. 记忆摘要将一系列相关记忆压缩成一条摘要记忆防止记忆爆炸 summarizeMemories(memoryIds: string[]): PromiseMemory; // 3. 查询理解将用户的自然语言查询或Agent的当前目标转换为搜索记忆的指令 formulateMemoryQuery(context: string): Promise{ semanticQuery?: string; // 用于生成向量的查询文本 filter?: MemoryFilter; // 用于精确过滤的条件 }; // 4. 上下文组装获取相关记忆后将其格式化成适合送入LLM上下文的文本 formatMemoriesForContext(memories: Memory[]): string; }核心实现细节extractMemoriesFromInteraction函数通常会调用LLM如GPT-4或更轻量的NLP模型。你可以设计一个Prompt让LLM从一段对话中识别出可能成为长期记忆的要点并以指定的JSON格式输出。例如你是一个记忆提取助手。请从以下对话中识别出需要存入长期记忆的关键信息。 输出格式为JSON数组每个元素包含type(observational/factual), content等字段。 对话 用户“我更喜欢用邮件接收报告别发Slack了。” Agent“好的已更新您的偏好。”LLM应输出一个包含type为factual,content为{“subject”: “user”, “predicate”: “prefers”, “object”: “email for reports”}的记忆。4. 集成到AI Agent工作流让记忆流动起来设计好模块后最关键的一步是将其融入Agent的推理循环。一个典型的基于LLM的Agent循环ReAct模式包括观察、思考、行动。记忆系统应深度嵌入“思考”阶段。4.1 在每次推理前自动检索相关记忆我们创建一个AgentWithMemory类它包装了基础的LLM调用。class AgentWithMemory { constructor( private llmClient: LLMClient, private memoryStore: MemoryStore, private memoryProcessor: MemoryProcessor ) {} async runCycle(userInput: string, currentContext: any): PromiseAgentAction { // 步骤1观察 - 将当前输入和上下文转化为记忆查询 const query await this.memoryProcessor.formulateMemoryQuery( 用户说“${userInput}”。当前任务上下文${JSON.stringify(currentContext)} ); // 步骤2检索 - 从记忆库中获取相关记忆 let relevantMemories: Memory[] []; if (query.semanticQuery) { // 生成查询向量需要嵌入模型 const queryEmbedding await generateEmbedding(query.semanticQuery); const vectorResults await this.memoryStore.searchByEmbedding(queryEmbedding, { limit: 5 }); relevantMemories vectorResults.map(r r.memory); } if (query.filter) { const filteredMemories await this.memoryStore.findByMetadata(query.filter); // 合并结果去重 relevantMemories this.mergeAndDedupeMemories(relevantMemories, filteredMemories); } // 步骤3思考 - 将相关记忆格式化为上下文与当前输入一起送给LLM const memoryContext this.memoryProcessor.formatMemoriesForContext(relevantMemories); const fullPrompt 以下是来自过去的相关记忆 ${memoryContext} 当前对话 用户${userInput} 请基于以上信息包括历史记忆进行思考并决定下一步行动。 ; const llmResponse await this.llmClient.complete(fullPrompt); const action this.parseLlmResponse(llmResponse); // 解析出工具调用或回复 // 步骤4行动 - 执行动作 const result await this.executeAction(action); // 步骤5记忆 - 将本轮交互的重要结果存储下来 const newMemories await this.memoryProcessor.extractMemoriesFromInteraction({ role: ‘user’, content: userInput, timestamp: new Date(), }); // 也可能存储Agent行动的结果作为记忆 const resultMemories await this.memoryProcessor.extractMemoriesFromInteraction({ role: ‘agent’, content: 执行了动作 ${action.type}结果${JSON.stringify(result)}, timestamp: new Date(), }); await this.memoryStore.createBatch([...newMemories, ...resultMemories]); // 更新相关记忆的“最后访问时间”强化它们 for (const mem of relevantMemories) { await this.memoryStore.touch(mem.metadata.id); } return { action, result }; } }注意事项记忆爆炸是常见问题。如果每一轮交互都存储多条记忆数据库会飞速膨胀。解决方案是设置重要性阈值只有importance分数高于某个值如0.7的记忆才被长期存储。定期摘要启动一个后台进程定期如每天将同一主题的大量细粒度记忆如关于“项目A”的20条讨论通过summarizeMemories合并成一条摘要记忆然后归档或删除原始记忆。遗忘机制实现一个“记忆清理”函数定期降低长时间未被访问的记忆的importance分数当分数低于阈值时自动删除。4.2 记忆的主动触发与提醒高级的记忆系统不应只是被动查询还应能主动提醒。例如当用户再次提到“项目A”时系统可以主动提示“根据上次记录您提到项目A的截止日期是本周五需要我为您查看当前进度吗”这需要在检索到高相关度记忆并且该记忆关联着未完成的待办事项Todo或重要提醒时由MemoryProcessor在格式化上下文时额外添加一个“主动提醒”部分。5. 实战基于本地向量数据库的实现方案理论说再多不如跑通一个最小可行产品。我们用一个完全可在本地运行的技术栈来实现核心流程。5.1 技术栈选择运行时/框架Node.js TypeScript。这是AI Agent生态的主流选择。向量数据库Chroma。它轻量、开源、可以嵌入运行非常适合原型开发和中小项目。也可以选择LanceDB或SQLite-VSS如果更喜欢SQL接口。嵌入模型OpenAI的text-embedding-3-small。对于原型来说它的效果、速度和成本平衡得最好。如果想完全本地化可以使用**Hugging Face上的all-MiniLM-L6-v2**模型通过xenova/transformers库在本地运行虽然速度慢些但无需网络和API密钥。LLM用于记忆提取和Agent推理。可以选择OpenAI GPT、Anthropic Claude的API或者本地运行的Ollama运行Llama 3、Qwen等开源模型。5.2 核心代码实现片段首先安装依赖npm install chromadb openaiimport { ChromaClient } from ‘chromadb’; import OpenAI from ‘openai’; // 1. 初始化客户端 const chromaClient new ChromaClient({ path: “http://localhost:8000” }); // 假设Chroma在本地运行 const openai new OpenAI({ apiKey: process.env.OPENAI_API_KEY }); // 2. 实现一个基于Chroma的MemoryStore class ChromaMemoryStore implements MemoryStore { private collection: any; constructor(collectionName: string) { this.initializeCollection(collectionName); } private async initializeCollection(name: string) { // 创建或获取一个集合指定嵌入向量的维度text-embedding-3-small是1536维 this.collection await chromaClient.getOrCreateCollection({ name, metadata: { “hnsw:space”: “cosine” } // 使用余弦相似度 }); } async create(memory: Memory): Promisestring { const id memory.metadata.id; const embedding memory.embedding || await this.generateEmbedding(memory); const metadata { timestamp: memory.metadata.timestamp.toISOString(), source: memory.metadata.source, tags: JSON.stringify(memory.metadata.tags), importance: memory.metadata.importance.toString(), }; await this.collection.add({ ids: [id], embeddings: [embedding], metadatas: [metadata], documents: [JSON.stringify(memory.content)], // 将内容作为文档存储 }); return id; } async searchByEmbedding( queryEmbedding: number[], options: { limit: number; similarityThreshold?: number } ): PromiseArray{ memory: Memory; similarity: number } { const results await this.collection.query({ queryEmbeddings: [queryEmbedding], nResults: options.limit, }); const memories: Array{ memory: Memory; similarity: number } []; for (let i 0; i results.ids[0].length; i) { const distance results.distances[0][i]; // Chroma返回的是距离余弦相似度1-距离 const similarity 1 - distance; if (options.similarityThreshold similarity options.similarityThreshold) { continue; } const memory: Memory { metadata: { id: results.ids[0][i], timestamp: new Date(results.metadatas[0][i].timestamp), source: results.metadatas[0][i].source, tags: JSON.parse(results.metadatas[0][i].tags), importance: parseFloat(results.metadatas[0][i].importance), }, content: JSON.parse(results.documents[0][i]), }; memories.push({ memory, similarity }); } return memories; } // ... 实现其他接口方法findByMetadata, update, delete等 // findByMetadata需要遍历或依赖Chroma的元数据过滤功能如果版本支持 private async generateEmbedding(memory: Memory): Promisenumber[] { // 根据记忆内容生成文本描述 const textToEmbed this.getTextRepresentation(memory); const response await openai.embeddings.create({ model: “text-embedding-3-small”, input: textToEmbed, }); return response.data[0].embedding; } private getTextRepresentation(memory: Memory): string { // 将记忆内容转换为用于生成嵌入的文本 switch (memory.content.type) { case ‘observational’: return memory.content.text; case ‘factual’: return ${memory.content.subject} ${memory.content.predicate} ${memory.content.object}; case ‘procedural’: return Skill: ${memory.content.skillName}. Steps: ${memory.content.steps.join(‘, ‘)}; case ‘summary’: return Summary: ${memory.content.text}; } } }踩坑记录嵌入维度一致性确保你使用的嵌入模型维度与创建向量数据库集合时指定的维度一致。text-embedding-3-small是1536维text-embedding-3-large是3072维混用会导致错误。元数据序列化Chroma的元数据字段值通常要求是字符串。存储数组或对象时需要使用JSON.stringify()读取时使用JSON.parse()。相似度阈值余弦相似度范围是[-1, 1]但通常处理后是[0, 1]。对于文本0.7~0.8以上通常表示强相关。需要根据你的嵌入模型和数据进行调整设置similarityThreshold可以过滤掉低质量结果。5.3 一个简单的记忆提取处理器实现class OpenAIMemoryProcessor implements MemoryProcessor { async extractMemoriesFromInteraction(interaction: any): PromiseMemory[] { const prompt 你是一个记忆提取专家。请分析以下对话提取出值得放入AI长期记忆的信息。 这些信息包括用户明确陈述的偏好或事实、重要的承诺或决定、关键的个人或项目信息。 对于每个提取出的记忆请按以下JSON格式输出 { “type”: “factual” | “observational”, “content”: { ... } // 根据type不同结构见下文 } 如果是事实factualcontent应为 {“subject”: “”, “predicate”: “”, “object”: “”} 格式。 如果是观察observationalcontent应为 {“text”: “”} 格式。 对话内容 ${interaction.content} 请只输出一个JSON数组。 ; const response await openai.chat.completions.create({ model: “gpt-4-turbo-preview”, messages: [{ role: ‘user’, content: prompt }], response_format: { type: “json_object” }, }); const extracted JSON.parse(response.choices[0].message.content || ‘[]’); // 将提取的数据包装成Memory对象 return extracted.map((item: any) ({ metadata: { id: uuidv4(), timestamp: interaction.timestamp, source: interaction.role, tags: [], // 可以尝试让LLM也生成标签 importance: 0.5, // 初始重要性 }, content: item, })); } // ... 实现其他接口方法 }6. 性能优化与高级特性探讨当系统跑起来后你会面临性能和扩展性挑战。6.1 检索优化策略分层记忆检索不要每次都用向量搜索全库。可以先通过时间、标签等元数据过滤出一个较小的候选集再在这个集合上做向量相似度计算。这能大幅减少计算量。缓存热点记忆对于当前会话或任务中频繁访问的记忆可以缓存在内存中如使用LRU Cache避免重复查询数据库。预计算记忆关联在记忆存储时通过图数据库或简单的关联表记录记忆与记忆之间的关系如“属于同一话题”、“是前因后果”。检索时可以先找“种子记忆”再通过关联关系找到相关记忆这比纯向量搜索有时更精准。6.2 记忆的评估与遗忘机制实现一个后台的“记忆管理”服务。class MemoryManager { constructor(private memoryStore: MemoryStore) {} async decayAndForget() { // 1. 重要性衰减长时间未访问的记忆重要性降低 const oldMemories await this.memoryStore.findByMetadata({ endTime: new Date(Date.now() - 30 * 24 * 60 * 60 * 1000), // 30天前 }); for (const mem of oldMemories) { // 如果超过90天未访问重要性降为原来的0.9倍 const daysSinceAccess (Date.now() - (mem.metadata.lastAccessed?.getTime() || mem.metadata.timestamp.getTime())) / (1000*60*60*24); if (daysSinceAccess 90) { await this.memoryStore.reinforce(mem.metadata.id, -0.1); // 减少0.1 } } // 2. 清理低重要性记忆 const lowImportanceMemories await this.memoryStore.findByMetadata({ maxImportance: 0.2, // 重要性低于0.2 }); for (const mem of lowImportanceMemories) { await this.memoryStore.delete(mem.metadata.id); } // 3. 定期摘要将同一标签下的大量记忆合并 const tags await this.getAllTags(); for (const tag of tags) { const memories await this.memoryStore.findByMetadata({ tags: [tag] }); if (memories.length 10) { // 超过10条就摘要 const summary await this.memoryProcessor.summarizeMemories(memories.map(m m.metadata.id)); await this.memoryStore.create(summary); // 可选删除或归档原始记忆 // await this.memoryStore.deleteByFilter({ tags: [tag], endTime: someTime }); } } } }6.3 多模态记忆未来的Agent不仅处理文本还会处理图像、音频。记忆系统也需要扩展。例如一张用户上传的图表可以提取其文本描述生成向量存储同时将原文件存储在对象存储如S3中并在记忆元数据里保存文件链接。检索时先通过文本描述找到相关记忆再根据需要获取原文件。7. 测试与评估如何判断记忆系统是否有效搭建完成后如何评估其好坏不能只靠感觉。定量评估检索准确率构建一个测试集包含一系列查询和“标准答案”记忆。计算系统召回的相关记忆中有多少是真正相关的。上下文相关性增益设计两组对话一组使用记忆系统提供历史上下文另一组不使用。让人类评估员或另一个LLM判断哪组对话的回复更连贯、更个性化、更准确。任务完成率对于需要历史信息的任务如“继续编辑我上周未完成的文档”对比有记忆和无记忆的Agent任务完成成功率。定性评估用户体验进行用户测试观察用户是否注意到Agent“记得”他们之前说过的话并询问他们的感受。错误分析定期检查被错误检索的记忆噪音和被漏检的关键记忆遗漏分析原因是嵌入模型问题、查询表述问题还是元数据设计问题。构建AI Agent的记忆系统是一个从认知理论到工程实践的深度旅程。它没有一劳永逸的解决方案需要你根据Agent的具体应用场景是客服、编程助手还是游戏NPC不断调整记忆的粒度、检索策略和更新机制。从本文介绍的基础架构开始实现一个最小可用的版本然后在真实交互中观察、迭代和优化你的Agent才会真正变得越来越“聪明”和“贴心”。
返回列表