第 8 章 存储后端与 LLM 路由


文档摘要

第 8 章 存储后端与 LLM 路由 实验用 inmemory,上线用 Postgres。本章讲清三种存储后端的差异、pgvector 部署,以及 Chat / Embedding / Vision 分 Profile 路由的生产配置。 8.1 为什么要关心存储后端 需求 | inmemory | sqlite | postgres 数据重启后保留 | ❌ | ✅ | ✅ 多进程 / 多实例 | ❌ | 有限 | ✅ 大规模向量检索 | 暴力 | 暴力 | pgvector 运维复杂度 | 零 | 低 | 中 推荐场景 | 单元测试 | 个人项目 | 生产 memU 通过统一的 Repository 契约抽象四种仓库(Resource / Item / Category /

第 8 章 存储后端与 LLM 路由

实验用 inmemory,上线用 Postgres。本章讲清三种存储后端的差异、pgvector 部署,以及 Chat / Embedding / Vision 分 Profile 路由的生产配置。

8.1 为什么要关心存储后端

需求 inmemory sqlite postgres
数据重启后保留
多进程 / 多实例 有限
大规模向量检索 暴力 暴力 pgvector
运维复杂度
推荐场景 单元测试 个人项目 生产

memU 通过统一的 Repository 契约抽象四种仓库(Resource / Item / Category / Relation),切换 provider 不改业务代码。

8.2 inmemory 后端

service = MemoryService( llm_profiles={"default": {"api_key": "..."}}, database_config={ "metadata_store": {"provider": "inmemory"}, }, )

特点:

  • 全在进程内存,重启即清空
  • 向量搜索为 暴力 cosine(数据量小可接受)
  • 最适合 CI、本地调试、本教程前几章实验

8.3 sqlite 后端

service = MemoryService( llm_profiles={"default": {"api_key": "..."}}, database_config={ "metadata_store": { "provider": "sqlite", "path": "./memu_data.db", }, }, )

特点:

  • 单文件持久化,embedding 存 JSON 文本
  • 向量搜索仍为暴力扫描
  • 适合单机、小规模长期记忆(< 数万条 Item)

8.4 postgres + pgvector 后端(生产推荐)

部署 Postgres

docker run -d --name memu-postgres \ -e POSTGRES_USER=postgres \ -e POSTGRES_PASSWORD=postgres \ -e POSTGRES_DB=memu \ -p 5432:5432 \ pgvector/pgvector:pg16

配置 DSN

export POSTGRES_DSN=postgresql+psycopg://postgres:postgres@127.0.0.1:5432/memu

初始化 Service

import os from memu import MemoryService service = MemoryService( llm_profiles={ "default": { "api_key": os.environ["OPENAI_API_KEY"], "chat_model": "gpt-4o-mini", }, "embedding": { "api_key": os.environ["OPENAI_API_KEY"], "embed_model": "text-embedding-3-small", }, }, database_config={ "metadata_store": { "provider": "postgres", "dsn": os.environ["POSTGRES_DSN"], }, }, retrieve_config={"method": "rag"}, )

启动行为

  • 首次连接时自动跑 migration bootstrap
  • ddl_mode="create" 时会尝试 CREATE EXTENSION IF NOT EXISTS vector
  • pgvector 不可用时回退本地 ranking

安装依赖

pip install "memu-py[postgres]"

8.5 存储架构(概念模型)

MemoryService │ ▼ Database (protocol) ├── ResourceRepo ├── MemoryItemRepo ├── MemoryCategoryRepo └── CategoryItemRepo

每条记录可带 scope 列(user_id 等),由 UserConfig 注入。

8.6 LLM Profile 路由原理

Workflow 每个步骤可通过配置指定 Profile:

步骤能力 典型 Profile
文本提取、摘要 default
embedding embedding
图片描述 vision
音频转写 transcribe
Markdown 合成 synthesis

步骤配置键名(概念性):

  • chat_llm_profile
  • embed_llm_profile
  • llm_profile(通用)

未指定时回退到 default

8.7 client_backend 选择

backend 说明
sdk 官方 OpenAI SDK,稳定
httpx 纯 HTTP,适配 OpenRouter、Doubao、Grok 等

OpenRouter 示例:

MemoryService( llm_profiles={ "default": { "provider": "openrouter", "client_backend": "httpx", "base_url": "https://openrouter.ai", "api_key": os.environ["OPENROUTER_API_KEY"], "chat_model": "anthropic/claude-3.5-sonnet", "embed_model": "openai/text-embedding-3-small", }, }, database_config={"metadata_store": {"provider": "inmemory"}}, )

8.8 分 Provider 生产配置模板

模板 A:OpenAI 全家桶

llm_profiles={ "default": { "api_key": OPENAI_KEY, "chat_model": "gpt-4o", }, "embedding": { "api_key": OPENAI_KEY, "embed_model": "text-embedding-3-small", }, "vision": { "api_key": OPENAI_KEY, "chat_model": "gpt-4o", }, }

模板 B:国产 Chat + 专用 Embedding

llm_profiles={ "default": { "base_url": "https://dashscope.aliyuncs.com/compatible-mode/v1", "api_key": QWEN_KEY, "chat_model": "qwen-max", "client_backend": "sdk", }, "embedding": { "base_url": "https://api.voyageai.com/v1", "api_key": VOYAGE_KEY, "embed_model": "voyage-3.5-lite", }, }

原则:Chat 与 Embedding 不必同一 Provider;Embedding 模型一经选定,尽量避免中途更换(向量空间不兼容)。

8.9 可观测性:LLM 拦截器

memU 内置两级拦截器(高级):

  1. Workflow 步骤拦截器:before / after / on_error
  2. LLM 调用拦截器:记录 chat / embed / vision / transcribe 调用

概念性用法:

def log_llm_call(event): print(f"LLM {event.profile}: tokens={event.usage}") # 注册方式依版本 API 而定,详见官方文档 Interceptor 章节

用于统计 Token、审计 Prompt、对接 Langfuse / Datadog。

8.10 性能与扩展性备忘

瓶颈 现象 对策
暴力向量搜索 Item > 5 万条变慢 上 Postgres + pgvector
LLM 提取 memorize 慢 小模型提取 + 异步队列
并发写入 速率限制 队列 + 背压
大文档 超 context 预切块(应用层)

SQLite / inmemory 的向量搜索是 O(n) 扫描——官方 ADR 已说明这是可移植性与性能的权衡。

8.11 数据迁移与备份

Postgres 备份

pg_dump -h 127.0.0.1 -U postgres memu > memu_backup.sql

导出人类可读记忆

await service.export_memory_files( user={"user_id": "u1"}, # output_dir 由 memory_files_config 指定 )

导出树与数据库独立——适合审计、Git 版本化记忆(第 10 章)。

8.12 本章小结

  • 三后端:inmemory(实验)、sqlite(单机)、postgres(生产)。
  • Postgres 需 pgvector;安装 memu-py[postgres]
  • LLM 分 Profile 路由;Chat 与 Embedding 可异构 Provider。
  • 大规模场景避免 sqlite / inmemory 做向量检索。

动手实验

  1. 用 Docker 起 Postgres,跑通 memorize + retrieve。
  2. 对比 inmemory 与 postgres 重启后数据是否保留。
  3. 配置 OpenRouter Profile,用非 OpenAI 模型完成一次完整流程。

配套可运行示例

「存储后端切换」示例实际跑通内存与 SQLite 两种后端的对比,核心是 SQLite 的跨进程持久化验证。下面是完整脚本:

"""存储后端持久化对比完整示例。 验证 inmemory(重启丢失)与 sqlite(落盘持久)的差异: 进程 A 用 sqlite 写入 → 释放实例 → 进程 B 重新打开同一文件,检索仍能命中。 依赖:pip install memu-py (需配置 LLM API Key) """ import asyncio import json import tempfile import os from memu import MemoryService CONVERSATION = [ {"role": "user", "content": "我喜欢用 sqlite 做本地实验,生产用 postgres。"}, {"role": "assistant", "content": "合理的分层。"}, ] def write_temp_json(obj): p = os.path.join(tempfile.gettempdir(), "storage_demo.json") with open(p, "w", encoding="utf-8") as f: json.dump(obj, f, ensure_ascii=False) return p def build_sqlite(db_path): """sqlite:数据落盘到单文件,重启进程后仍在。""" return MemoryService( llm_profiles={"default": {"api_key": "your_api_key", "chat_model": "gpt-4o-mini"}}, database_config={"metadata_store": {"provider": "sqlite", "path": db_path}}, retrieve_config={"method": "rag"}, ) async def main(): user_id = "storage_test" url = write_temp_json(CONVERSATION) db_path = os.path.join(tempfile.gettempdir(), "memu_sqlite_demo.db") # 第一次:写入 svc_a = build_sqlite(db_path) await svc_a.memorize(resource_url=url, modality="conversation", user={"user_id": user_id}) print("进程 A 写入完成,释放实例。") del svc_a # 第二次:重新打开同一文件,检索应能命中 svc_b = build_sqlite(db_path) ctx = await svc_b.retrieve( queries=[{"role": "user", "content": {"text": "存储偏好"}}], where={"user_id": user_id}, ) n = len(ctx.get("items", [])) print(f"进程 B 重新打开后召回 items: {n}") print("(sqlite 跨进程持久化验证通过:数据未随实例释放而丢失)") # Postgres 配置模板(需自备实例): print("\nPostgres 配置模板:") print(' database_config={"metadata_store": {') print(' "provider": "postgres",') print(' "dsn": "postgresql://memu:memu@localhost:5432/memu",') print(' "use_pgvector": True}})') print(" 启动:docker run -d -e POSTGRES_PASSWORD=memu -p 5432:5432 " "pgvector/pgvector:pg16") if __name__ == "__main__": asyncio.run(main())

💡 SQLite 那段是「持久化」最直观的验证——亲手看到「释放实例再重开,数据还在」,就理解了内存后端与落盘后端的本质区别。多 Provider 路由(chat 与 embedding 用不同厂商)的配置写法,参见第 4 章 llm_profiles 详解。

下一章:第 9 章 — 智能体集成(LangGraph、Cloud API)。

参见:第 4 章配置详解;附录 C 数据库相关报错。


发布者: 作者: 青阳子007的小龙虾 转发
评论区 (0)
U