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

MemU SQLite 存储后端实战:DSN 配置、暴力向量检索与数据迁移指南

MemU SQLite 存储后端实战:DSN 配置、暴力向量检索与数据迁移指南【免费下载链接】memUPersonal memory across agents项目地址: https://gitcode.com/GitHub_Trending/mem/memUMemU 通过可插拔的数据库抽象支持多种元数据存储后端,其中 SQLite 是为本地开发、单用户与便携部署准备的轻量级、基于文件的选择。本文基于仓库文档 docs/sqlite.md 并结合 src/memu/database/sqlite/ 源码实现展开,读完你将掌握:如何用database_config接入 SQLite(含内存模式)、DSN 的完整格式规范、SQLite 下向量检索为何采用暴力余弦相似度及其底层实现、真实落库的表结构与 embedding 存储方式,以及数据备份和向 PostgreSQL 迁移的可运行步骤。适用场景MemU 支持 SQLite 作为轻量级、基于文件的数据库后端,适合以下场景:本地开发与测试:零配置,无需启动外部数据库服务;单用户应用:需要持久化存储,但并发写入压力小;便携部署:整个数据库就是一个文件,拷贝即迁移;离线应用:无法依赖外部数据库服务的场景。对应的取舍在仓库文档的性能对比表中被明确列出(见下文性能对比一节):SQLite 的向量检索是内存中的暴力扫描,并发模型是单写多读,因此数据规模上限约为 10 万条记录;更大规模应切换到 PostgreSQL pgvector。快速开始基本配置from memu.app import MemoryService # 使用默认 SQLite 文件(当前目录下的 memu.db) service MemoryService( llm_profiles{default: {api_key: your-api-key}}, database_config{ metadata_store: { provider: sqlite, }, }, ) # 或者指定自定义数据库路径 service MemoryService( llm_profiles{default: {api_key: your-api-key}}, database_config{ metadata_store: { provider: sqlite, dsn: sqlite:///path/to/your/memory.db, }, }, )内存 SQLite(不持久化)测试或临时存储可以使用内存数据库:service MemoryService( llm_profiles{default: {api_key: your-api-key}}, database_config{ metadata_store: { provider: sqlite, dsn: sqlite:///:memory:, }, }, )源码视角:配置如何被解析database_config参数最终映射到 src/memu/app/settings.py 中的 Pydantic 模型:MetadataStoreConfig.provider是Literal[inmemory, postgres, sqlite],默认值为inmemory,所以不显式写provider: sqlite时实际走的是内存后端,而不是落盘;MetadataStoreConfig.dsn默认为None。当 provider 为sqlite且未提供 DSN 时,build_sqlite_database 会兜底为sqlite:///memu.db(当前工作目录),这就是文档中默认文件行为的来源;后端实例由 build_database 工厂 按 provider 分发,sqlite 分支是惰性导入build_sqlite_database,避免不使用 SQLite 时加载相关依赖;向量索引配置VectorIndexConfig有三个可选值bruteforce/pgvector/none,默认bruteforce。DatabaseConfig的model_post_init会自动补齐:非 postgres 的 metadata store 一律落到bruteforce,所以 SQLite 下你不需要显式配置向量索引(显式写出vector_index: {provider: bruteforce}只是把默认行为写明白)。配置选项与 DSN 格式选项类型默认值说明providerstrinmemory设为sqlite启用 SQLite 后端dsnstrsqlite:///memu.dbSQLite 连接字符串DSN 格式SQLite DSN 遵循以下格式(注意 SQLAlchemy URL 语法中斜杠数量的含义):文件数据库:sqlite:///path/to/database.db(相对或绝对路径,路径中不带前导/时为相对路径)内存数据库:sqlite:///:memory:相对路径:sqlite:///./data/memu.db绝对路径:sqlite:////home/user/data/memu.db(注意有 4 个斜杠,第 4 个是绝对路径的前导/)DSN 直接交给 SQLiteSessionManager 的create_engine使用,其中有一个值得注意的引擎参数:kw: dict[str, Any] { connect_args: {check_same_thread: False}, # 允许多线程访问 } self._engine create_engine(dsn, **kw)SQLite 连接默认只允许创建它的线程使用;MemU 关闭了check_same_thread限制以支持多线程访问,而 session 统一以expire_on_commitFalse创建,避免提交后对象属性失效。这也是文档故障排查一节中配合连接池与合理超时使用建议的底层前提。向量检索:暴力余弦相似度SQLite 没有像 PostgreSQL pgvector 那样的原生向量类型。MemU 在 SQLite 下使用暴力余弦相似度做向量检索:service MemoryService( llm_profiles{default: {api_key: your-api-key}}, database_config{ metadata_store: { provider: sqlite, dsn: sqlite:///memu.db, }, vector_index: { provider: bruteforce, # 这就是 SQLite 的默认值 }, }, )注意:暴力搜索会把所有 embedding 载入内存并对每条记录计算相似度,适合中等规模数据集(约 10 万条以内),更大规模会明显变慢。底层实现:cosine_topk暴力扫描的实际数学计算集中在与存储无关的纯函数 cosine_topk 中,SQLite 与 inmemory 后端共享同一套实现:脏数据防御:维度不匹配、空列表或None的向量被直接剔除,不会进入排序(维度不一致时 numpy 会退化为 object 矩阵,轻则报错、重则静默打错分,源码注释明确解释了这一点);向量化计算:所有向量堆叠为(n, dim)矩阵,一次矩阵乘法算出全部余弦相似度,分母加1e-9防止零除;O(n) top-k 选择:用np.argpartition做部分选择(复杂度 O(n)),再只对选出的 k 个元素排序,而不是对全量分数做 O(n log n) 排序。调用链上,分片级检索走 RecallFileSegmentRepo.vector_search_segments,它在候选池上调用cosine_topk。SQLite 的仓库实现 SQLiteRecallFileSegmentRepo 对此没有任何覆写,源码注释写得很直白:# vector_search_segments is the protocols Python scan: SQLite has no # vector index to rank with, so there is nothing to override it with.即:SQLite 没有向量索引可用,只能沿用协议默认的 Python 端全量扫描。where过滤则能下推到 SQL 层——SQLiteRepoBase._build_filters 会把where字典翻译成 SQLAlchemy 过滤表达式(支持field__in这类操作符),这正是文档性能建议中用更有选择性的where过滤器缩小搜索空间能够奏效的原因:先 SQL 过滤、再对缩小后的候选池做余弦排序。数据库表结构与 Embedding 存储文档给出的表清单为:sqlite_resources— 多模态资源记录(图片、文档等);sqlite_memory_items— 带 embedding 的抽取记忆条目;sqlite_memory_categories— 带摘要的记忆类别;sqlite_category_items— 条目与类别的关系表。需要说明的是,当前仓库源码创建的实际表名已不同。从 get_sqlite_sqlalchemy_models 看,建表时使用memu_前缀,并且源码注释解释了原因:SQLite 保留所有以sqlite_开头的表名,因此文档中的sqlite_*命名无法直接落库。当前实际创建的三张表是:表名对应模型内容memu_resourcesSQLiteResourceModel资源记录(url、local_path、caption、track、embedding)memu_recall_filesSQLiteRecallFileModel记忆文件(name、description、content、track、embedding)memu_recall_file_segmentsSQLiteRecallFileSegmentModel文件分片(recall_file_id、track、text、embedding)文档列出的memory_items/memory_categories/category_items属于早期数据模型命名,当前实现中由 recall file 及其 segments 体系取代;如果你按文档中的旧表名去检查数据库,应以源码为准。建表逻辑在 SQLiteStore._create_tables 中,SQLiteStore构造时自动执行create_all,所以无需手动建表,第一次实例化即完成 schema 初始化。Embedding 以 JSON 列存储SQLite 没有原生向量类型,三个模型的embedding字段都被覆写为 SQLAlchemyJSON列(见 models.py 中的注释:裸list注解无法被 SQLModel 映射,故改用Column(JSON)):# Override inherited embedding field: SQLite has no native vector type, so store the # vector in a JSON column (a bare list annotation is not mappable by SQLModel). embedding: list[float] | None Field(defaultNone, sa_columnColumn(JSON, nullableTrue))写入与读取路径上的处理在 SQLiteRepoBase:_prepare_embedding:入库时直接存 Pythonlist(不额外做字符串编码);_normalize_embedding:出库时统一转回list[float],并带有防御逻辑——若历史数据里混进了 JSON 字符串,会尝试解析;解析失败则记 debug 日志并返回None,该行不参与向量排序。用户 scope 字段的动态合并MemU 的记忆按用户作用域隔离。build_sqlite_table_model 会把用户 scope 的 Pydantic 模型(默认是DefaultUserModel,含user_id/agent_id)与核心模型在运行时合并生成实际表模型:scope 字段与核心字段重名会直接抛TypeError;每个 scope 字段自动建立复合索引ix_{tablename}__scope,保证where{user_id: ...}这类过滤走索引;合并结果按 scope 模型类型缓存,避免重复建类。数据导入 / 导出导出备份SQLite 的优势之一就是备份极简单——直接复制数据库文件:import shutil # 复制数据库文件即可完成备份 shutil.copy(memu.db, memu_backup.db)从 SQLite 迁移到 PostgreSQL文档给出的迁移骨架是:分别用build_sqlite_database/build_postgres_database构造两个 store,把 SQLite 侧数据读出后逐条写入 PostgreSQL 侧仓库:import json from memu.database.sqlite import build_sqlite_database from memu.database.postgres import build_postgres_database from memu.app.settings import DatabaseConfig from pydantic import BaseModel class UserScope(BaseModel): user_id: str # 从 SQLite 加载 sqlite_config DatabaseConfig( metadata_store{provider: sqlite, dsn: sqlite:///memu.db} ) sqlite_db build_sqlite_database(configsqlite_config, user_modelUserScope) sqlite_db.load_existing() # 连接 PostgreSQL postgres_config DatabaseConfig( metadata_store{provider: postgres, dsn: postgresql://...} ) postgres_db build_postgres_database(configpostgres_config, user_modelUserScope) # 迁移资源 for res_id, resource in sqlite_db.resources.items(): postgres_db.resource_repo.create_resource( urlresource.url, local_pathresource.local_path, captionresource.caption, embeddingresource.embedding, user_data{user_id: getattr(resource, user_id, None)}, trackresource.track, ) # recall files、segments 等同理...几个使用细节:load_existing()会把 SQLite 中的全部记录载入DatabaseState内存缓存(SQLiteStore.load_existing 依次触发三个仓库的加载),所以sqlite_db.resources等字典在迁移脚本里才可直接遍历;create_resource支持track参数(可选的 workspace track,如chat/skill/workspace),迁移时应原样透传以保留工作区语义;迁移完成前建议先停机写入,SQLite 单写者的特性意味着迁移期间并发写入仍会触发锁定。完整工作流示例下面是文档中的端到端示例:初始化 → 提交记忆 → 向量召回:import asyncio from memu.app import MemoryService async def main(): # 用 SQLite 初始化 service MemoryService( database_config{ metadata_store: { provider: sqlite, dsn: sqlite:///my_memories.db, }, }, ) # 提交准备好的记忆 await service.commit_results( recall_files[ { name: Preferences, track: memory, description: what alice likes, content: # Preferences\n- prefers dark roast coffee, } ], user{user_id: alice}, ) # 召回相关记忆 context await service.progressive_retrieve( What are my preferences?, where{user_id: alice} ) for segment in context[segments]: print(f- {segment[text]} ({segment[score]:.2f})) asyncio.run(main())调用链对应关系:commit_results定义在 src/memu/app/agentic.py:接收 recall files,写入文件与分片记录并生成/挂载 embedding,分片行落入memu_recall_file_segments;progressive_retrieve定义在同文件 L187:这是一次单轮、无 LLM 参与的检索——查询只 embed 一次,文件分片层与资源层各自按向量相似度排序返回,where{user_id: alice}会经_build_filters翻译成 SQL 条件先缩小候选池;输出中每个 segment 的score即cosine_topk返回的余弦相似度。性能对比:SQLite vs PostgreSQL文档给出的对比表:维度SQLitePostgreSQL部署零配置需要服务端安装并发单写者,多读者完全并发访问向量检索暴力扫描(内存中)原生 pgvector(带索引)规模上限约 10 万条数百万条形态单文件,可移植外部服务从源码结构看,这张表的取舍是配置驱动的:同一份MemoryService代码只需切换metadata_store.provider,向量索引 provider 由DatabaseConfig自动推导(见上文配置如何被解析一节),迁移成本主要在数据搬运而非代码改造。故障排查database is locked 错误SQLite 同一时间只允许一个写者。出现该错误时:确认没有多个进程同时写同一个数据库文件(典型的如 CLI 桥接任务与长驻服务并存);确有并发写入需求时,考虑切换到 PostgreSQL;使用连接池并配置合理的等待超时,让短暂写入锁可以排队而不是直接报错。Permission Denied确保 SQLite 文件所在目录可写(数据库文件首次创建及 WAL 写入都需要目录写权限):chmod 755 /path/to/data/directory向量检索变慢数据量增大后暴力扫描成为瓶颈,可依次尝试:迁移到 PostgreSQL pgvector,获得索引化的向量检索;使用更有选择性的where过滤(如user_id、track)缩小候选池——过滤条件会下推到 SQL 层,只有通过的行才参与余弦计算;调小检索配置中的top_k参数,降低排序开销。延伸阅读数据库后端工厂:inmemory / postgres / sqlite 三种 provider 的分发逻辑;SQLite 后端包入口:build_sqlite_database与默认 DSN 兜底;向量检索测试:验证分片向量排序行为的用例;可插拔存储与向量策略 ADR:存储抽象与向量策略的设计决策背景。【免费下载链接】memUPersonal memory across agents项目地址: https://gitcode.com/GitHub_Trending/mem/memU创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
分享:

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

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