☰
拆解fenic内部架构:从逻辑计划、查询优化到Rust加速执行的源码级导读
2026/11/4 2:07:58 网站建设 项目流程

【免费下载链接】fenic

Semantic DataFrames for humans and agents

项目地址:https://gitcode.com/gh_mirrors/fe/fenic
点击查看免费下载

fenic 是一个面向人类和 AI Agent 的语义 DataFrame 查询引擎,把extract、classify、semantic join等 LLM 算子直接构建进查询模型。本文带你深入其源码,逐一拆解逻辑计划、查询优化、物理执行与 Rust 加速执行四大核心环节,看清一行 DataFrame 流水线是如何被规划、优化并最终跑起来的。

如果你写过 PySpark 或 Polars,fenic 的架构会让你感到亲切:API 层 → 逻辑计划 → 优化器 → 物理计划 → 执行引擎,这是经典查询引擎的分层套路;不同之处在于,fenic 把 LLM 调用也当成了一等公民,围绕它设计了专门的优化规则与推理基础设施。

fenic 整体架构:四层管线一张图

从 README.md 的定义出发,fenic 的核心链路可以浓缩为四条管线:

层级目录职责
API 层src/fenic/api/DataFrame / Session 接口,select、filter、explain等入口
逻辑层src/fenic/core/_logical_plan/构建可检查的计划树 + 类型校验 + 逻辑优化
物理层src/fenic/_backends/local/转译为 Polars 算子树并执行,管理缓存与指标
加速层rust/Rust 编写的 Polars 插件:文本切块、字幕解析、Markdown 转换等

一个有意思的设计:同一套逻辑计划还能序列化成 Protobuf 字节流(见 src/fenic/core/_serde/README.md),这意味着流水线本身是可传输、可复用的"工件",而不只是内存里的临时对象。

逻辑计划:把查询构建成一棵可检查的计划树

fenic 里的每一个select、filter都不会立即执行,而是往一棵树上加节点。树的节点基类在 plans/base.py,有三个关键机制值得新手注意:

  • 构建即校验:LogicalPlan初始化时就调用_build_schema()推导输出 schema,并拒绝重名列。也就是说,类型错误在计划构建期就报错,而不是执行时才崩;
  • 缓存标记:每个节点携带cache_info,explain输出里会显示(cached=true);
  • 惰性表示:节点通过children()/with_children()递归组织,__str__递归缩进打印——这正是df.explain()能"画"出整棵计划树的原因(见 dataframe.py)。

计划节点按职责分为五类,定义在 plans/ 目录:

节点类型代表类说明
数据源InMemorySource、FileSource、TableSource数据从哪来
转换Projection、Filter、Sort、Limit单输入的行/列变换
连接Join、SemanticJoin含基于语义的语义连接
聚合Aggregategroup_by+agg
输出FileSink、TableSink结果写回哪里

而语义能力则藏在表达式里:expressions/semantic.py 中的SemanticMapExpr、SemanticExtractExpr等继承自SemanticExpr,在 signatures/ 的签名校验器约束下,保证"对非字符串列做semantic.map"这类错误在构建期就被拦截。

tickets = df.select("id", fc.semantic.extract("text", Ticket).alias("t")).unnest("t") tickets.explain() # 打印优化后的逻辑计划树

查询优化:三条为你省 LLM 调用费的优化规则

fenic 的逻辑优化器位于 optimizer/,框架极简:optimizer/base.py 定义抽象规则LogicalPlanOptimizerRule,LogicalPlanOptimizer按顺序依次套用所有规则。真正有含金量的有三条:

规则一:MergeFiltersRule —— 合并相邻过滤

连续两个filter会被合并成一个AND谓词(merge_filters_rule.py),为下一条规则创造改写空间。

规则二:SemanticFilterRewriteRule —— 便宜的判断先跑

这是 fenic 最有特色的规则。semantic_filter_rewrite_rule.py 的文档注释写得很直白:把AND条件拆解重排,让不涉及 LLM 的普通谓词先执行,LLM 谓词按调用数量从少到多排序靠后执行。LLM 是最贵的操作,能先用便宜的过滤条件把行数砍下来,就省下真金白银的 token。

规则三:NotFilterPushdownRule —— 过滤下推

把NOT条件向数据源方向下推,减少无效行向上传播——经典查询优化器思路在语义场景的复用。

从逻辑计划到物理执行:转译器与算子树

逻辑计划不会直接执行。transpiler/plan_converter.py 中的PlanConverter负责把每个逻辑节点转译成对应的物理算子(如Filter→FilterExec、SemanticJoin→SemanticJoinExec),同时把LogicalExpr转成 Polars 表达式。

