☰
为什么 Agent 需要一层“上下文“才能碰你的数据库?
2026/10/11 8:52:41 网站建设 项目流程

WrenAI 核心概念与快速上手:从 raw LLM 的"自信犯错"到受治理的 GenBI


1. 从一个失败案例开始

假设你有一个电商数据库,里面有一张orders表。你让 GPT-4 直接查询:

“统计上季度 top 10 客户的消费金额”

在没有上下文的情况下,模型会"自信地"写出类似这样的 SQL:

SELECTcustomer_id,SUM(amount)AStotal_spentFROMordersWHEREcreated_at>=DATE_TRUNC('quarter',CURRENT_DATE-INTERVAL'3 months')GROUPBYcustomer_idORDERBYtotal_spentDESCLIMIT10;

看起来没问题?但当你把这条 SQL 交给 DBA 审核,问题一个接一个浮出水面:

  1. amount字段的币种是什么?表里没有标注,但业务规则说amount是 USD——模型不知道,如果库里有 EUR 订单,结果就错了。
  2. created_at还是paid_at?模型猜了created_at,但财务口径通常以paid_at为准。
  3. orders表包含取消订单吗?模型没加status != 'cancelled'的过滤,因为 schema 没告诉它status = 4代表已取消。
  4. customer_id有 guest checkout 为 NULL 的情况吗?模型把 NULL 也 group 进去了。
  5. 用的是customers表还是customers_v3?仓库里有三个版本的 customers 表,模型随便挑了一个。

这不是"模型不够聪明"的问题。AI agents over business data are bottlenecked on context, not on intelligence.

问题的根源在于:schema 只描述了"数据存在哪里",没有描述"数据意味着什么"。而企业的业务知识——币种、状态码含义、 canonical 表选择、默认过滤条件——散落在文档、Slack 线程、分析师的笔记本里。

WrenAI 就是为了解决这个问题而设计的。


2. WrenAI 是什么?从源码视角解剖

WrenAI 是一个开源的GenBI (Generative BI)引擎。但理解它的最好方式,是先看它不是什么:

  • 不是又一个 ChatBI 聊天界面——它不直接面向终端用户做问答 UI,而是作为基础设施层,让你已有的 Agent(Claude Code、Cursor、LangChain 应用)能够安全地操作数据。
  • 不是替代你的数据仓库——它不存储数据,而是坐在仓库之上,提供一个受治理的语义层。
  • 不是黑盒——核心引擎(MDL 语义层、 governed text-to-SQL、MCP server、CLI、22+ 连接器)全部 Apache-2.0 开源。

从源码架构看,WrenAI 是一个四层栈:

┌─────────────────────────────────────────┐ │ Agent 工作流层 │ ← Skills (Markdown 工作流指南) │ 告诉 Agent: 先查记忆 → 写 SQL → 校验 → 执行 │ ├─────────────────────────────────────────┤ │ 项目上下文层 │ ← MDL YAML + knowledge/ 规则与示例 │ 定义"数据意味着什么" │ ├─────────────────────────────────────────┤ │ 规划引擎层 │ ← SQL planner (sqlglot + CTE rewriter) │ 把"面向模型的 SQL"翻译成"面向数据库的 SQL" │ + wren-core (Rust 语义引擎) ├─────────────────────────────────────────┤ │ 执行层 │ ← Connectors (22+ 数据源) │ 在真实数据库上执行 │ └─────────────────────────────────────────┘

我们后面会逐层深入,但这一篇的目标是让你先跑起来,理解"上下文层"解决了什么问题。


3. 安装与快速上手

3.1 安装 CLI

pipinstall"wrenai[postgres,memory]"

postgres是数据源连接器,memory升级记忆系统为语义检索(基于 LanceDB + sentence-transformers)。如果你用其他数据库,替换为mysql、bigquery、snowflake、duckdb等。

安装后验证:

wren--help

3.2 使用内置的 jaffle_shop 示例

WrenAI 自带了一个经典的 dbt jaffle_shop 示例项目,让我们用它走通第一个查询。

