PythonRust经营分析与问数

项目目录

项目简介

WrenAI 为现有 Agent 提供业务语义层与查询能力,当前由 Python SDK、CLI 和 Rust 查询引擎组成。指标、维度、模型关系和业务说明保存在可审阅文件中,查询经模型抽取和 SQL 重写后交给目标数据源执行。

如何写进简历

数衡 · 订阅业务经营指标服务 | 场景化示例

项目描述:基于 WrenAI,供订阅业务运营与财务分析人员复用订阅实收、续费和退款口径,为 Agent 与报表提供语义查询服务。

技术栈:Python、Rust、Apache DataFusion、sqlglot、PyArrow

主要工作:

  • 订阅指标建模与编译:基于 WrenAI 将订阅实收、到期客户续费率和退款的粒度、度量与关联写入 YAML 模型,编译为 MDL,供 Agent 与报表复用同一套指标定义。
  • 语义 SQL 规划与改写:基于 WrenEngine 接入语义查询,按 SQL 引用抽取最小模型,经 Rust/DataFusion 规划、CTE 和目标方言改写生成数据库 SQL,连接指标定义与底层执行。
  • 查询范围与执行验证:将只读检查、strict_mode 模型范围限制与数据源 dry_run 分开接入查询入口,配合 dry_plan 暴露目标 SQL,便于定位关联和聚合偏差。

