ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

memU 存储架构解析:可插拔数据库抽象与后端感知的向量检索策略(ADR 0002)

memU 存储架构解析:可插拔数据库抽象与后端感知的向量检索策略(ADR 0002) memU 存储架构解析可插拔数据库抽象与后端感知的向量检索策略ADR 0002【免费下载链接】memUPersonal memory across agents项目地址: https://gitcode.com/GitHub_Trending/mem/memU本文围绕 memU 的架构决策记录 ADR 0002 展开讲解 memU 如何用一套Database协议统一内存、SQLite 与 PostgreSQL 三种存储后端以及向量相似度检索如何做到后端感知——粗扫bruteforce与 pgvector 索引查询并存。读完后你将理解 memU 从本地零配置开发到生产级向量检索的完整存储分层设计、各后端的配置方式与取舍边界并能对照仓库源码验证每一项设计决策的实际落地。背景为什么没有一种存储引擎能通吃ADR 0002见 0002-pluggable-storage-and-vector-strategy.md开篇明确了 memU 必须同时满足的三种部署形态零配置的本地开发zero-setup local development进程内状态即可不依赖任何外部服务轻量持久化部署lightweight persisted deployments需要文件级持久化但不能引入完整数据库服务器需要可扩展向量相似度的生产部署production deployments that need scalable vector similarity记忆检索的核心是向量相似度排序规模上去后必须有索引加速。原文的判断很直接No single storage engine fits all three cases.没有任何单一存储引擎能同时覆盖这三种场景。这是整条决策链的出发点存储层必须可插拔而向量行为必须随后端切换。决策Database协议 三个可选 ProviderADR 的决策是在Database协议背后采用基于 Repository 的存储抽象并支持可选的 providerProvider持久化向量行为inmemory进程内状态无持久化暴力brute-force余弦相似度sqlite文件持久化embedding 以 JSON 文本存储暴力余弦相似度postgresSQL 持久化配置启用时走 pgvector 距离查询向量行为是后端感知backend-aware的ADR 给出三条规则暴力余弦检索作为可移植性基线在任何后端都可用Postgres 在向量支持启用时使用 pgvector 距离查询salience 排序reinforcement/recency-aware使用本地打分逻辑不绑定存储后端。下面对照仓库源码逐层验证这套决策的实际实现。Database协议后端无关的契约所有后端实现同一份协议定义在 interfaces.pyruntime_checkable class Database(Protocol): Backend-agnostic database contract. resource_repo: ResourceRepo recall_file_repo: RecallFileRepo recall_file_segment_repo: RecallFileSegmentRepo resources: dict[str, ResourceRecord] recall_files: dict[str, RecallFileRecord] segments: list[RecallFileSegmentRecord] def close(self) - None: ...从源码结构看这份契约包含两层三个 Repository 属性resource_repo/recall_file_repo/recall_file_segment_repo业务代码只面向这三个仓储接口编程从不直接触碰具体后端。仓储契约本身也是Protocol定义在 src/memu/database/repositories/ 下例如ResourceRepo.vector_search_resources的契约明确写着按存储 embedding 的余弦相似度对资源排序返回降序的(resource_id, score)列表见 resource.py进程内状态视图resources/recall_files/segments三个集合每个后端实例都暴露一份进程内的记录镜像供上层做快速回滚与提交。这一设计让上层服务 API 在三种后端之间行为一致——这正是 ADR 正面收益第一条one service API works across local and production footprints的落地方式。工厂分发build_database按 provider 构建后端后端的实例化集中在 factory.pydef build_database( *, config: DatabaseConfig, user_model: type[BaseModel], ) - Database: provider config.metadata_store.provider if provider inmemory: return build_inmemory_database(configconfig, user_modeluser_model) elif provider postgres: # Lazy import to avoid requiring pgvector when not using postgres from memu.database.postgres import build_postgres_database return build_postgres_database(configconfig, user_modeluser_model) elif provider sqlite: # Lazy import to avoid loading SQLite dependencies when not needed from memu.database.sqlite import build_sqlite_database return build_sqlite_database(configconfig, user_modeluser_model) else: msg fUnsupported metadata_store provider: {provider} raise ValueError(msg)几个值得注意的实现细节惰性导入只有真正选用postgres/sqlite时才导入对应包使用inmemory时不会加载任何数据库依赖这支撑了零配置本地开发的诉求不支持的 provider 直接抛错而不是静默降级避免配置错误被掩盖三个后端的构建入口分别在 src/memu/database/inmemory/、src/memu/database/sqlite/与src/memu/database/postgres/中目录结构一一对应 ADR 列出的三个 provider。配置面metadata_store与vector_index配置模型定义在 settings.pyclass MetadataStoreConfig(BaseModel): provider: Annotated[Literal[inmemory, postgres, sqlite], Normalize] inmemory ddl_mode: Annotated[Literal[create, validate], Normalize] create dsn: str | None Field(defaultNone, descriptionDatabase connection string (required for postgres/sqlite).) class VectorIndexConfig(BaseModel): provider: Annotated[Literal[bruteforce, pgvector, none], Normalize] bruteforce dsn: str | None Field(defaultNone, descriptionPostgres connection string when providerpgvector.) class DatabaseConfig(BaseModel): metadata_store: MetadataStoreConfig Field(default_factoryMetadataStoreConfig) vector_index: VectorIndexConfig | None Field(defaultNone)配置语义上配置项类型默认值说明metadata_store.providerinmemory/postgres/sqliteinmemory元数据存储后端metadata_store.dsnstrNonepostgres/sqlite 的连接串vector_index.providerbruteforce/pgvector/nonebruteforce向量检索策略DatabaseConfig的model_post_init实现了一个关键的默认联动见 settings.py如果用户没有显式配置vector_index则postgres后端自动落到pgvector并复用metadata_store.dsn其他后端落到bruteforce。也就是说选 Postgres 就默认启用索引向量检索选 SQLite/内存就默认粗扫——backend-aware 首先体现在配置默认值上。Normalize校验器还会对 provider 值做strip().lower()归一化大小写与首尾空格不会导致配置解析失败。以 SQLite 为例最小可用的服务初始化示例取自 docs/sqlite.mdfrom memu.app import MemoryService service MemoryService( llm_profiles{default: {api_key: your-api-key}}, database_config{ metadata_store: { provider: sqlite, dsn: sqlite:///path/to/your/memory.db, # 省略时使用默认文件 memu.db }, }, )DSN 遵循标准 SQLModel/SQLAlchemy 风格文件型sqlite:///path/to/db.db、绝对路径需要四个斜杠sqlite:////home/user/data/memu.db、内存库sqlite:///:memory:后者常用于测试无持久化。后端感知的向量检索实现这是 ADR 0002 最核心的部分同一份vector_search_segments契约在不同后端有不同的执行路径且始终有可预测的回退行为。默认实现存储中立的暴力余弦 Top-KRecallFileSegmentRepo协议recall_file_segment.py内置了一份 Python 扫描的默认实现def vector_search_segments(self, query_vec, top_k, whereNone): pool self.list_segments(where) by_id {seg.id: seg for seg in pool} ranked cosine_topk(query_vec, [(seg.id, seg.embedding) for seg in pool], ktop_k) return [(by_id[seg_id], score) for seg_id, score in ranked]契约文档注释写得很明确这是每个后端免费获得every backend gets for free的回退路径能原生排序的后端应当覆盖它索引不可用时还可以super()回退到这条路径。真正的相似度数学在 vector.py 中该模块的 docstring 特意强调自己是 storage-neutral、不得导入任何具体memu.database.*后端以避免抽象倒置后端或应用层依赖到另一个后端的内部实现。cosine_topkvector.py本身实现得相当讲究向量化的批量计算所有候选向量堆叠为(n, dim)矩阵后一次性算完余弦分数而非逐条点积健壮性过滤None、空列表、维度不匹配的向量一律剔除——注释里解释了原因空列表会让np.array()退化成 object 矩阵导致矩阵乘法崩溃或静默给出错误分数O(n) 的 Top-K 选择用np.argpartition而非全量排序只在需要时才对选出的 K 个元素排序。Postgres pgvector在数据库内完成排序与截断Postgres 后段的 segment 仓储覆盖了这个默认方法recall_file_segment_repo.pydef vector_search_segments(self, query_vec, top_k, whereNone): if top_k 0: return [] if not self._use_vector: return super().vector_search_segments(query_vec, top_k, where) # 回退到 Python 扫描 distance model.embedding.cosine_distance(query_vec) # pgvector 余弦距离 filters [*self._build_filters(model, where), model.embedding.is_not(None)] with self._sessions.session() as session: rows session.exec( select(model, distance.label(distance)).where(*filters).order_by(distance).limit(top_k) ).all() return [(self._cache_segment(row), 1.0 - float(dist)) for row, dist in rows]三个实现要点排序和截断都在 Postgres 内完成。源码注释点出了与粗扫的本质区别Postgres 做排序与截断所以只有top_k行会过网络而不是作用域内的所有 segment——这是继承来的 Python 扫描做不到的事情距离转相似度pgvector 的算子返回的是余弦距离契约要求的是相似度所以统一返回1 - distance保证各后端分数语义一致无 embedding 的行被显式过滤is_not(None)而不是听凭 NULL 排到某一端可预测的回退use_vectorFalse即部署侧显式选择了非 pgvector 的索引时即使列类型仍是VECTOR也会super()回退到继承的 Python 扫描。这就是 ADR 中predictable fallback behavior when native vector index is unavailable的具体落地。SQLite 与 in-memory免费继承 Python 扫描SQLite 后端的 segment 仓储recall_file_segment_repo.py干脆不覆盖这个方法源码注释解释# vector_search_segments is the protocols Python scan: SQLite has no # vector index to rank with, so there is nothing to override it with.embedding 在 SQLite 中以 JSON 序列化文本形式存储ADR 原文即写明 embeddings stored as JSON text写入时经_prepare_embedding转换、读出时经_normalize_embedding还原。in-memory 后端同理直接依赖协议默认实现。这也对应 ADR 负面后果里承认的一点SQLite 与内存后端的向量检索规模扩展性不如带索引的 pgvector——docs/sqlite.md 给出的经验参考是粗扫适合约 10 万条以内的数据量更大的数据集应考虑迁移到 PostgreSQL pgvector。salience 排序独立于存储后端的本地打分ADR 还提到 salience 排序reinforcement/recency-aware使用本地打分逻辑。需要说明的是在当前源码树中salience 仅出现在 ADR 与 CHANGELOG.md 的记录里未见独立实现——可以推断这套打分逻辑要么内联在检索路径中、要么随记忆模型演进被重构了。无论具体实现如何ADR 确立的原则不变基于行为信号强化/新近度的排序是应用层的本地计算不需要也不应该依赖存储后端的向量能力。后果这个决策的收益与代价ADR 的 Consequences 一节值得完整保留因为它如实记录了架构权衡正面收益一套服务 API 通吃本地与生产两种 footprint——上层代码无需感知后端差异通过 Repository 接口形成清晰的后端契约新后端只需实现 Protocol 即可接入原生向量索引不可用时回退行为可预测自动退化为 Python 粗扫检索语义不变只是性能下降。负面代价Repository 逻辑在多个后端间重复——inmemory/、sqlite/、postgres/三个目录下各有一套resource_repo、recall_file_repo、recall_file_segment_repo这是契约清晰付出的直接维护成本各 provider 之间存在行为/性能差异例如 SQLite 单写者并发限制 vs Postgres 全并发访问见 docs/sqlite.md 的性能对照表;SQLite 与内存后端的向量检索扩展性不如带索引的 pgvector。对使用者而言这些代价转化成了明确的选型建议开发测试用inmemory零配置单用户或便携部署用sqlite单文件、可备份迁移需要并发写入或大规模向量检索时上postgrespgvector。延伸阅读本决策在 ADR 序列中的位置docs/adr/README.md前一篇 ADR 0001 确立了工作流管道架构0001-workflow-pipeline-architecture.md本决策为它提供了存储底座SQLite 后端的完整配置与排错指南docs/sqlite.md仓储契约三件套ResourceRepo、RecallFileRepo、RecallFileSegmentRepo向量数学的共享实现src/memu/vector.py与 ADR 0002 直接相关的测试test_vector.py、test_segment_vector_search.py、test_postgres_migration_config.py。【免费下载链接】memUPersonal memory across agents项目地址: https://gitcode.com/GitHub_Trending/mem/memU创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表