# 1. 进入示例目录(如果你克隆了 WrenAI 仓库)cdexamples/v5-jaffle# 2. 查看项目结构tree-L2

你会看到:

v5-jaffle/ ├── wren_project.yml # 项目配置文件 ├── models/ # MDL 模型定义 │ ├── customers/ │ │ └── metadata.yml │ └── orders/ │ └── metadata.yml ├── relationships.yml # 模型间关系 ├── views/ │ └── customer_orders/ │ └── metadata.yml ├── cubes/ │ └── order_metrics/ │ └── metadata.yml └── knowledge/ # 业务知识与记忆 ├── knowledge.yml ├── rules/ │ └── business-rules.md └── sql/ └── total-revenue.md

3.3 项目配置文件解读

wren_project.yml是项目的入口:

schema_version:5name:jaffle_shopcatalog:wrenschema:publicdata_source:postgres

这里指定了:

  • schema_version: 5—— 当前项目使用的 MDL schema 版本
  • data_source: postgres—— 目标数据库类型
  • catalog/schema—— 数据库的 catalog 和 schema 名称

这个文件是 WrenAI项目发现机制的起点。在源码中,discover_project_path()函数会按以下优先级查找项目:

# core/wren/src/wren/context.py:380-410defdiscover_project_path(explicit:str|None=None)->Path:"""Return the project directory path. Priority: 1. explicit arg (--project / --path flag) 2. WREN_PROJECT_HOME env var 3. Walk up from cwd looking for wren_project.yml 4. default_project in ~/.wren/config.yml 5. Raise SystemExit with actionable message """

这意味着:只要你站在项目目录(或子目录)里执行wren命令,CLI 会自动向上遍历找到wren_project.yml——类似 Git 的.git发现机制。

3.4 编译项目

在使用之前,需要把 YAML 源文件编译成引擎可读的target/mdl.json:

wren context build

这个命令的背后发生了什么?在context.py中,WrenAI 会:

  1. 读取wren_project.yml
  2. 遍历models/、views/、cubes/下的metadata.yml
  3. 读取relationships.yml
  4. 将所有定义合并、校验、转换为 camelCase 的 MDL JSON 格式
  5. 输出到target/mdl.json
# core/wren/src/wren/context.py:155-226 (简化)defconvert_mdl_to_project(mdl_json:dict)->list[ProjectFile]:# 将 MDL JSON 反向转换为项目文件(用于 import)# ...formodelinmdl_json.get("models",[]):model_snake=_convert_keys_to_snake(model)name=model_snake["name"]files.append(ProjectFile(relative_path=f"models/{name}/metadata.yml",content=yaml.dump(model_snake,...)))

注意源码中反复出现的_convert_keys()和_convert_keys_to_snake()——这揭示了 MDL 的一个设计细节:YAML 源文件使用 snake_case(对人类友好),而引擎内部使用 camelCase(与 JSON schema 对齐)。两者之间的自动转换由 WrenAI 处理,用户只需要写is_primary_key,引擎看到的是isPrimaryKey。

编译成功后验证:

wren context show

你应该能看到customers、orders等模型的摘要信息。

3.5 连接数据库并执行第一个查询

jaffle_shop 示例使用 DuckDB 内存数据库(无需外部数据库),但如果你要连接真实的 Postgres:

# 创建连接配置文件wren profile create--namelocal-postgres\--data-source postgres\--hostlocalhost\--port5432\--databasejaffle_shop\--userpostgres

连接配置存储在~/.wren/profiles.yml,不会进入项目仓库——这是安全设计,凭证与项目分离。

现在执行查询:

wren--sql"SELECT * FROM customers LIMIT 5"

注意这里的FROM customers不是数据库里的 raw table,而是MDL 中定义的模型。WrenAI 会把这条 SQL:

  1. 用sqlglot解析为目标方言(如 Postgres)
  2. 识别引用的模型(customers)
  3. 通过wren-coreRust 引擎展开模型语义(table_reference→ 实际表名)
  4. 注入 CTE(Common Table Expression)
  5. 转译为最终可在数据库执行的 SQL
  6. 通过 connector 执行,返回 PyArrow 表格