面试题与回答

  1. Text-to-SQL 如何区分表结构与业务规则知识,并适应表结构变更?

    展开收起

    核心回答

    WrenAI 把物理结构与业务语义通过逻辑模型连接起来:数据库表列说明数据存在哪里,MDL 语义模型说明对外使用什么名称、怎样关联和计算。 当前架构由 Python SDK/CLI 加载模型、组织查询并连接数据源,Rust 语义引擎基于 Apache DataFusion 参与规划,将模型引用展开为目标数据库可执行的 SQL。

    表结构改变后,更新物理映射和受影响的字段、关系或度量定义,重新构建 manifest,再让查询调用方使用新版本。仅改数据库,或者只改一段业务说明,都不会自动完成这条更新链;编译和查询可执行也不等于业务口径正确。

    YAML 中哪些是结构,哪些是业务规则

    项目把模型、关系、视图和聚合定义保存为可审阅的 YAML 与 SQL 文件:

    • 模型与字段: metadata.yml 声明逻辑字段、类型和主键等信息,ref_sql.sql 将逻辑模型映射到物理表或查询。类型是结构事实,对外名称、描述和计算表达式则带有语义选择。
    • 度量与维度: cube 的 measures 定义聚合或计算,dimensions 定义按什么字段分组。它们明确“怎样算、按什么粒度看”,并非从字段类型自动推导。
    • 关系: relationships.yml 保存模型间的连接条件及 join_type,让可复用关系进入规划,而不是每次让模型仅凭同名字段猜连接。

    原仓库的示例很具体:逻辑模型 orders 通过 ref_sql.sql 读取 public.raw_orders,声明每行是一笔订单;order_metrics 以 orders 为 base_object,定义 SUM(amount) 和 COUNT(*),以 customer_id 为维度。orders_customer 则声明订单到客户的 MANY_TO_ONE 关系。这里金额如何聚合、按谁分组、使用哪个连接条件,都是明确维护的定义。见示例度量与维度。

    cube 在这条链中是逻辑聚合定义:查询先转换为普通 SQL,再交给引擎执行,不意味着已经自动生成物化聚合表。类似地,声明 MANY_TO_ONE 也不证明客户键在实际数据中唯一。关系粒度或数据质量不符合声明时,仍可能重复加总。

    定义怎样进入查询规划

    加载器读取模型目录的 metadata.yml,合并同目录的 ref_sql.sql,并加载关系、视图与 cube。wren context build 默认先检查项目结构,再调用 build_json(),将定义转成引擎使用的命名形式并写入 target/mdl.json。它不是把原始 YAML 直接塞进提示词。见模型加载与构建入口。

    查询时,WrenEngine.dry_plan() 解析对逻辑模型的引用,提取相关 manifest,借助语义引擎与改写器展开模型和视图,生成目标方言的 SQL;query() 再通过对应连接器执行。cube 查询也先由 cube_query_to_sql() 生成基于其 base_object 的查询,再走相同引擎链。

    因此,业务定义不只作为自然语言提醒,还会参与实际计算与连接的规划。Rust 侧能够检查部分模型依赖,例如 cube 基对象是否存在、派生度量是否成环、层级是否引用已声明维度;但“退款是否应该扣除”“金额单位是否一致”仍需先由业务定义并用数据核对。

    结构变化后怎样更新

    如果物理字段从 amount 改为 gross_amount,而含义和单位不变,可以修改模型映射,例如用 gross_amount AS amount 继续暴露逻辑字段 amount。这样,下游已确认的度量可以继续引用逻辑字段。若新字段已经变成含税金额或统计范围改变,就不能只加别名维持表面兼容,还要修改度量、说明及相关查询。

    更新过程需要区分三件事:

    1. 修改源定义。 检查物理映射、列类型、连接条件、指标公式和业务说明的影响范围。
    2. 重新构建与索引。 构建新的 target/mdl.json;使用语义检索时,按项目工作流更新相应 memory 索引,使 Agent 读取的模型说明与执行定义一致。
    3. 让调用方加载新定义。 直接创建的 WrenEngine 保存传入的 manifest_str,不会因为 YAML 改动自动热更新。当前 LangChain SDK 的 ProjectMDLSource 则在加载时重读 target/mdl.json,工具调用据此创建引擎,因此外部构建的新文件可在后续调用中被拾取。不能把“改文件”“构建完成”“调用已使用新版本”视为同一时刻。见SDK 的 manifest 读取。

    验证时先看规划后的物理 SQL 是否使用新字段与预期关系,再用固定数据检查总额、分组、空值和多表连接结果。还应保留一个会发生一对多放大的样例,核对聚合粒度。物理映射保持兼容,可以降低结构变化对上层查询的影响;业务口径本身改变时,则需要明确版本和生效范围,不能由兼容映射替业务做决定。

    参考资料

  2. Text-to-SQL 在 Agent 中如何应用?存在哪些风险和限制?

    展开收起

    核心回答

    WrenAI 可以作为现有 Agent 的语义查询层:Agent 根据问题和业务上下文,编写引用 MDL 模型的 SQL;Python SDK 负责策略检查、规划与连接器执行,Rust 引擎负责展开模型、计算字段及关联语义。模型中维护的指标与关系可以复用,Agent 不必每次从物理表重新猜测口径。

    这条链路要区分三种正确性:查询符合执行策略、模型定义展开正确、最终统计符合业务要求。 只读检查和模型对象限制主要约束前一项;语义模型帮助稳定计算定义,但不能证明模型定义本身、Agent 选择的日期条件或聚合粒度一定正确。

    Agent 怎样把问题交给语义层

    当前代码提供 SDK、CLI 及 Agent 工具集成。以 LangChain 集成为例,WrenToolkit 暴露列出模型、规划和查询等工具;启用项目记忆后,还能读取相关结构与业务上下文、召回已确认的查询示例。Agent 据此写引用模型名称和字段的 SQL,交给 wren_query,而不是把自然语言直接交给 WrenEngine 自动完成全部推理。工具集成。

    例如问“本月订阅实收净额”,应先确定订阅付款、退款及其时间归属,再选相应模型和指标。业务说明提供理解依据,MDL 中可执行的表达式承接已建模的计算;仅在说明文件里写一句口径,不等于查询引擎已经强制实现了该口径。

    只读与严格模式分别限制什么

    WrenEngine._plan() 用目标数据库方言解析输入,调用 validate_sql_policy()。其中只读检查始终执行:除检查查询根节点外,还遍历整棵语法树,拒绝写入、建表、修改会话状态、SELECT INTO、写意图锁等节点,因此不会只看开头是不是 SELECT。规划入口、只读检查。

    strict_mode 默认是 False。开启后,额外检查表引用是否对应 MDL 中的模型或视图,并检查已知的数据读取型函数,防止通过文件、外部数据库等入口绕过模型范围;denied_functions 则是另行配置的函数禁用集合。这些策略不能等同于任意函数和方言都安全的沙箱,模型对象检查也不能替代按用户配置的行列权限和数据库授权。默认配置、严格模式与函数检查。

    模型怎样展开成数据库查询

    策略检查后,程序解析查询引用的模型或视图,抽取所需的 MDL;抽取还会保留关联模型、视图依赖及相关规则依赖。CTERewriter 识别实际使用的模型和列,调用经 PyO3 暴露的 Rust 语义引擎展开模型定义,将结果作为 CTE 注入查询,再输出目标数据库方言。模型抽取、CTE 重写、逐模型展开。

    策略错误会直接抛出,不会靠回退绕过。模型抽取遇到普通异常时,仅在未开启严格模式且没有函数禁用配置的情况下,才允许改用完整模型继续规划。改写后还会调用 validate_planned_sql() 检查展开内容中的写操作;但该函数遇到输出重解析失败会直接返回,不能把它描述为所有方言下都严格拒绝不确定输出的完整安全证明。抽取回退与输出检查、输出重解析边界。

    dry_plan() 返回展开后的查询,不访问数据库;dry_run() 再调用连接器做数据库侧检查;query() 才执行并返回 PyArrow 表。例如当前 MySQL 连接器的 dry_run() 使用普通 EXPLAIN,它不会验证业务统计值是否正确,也不等同于真正运行查询测量耗时。三个入口、MySQL 检查方式。

    通过规划后为什么仍可能算错

    模型展开不会自动纠正 Agent 在外层查询中写错的连接条件、过滤条件或聚合。假设一笔订阅付款有两笔退款,直接连接后累加付款金额,可能重复计算实收;即使引用的模型都合法、查询成功执行,订阅实收净额仍然错误。

    业务集成应明确订阅付款与退款粒度、日期归属和空值处理,必要时先按各自粒度聚合,再按确定的键组合。用一对多退款、无退款、跨月退款等样本与参考查询比较,检查最终数值;语义模型变更后也复查这些样本。这是验证设计,不是已经取得的效果数据。

    生产执行还要依据真实用户配置访问范围,限制查询时间、并发及返回结果。只读查询仍可能读取敏感数据或进行昂贵扫描;dry_plan() 成功、模型名称合法、数据库能够解析,都不能替代这些运行约束与业务验收。