wren-core-py 深入指南:用 PyO3 打通 WrenAI 的 Rust 语义引擎与 Python 生态
2026/9/13 23:31:07 网站建设 项目流程

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.rsPyO3 模块入口点,注册所有对外暴露的类与函数
context.rsPython 面向的会话上下文,包装 wren-core 的 SessionContext
manifest.rsPython 侧 Manifest 类型(由wren-manifest-macro自动生成)及 base64/JSON 转换
validation.rs暴露给 Python 的查询校验能力
extractor.rsMDL 抽取工具(裁剪未使用的数据集)
remote_functions.rs远程函数注册与描述
errors.rsRust → Python 异常的类型转换

入口 lib.rs:注册了什么

core/wren-core-py/src/lib.rs 展示了模块对外契约的完整清单:

  • 4 个类:PySessionContext(Python 名SessionContext)、PyRemoteFunctionManifestPyManifestExtractor
  • 6 个函数:to_json_base64to_manifestvalidate_rlac_ruleis_backward_compatiblemigrate_manifest_jsoncube_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-commonserde_jsontokiocsvenv_logger等,用于 IPC 流、序列化、异步运行时与日志。

特性设计上有两个关键点(对应 core/wren-core-py/.claude/CLAUDE.md 的 Build Notes):

  1. extension-module特性是默认开启且必需的——构建为 Python 扩展模块时必须启用;
  2. --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;
  • 构建后端为maturinbuild-system.requires = ["maturin>=1.0,<2.0"]);
  • [tool.maturin]指定module-name = "wren_core"locked = true,并从 sdist 中排除tests/**target/**
  • 开发依赖组固定了maturin==1.9.4pyarrow==25.0.0pytest==9.1.1ruff==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 developmaturin 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::UnparseMode::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_ctxexec_ctx(context.rs)。

并发契约

core/wren-core-py/README.md 的 Concurrency 一节给出了明确的并发语义:

  • transform_sqlquery以及注册类 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_threadstest_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:反向解码并反序列化,是SessionContextManifestExtractor共用的解析入口;
  • migrate_manifest_json(manifest_json, target_version) -> str:将 Manifest JSON 迁移到指定 layout 版本,底层委托wren-core-basemigration::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_namestest_extract_by

远程函数:扩展引擎能力

PyRemoteFunction(remote_functions.rs)描述一个远程函数的六元组:function_type(scalar/aggregate/window)、namereturn_typeparam_namesparam_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::DecodeErrorserde_json::ErrorDataFusionErrorcsv::ErrorParsedDataSourceError等十余种底层错误的From转换;反过来CoreError → PyErr统一映射为PyException。特别地,DataFusionError的转换会向下穿透解包WrenError,让 Python 侧拿到的错误信息尽量贴近语义引擎的真实报错。

测试与发布

测试矩阵

just test-rs运行 Rust 侧cargo test --no-default-features(覆盖 manifest 编解码往返等单元测试),just test-py运行 tests/ 下的 Python 测试,覆盖:

  • 会话上下文与函数注册(test_session_contexttest_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_querytest_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-moduleabi3-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),仅供参考

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

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

立即咨询