我们来看引擎层的核心代码:

# core/wren/src/wren/engine.py:97-139classWrenEngine:defdry_plan(self,sql:str,properties:dict|None=None)->str:"""Plan SQL through MDL and return the expanded SQL in the target dialect."""# ...defquery(self,sql:str,limit:int|None=None,...)->pa.Table:"""Transpile and execute SQL, return results as an Arrow table."""dialect_sql=self.dry_plan(sql,properties)connector=self._get_connector()returnconnector.query(dialect_sql,limit)

dry_plan()只规划不执行,query()先规划再执行。这是 WrenAI 正确性机制的基础——Agent 可以先看展开的 SQL 对不对,再决定要不要执行。

你可以亲自验证:

# 只看规划后的 SQL,不执行wren dry-plan--sql"SELECT * FROM customers LIMIT 5"

输出大概长这样(取决于你的数据源):

WITH"customers"AS(SELECT"id","name"FROM"public"."customers")SELECT*FROM"customers"LIMIT5

模型被展开为了 CTE,字段被限定,表名被解析为完整的catalog.schema.table路径。这就是语义层在工作。


4. 五层上下文模型:WrenAI 的上下文哲学

WrenAI 的文档提出了一个"五层上下文"框架,这是理解整个项目设计意图的关键:

层级回答的问题WrenAI 的承载方式状态
Structural什么数据存在?MDLmodels/的table_reference、columns已交付
Semantic数据意味着什么?MDL 的description、calculated fields、views已交付
Business这家公司怎么定义指标?knowledge/rules/的业务规则、instructions已交付
Operational数据应该怎么安全使用?relationships.yml的 approved joins、policy filters活跃开发
Behavioral什么做法以前有效?knowledge/sql/的 NL→SQL 对、memory index活跃开发

4.1 Structural:模型不是表

看models/customers/metadata.yml:

name:customersproperties:description:One row per customer. name may be NULL for guest checkouts.table_reference:catalog:""schema:publictable:customerscolumns:-name:idtype:INTEGERis_primary_key:true-name:nametype:VARCHARnot_null:falseprimary_key:id

这里有几个关键设计:

  • name: customers是模型名(面向业务),table_reference.table: customers是物理表名(面向数据库)。两者可以不同——这是解耦。
  • description直接写在模型定义里,Agent 查询时会通过 memory fetch 检索到这段描述。
  • is_primary_key、not_null是结构元数据,帮助 Agent 理解表的关系约束。

4.2 Semantic:让 Agent 读懂业务

models/orders/metadata.yml:

name:ordersproperties:description:One row per customer order,with the order amount in USD.columns:-name:idtype:INTEGERis_primary_key:true-name:customer_idtype:INTEGER-name:amounttype:DOUBLE

关键信息在description里:“order amount in USD”。如果没有这行描述,Agent 看到amount字段时完全不知道币种。有了它,memory fetch 会把这段描述和amount字段一起送入 Agent 的 prompt。

4.3 Business:规则不进数据库

knowledge/rules/business-rules.md:

# Business rules - An order's `amount` is recorded in USD. - `customers.name` may be NULL for guest checkouts.

