数据截至 (上游 commit 538b61f24529)
Rust 核心引擎:同步嵌入 + 有界后台 worker + 跨语言绑 定
30 秒导读: Memori 把最重、最要命的两件事——把文本变成向量(嵌入) 和 在后台异步跑记忆生成/落库——都下沉到一个 Rust crate
engine-orchestrator。Python SDK、Node SDK、TypeScript SDK 三家共用同一份引擎,各自只写一层"翻译 JSON、抛本语言异常"的薄适配。这一章讲这个引擎内部怎么转、以及它如何被三种语言复用。
1. 这是什么(零基础也能懂)
一句话定义: engine-orchestrator 是 Memori 的"发动机舱"——一个纯 Rust 库,对外暴露少数几个方法(嵌入、检索、提交记忆生成、优雅关闭),对内藏着一个嵌入模型和两个后台工作池。
它解决什么问题。 Memori 是个记忆库(见 index),它要反复做两类脏活累活:
- 嵌入:把一句话变成一个几百维的浮点向量,这样才能算"语义相似"。这一步跑的是神经网络(ONNX 模型),CPU 密集、慢。
- 后台处理:把一段对话炼成结构化记忆(见 02-augmentation)、再写进数据库。这一步要发网络请求、要写库,慢且不能卡住主线程。
如果每个语言的 SDK 各写一遍这些逻辑,就会有三份不一致、三份 bug。Memori 的选择是:写一遍 Rust,三家共用。
给谁用。 直接使用者是 Memori 自己的三个 SDK(Python / Node / TS),不是终端开 发者。终端开发者调的是 memori.recall(...) 这种高层 API,底下才转到这个引擎。
用起来什么样(以 Python 为例)。 引擎被包成一个 Python 类,调用像这样:
# 示意,非源码 —— 展示引擎对外的手感
from memori_python import EngineHandle
engine = EngineHandle(model_name, fetch_embeddings_cb, fetch_facts_cb, write_batch_cb)
engine.embed_texts(["hello world"]) # 同步:立刻拿到向量
engine.submit_augmentation(payload_json) # 异步:塞进后台队列,立刻返回一个 job_id
engine.wait_for_augmentation(timeout_ms=5000) # 等后台把这批活干完
一句话直觉。 把引擎想成一家快餐店的后厨:
- 前台点单(
embed)——你站着等,当场出餐( 同步)。 - 外卖单(
submit_augmentation)——丢进出单口就走,后厨有固定几个灶台(并发上限)慢慢做;出单口格子有限(队列有界),满了就直接告诉你"稍后再来"(拒单,而不是无限堆积)。 - 打烊流程(
shutdown)——不再收新单,但把手上的单做完再关灯(优雅关闭)。
本节不出现代码细节;记住这三个手感即可。
2. 顶层全景(它大概怎么转)
怎么读下面这张图: 上层是三种语言的 SDK,中间那道虚线是"薄适配层",线以下全是同一个 Rust crate。数据都以 JSON 字符串穿过语言边界。
Python SDK Node SDK TS SDK
│ │ │
┌─────┴──────┐ ┌───────┴───────┐ ┌──────┴───────┐
│ PyO3 适配 │ │ napi-rs 适配 │ │ 复用 napi │ ← 薄:只翻译 JSON / 抛异常
│ memori_ │ │ node-bindings │ │ (同一个 .node)│
│ python │ │ │ │ │
└─────┬──────┘ └───────┬───────┘ └──────┬───────┘
└──────────────────┼──────────────────┘
▼
┌───────────────────────────────────────┐
│ engine-orchestrator (Rust 核心) │
│ │
│ EngineOrchestrator ← 一个把手 │
│ ├─ embedder ......... 同步嵌入 │
│ ├─ postprocess_rt ... 后台 worker① │
│ ├─ augmentation_rt .. 后台 worker② │
│ └─ storage_bridge ... 回调宿主的库 │
└───────────────────────────────────────┘
│
┌───────────┴───────────┐
▼ ▼
fastembed / ONNX MemoriClient(HTTP)
(向量模型) (记忆生成 API)
部件一句话职责:
| 部件 | 干什么 | 在哪(相对 core/) |
|---|---|---|
EngineOrchestrator | 顶层把手:持有下面所有资源,对外暴露 embed/retrieve/submit/shutdown | src/lib.rs:86 |
EmbeddingEngine | 只做嵌入的轻量把手(不建后台池) | src/lib.rs:57 |
SentenceTransformersEmbedder | 包住 fastembed/ONNX 模型,懒加载 | src/embeddings/models.rs:11 |
embed_texts | 嵌入流水线:分块→批推理→池化→逐级降级 | src/embeddings/api.rs:159 |
WorkerRuntime<J> | 通用后台工作池:有界队列 + 并发上限 + 生命周期 | src/runtime/worker.rs:90 |
MemoriClient | 调 Memori 云端记忆生成 API 的 HTTP 客户端 | src/network/client.rs:26 |
StorageBridge | 让引擎回调宿主语言的数据库(BYODB) | src/storage/bridge.rs:6 |
OrchestratorError | 统一错误类型,带稳定 status_code() | src/error.rs:5 |
主线走一遍(高层,不进代码):
- SDK 构造引擎 →
EngineOrchestrator::new_with_storage(src/lib.rs:104)加载嵌入模型、建两个后台池、建 HTTP 客户端。 - 要向量 →
embed(texts)同步返回,当场算完(src/lib.rs:134)。 - 要存记忆 →
submit_augmentation(input)把活塞进队列立刻返回(src/lib.rs:201);后台 worker 稍后调 API → 生成记忆 → 嵌入 → 写库。 - 要等后台跑完 →
wait_for_augmentation(src/lib.rs:213)。 - 收工 →
shutdown()优雅关闭两个池(src/lib.rs:234)。
一条铁律(架构约束)。 依赖是单向的:核心 crate → 绑定适配,绝不反向;适配层"必须是薄的转换层,不得绕过引擎直接碰运行时内部"(core/docs/architecture.md)。这条约束是本章一切设计的底色。