wren-core-py 深入指南:用 PyO3 打通 WrenAI 的 Rust 语义引擎与 Python 生态
【免费下载链接】WrenAIGenBI (Generative BI) for AI agents, an open-source, governed text-to-SQL through an open context layer that turns natural-language questions into trusted dashboards, charts, and SQL across 20+ data sources, such as BigQuery, Snowflake, PostgreSQL, ClickHouse, Amazon Redshift, Databricks and more.项目地址: https://gitcode.com/GitHub_Trending/wr/WrenAI
WrenAI 的核心是 Rust 编写的语义引擎 wren-core,而 wren-core-py 正是这座引擎面向 Python 世界的官方桥梁:它通过 PyO3 将 wren-core 的 MDL(Modeling Definition Language)语义层能力封装成 Python 模块,再由 Maturin 构建为可分发的 wheel 包,供 Python 端的 ibis-server 等上层服务直接调用。读完本文,你将掌握 wren-core-py 的模块结构、构建链路、全部核心 Python API(会话上下文、MDL 解析、行级访问控制校验、Manifest 抽取与迁移等)、并发契约与测试方法,并能基于仓库源码定位每一处能力的具体实现。
定位:语义引擎与 Python 服务器之间的那座桥
wren-core-py 位于 core/wren-core-py 目录,其角色可以概括为:PyO3 bindings exposing wren-core to Python,Built with Maturin(见 core/wren-core-py/.claude/CLAUDE.md)。它是连接两端的中间层:
- 上游是 Rust 语义引擎 wren-core,负责 SQL 的语义层转换、MDL 解析、计划生成;
- 下游是 Python 侧的 ibis-server,以及所有需要把自然语言问题转化为可信 SQL 与图表的 AI Agent 服务。
在 core/wren-core-py/README.md 中,作者明确描述了这一定位:Wren Engine 通过语义层 MDL 翻译 SQL 查询,并支持 22+ 种数据源(PostgreSQL、BigQuery、Snowflake 等)。wren-core-py 让这些能力对 Python 开发者完全透明——只要pip install wren-core-py,就能在 Python 进程内直接获得 Rust 引擎的语义分析能力,而无需感知底层 Rust 的存在。
从工程角度看,这种"Rust 核心 + Python 胶水层"的架构,既保留了语义引擎的高性能和类型安全,又复用了 Python 生态庞大的数据工具链(如 PyArrow),是 WrenAI 面向 AI Agent 场景的关键基础设施。
模块地图:src/ 下七个源文件的职责划分
wren-core-py 的源码非常克制,全部集中在src/目录。按照 core/wren-core-py/.claude/CLAUDE.md 的说明,各文件职责如下:
| 文件 | 职责 |
|---|---|
lib.rs | PyO3 模块入口点,注册所有对外暴露的类与函数 |
context.rs | Python 面向的会话上下文,包装 wren-core 的 SessionContext |
manifest.rs | Python 侧 Manifest 类型(由wren-manifest-macro自动生成)及 base64/JSON 转换 |
validation.rs | 暴露给 Python 的查询校验能力 |
extractor.rs | MDL 抽取工具(裁剪未使用的数据集) |
remote_functions.rs | 远程函数注册与描述 |
errors.rs | Rust → Python 异常的类型转换 |
入口 lib.rs:注册了什么
core/wren-core-py/src/lib.rs 展示了模块对外契约的完整清单:
- 4 个类:
PySessionContext(Python 名SessionContext)、PyRemoteFunction、Manifest、PyManifestExtractor; - 6 个函数:
to_json_base64、to_manifest、validate_rlac_rule、is_backward_compatible、migrate_manifest_json、cube_query_to_sql。
这意味着 Python 侧import wren_core后,可以得到与 Rust 引擎一一对应的能力面。模块还通过env_logger::init()初始化日志,便于排查。
构建链路:PyO3 + Maturin + uv 的三层协作
Cargo.toml:依赖与特性
core/wren-core-py/Cargo.toml 定义了关键依赖:
pyo3 = { version = "0.29.0", features = ["extension-module", "abi3-py311"] }:PyO3 稳定 ABI,目标 Python 3.11+;wren-core = { version = "0.3.2", path = "../wren-core/core", package = "wren-semantic-core" }:语义引擎本体,通过 path 依赖指向仓库内 core/wren-core/core;wren-core-base = { path = "../wren-core-base", features = ["python-binding"] }:带 PyO3 支持的共享 Manifest 类型;- 其余包括
datafusion-common、serde_json、tokio、csv、env_logger等,用于 IPC 流、序列化、异步运行时与日志。
特性设计上有两个关键点(对应 core/wren-core-py/.claude/CLAUDE.md 的 Build Notes):
extension-module特性是默认开启且必需的——构建为 Python 扩展模块时必须启用;--no-default-features用于纯 Rust 测试(即just test-rs),此时禁用 PyO3 扩展模块链接,Rust 单元测试可以脱离 Python 运行。
pyproject.toml:Maturin 作为构建后端
core/wren-core-py/pyproject.toml 声明了完整的打包信息:
requires-python = ">=3.11",项目名wren-core-py,版本 0.7.6;- 构建后端为
maturin(build-system.requires = ["maturin>=1.0,<2.0"]); [tool.maturin]指定module-name = "wren_core",locked = true,并从 sdist 中排除tests/**与target/**;- 开发依赖组固定了
maturin==1.9.4、pyarrow==25.0.0、pytest==9.1.1、ruff==0.13.1; - ruff 规则集相当严格(pydocstyle、pyflakes、isort、pylint 等),并显式
extend-exclude = ["*.md"]避免格式化手写文档。
稳定 ABI 的红利
文档特别强调:使用abi3-py311稳定 ABI,同一个 wheel 可以覆盖 Python 3.11 及以上的所有版本,无需为每个小版本分别编译。这是构建产物分发成本大幅下降的关键设计。
开发命令:justfile 即完整工作流
core/wren-core-py/justfile 提供了全套开发命令,与 core/wren-core-py/.claude/CLAUDE.md 中列举的一致:
just install # uv sync --no-install-project(仅同步依赖,不安装项目自身) just develop # uv run --no-sync maturin develop(构建开发版 wheel 供本地测试) just build # 构建发布版 wheel,输出到 target/wheels/;ENV=prod 时加 --release just test-rs # cargo test --no-default-features(仅 Rust 测试) just test-py # uv run --no-sync pytest(仅 Python 测试) just test # 先 Rust 后 Python,全部测试 just format # cargo fmt + ruff format + ruff check --fix + taplo fmt其中install特意使用--no-install-project,因为项目本体是 Rust 扩展模块,必须由maturin develop或maturin build编译。环境要求(见 core/wren-core-py/README.md 的 Developer Guide):Rust 工具链、Python 3.11+、uv、casey/just。
just develop是 Python 测试前的必需步骤——必须先编译出可导入的wren_core扩展模块,pytest才能运行。
核心 API:SessionContext 的完整能力面
SessionContext是 Python 侧使用频率最高的类,实现在 core/wren-core-py/src/context.rs(Rust 结构体名PySessionContext,通过#[pyclass(name = "SessionContext")]暴露为 Python 名)。
构造与初始化
构造函数签名(源码 context.rs)为:
SessionContext(mdl_base64=None, remote_functions_path=None, properties=None, data_source=None)mdl_base64:base64 编码的 MDL JSON。提供时直接基于该 Manifest 初始化语义层;不提供时创建空 MDL;remote_functions_path:CSV 文件路径,用于注册远程函数(每行一个函数描述,经csv::Reader反序列化为PyRemoteFunction);properties:会话属性,frozenset 形式的(key, value)二元组集合;data_source:数据源字符串(如bigquery),用于按数据源注册对应函数集合。源码中有一个细节:DataSource::BigQuery分支会跳过远程函数注册(见register_function_by_data_source)。
构造时,引擎会以Mode::Unparse和Mode::LocalRuntime两种模式分别把 MDL 应用到上下文上,生成unparser_ctx(用于 SQL 转换)与exec_ctx(用于本地执行)两个内部上下文。
SQL 语义转换:transform_sql
from wren_core import SessionContext base64_mdl_json = "<your-base64-encoded-mdl-json>" ctx = SessionContext(base64_mdl_json) planned_sql = ctx.transform_sql("SELECT * FROM my_model")transform_sql将 Wren SQL 经过语义层转换为目标数据源的 Planned SQL。实现上,该方法把 SQL 字符串拷贝为自有所有权后释放 GIL,再在进程级 Tokio runtime 上调用mdl::transform_sql_with_ctx完成转换(context.rs),保证阻塞期间不卡住其他 Python 线程。
limit 下推:pushdown_limit
pushdown_limit(sql, limit=None)用于把 LIMIT 下推到 SQL 中(context.rs):
limit=None时原样返回 SQL;- 已存在 LIMIT 且大于下推值时,替换为下推值;小于则保持不变;
- 不存在 LIMIT 时,直接追加
LIMIT <limit>; - 一次只允许一条语句,否则报错。
本地执行:query、dry_run、list_tables
ipc_bytes = ctx.query("SELECT * FROM my_catalog.my_schema.customer")query使用 DataFusion LocalRuntime 执行 SQL,返回Arrow IPC stream 字节(Vec<u8>),Python 侧用 PyArrow 即可解析:
import io from pyarrow import ipc table = ipc.open_stream(io.BytesIO(bytes(ipc_bytes))).read_all()值得注意的实现细节(见 test_query_ipc_schema.py):IPC 流写入的是执行时 schema而非计划声明的 MDL 类型。当 MDL 声明integer/varchar,而物理列实际是 int64/Utf8View 时,流依然可解码且数值正确——空结果集也保持一致 schema。
dry_run(sql)通过EXPLAIN {sql}校验计划可行性并返回格式化执行计划文本;list_tables()枚举执行上下文中的所有表名(best-effort 语义,遍历中途的注册可能不出现,但结果始终良构)。
本地文件注册:两阶段初始化
MDL 模型可以由本地 Parquet/CSV 文件回填,采用两阶段初始化(详见 core/wren-core-py/README.md 与 tests/test_physical_tables.py):
ctx = SessionContext() ctx.register_parquet("customer", "/data/customer.parquet") ctx.register_csv("orders", "/data/orders.csv") ctx.load_mdl(base64_mdl_json) # MDL 模型现在解析到这些文件可见性契约要点:
- 文件表落在默认 catalog(
datafusion.public),其内部状态与派生上下文实时共享,因此即使在上下文创建之后再注册,query/dry_run/list_tables也能看到; - MDL 模型要解析到已注册文件,其
tableReference必须为{"catalog": "datafusion", "schema": "public", "table": "<注册名>"},且声明的列必须存在于文件中; - 例外是全新的顶层 catalog——它必须在 MDL 构造、
load_mdl或 transform 之前存在,因为这几步都会对顶层 catalog 列表做快照(对应 wren-core 中clone_catalog_list的语义)。
load_mdl实现上从base_ctx抽取物理表 provider,调用AnalyzedWrenMDL::analyze_with_tables后重建unparser_ctx与exec_ctx(context.rs)。
并发契约
core/wren-core-py/README.md 的 Concurrency 一节给出了明确的并发语义:
transform_sql、query以及注册类 API 支持并发执行——每个transform_sql作用于私有顶层 catalog 快照,分析器状态按调用隔离;dry_run对仅做EXPLAIN的语句是并发安全的;ANALYZE前缀输入会变成EXPLAIN ANALYZE并真正执行,不在并发契约内;register_parquet/register_csv在不同表名下安全,同名并发注册不受支持;load_mdl与其他调用不能重叠:它接收&mut self,PyO3 的独占借用会对同一上下文上的重叠调用抛出RuntimeError。
相关测试见 test_modeling_core.py 中的test_concurrent_calls_from_threads与test_fork_child_gets_working_runtime——后者验证了进程级 runtime 的 fork 安全性:子进程继承句柄但不继承 worker 线程,PID 不匹配时会惰性重建 runtime。
Manifest 工具链:base64 编解码、迁移与兼容性检查
core/wren-core-py/src/manifest.rs 提供了一组与 MDL 打交道的基础函数:
to_json_base64(manifest) -> str:将Manifest序列化为 JSON 再 base64 编码,是 Python 侧构造 MDL 的出口;to_manifest(base64_str) -> Manifest:反向解码并反序列化,是SessionContext、ManifestExtractor共用的解析入口;migrate_manifest_json(manifest_json, target_version) -> str:将 Manifest JSON 迁移到指定 layout 版本,底层委托wren-core-base的migration::migrate_manifest;is_backward_compatible(base64_str) -> bool:检查 MDL 是否可被 v2 wren core 使用——只要存在行级访问控制(RLAC)或列级访问控制(CLAC)规则即返回False,此类 MDL 仅能由 v3 核心使用。
Manifest类型本身由wren-manifest-macro自动生成(见 core/wren-core-base/manifest-macro),manifest.rs中通过pub use wren_core_base::mdl::*直接再导出。
校验与安全:行级访问控制的规则验证
validate_rlac_rule(rule, model)(validation.rs)是安全相关的关键能力:它将 Python 传入的RowLevelAccessControl规则与Model交给 wren-core 的logical_plan::analyze::access_control::validate_rlac_rule校验,规则不合法时抛出带具体信息的异常。结合 test_modeling_core.py 中的test_rlac/test_validate_rlac_rule测试,可以看到它覆盖了规则字段合法性、表达式可解析性等场景——这是 MDL 应用到引擎前的一道安全闸门。
Manifest 抽取:为问答裁剪语义模型
extractor.rs 中的ManifestExtractor解决一个实际痛点:Agent 在回答具体问题时只需要语义模型的一小部分,而非全部。
from wren_core import ManifestExtractor extractor = ManifestExtractor(base64_mdl_json) used_tables = extractor.resolve_used_table_names("SELECT * FROM my_model") smaller_manifest = extractor.extract_by(used_tables) # 仅保留被使用的数据集resolve_used_table_names(sql):解析 SQL 中引用的表名列表(解析时关闭标识符归一化以保证大小写敏感);extract_by(used_datasets):从原 Manifest 中裁掉未被使用的数据集,保留与使用数据集相关的模型/视图及其 relationship,输出精简后的Manifest。
相关测试见 test_modeling_core.py 的test_resolve_used_table_names与test_extract_by。
远程函数:扩展引擎能力
PyRemoteFunction(remote_functions.rs)描述一个远程函数的六元组:function_type(scalar/aggregate/window)、name、return_type、param_names、param_types(均为逗号分隔字符串)、description,并可通过to_dict()转为 Python dict。注册时,函数名会统一小写以匹配 DataFusion 的解析归一化规则,且会与已注册函数做名称去重(见 context.rs 的register_remote_function)。get_available_functions()/get_available_function(name)通过查询information_schema.routines返回当前上下文中可用的函数清单。
错误处理:从 Rust 错误到 Python 异常
errors.rs 定义了统一的CoreError,并为它实现了从base64::DecodeError、serde_json::Error、DataFusionError、csv::Error、ParsedDataSourceError等十余种底层错误的From转换;反过来CoreError → PyErr统一映射为PyException。特别地,DataFusionError的转换会向下穿透解包WrenError,让 Python 侧拿到的错误信息尽量贴近语义引擎的真实报错。
测试与发布
测试矩阵
just test-rs运行 Rust 侧cargo test --no-default-features(覆盖 manifest 编解码往返等单元测试),just test-py运行 tests/ 下的 Python 测试,覆盖:
- 会话上下文与函数注册(
test_session_context、test_get_available_functions); - limit 下推、大小写敏感性、并发与 fork 场景(
test_modeling_core.py); - 本地文件注册与两阶段初始化(
test_physical_tables.py); - IPC schema 正确性,包括 MDL 类型与物理类型不一致、空结果集场景(
test_query_ipc_schema.py); - Cube 查询的 JSON DSL(test_cube.py 中的
test_basic_cube_query、test_time_dimension_with_date_range等)。
发布脚本
scripts/publish.sh 支持发布到 PyPI/TestPyPI:
./scripts/publish.sh --build # 仅构建 wheel ./scripts/publish.sh --test # 构建并发布到 TestPyPI ./scripts/publish.sh # 构建并发布到 PyPI发布物(wheel)覆盖 Linux x86_64、macOS x86_64/ARM64、Windows x86_64 等平台(见 core/wren-core-py/README.md);Linux ARM64 尚无预编译 wheel,需在目标平台用 Rust 工具链从源码构建。
结语
wren-core-py 是一个"小而精"的桥接模块:源码仅 7 个 Rust 文件,却完整承载了 WrenAI 语义引擎对 Python 生态的全部能力出口——从 MDL 的编解码、迁移、兼容性检查,到会话上下文的 SQL 转换、本地执行与本地文件回填,再到行级访问控制校验和 Manifest 裁剪。理解了它的模块划分、构建链路的两个特性开关(extension-module与abi3-py311)以及just命令的完整工作流,你就掌握了在 Python 侧驱动 Rust 语义引擎的标准姿势,也就能基于 core/wren-core-py/.claude/CLAUDE.md 这份开发者指南快速上手或扩展这一层能力。
【免费下载链接】WrenAIGenBI (Generative BI) for AI agents, an open-source, governed text-to-SQL through an open context layer that turns natural-language questions into trusted dashboards, charts, and SQL across 20+ data sources, such as BigQuery, Snowflake, PostgreSQL, ClickHouse, Amazon Redshift, Databricks and more.项目地址: https://gitcode.com/GitHub_Trending/wr/WrenAI
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考