拓冰建站拓冰建站
首页 / 资讯中心 / 正文

零依赖Agent记忆存储方案:基于SQLite的Remembrane实战指南

大家好我是专注于技术实战分享的博主。在构建AI Agent或需要长期记忆的智能应用时你是否遇到过记忆存储的难题使用向量数据库太重维护复杂依赖外部服务又担心网络和成本。今天我将为大家深入剖析一个名为Remembrane的开源项目它提出了一种极简而强大的解决方案将Agent的记忆存储在一个SQLite文件中且零外部依赖。无论你是AI Agent的初学者还是正在寻找轻量级记忆方案的资深开发者这篇文章都将带你从零开始完整掌握Remembrane的核心原理、实战应用与最佳实践。1. 背景与核心概念为什么需要“Agent Memory”在深入Remembrane之前我们首先要理解“Agent Memory”这个概念。在AI领域特别是基于大语言模型LLM的智能体Agent中“记忆”指的是Agent在与用户或环境交互过程中需要持久化存储的信息。这包括但不限于对话历史用户与Agent的多轮对话内容。知识片段Agent从外部获取或内部生成的重要事实、规则。状态信息Agent执行任务时的中间状态、用户偏好等。长期上下文超越单次会话Session的、需要被长期记住的信息。没有有效的记忆管理Agent就像患上了“健忘症”每次交互都是全新的开始无法进行连贯的、个性化的深度对话或任务执行。1.1 现有方案的痛点目前常见的Agent记忆存储方案主要有以下几种但各有其局限性向量数据库如Chroma, Pinecone, Weaviate优点擅长基于语义的相似性搜索适合知识库检索。痛点部署和维护复杂需要单独的服务进程增加了系统架构的复杂度和运维成本。对于小型项目或原型开发来说过于“重型”。传统关系型数据库如MySQL, PostgreSQL优点功能强大事务支持完善。痛点同样需要独立的数据库服务配置连接繁琐。对于简单的键值对或JSON存储显得有些“杀鸡用牛刀”。内存存储或纯文件存储优点简单直接。痛点内存存储无法持久化服务重启数据即丢失纯文件如JSON、TXT存储则在查询、更新和并发访问方面效率低下且难以管理。1.2 Remembrane的核心理念Remembrane正是为了解决上述痛点而生。它的设计哲学是“极简”和“自包含”一个SQLite文件所有记忆数据都存储在一个标准的SQLite数据库文件中例如memory.db。SQLite是一个进程内的、零配置的、轻量级的关系型数据库引擎其数据库就是一个独立的文件。零依赖Remembrane本身不依赖任何外部数据库服务或复杂的第三方库。它直接利用编程语言如Python内置的或标准库支持的SQLite接口进行操作实现了开箱即用。为Agent设计其API和数据结构是专门为Agent记忆场景优化的提供了对会话Session、记忆条目Memory Item的增删改查等便捷操作而无需开发者从零设计数据库表结构。简单来说Remembrane让你能用最简单的方式一个文件为你的Agent赋予持久化、可查询的记忆能力特别适合原型验证、个人项目、边缘计算场景以及对部署简洁性有极高要求的应用。2. 环境准备与版本说明Remembrane的另一个巨大优势是环境准备极其简单。由于它基于SQLite且零依赖你几乎可以在任何支持SQLite的环境中使用它。2.1 基础环境要求操作系统Windows 10/11, macOS, Linux (包括WSL) 均可。SQLite是跨平台的。编程语言以Python为例这也是Remembrane最可能实现的版本需要Python 3.7及以上版本。其他语言如Node.js、Rust、Go等只要有SQLite驱动理论上也可实现类似方案。开发工具任何你喜欢的代码编辑器或IDE如VS Code, PyCharm等。SQLite可视化工具可选但推荐为了直观地查看和调试数据库内容建议安装一个SQLite浏览器如DB Browser for SQLite (DB4S)。你可以从其官网下载安装这是一个免费、开源、图形化的管理工具。2.2 项目初始化与“依赖”确认对于Python环境我们首先创建一个纯净的项目目录并确认SQLite支持。# 1. 创建项目目录并进入 mkdir my_agent_with_memory cd my_agent_with_memory # 2. 创建虚拟环境推荐避免包冲突 python -m venv venv # 3. 激活虚拟环境 # Windows (cmd或PowerShell) venv\Scripts\activate # Linux/macOS source venv/bin/activate # 4. 验证Python和SQLite python --version # 输出类似Python 3.9.13 # Python标准库自带sqlite3模块无需安装 python -c “import sqlite3; print(sqlite3.sqlite_version)” # 输出SQLite库版本如3.37.2看到SQLite版本号输出就证明你的环境已经完全具备了运行Remembrane核心逻辑的条件。所谓的“零依赖”就是指除了语言本身你不需要pip install任何额外的包来实现核心的记忆存储功能。3. 核心原理与自实现设计虽然我们可能没有Remembrane的官方源码但我们可以根据其描述自己设计并实现一个具备同样核心特性的Remembrane类。这能帮助我们更深刻地理解其工作原理。3.1 数据模型设计Agent的记忆不是杂乱无章的文本堆砌。我们需要一个结构化的存储方式。一个典型的记忆条目MemoryItem可能包含以下字段id: 唯一标识符主键自增。session_id: 会话ID用于区分不同用户或不同对话线程的记忆。content: 记忆的具体内容文本。metadata: 附加的元数据以JSON格式存储例如时间戳、来源、重要性权重、嵌入向量可选等。created_at: 创建时间戳。last_accessed_at: 最后访问时间可用于实现基于时间的记忆衰减或清理策略。3.2 核心API设计我们的Remembrane类应该提供以下基本方法__init__(db_path“memory.db”): 初始化连接到指定的SQLite文件。initialize(): 创建记忆表如果不存在。add_memory(session_id, content, metadataNone): 添加一条记忆。get_memories(session_id, limit10, offset0): 获取某个会话的最新记忆。search_memories(session_id, query, limit5): 基础版在指定会话的记忆中进行全文或关键词搜索。update_memory(memory_id, contentNone, metadataNone): 更新一条记忆。delete_memory(memory_id): 删除一条记忆。close(): 关闭数据库连接。4. 完整实战案例从零实现一个简易Remembrane接下来我们将动手实现一个具备上述功能的简易版Remembrane并将其集成到一个模拟的对话Agent中。4.1 创建项目结构my_agent_with_memory/ ├── venv/ # Python虚拟环境忽略 ├── remembrane.py # 我们的Remembrane核心实现 ├── agent.py # 使用记忆的模拟Agent └── memory.db # 运行后生成的SQLite数据库文件4.2 实现remembrane.py这是最核心的部分我们实现记忆存储引擎。# remembrane.py import sqlite3 import json from datetime import datetime from typing import List, Dict, Any, Optional class Remembrane: 一个极简的Agent记忆存储引擎基于SQLite零依赖。 def __init__(self, db_path: str “memory.db”): 初始化记忆库。 :param db_path: SQLite数据库文件路径。 self.db_path db_path self.conn sqlite3.connect(db_path, check_same_threadFalse) # 启用外键和WAL模式提升并发性能 self.conn.execute(“PRAGMA foreign_keys ON”) self.conn.execute(“PRAGMA journal_mode WAL”) self._initialize_table() def _initialize_table(self): 创建记忆表。 create_table_sql “”” CREATE TABLE IF NOT EXISTS memories ( id INTEGER PRIMARY KEY AUTOINCREMENT, session_id TEXT NOT NULL, content TEXT NOT NULL, metadata TEXT, -- 存储JSON字符串 created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, last_accessed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); “”” # 创建索引以加速按session_id的查询 create_index_sql “”” CREATE INDEX IF NOT EXISTS idx_memories_session ON memories (session_id); “”” self.conn.execute(create_table_sql) self.conn.execute(create_index_sql) self.conn.commit() print(f“[Remembrane] 记忆表已初始化 (数据库: {self.db_path})”) def add_memory(self, session_id: str, content: str, metadata: Optional[Dict] None) - int: 添加一条记忆。 :return: 新插入记忆的ID。 metadata_str json.dumps(metadata) if metadata else None cursor self.conn.cursor() cursor.execute( “”” INSERT INTO memories (session_id, content, metadata) VALUES (?, ?, ?) “””, (session_id, content, metadata_str) ) self.conn.commit() memory_id cursor.lastrowid print(f“[Remembrane] 会话 ‘{session_id}’ 添加记忆 ID:{memory_id}”) return memory_id def get_memories(self, session_id: str, limit: int 10, offset: int 0) - List[Dict]: 获取指定会话的记忆按时间倒序排列最新的在前。 # 更新最后访问时间 self.conn.execute( “UPDATE memories SET last_accessed_at CURRENT_TIMESTAMP WHERE session_id ?”, (session_id,) ) self.conn.commit() cursor self.conn.cursor() cursor.execute( “”” SELECT id, session_id, content, metadata, created_at, last_accessed_at FROM memories WHERE session_id ? ORDER BY created_at DESC LIMIT ? OFFSET ? “””, (session_id, limit, offset) ) rows cursor.fetchall() memories [] for row in rows: mem { “id”: row[0], “session_id”: row[1], “content”: row[2], “metadata”: json.loads(row[3]) if row[3] else {}, “created_at”: row[4], “last_accessed_at”: row[5] } memories.append(mem) return memories def search_memories_basic(self, session_id: str, query: str, limit: int 5) - List[Dict]: 基础关键词搜索在指定会话的记忆内容中进行LIKE匹配。 注意这只是最简单的实现对于生产环境应考虑使用SQLite的FTS5全文搜索扩展。 cursor self.conn.cursor() search_term f“%{query}%” cursor.execute( “”” SELECT id, session_id, content, metadata, created_at FROM memories WHERE session_id ? AND content LIKE ? ORDER BY created_at DESC LIMIT ? “””, (session_id, search_term, limit) ) rows cursor.fetchall() results [] for row in rows: mem { “id”: row[0], “session_id”: row[1], “content”: row[2], “metadata”: json.loads(row[3]) if row[3] else {}, “created_at”: row[4] } results.append(mem) return results def update_memory(self, memory_id: int, content: Optional[str] None, metadata: Optional[Dict] None): 更新一条记忆的内容或元数据。 updates [] params [] if content is not None: updates.append(“content ?”) params.append(content) if metadata is not None: updates.append(“metadata ?”) params.append(json.dumps(metadata)) if not updates: return # 没有要更新的字段 params.append(memory_id) update_sql f“UPDATE memories SET {‘, ‘.join(updates)} WHERE id ?” self.conn.execute(update_sql, params) self.conn.commit() print(f“[Remembrane] 记忆 ID:{memory_id} 已更新”) def delete_memory(self, memory_id: int): 删除一条记忆。 self.conn.execute(“DELETE FROM memories WHERE id ?”, (memory_id,)) self.conn.commit() print(f“[Remembrane] 记忆 ID:{memory_id} 已删除”) def close(self): 关闭数据库连接。 if self.conn: self.conn.close() print(“[Remembrane] 数据库连接已关闭”) def __enter__(self): 支持上下文管理器 with 语法。 return self def __exit__(self, exc_type, exc_val, exc_tb): 退出上下文时自动关闭连接。 self.close()4.3 实现agent.py一个使用记忆的简单对话Agent现在我们创建一个模拟Agent它会在对话中记住用户的信息。# agent.py import uuid from remembrane import Remembrane class SimpleAgent: def __init__(self, agent_name: str “Assistant”): self.agent_name agent_name # 为每个对话线程创建一个唯一的session_id # 在实际应用中session_id可能对应一个用户ID或一个聊天窗口 self.session_id str(uuid.uuid4()) # 初始化记忆引擎 self.memory Remembrane() print(f“Agent ‘{agent_name}’ 已启动。会话ID: {self.session_id}”) def chat_loop(self): 一个简单的对话循环。 print(“\n 对话开始 (输入 ‘quit’ 退出’history’ 查看记忆’search 关键词’ 搜索记忆) ”) while True: try: user_input input(“\n你: “).strip() if user_input.lower() ‘quit’: break elif user_input.lower() ‘history’: self._show_memories() continue elif user_input.startswith(‘search ‘): query user_input[7:].strip() self._search_memories(query) continue # 1. 将用户输入存储为记忆 self.memory.add_memory( session_idself.session_id, contentf“用户说: {user_input}”, metadata{“role”: “user”, “turn”: “input”} ) # 2. 模拟Agent处理并生成回复 # 这里可以集成LLM API调用例如OpenAI, Claude等。 # 为了演示我们做一个简单的规则回复。 response self._generate_response(user_input) print(f“{self.agent_name}: {response}”) # 3. 将Agent的回复也存储为记忆 self.memory.add_memory( session_idself.session_id, contentf“{self.agent_name} 说: {response}”, metadata{“role”: “assistant”, “turn”: “output”} ) except KeyboardInterrupt: print(“\n对话被中断。”) break except Exception as e: print(f“发生错误: {e}”) self.memory.close() print(“对话结束记忆已保存。”) def _generate_response(self, user_input: str) - str: 模拟的响应生成逻辑。在实际中这里会调用LLM。 # 一个非常简单的规则如果用户提到名字就记住并问候。 if “名字” in user_input and “叫” in user_input: # 提取名字的逻辑非常简陋 parts user_input.split(“叫”) if len(parts) 1: name_guess parts[-1].strip(” 。.!?”) # 将名字作为一条特殊的记忆存储 self.memory.add_memory( session_idself.session_id, contentf“用户的名字可能是: {name_guess}”, metadata{“type”: “fact”, “key”: “user_name”} ) return f“你好{name_guess}很高兴认识你我会记住你的名字。” # 检查记忆里是否有名字 memories self.memory.get_memories(self.session_id, limit5) for mem in memories: meta mem.get(“metadata”, {}) if meta.get(“key”) “user_name”: name mem[“content”].split(“:”)[-1].strip() return f“我知道你的名字是 {name}。你今天想聊什么” # 默认回复 default_responses [ “这是一个有趣的看法。”, “你能详细说说吗”, “我明白了。”, “让我们继续这个话题。” ] import random return random.choice(default_responses) def _show_memories(self): 显示当前会话的所有记忆。 print(“\n— 当前会话记忆历史 —”) memories self.memory.get_memories(self.session_id, limit20) if not memories: print(“(暂无记忆)”) for mem in memories: print(f“[{mem[‘created_at’]}] {mem[‘content’]}”) def _search_memories(self, query: str): 搜索当前会话的记忆。 print(f“\n— 搜索 ‘{query}’ 的结果 —”) results self.memory.search_memories_basic(self.session_id, query, limit5) if not results: print(“(未找到相关记忆)”) for mem in results: print(f“[{mem[‘created_at’]}] {mem[‘content’]}”) if __name__ “__main__”: agent SimpleAgent(“记忆助手”) agent.chat_loop()4.4 运行与验证运行Agentpython agent.py你会看到类似输出[Remembrane] 记忆表已初始化 (数据库: memory.db) Agent ‘记忆助手’ 已启动。会话ID: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx 对话开始 (输入 ‘quit’ 退出’history’ 查看记忆’search 关键词’ 搜索记忆) 进行对话测试你: 你好 记忆助手: 这是一个有趣的看法。 [Remembrane] 会话 ‘xxxxxxxx…’ 添加记忆 ID:1 [Remembrane] 会话 ‘xxxxxxxx…’ 添加记忆 ID:2 你: 我的名字叫张三 记忆助手: 你好张三很高兴认识你我会记住你的名字。 [Remembrane] 会话 ‘xxxxxxxx…’ 添加记忆 ID:3 [Remembrane] 会话 ‘xxxxxxxx…’ 添加记忆 ID:4 你: history — 当前会话记忆历史 — [2023-10-27 10:30:15] 记忆助手 说: 你好张三很高兴认识你我会记住你的名字。 [2023-10-27 10:30:12] 用户的名字可能是: 张三 [2023-10-27 10:30:10] 记忆助手 说: 这是一个有趣的看法。 [2023-10-27 10:30:08] 用户说: 你好 你: search 名字 — 搜索 ‘名字’ 的结果 — [2023-10-27 10:30:12] 用户的名字可能是: 张三 你: 你还记得我叫什么吗 记忆助手: 我知道你的名字是 张三。你今天想聊什么查看数据库文件 退出对话后你会发现在项目目录下生成了一个memory.db文件。你可以使用DB Browser for SQLite打开它直观地查看memories表中的所有数据。这印证了“所有记忆在一个SQLite文件中”的核心特性。4.5 结果说明通过这个实战案例我们成功实现了一个简化版的Remembrane。它展示了如何用纯Python和SQLite创建一个零依赖的记忆存储引擎。为Agent的每个会话session_id独立管理记忆流。实现记忆的增、删、查、改以及基础搜索。将记忆功能无缝集成到一个模拟的对话Agent中使其具备跨轮次的记忆能力。5. 常见问题与排查思路在实际使用自实现的Remembrane或类似方案时你可能会遇到以下问题问题现象可能原因排查与解决思路sqlite3.OperationalError: database is locked多线程或多进程同时写入同一个数据库文件SQLite的默认锁机制导致。1.确保单线程写入在Web服务等并发场景使用连接池或为每个线程/请求创建独立连接时注意写操作的同步如加锁。2.使用WAL模式如我们在_initialize_table中设置的PRAGMA journal_mode WAL这能显著提升读并发和部分写并发能力。3.设置超时在连接时设置timeout参数sqlite3.connect(‘memory.db’, timeout5)。数据库文件memory.db体积增长过快记忆条目只增不减没有清理策略。1.实现记忆衰减/清理定期删除last_accessed_at时间过久或低重要性的记忆。2.内容摘要对于长内容可以存储摘要而非全文。3.分表或归档按时间如每月将旧记忆移动到归档表或文件中。基础LIKE搜索效率低、不准确LIKE ‘%keyword%’无法利用索引且是简单的字符串匹配不支持语义搜索。1.启用SQLite FTS5扩展创建虚拟表进行全文搜索支持分词和更高效的查询。这是生产级应用推荐的做法。2.集成轻量级向量库如果需要进行语义搜索可以考虑集成sentence-transformers生成嵌入向量并将其存储在metadata的JSON字段中但这会引入外部依赖。metadataJSON字段查询复杂直接查询JSON字段内的特定键值对比较麻烦。1.SQLite JSON1扩展现代SQLite支持JSON1扩展可以使用json_extract(metadata, ‘$.key’)进行查询。2.反规范化设计如果某些元数据字段需要频繁查询可以考虑将其拆分成单独的列。不同会话的记忆混淆代码中错误地复用了session_id。1.严格管理Session生命周期为每个独立的对话上下文生成唯一的session_id如使用UUID。2.在API层面隔离确保get_memories、add_memory等方法总是传入正确的session_id。6. 最佳实践与工程建议将Remembrane思想应用到实际项目中需要考虑更多工程化细节。6.1 连接管理与并发连接池在Web服务器如FastAPI、Flask中不应全局共享一个SQLite连接。可以为每个请求创建新连接或使用轻量级的连接池。注意SQLite的写并发限制。上下文管理器务必使用with语句如我们实现的__enter__和__exit__或try…finally块来确保数据库连接被正确关闭避免资源泄漏。只读从库对于读多写少的场景可以考虑将SQLite文件复制到只读位置供多个只读实例访问但写操作仍需指向主文件。6.2 数据安全与备份文件权限确保memory.db文件所在目录有适当的读写权限并防止被未授权访问。定期备份SQLite文件虽然方便但也是单点。应建立定期备份机制例如每天将memory.db复制到备份存储。敏感信息避免在content或metadata中明文存储密码、密钥、个人身份信息PII。如需存储应先进行加密处理。6.3 性能优化索引是核心除了session_id根据你的查询模式考虑为created_at、last_accessed_at或经常用于WHERE或ORDER BY的字段创建索引。批量操作当需要插入或更新大量记忆时使用事务BEGIN…COMMIT可以极大提升速度。控制单次读取量get_memories方法一定要使用LIMIT避免一次性加载海量历史记录导致内存溢出。6.4 与LLM集成的高级模式我们上面的例子只是简单模拟。与真实LLM如OpenAI GPT、Claude集成时记忆的使用模式更关键记忆作为上下文在调用LLM API前从Remembrane中取出最近N条相关记忆拼接到系统提示词System Prompt或用户消息历史中作为上下文提供给LLM。记忆的总结与提炼长时间对话后记忆会很长。可以定期让LLM对过往记忆进行总结然后将总结作为一条新的“元记忆”存储并清理掉原始的琐碎记录从而压缩上下文长度。记忆的检索增强不仅仅是获取最近记忆可以结合我们实现的search_memories功能当用户提到某个特定话题时主动去检索历史上相关的深度记忆实现更精准的上下文补充。6.5 扩展方向记忆向量化在metadata中存储文本的嵌入向量Embedding。搜索时先计算查询词的向量然后通过向量相似度如余弦相似度在内存或扩展库中进行初步筛选再结合SQL查询实现混合检索。记忆分类与打标为记忆增加tags字段或通过LLM自动分析记忆类型如“事实”、“用户偏好”、“任务步骤”、“情绪”便于更精细的管理和检索。多模态记忆metadata的JSON格式可以存储非文本信息的引用如图片的路径或缩略图特征实现简单的多模态记忆。通过遵循这些最佳实践你可以将一个简单的“单文件记忆库”升级为支撑复杂Agent应用的可靠存储层。Remembrane所代表的“零依赖、单文件”哲学为AI应用的小型化、轻量化部署提供了极具吸引力的基础架构选择。它降低了Agent开发的门槛让开发者能更专注于Agent的逻辑和体验本身。
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门