这些规则不适合放在数据库 schema 里(它们不是约束,而是业务解释),但又必须让 Agent 知道。WrenAI 把它们放在knowledge/rules/*.md里,作为独立的业务知识层。wren memory index会把这些规则编入索引,wren memory fetch会根据问题相关性检索它们。

4.4 Operational:受控的 Join 路径

relationships.yml:

relationships:-name:orders_customermodels:-orders-customersjoin_type:MANY_TO_ONEcondition:orders.customer_id = customers.id

这是 WrenAI 相比 raw text-to-SQL 的核心优势之一:join 不是让 Agent 猜的,而是预定义在语义层里的。Agent 知道orders和customers之间有一个MANY_TO_ONE的关系,join 条件是orders.customer_id = customers.id——不需要每次重新推理。

4.5 Behavioral:经验会累积

knowledge/sql/total-revenue.md:

---nl:What is the total revenue across all orders?sql:|SELECT SUM(amount) AS total_revenue FROM orderssource:usertags:-revenue---

这是 WrenAI 的记忆系统的基础单元:一个确认的 NL→SQL 对。当用户问"总收入是多少",Agent 可以wren memory recall找到这个历史示例,直接复用 SQL 模式。下篇文章我们会深入记忆系统的源码实现。


5. 与"Agent 记忆系统"专栏的衔接

如果你读过我的《Agent 记忆系统》专栏(以 SeptMuse 为解剖样本),你可能会发现一些有趣的对应关系:

SeptMuse 概念WrenAI 对应说明
工作记忆(Block XML)MDL + memory fetch 的上下文切片不是把整个 schema dump 进 prompt,而是只取相关模型和规则
语义记忆(S-P-O 三元组)MDL 模型定义 +knowledge/rules/业务知识的结构化存储
情节记忆(时间轴)knowledge/sql/*.md的查询历史按时间累积的 NL→SQL 经验
程序记忆(规则系统)Skills (Markdown 工作流)“先查记忆 → 再写 SQL → 再校验 → 再执行” 的固定流程

WrenAI 可以看作是在企业数据问答这一特定领域,对通用 Agent 记忆系统的工程化落地。它做出了一些领域特化的取舍:

  • 存储介质:SeptMuse 用 SQLite + 向量数据库做通用存储;WrenAI 用markdown 文件做 durable source of truth,LanceDB 做 derived index——因为业务知识需要被人类 review 和版本控制。
  • 检索粒度:SeptMuse 做通用的语义检索;WrenAI 分化为schema_items(检索模型/字段)和query_history(检索历史查询)两个 collection——因为 BI 场景下,"找相关表"和"找类似问题"是两种完全不同的检索需求。
  • 遗忘策略:SeptMuse 实现了艾宾浩斯遗忘曲线;WrenAI 不做主动遗忘——业务知识只增不减,错误定义通过版本控制回滚。

6. 本章小结

这一篇我们完成了三件事:

  1. 建立了问题意识:raw LLM 在企业数据上"自信犯错",根源是缺少上下文——不是模型不够聪明,而是 schema 不等于业务知识。

  2. 理解了 WrenAI 的核心定位:它不是 ChatBI UI,而是 Agent 的开放上下文层——用 MDL 定义语义、用knowledge/存储规则与经验、用 Rust 引擎做 SQL 规划、用 connector 对接 22+ 数据源。

  3. 跑通了第一个查询:从wren context build到wren --sql,亲眼看到"面向模型的 SQL"如何被展开为"面向数据库的 SQL"。

下一篇,我们将深入 MDL 的设计——如何把散落在文档、Slack、分析师大脑里的业务知识,变成 Agent 能读懂、Git 能版本控制、引擎能执行的语义契约。


参考命令速查

# 安装pipinstall"wrenai[postgres,memory]"# 项目操作wren context build# 编译 MDL YAML → target/mdl.jsonwren context show# 查看可用模型wren context validate# 校验项目结构# 查询操作wren--sql"SELECT ..."# 执行查询wren dry-plan--sql"..."# 只规划,不执行wren dry-run--sql"..."# 校验语法和对象存在性# 记忆操作wren memory index# 构建/重建记忆索引wren memory fetch-q"..."# 检索相关 schema 上下文wren memory recall-q"..."# 召回历史查询wren memory store--nl"..."--sql"..."# 存储确认查询# 连接配置wren profile create--name... --data-source postgres... wren profile list

源码索引

文件职责
core/wren/src/wren/cli.pyCLI 入口,命令分发,MDL/连接自动发现
core/wren/src/wren/context.py项目上下文管理,YAML ↔ JSON 转换,discover_project_path()
core/wren/src/wren/engine.pyWrenEngine类,dry_plan()/query()/dry_run()
core/wren/src/wren/mdl/cte_rewriter.pySQL 规划核心:CTE 注入、模型展开
examples/v5-jaffle/官方示例项目,包含完整 MDL 定义和 knowledge/ 目录

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

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

立即咨询