Memori Python SDK 3.3 系列演进深度解析:Rust 原生内核、混合检索与 BYODB 供应能力全览
2026/9/14 6:43:17 网站建设 项目流程

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 系列的所有改动可以归纳为三条相互交织的主线:

  1. Rust 原生内核从实验到默认:3.3.0rc1 以MEMORI_USE_RUST_CORE=1实验性引入,3.3.2 起在 BYODB 模式下默认启用,3.3.6 进一步把本地向量化(Memori.embed_texts(...)、recall 查询向量、advanced augmentation 事实向量)全部收敛到 Rustfastembed后端。
  2. BYODB(自带数据库)能力的持续增强:新增 TiDB Zero 供应、Android 平台支持、预编译 wheels 矩阵,并围绕原生引擎修复了一系列真实场景缺陷(如 datetime 序列化)。
  3. LLM 兼容性修复:针对 OpenAI 兼容端点对 tool 消息序列的严格要求,重写了历史消息注入前的清洗逻辑。

文章后续各节分别深入这三大主线,并给出对应的源码路径与可验证依据。


二、Rust 原生内核:从实验开关到默认路径

2.1 版本演进时间线

版本日期Rust 内核状态关键动作
3.3.0rc12026-04-16实验性,需显式开启MEMORI_USE_RUST_CORE=1提供原生混合检索与 tokio 后台增强;发布预编译 wheels;sdist 纳入core/crate
3.3.22026-04-28BYODB 模式默认启用Rust 检索与增强默认开启,加载失败自动回退纯 Python;新增 Android wheel
3.3.62026-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 中,加载顺序为:

  1. 先调用_ensure_onnxruntime_dylib()确保 ONNX Runtime 动态库就绪;
  2. 若设置了MEMORI_PYTHON_LIB环境变量,直接按该路径加载;
  3. 若设置了CARGO_TARGET_DIR,依次探测release/debug下的libmemori_python.{dylib,so}memori_python.dll
  4. 回落到仓库内常见构建目录(target/...core/target/...);
  5. 最后尝试正常import memori_python(wheel 安装场景)。

这意味着从源码构建内核后,即使不安装 wheel,也可以通过设置MEMORI_PYTHON_LIBCARGO_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.createknowledge_graph.createprocess_attribute.createconversation.updateupsert_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 的实现看,检索分两步:

  1. dense 阶段:先用向量余弦相似度从事实池中召回候选集(FactCandidate.score即原始余弦分数);
  2. 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_WEIGHT0.15[0.05, 0.40]常规查询的 BM25 权重
MEMORI_RECALL_LEX_WEIGHT_SHORT0.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_querylexical_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_v8aandroid_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_v8aandroid_24_x86_64)。

对于不受支持的平台,sdist 已包含core/Rust crate,可在具备 Rust 工具链的机器上从源码构建(rust-toolchain.tomlMakefile位于 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 KeyTIDB_ZERO_API_KEY环境变量或api_key参数Bearer方式注入 Authorization 头
端点 URLMEMORI_TIDB_ZERO_URL环境变量或url参数默认官方端点
超时timeout参数默认 30 秒
标签tag参数实例标识,默认"memori"

响应解析(parse_tidb_zero_response)要求包含instance.connectionString,否则抛出ValueError;同时提取claimUrlexpiresAt,并剔除密码字段_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_callstool_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-IdX-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.pymemori/_rust_core.pytests/test_rust_core.py等路径变更时触发,覆盖cargo fmtclippy、单元测试与跨平台 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_coreengine_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,空或纯空白抛ValueErrorlimit非整数抛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),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询