物理算子基类在 physical_plan/base.py,execute()方法做了四件大事:

  1. 查缓存:有cache_info且已缓存则直接读结果;
  2. 递归执行子节点并汇总每层指标;
  3. 记录算子级指标:输出行数、耗时、LM/RM 用量;
  4. 写缓存:中间结果落盘供后续查询复用。

物理计划也有自己的优化器:merge_duckdb_nodes.py 会把相邻的 DuckDB 算子融合成MergedDuckDBExec,让 DuckDB 自己的 planner 接管子树内部的融合优化——外层引擎只看到边界,内层交给更专业的引擎,这是"混合引擎"架构的精髓。

执行入口 execution.py 中的LocalExecution负责collect/show/count,还能从物理计划构建行级血缘图(build_lineage),支撑lineage()API 的逐行追踪。

Rust 加速执行:把计算密集型环节交给原生代码

fenic 的纯计算环节(文本切块、字幕解析、格式转换)写在 rust/ 里,以Polars 插件形式暴露。Rust 侧模块一览(lib.rs):

Rust 模块能力
chunking/递归文本切块 +count_tokenstoken 计数
transcript/SRT / WebVTT / 通用字幕解析为统一结构化 Schema
markdown_json/Markdown 与 JSON 互转(基于 JSON Schema)
json/jq 查询验证与执行
jinja/Jinja 模板渲染
dtypes/ / regex/类型转换、正则验证

以字幕解析为例:rust/src/transcript/mod.rs 用#[polars_expr]宏把 Rust 函数注册为 Polars 表达式,任何格式的字幕都会被解析成含speaker、start_time、end_time、content的统一结构列。Python 侧则通过 polars_plugins/transcripts.py 的register_plugin_function完成桥接,用户只需写pl.col("raw").transcript.parse(format="srt")。

上图是仓库测试资产中的标准图片,正是 tests/conftest.py 生成 PDF 测试文档时嵌入的素材——PDF 解析算子(semantic.parse_pdf)的测试链路就围绕这类图片资产展开。

Protobuf 序列化:让流水线成为可移植工件

逻辑计划不仅是内存对象,还能序列化为跨语言字节流。protos/logical_plan/v1/ 定义了plans.proto、expressions.proto、datatypes.proto等协议,生成的 Python 代码位于 src/fenic/_gen/protos/。

序列化系统(src/fenic/core/_serde/)采用@singledispatch按类型自动路由:ProtoSerde.serialize(plan)一行即可把整棵计划树压成字节,反序列化时精确还原。详细的扩展指南(如何新增一个可序列化的表达式)写在 core/_serde/README.md,是贡献者必读文档。

推理层(src/fenic/_inference/)则负责 OpenAI / Anthropic / Google / Cohere 等供应商的统一接入,内置批量请求、限流策略(rate_limit_strategy.py)、token 计量与响应缓存——物理算子里那些 LM 指标的源头。

源码导读路线:建议的 5 步阅读顺序

  1. 从 README.md 的 60 秒示例建立整体印象;
  2. 读 plans/base.py 和 expressions/base.py,理解"计划 = 带 schema 的树";
  3. 读 optimizer/ 三条规则,重点体会SemanticFilterRewriteRule如何为 LLM 成本服务;
  4. 读 physical_plan/base.py 的execute(),掌握缓存 + 指标 + 递归执行三件套;
  5. 挑一个 rust/ 模块(推荐chunking)通读,理解 Polars 插件桥接机制。

配套测试同样值得翻:tests/_logical_plan/ 覆盖计划等值与 serde 往返,tests/_backends/local/ 覆盖执行细节。

小结

fenic 的架构可以一句话概括:用经典查询引擎的分层骨架(逻辑计划 → 优化 → 物理执行),承载 LLM 这一新的高成本算子,并用专门的优化规则、缓存、指标与 Rust 加速让它变得可检查、可复用、可负担。理解这套源码后,你再去看任何"AI + DataFrame"类项目,都会多一双识图的眼睛。

【免费下载链接】fenic

Semantic DataFrames for humans and agents

项目地址:https://gitcode.com/gh_mirrors/fe/fenic
点击查看免费下载

相关推荐

上一篇:BentoPDF 自托管:基于 Apache HTTP Server 的完整部署与多语言路由配置指南
下一篇:Selective Brain Damage 复现指南:用 pruning_identified_exemplars 量化模型剪枝对类别与样本的差异化影响

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

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

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

立即咨询