【免费下载链接】fenic
Semantic DataFrames for humans and agents
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 | 含基于语义的语义连接 |
| 聚合 | Aggregate | group_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()方法做了四件大事:
- 查缓存:有
cache_info且已缓存则直接读结果; - 递归执行子节点并汇总每层指标;
- 记录算子级指标:输出行数、耗时、LM/RM 用量;
- 写缓存:中间结果落盘供后续查询复用。
物理计划也有自己的优化器: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 步阅读顺序
- 从 README.md 的 60 秒示例建立整体印象;
- 读 plans/base.py 和 expressions/base.py,理解"计划 = 带 schema 的树";
- 读 optimizer/ 三条规则,重点体会
SemanticFilterRewriteRule如何为 LLM 成本服务; - 读 physical_plan/base.py 的
execute(),掌握缓存 + 指标 + 递归执行三件套; - 挑一个 rust/ 模块(推荐
chunking)通读,理解 Polars 插件桥接机制。
配套测试同样值得翻:tests/_logical_plan/ 覆盖计划等值与 serde 往返,tests/_backends/local/ 覆盖执行细节。
小结
fenic 的架构可以一句话概括:用经典查询引擎的分层骨架(逻辑计划 → 优化 → 物理执行),承载 LLM 这一新的高成本算子,并用专门的优化规则、缓存、指标与 Rust 加速让它变得可检查、可复用、可负担。理解这套源码后,你再去看任何"AI + DataFrame"类项目,都会多一双识图的眼睛。
【免费下载链接】fenic
Semantic DataFrames for humans and agents
相关推荐
Databend查询优化器:从逻辑计划到物理执行的全流程解析
Databend查询优化器:从逻辑计划到物理执行的全流程解析 在数据仓库系统中,查询优化器(Optimizer)扮演着"智能大脑"的角色,负责将用户输入的SQL
数据库数据分析向量数据库全文检索云原生AI 应用OctoSQL查询优化器原理:从逻辑计划到物理执行的转换过程
OctoSQL查询优化器原理:从逻辑计划到物理执行的转换过程 OctoSQL是一个强大的SQL查询引擎,它允许您对存储在多个SQL数据库、NoSQL数据库和各种
数据分析数据集成CLI流处理数据工程如何永久免费使用IDM下载器:激活脚本完整指南
如何永久免费使用IDM下载器:激活脚本完整指南 你是否正在寻找一种安全、可靠的方法来永久免费使用Internet Download Manager(IDM)?I
CLI开发工具
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考