Memori Python SDK 3.3 系列演进深度解析:Rust 原生内核、混合检索与 BYODB 供应能力全览
【免费下载链接】MemoriMemori is agent-native memory infrastructure. A LLM-agnostic layer that turns agent execution and conversation into structured, persistent state for production systems. Built for enterprise, Memori works with the data infrastructure you already run, no rip-and-replace, and deploys across managed cloud, single-tenant cloud, VPC, and on-premises.项目地址: https://gitcode.com/GitHub_Trending/me/Memori
本篇指南以仓库根目录 CHANGELOG.md 为核心脉络,系统拆解 Memori Python SDK 从 3.3.0rc1 到 3.3.6(含 Unreleased 改动)的关键演进:Rust 原生内核(engine-orchestrator/memori_python扩展)如何从实验特性逐步成为 BYODB 模式默认路径、dense + BM25 混合检索与 tokio 后台增强工作线程的实现细节、TiDB Zero 一键供应、以及 OpenAI/Anthropic/Bedrock 对话注入兼容性修复。读完本文,你将掌握各版本开关的精确用法(环境变量、构造函数参数)、Rust 内核的加载与回退机制、混合检索的权重调优参数,以及如何通过仓库内源码与测试用例验证每一项改动。
一、3.x 演进的三大主线
阅读 CHANGELOG.md 可以发现,3.3 系列的所有改动可以归纳为三条相互交织的主线:
- Rust 原生内核从实验到默认:3.3.0rc1 以
MEMORI_USE_RUST_CORE=1实验性引入,3.3.2 起在 BYODB 模式下默认启用,3.3.6 进一步把本地向量化(Memori.embed_texts(...)、recall 查询向量、advanced augmentation 事实向量)全部收敛到 Rustfastembed后端。 - BYODB(自带数据库)能力的持续增强:新增 TiDB Zero 供应、Android 平台支持、预编译 wheels 矩阵,并围绕原生引擎修复了一系列真实场景缺陷(如 datetime 序列化)。
- LLM 兼容性修复:针对 OpenAI 兼容端点对 tool 消息序列的严格要求,重写了历史消息注入前的清洗逻辑。
文章后续各节分别深入这三大主线,并给出对应的源码路径与可验证依据。
二、Rust 原生内核:从实验开关到默认路径
2.1 版本演进时间线
| 版本 | 日期 | Rust 内核状态 | 关键动作 |
|---|---|---|---|
| 3.3.0rc1 | 2026-04-16 | 实验性,需显式开启 | MEMORI_USE_RUST_CORE=1提供原生混合检索与 tokio 后台增强;发布预编译 wheels;sdist 纳入core/crate |
| 3.3.2 | 2026-04-28 | BYODB 模式默认启用 | Rust 检索与增强默认开启,加载失败自动回退纯 Python;新增 Android wheel |
| 3.3.6 | 2026-05-27 | 全面巩固 | 本地 embedding 全部走 Rust fastembed;AA 事实向量在 Rust worker 内附加;修复 datetime 序列化等缺陷 |
2.2 开关优先级与回退语义(3.3.2 起)
按照 changelog 的描述,3.3.2 之后 Rust 内核的启用规则是:BYODB 模式下只要memori_python扩展加载成功即默认启用,Python SDK 仍负责 provider 包装、存储适配器、会话持久化与回退。关闭方式有三种:
from memori import Memori # 方式一:构造函数参数(优先级最高,覆盖一切环境变量) mem = Memori(conn=my_conn_factory, use_rust_core=False)# 方式二:显式禁用环境变量(推荐用于故障排查) export MEMORI_DISABLE_RUST_CORE=1 # 方式三:旧式布尔开关(3.3.0rc1 时代的 opt-in 变量) export MEMORI_USE_RUST_CORE=0在 memori/_config.py 中可以精确看到这一优先级逻辑:
if _env_bool("MEMORI_DISABLE_RUST_CORE", False): self.use_rust_core = False elif os.environ.get("MEMORI_USE_RUST_CORE") is not None: self.use_rust_core = _env_bool("MEMORI_USE_RUST_CORE", False) else: self.use_rust_core = True即:MEMORI_DISABLE_RUST_CORE=1最高优先;其次兼容旧变量MEMORI_USE_RUST_CORE(3.3.0rc1 中设为1为开启,3.3.2 后设为0为关闭);两者都未设置时默认开启。而在 memori/init.py 中,构造函数参数use_rust_core只要不为None,就会在Config()初始化之后覆盖环境变量结果,因此它是三者中优先级最高的。
如果 Rust 内核加载失败(例如未安装对应平台 wheel),SDK 会打印警告并回退到纯 Python 路径,不会中断业务——这是 3.3.2 明确保证的降级语义。
2.3 内核加载机制:memori_python扩展发现
memori_python是 Rust crateengine-orchestrator(目录core/)编译产出的 Python 扩展。在 memori/native/_loader.py 中,加载顺序为:
- 先调用
_ensure_onnxruntime_dylib()确保 ONNX Runtime 动态库就绪; - 若设置了
MEMORI_PYTHON_LIB环境变量,直接按该路径加载; - 若设置了
CARGO_TARGET_DIR,依次探测release/debug下的libmemori_python.{dylib,so}或memori_python.dll; - 回落到仓库内常见构建目录(
target/...、core/target/...); - 最后尝试正常
import memori_python(wheel 安装场景)。
这意味着从源码构建内核后,即使不安装 wheel,也可以通过设置MEMORI_PYTHON_LIB或CARGO_TARGET_DIR让 SDK 找到本地编译产物,便于调试。
2.4 适配器结构:Python 侧回调桥接
Rust 内核并不是"黑盒替代",而是通过 memori/native/_adapter.py 中的RustCoreAdapter与数据库驱动建立桥接。EngineHandle构造时需要传入四个回调:
| 回调 | 职责 | 对应实现 |
|---|---|---|
fetch_embeddings | 按 entity 拉取已持久化的事实向量,供 Rust 侧做 dense 检索 | _fetch_embeddings_cb(memori/native/_adapter.py) |
fetch_facts_by_ids | 按 ID 批量取回事实内容(含 summaries),供重排序后返回 | _fetch_facts_by_ids_cb(memori/native/_adapter.py) |
write_batch | 执行 Rust worker 提交的写操作批次 | _write_batch_cb(memori/native/_adapter.py) |
| 内嵌向量化 | 通过NativeEmbedder完成本地文本向量化 | memori/native/_embeddings.py |
_write_batch_cb支持的操作类型(op_type)在_apply_write_op中分发,包括entity_fact.create、knowledge_graph.create、process_attribute.create、conversation.update、upsert_fact五种,分别落盘到 entity facts、知识图谱三元组、进程属性与对话摘要(memori/native/_adapter.py)。从源码结构看,Rust 侧负责调度与计算,Python 侧负责所有数据库读写,这种"计算与 IO 分离"的架构是内核能够透明替换纯 Python 实现的关键。
三、混合检索管线:dense 召回 + BM25 词法重排序
3.3.0rc1 首次引入的原生能力是"混合搜索 recall 管线(dense + lexical re-ranking)",其 Rust 实现在 core/src/search/mod.rs 下,分为三个文件:
- core/src/search/api.rs:公开入口
search_facts,融合余弦相似度与 BM25 分数; - core/src/search/lexical.rs:BM25 打分与混合权重选择;
- core/src/search/models.rs:候选与结果数据结构。
3.1 两阶段打分流程
从 core/src/search/api.rs 的实现看,检索分两步:
- dense 阶段:先用向量余弦相似度从事实池中召回候选集(
FactCandidate.score即原始余弦分数); - lexical 阶段:对查询做 tokenize,对每个候选计算 BM25 词法分数,最终
rank_score = w_cos * cos_score + w_lex * lex_score,再按rank_score排序,且只对前limit个结果做部分排序(select_nth_unstable_by),避免对全量候选做完整排序。
3.2 词法权重可调参数
core/src/search/lexical.rs 定义了通过环境变量控制的混合权重(首次读取后缓存,运行时不再重读):
| 环境变量 | 默认值 | 取值范围(clamp) | 语义 |
|---|---|---|---|
MEMORI_RECALL_LEX_WEIGHT | 0.15 | [0.05, 0.40] | 常规查询的 BM25 权重 |
MEMORI_RECALL_LEX_WEIGHT_SHORT | 0.30 | [0.05, 0.40] | 短查询(≤ 2 个 token)的 BM25 权重 |
短查询默认给予更高的词法权重,因为dense_lexical_weights认为短查询的语义向量信息量有限,词法精确匹配更具判别力(core/src/search/lexical.rs)。tokenize会统一小写、按非字母数字字符切分,并用二分查找过滤 40 余个英文停用词(core/src/search/lexical.rs)。
3.3 调用链与测试验证
Python 侧入口是 memori/native/_adapter.py 的retrieve_facts:它构造{entity_id, query_text, dense_limit, limit}的 JSON payload,调用EngineHandle.retrieve(...)得到 JSON 数组后逐条解析为 dict。dense_limit(即MEMORI_RECALL_EMBEDDINGS_LIMIT,默认 1000,见 memori/_config.py)控制 dense 阶段候选池大小。
仓库内的单元测试直接验证了这条链路:test_retrieve_facts_initializes_engine_on_first_use断言首次调用会惰性初始化引擎并精确调用一次engine.retrieve(tests/test_rust_core.py);Rust 侧search_facts_blends_cosine_and_lexical_with_query与lexical_scores_ranks_matching_document_highest则验证了混合打分与 BM25 排序的正确性(core/src/search/api.rs、core/src/search/lexical.rs)。
四、本地向量化:fastembed/ONNX 与多平台分发
4.1 统一到 Rust fastembed 后端(3.3.6)
3.3.6 之前,本地 embedding 存在双轨:Rust 内核可用时走fastembed,否则回退 Pythonsentence-transformers。3.3.6 移除了 Pythonsentence-transformers回退,本地向量化统一由 Rustfastembed后端完成,覆盖三处:Memori.embed_texts(...)、recall 查询向量、advanced augmentation 事实向量。embeddingsoptional extra 保留但变为安装兼容性 no-op(见 CHANGELOG.md)。
Rust 侧的 embedder 实现在 core/src/embeddings/models.rs:SentenceTransformersEmbedder内部持有fastembed::TextEmbedding、HuggingFace Hub 缓存的 tokenizer 与向量维度dim,模型初始化是惰性的——引擎启动时不加载 ONNX Runtime,首次 embedding 时才初始化,从而降低冷启动开销。
Python 侧则通过 memori/native/_embeddings.py 的_embed_with_native_cache按模型名缓存NativeEmbedder实例(线程安全,_NATIVE_EMBEDDER_LOCK保护),并用_embed_texts_with_cardinality保持输入基数一致性:不可嵌入的输入返回空向量占位,可嵌入文本逐条对齐向量下标,若数量不匹配会抛出RustCoreAdapterError(memori/native/_embeddings.py)。
4.2 首次使用的模型下载
根据 3.3.0rc1 的说明,首次使用 Rust 内核的用户会从 Hugging Face 下载约 25 MB 的 ONNX embedding 模型,缓存在~/.fastembed_cache/目录下。默认模型是all-MiniLM-L6-v2(memori/_config.py),可通过MEMORI_EMBEDDINGS_MODEL环境变量或config.embeddings.model覆盖。注意 memori/native/_loader.py 的模型名归一化:all-minilm-l6-v2会被归一化为None,即 Rust 侧使用 fastembed 的默认模型,其余名称按字面传给EmbeddingModel解析。
4.3 ONNX Runtime 自举(含 Android)
Rust 扩展依赖 ONNX Runtime 动态库。memori/native/_onnxruntime.py 实现了一套完整的自举逻辑:
- 固定版本
_ORT_VERSION = "1.23.2",按(系统, 架构)维护资产清单与 SHA-256 校验和(linux/macos/windows/android 全覆盖); - 优先复用已配置的
ORT_DYLIB_PATH;可用MEMORI_ORT_AUTO_DOWNLOAD=0关闭自动下载; - 下载资产缓存在
~/.cache/memori/onnxruntime/<version>/,使用跨进程文件锁(O_CREAT | O_EXCL)避免并发重复下载,最多重试 3 次; - 解压前做路径穿越防护(
_is_within_directory),下载后校验 SHA-256,不匹配则拒绝使用。
Android 支持(3.3.2)即依赖于此:cibuildwheel 目标为android_24_arm64_v8a与android_24_x86_64,运行时从 Microsoft 的 Android AAR 中下载并选取匹配 ABI 的libonnxruntime.so(memori/native/_onnxruntime.py)。
4.4 预编译 wheels 矩阵与从源码构建
3.3.0rc1 起发布的 wheel 标签为cp310-abi3,覆盖:
- Python 3.10 ~ 3.14(abi3 稳定 ABI);
manylinux_2_28_{x86_64,aarch64}、macosx_{x86_64,arm64}、win_amd64;- 3.3.2 追加 Android(
android_24_arm64_v8a、android_24_x86_64)。
对于不受支持的平台,sdist 已包含core/Rust crate,可在具备 Rust 工具链的机器上从源码构建(rust-toolchain.toml与Makefile位于 core/)。rust-core/目录在 3.3.0rc1 中更名为core/,但 crate 名engine-orchestrator与 Python 扩展名memori_python均未改变,公共导入路径不受影响。
五、BYODB 供应:TiDB Zero 一键开通(3.3.6)
5.1 三层入口
3.3.6 新增 TiDB Zero BYODB 供应,提供三种等价入口:
from memori import Memori # 入口一:SDK 方法 mem = Memori.provision( provider="tidb-zero", build=True, tag="my-agent", )# 入口二:CLI 命令 export TIDB_ZERO_API_KEY=your_key python -m memori provision tidb-zero # 也支持 --provider 形式: python -m memori provision --provider tidb-zero# 入口三:optional extra 安装 pip install 'memori[tidb-zero]'CLI 用法由 memori/provisioning/_manager.py 解析:位置参数或--provider两种形式,成功后会打印 Provider、Family、脱敏后的 DSN(redact_dsn)、Claim URL 与过期时间(如存在)。
5.2 供应实现细节
供应器注册在 memori/provisioning/providers/tidb_zero.py:通过@Registry.register_provider("tidb-zero")装饰器注册(注册表机制见 memori/provisioning/_registry.py),向https://zero.tidbapi.com/v1beta1/instances发送POST请求,请求体仅含{"tag": tag}(默认"memori")。认证与端点均可配置:
| 配置项 | 来源 | 说明 |
|---|---|---|
| API Key | TIDB_ZERO_API_KEY环境变量或api_key参数 | 以Bearer方式注入 Authorization 头 |
| 端点 URL | MEMORI_TIDB_ZERO_URL环境变量或url参数 | 默认官方端点 |
| 超时 | timeout参数 | 默认 30 秒 |
| 标签 | tag参数 | 实例标识,默认"memori" |
响应解析(parse_tidb_zero_response)要求包含instance.connectionString,否则抛出ValueError;同时提取claimUrl与expiresAt,并剔除密码字段(_safe_connection_metadata过滤 key 含 password/pwd 的元数据),返回的ProvisionResult带有 MySQL 系列 TLS 连接参数(mysql_tls_connect_args())。整个供应流程按 MySQL 家族验证:MYSQL_PROVIDERS = {"tidb-zero"},且会调用require_mysql_driver前置检查驱动是否安装(memori/provisioning/init.py)。
5.3 缓存与会话
get_provision_result支持缓存(cache=True默认开启):以(provider, tag, cache_key_override)为键,命中后直接复用已供应的实例,避免重复开通(memori/provisioning/init.py)。provision_memori最终返回一个已就绪的Memori实例,build=True时会连带完成连接与记忆管线初始化。
六、LLM 对话注入兼容性修复
6.1 tool 消息序列修复(3.3.2,#434)
这是 3.3 系列最有代表性的"真实生产缺陷修复"。问题现象:recall 出来的历史消息被注入对话后,OpenAI 兼容端点返回:
400: An assistant message with 'tool_calls' must be followed by tool messages responding to each 'tool_call_id'根因在 memori/llm/pipelines/conversation_injection.py 的注释中讲得很清楚:conversation_message表只持久化(role, content),tool_calls与tool_call_id字段并不会被保存。于是回放一段使用了工具的历史时,会注入两类坏消息:
- 原本只有
tool_calls的 assistant 消息(其tool_calls已丢失,content 为空); - 对应的
role="tool"消息(其tool_call_id已丢失)。
修复方式是_sanitize_history_for_openai_compat在注入前清洗:丢弃role="tool"消息、丢弃 content 为空的 assistant 消息、并把 Gemini 时代的role="model"归一化为role="assistant"。OpenAI/Anthropic/Bedrock 三条注入路径均经过此清洗(memori/llm/pipelines/conversation_injection.py)。同时,注入消息计数器改为统计清洗后的数量,确保持久化与增强阶段的 payload 不会切进当前用户消息。
6.2 多轮对话摄取修复(3.3.0rc1,#83)
另一个修复针对 AzureOpenAI/OpenAI 客户端:此前只有第一轮对话会被记录,原因是conversation_id在请求生命周期中解析过晚。修复后在请求早期解析conversation_id,保证同一会话的所有轮次都能正确落库(见 CHANGELOG.md)。
6.3 MCP 项目级归属设置(3.3.6,#404)
3.3.6 补充了 MCP 客户端配置指引:使用工作区派生值为X-Memori-Entity-Id与X-Memori-Process-Id赋值,防止跨项目记忆混淆。仓库文档 docs/memori-cloud/mcp/client-setup.mdx 给出了具体配置:
"X-Memori-Entity-Id": "${workspaceFolderBasename}", "X-Memori-Process-Id": "${workspaceFolderBasename}"归属语义的要点:当每个项目只有一个 agent 且需要项目级隔离记忆时,两个头取相同值;当多个 agent 共享一个实体、但各自需要隔离的会话历史时,X-Memori-Entity-Id保持稳定标识(如${env:MEMORI_ENTITY_ID}或user_123),X-Memori-Process-Id按工作区/agent/集成变化。这对应 Python SDK 中Memori.attribution(entity_id, process_id)的职责(校验非空、≤ 100 字符,见 memori/init.py)。
七、发布工程与可观测性
7.1 Rust 内核 CI
仓库新增.github/workflows/core-ci.yml,在core/**、setup.py、memori/_rust_core.py、tests/test_rust_core.py等路径变更时触发,覆盖cargo fmt、clippy、单元测试与跨平台 wheel 构建冒烟(cibuildwheel)。其环境设定了RUSTFLAGS: "-D warnings",保证 clippy 告警即失败,维持内核代码质量基线。
7.2 PyPI 发布流水线改造
3.3.0rc1 将 PyPI 发布流水线重写为基于cibuildwheelv3.4.0,产出符合 PyPI 规范的 wheel 标签;纯 Python 回退仍通过 sdist 提供。此外发布工作流新增两个输入:
| 输入 | 作用 |
|---|---|
dry_run | 发布彩排,不触碰索引 |
publish_memorisdk | 控制是否实际发布 SDK 包 |
这允许维护者在正式发版前做完整的"彩排"验证。
7.3 调试日志改造
3.3.0rc1 起,增强管线中的 debug payload 日志不再依赖MEMORI_DEBUG_AA_PAYLOAD=1的 stdout 输出,而是统一走标准logging模块的DEBUG级别。需要排查时,对memori._rust_core与engine_orchestrator两个 logger 开启 debug 级别即可:
import logging logging.basicConfig(level=logging.DEBUG) logging.getLogger("memori._rust_core").setLevel(logging.DEBUG) logging.getLogger("engine_orchestrator").setLevel(logging.DEBUG)同时,3.3.6 起 debug 输出还会经过_logging.set_truncate_enabled控制的截断策略(debug_truncate=True默认开启,长内容被截断,见 memori/_config.py)。
八、面向未来的输入校验(Unreleased)
Unreleased 部分预告了Memori.recall(...)的输入校验强化,与既有limit校验对齐:
mem.recall(query=123) # 抛 TypeError: query must be a string mem.recall(query=" ") # 抛 ValueError: query cannot be empty这一改动在 memori/init.py 中已经落地:非字符串抛TypeError,空或纯空白抛ValueError,limit非整数抛TypeError、≤ 0 抛ValueError。其价值在于快速失败(fail fast)——在发出空查询之前就拦截,避免对数据库/LLM 路径发起无意义的空 recall。对于依赖错误类型的下游测试,仓库测试目录中的pytest.raises(ValueError/TypeError, match=...)模式(如 tests/storage/test_connection_factory.py)展示了同类校验的断言写法。
九、如何在本仓库验证以上结论
以下文件路径可供读者按图索骥,逐一验证本文所述内容:
- Changelog 全貌:CHANGELOG.md
- Rust 内核开关与配置:memori/_config.py、memori/init.py
- 适配器与回调桥接:memori/native/_adapter.py
- 扩展加载与模型归一化:memori/native/_loader.py
- ONNX Runtime 自举:memori/native/_onnxruntime.py
- 混合检索核心:core/src/search/api.rs、core/src/search/lexical.rs
- 原生 embedder:core/src/embeddings/models.rs
- TiDB Zero 供应:memori/provisioning/providers/tidb_zero.py、memori/provisioning/_manager.py
- 对话注入清洗:memori/llm/pipelines/conversation_injection.py
- 测试用例:tests/test_rust_core.py
- Rust 内核 CI:.github/workflows/core-ci.yml
- MCP 归属配置文档:docs/memori-cloud/mcp/client-setup.mdx
十、小结
纵观 3.3 系列,Memori Python SDK 的演进路径清晰而克制:Rust 原生内核并非一次性替换,而是以"实验开关 → 默认启用 → 全面巩固"的三步节奏逐步落地,始终保留use_rust_core=False/MEMORI_DISABLE_RUST_CORE=1/MEMORI_USE_RUST_CORE=0与纯 Python 回退作为逃生通道;混合检索通过MEMORI_RECALL_LEX_WEIGHT系列参数提供了可调空间;TiDB Zero 供应让 BYODB 上手成本显著降低;而 tool 消息序列修复则体现了对上游 LLM 协议严格性的务实适配。对于正在评估或使用 Memori 的开发者,理解这一演进路径有助于在 BYODB 场景下正确选择开关组合、调优检索权重,并借助仓库内测试用例快速验证行为。
【免费下载链接】MemoriMemori is agent-native memory infrastructure. A LLM-agnostic layer that turns agent execution and conversation into structured, persistent state for production systems. Built for enterprise, Memori works with the data infrastructure you already run, no rip-and-replace, and deploys across managed cloud, single-tenant cloud, VPC, and on-premises.项目地址: https://gitcode.com/GitHub_Trending/me/Memori
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考