每天一个开源项目#152 Auto Ontology:让业务口径进入SQL生成链

日期:2026-11-05 GitHub Trending 日榜第 1 位;榜单采集时间:16:00:25(北京时间)。

"本月收入是多少?"看起来只需要一个 SUM。接到真实业务数据库,问题很快就变了:订单金额还是实收金额,取消订单算不算,时间按创建还是支付,金额存的是元还是分?SQL 能运行,与回答了正确的问题,是两件事。

NVIDIA 的 Auto Ontology 把一部分工作放到提问之前。它读取数据目录,生成映射到物理列的业务术语、关系和 SQL 属性,让人修订这些定义,再让查询 Agent 从这份语义上下文中选材料、写 SQL、检查并执行。值得看的是这些材料怎样进入执行链,以及检查失败以后系统究竟怎么做。

📋 项目概览

项目 内容
主仓库 NVIDIA/auto-ontology
定位 面向结构化数据问答的语义目录、业务本体与 NL-to-SQL 系统
核心实现 Python 后端、LangGraph 查询编排、Next.js 前端、Postgres 与 pgvector
后端清单版本 1.1.0,Python 要求为 3.12 至 3.13
对外入口 Web、经过认证的 REST API、单独发行的 MCP 服务
部署方式 Docker Compose 或 Kubernetes Helm
许可证 Apache-2.0;第三方组件另见仓库的许可说明
快照热度 5,898 Stars、461 Forks、46 个 open issues

这里的本体可以先理解成"有物理落点的业务词汇表"。Customer 这样的 Term 有描述、同义词和对应列;ColumnAttribute 把术语连接到列;SqlAttribute 保存业务概念下可复用的 SQL 表达式。表之间的关系给查询提供连接路径。

它不是要求企业先建一个独立图数据库。目录、本体和向量检索所需的数据保存在 Postgres 体系里,业务数据仍由各自的数据源连接器访问。

🔥 为什么值得关注

直接把建表语句塞进提示词,能让模型知道某张表有 amount 列,却不能稳定解释这个列在业务上代表什么。复杂查询还会遇到更隐蔽的问题:两张表都含 customer_id,连接到哪一张才符合统计粒度;中间桥接表没进入上下文,生成器可能写出能跑但重复计数的 JOIN。

Auto Ontology 试图把这些信息变成共享资产。术语说明、列映射、连接关系和审核过的表达式可以反复进入不同问题的上下文,而不必每次让模型重新猜一遍。

这条路线的成本也很明确:接入数据库只是开始,还要处理术语质量、采样权限、模型调用、索引和业务人员的修订。数据库里没有的业务定义,换一个模型也不会凭空变得可信。项目更适合愿意维护语义层的团队,而不是只想加一个聊天输入框的团队。

另一个值得读源码的原因是,它并没有把"检查"做成一个神秘黑盒。静态引用解析、模型意图判断、可选数据库探测、执行错误修复、空结果处理,各自有自己的失败路径。理解这些边界,比记住一个准确率数字更有用。

🏗️ 核心特性

从数据目录生成语义初稿

摄取入口 ingest_catalog 调用目录提取和写入逻辑,返回带有存储标识的表、列 DataFrame。语义编译再按表提取术语,组合声明外键、推测关系和列画像,保存业务节点及其向量表示。

"自动"主要解决初稿生产,不等于自动完成业务认证。一个叫 revenue 的表达式是否符合财务口径,仍需要负责人确认。Web 界面允许修改术语、描述和 SQL 属性,并对定义进行认证。

查询上下文同时包含语义与结构

候选检索按对象类型工作:实体找 ColumnAttribute,问题找 SqlAttribute,主题可找 Term;候选准备还会选择连接锚点、查找 join 路径,补回连接所需的桥接表。生成器拿到的不是一串相似文本,而是带表列落点、关系路径和表达式的上下文。

不过,最终 SQL 仍由模型生成。结构化输出限定了返回形状,并不证明模型选择了正确的聚合粒度或过滤条件。

API 与 MCP 共用后端问答能力

REST 的 POST /api/chat/completions 返回 SSE 流。MCP 的 ask_question 包装这条流,把回答、SQL、行数据和步骤拆成字段;其他工具提供术语检索、部署就绪检查和问题覆盖检查。

MCP 服务是独立 Python 包,通过 HTTP 访问部署,不需要把整套数据库驱动和模型依赖安装到每个 Agent 客户端。它也不是一套独立运行的查询引擎。

有权限入口,但部署网络仍是边界

Next.js 网关负责解析用户身份和权限,再向 FastAPI 转发内部身份头。后端信任这个内部身份;摄取服务还有自身不带认证的管理端点。因此,对外发布 Web 入口,不意味着可以把内部服务端口一并暴露。

🔬 技术架构深度解析

1. 数据先进入目录,业务含义再进入语义图

下面把准备阶段和提问阶段分开。两者使用同一份目录与本体,但成本、更新频率和失败语义不同。

text 复制代码
源数据库
   │ schema、键关系、列画像、有限样本
   ▼
目录摄取:extract → populate_tabular_data
   │
   ▼
按表语义编译:采样 → 外键建议 → 术语/列属性生成
   │                         │
   │              人工修订、认证 SQL 属性
   ▼                         ▼
Postgres:目录 + Term / ColumnAttribute / SqlAttribute / 关系
   │
   └── pgvector:对应语义对象的检索表示
                    │
用户问题            ▼
   │       分类与实体抽取 → 候选检索 → 连接路径/桥接表准备
   │                                            │
   └────────────────────────────────────────────▼
                                      模型生成 SQL
                                            │
                         可选值验证 → 静态/意图检查
                                            │
                                  可选数据库前置探测
                                            │
                                连接器执行 → 结果检查
                                            │
                                  回答 / SQL / 行数据

检查或执行失败 ──→ 重构 SQL ──→ 重新验证
耗尽预算或缺少数据 ──→ 无法构造回答,部分路径保留候选 SQL

源码中的语义编译入口 compile_semantic_layer 只取尚未关联 Term 的表,并按库、schema、表名等组成的规范键排序。它使用三个 worker 并行执行昂贵的表处理,但把术语持久化安排在规范顺序的 commit slot 中。

这解决一个容易被忽略的问题:两张表提议了相同的无前缀术语名,如果谁先得到名字取决于模型网络延迟,同一批输入的命名就会漂移。并行提取、顺序提交让这种名称归属更稳定。它保证的是提交顺序,不是模型每次生成完全相同的词。

队列还记录提前放弃的 slot。后续表取消或失败时,不用占着 worker 等前序表结束;当前 slot 推进时会消费连续的跳过位置。已经进入模型调用的表不能因此立即中断。停止编译意味着不再继续积压任务,不能当作对外部请求的强制撤销。

这段实现可以直接在 semantic/pipeline.py 中找到。

2. 采样降级会影响语义质量,不一定让编译整体失败

process_table 先尝试列画像,再提议外键、生成非外键列的属性规格,然后让模型提出术语与显示名称。没有任何可落到真实列上的分配时,它会跳过术语创建,而不是保存一个空泛概念。

画像路径使用最多 1,000 行的样本,支持超时能力的连接器会收到画像查询的时间限制。分类值的 DISTINCT 探测受单独开关控制;关闭它,并不会同时关闭普通行样本。

系统还有按数据源记录的采样熔断器:连续三次超时或连接类失败后,本轮剩余采样可以跳过,继续编译时缺少样本信息。普通权限或类型错误不会一概算成仓库不可达。新一轮编译会重置对应数据源的熔断状态。

这是一种可用性取舍。它避免对失联数据源逐表耗尽等待时间,却也意味着"编译完成"可能包含质量下降的对象。术语持久化后,内联 embedding 失败会记录警告;这同样不能被理解为术语已经可以通过向量检索完整召回。运维上应分别观察目录写入、术语覆盖和检索索引,而不是只看一个完成提示。

3. 候选准备决定了模型有机会答对什么

NL-to-SQL 的正常计算路径依次经过意图分类、问题实体抽取、候选检索与候选准备。information 类型问题会走解释信息的旁路,不进入 SQL 生成;预测节点则需要相应 Kumo 配置,不是普通分析问题的必经流程。

候选准备比较有工程价值。它围绕主 ColumnAttribute 建立 join 路径,再做表相关性筛选,随后由代码补回必要的 junction 和 bridge 表。后面这一步防止语言模型在"删掉不相关表"的同时,把连接两端唯一的中间表删掉。

例如,客户与商品可能通过订单、订单明细关联。问题只提到客户和商品,并不代表查询上下文可以省掉订单明细。向量相似度很难独自表达这种连接必要性,图关系提供的是另一种约束。

但关系信息也有来源质量。声明外键与推测关系不能不加区别地当成业务事实;路径存在,只能说明系统找到了可用连接线索,不能证明它符合某次统计的粒度。

4. "验证通过"包含几种强度不同的判断

生成器优先使用 reasoning client,把 schema、关系路径、SQL 属性、evidence、值锚和参考查询结构等放入提示词。之后的统一验证先做 catalog/static 处理,再让模型审查 SQL 是否符合问题意图。

catalog 解析会关联已知表列,完全找不到目录中的表会成为错误。它不是数据库优化器,也不是一个覆盖全部业务语义的证明器。某些静态检查还受功能开关控制,不能把文件里存在一个检查函数当成所有部署都启用了它。

更值得注意的是意图判断的降级:调用意图模型抛异常,或返回空结果时,代码会返回 valid_sql;明确发现连接、聚合、投影或 evidence 问题时,才进入重构。这个标签表达的是流程路由决定,不能直接翻译成"答案正确"。

可选数据库探测又是另一层。值、join 路径、JSONB 路径的前置探测在默认配置下并非全部开启。有些值验证由带预算的模型工具循环完成;它们可以提供纠错证据,但不等于穷尽检查所有输入。

具体降级条件见 sql_parse_validation.py。把这些分支保留在接入评估里,比只验证正常查询更重要。

5. 重试有三层,返回 SQL 也未必代表执行成功

层次 实际控制 接入时的含义
首轮 SQL writer 生成返回空值等情况下,局部最多尝试三轮 不能据此估算整个请求的调用次数
SQL 重构 硬失败依据失败历史计数,阈值为七 还受图执行深度及提前退出影响
软检查 失败计数超过五后可不再阻塞 探测失败并不总会禁止继续执行
完整流程 特定无法构造、无 SQL 的终态最多运行两次流程 可能重新做意图分类和候选检索

普通数据库语句错误可以触发重写;连接失效等基础设施问题会结束当前内层链。但是外层重启逻辑没有因此自动排除所有基础设施故障,不能概括成"连接错误绝不重试"。

还有一个需要调用方特别处理的 fallback:第二次完整流程失败后,如果保存着此前被意图审查拒绝的 SQL,系统可以把它放进最终 sql_code 返回。这个分支直接复制候选,不会再执行那段 SQL。仓库对应测试也明确覆盖了返回这种候选的情形。

因此,接业务系统时应分别判断自然语言回答、sql_code、数据库行数据和错误状态。不能用"SQL 字段非空"作为成功记账、自动导出或触发下游流程的条件。main.py 里的完整流程恢复与 fallback 是理解这点的入口。

6. 主查询执行没有因为 MCP 的"只读工具"定位而获得沙箱

主 SQL 最终经 execute_chat_sql 交给选中的 connector。多个数据库连接同时存在时,代码根据相关表的 database_name 路由;跨库集合、找不到对应库或多连接却没有库元数据等情况会拒绝,而不是随意选择第一个连接。

数据库 probe 有自己的 SELECT/WITH 形态检查、调用预算和返回行限制,但这套 guard 不是主查询的通用隔离层。语句超时也依赖 connector 是否声明支持,不是整个自然语言请求的总截止时间。

MCP 工具面向读取目录与查询数据,不提供目录编辑工具,这是接口设计。它不代替数据库侧的最小权限。生产数据源仍应使用受限账号、合适的视图和数据库资源限制,内部服务放在可信网络,采样数据和模型请求的去向也要符合企业要求。

📖 README 核心内容摘要

README 对使用流程的描述是"生成、认证、回答":先摄取目录并生成语义初稿,再由人修订定义,最后通过聊天或 Agent 访问。业务 SQL 属性的复用是这套方案的重要收益,不过已有定义并不会自动变成不可违反的形式化约束。

部署包含 Web 网关、FastAPI 后端、摄取服务,以及保存目录和本体的 Postgres/pgvector。Web 默认端口是 3000,内部后端与摄取服务分别为 3001、3002。Compose 文件包含数据库迁移任务,启动顺序要求迁移成功后再启动相关服务。绕开这些任务直接运行容器,可能得到界面可访问、目录接口却报缺表的半成品部署。

模型按角色设置 endpoint、API key 和 model 三元组。未设置的字段回落到共享默认值。一个实际容易踩的坑是:.env.example 各角色已经写了示例 key,只修改共享默认 key,不会覆盖仍然存在的角色示例值。应替换它们,或删除对应示例行让回落生效。Embedding 的接入还要求摄取与查询使用相同模型,不能仅因为维度碰巧相同就随意替换。

README 和环境模板在具体模型名称上可能不同,配置应以所用版本的环境模板及实际端点可用性为准。换 endpoint 也不能只改聊天模型,还需要确认 embedding 和 rerank 的协议、模型名称及认证方式。

Agent 接入有两个不同的前置检查:check_readiness 判断部署是否具备回答能力,check_answerable 判断本体是否覆盖某个问题。后者说"能覆盖"不保证查询必定成功,也不能替代业务口径审查。

许可证允许按 Apache-2.0 的条款使用与分发,仓库同时声明当前不接收外部贡献。开放源码、允许复用和维护者是否接受 PR,是不同的问题。

🚀 快速上手

先从已有的小型、无敏感数据源入手。以下使用清单版本 1.1.0 的 Compose 服务名和接口,前提是安装了 Git、Docker 与 Compose v2,并准备了可用的模型端点。模型调用可能计费。

1. 准备仓库与环境

bash 复制代码
git clone https://github.com/NVIDIA/auto-ontology.git
cd auto-ontology
cp .env.example .env

编辑 .env,更换数据库、pgAdmin、管理员的默认密码及 AUTH_SECRET,填写模型配置。不要把真实 key 提交到 Git。演示数据使用下面的连接,密码与 POSTGRES_PASSWORD 一致;若密码含 URI 特殊字符,需要做 URL 编码。

dotenv 复制代码
APP_URL=http://localhost:3000
CONNECTION_STRINGS=postgresql://postgres:YOUR_URL_ENCODED_PASSWORD@postgres:5432/report_demo

这里 postgres 是 Compose 网络里的服务名,不是本机地址;report_demo 是随后创建的演示数据源,不要把它与保存应用目录的 POSTGRES_DATABASE 混为一谈。

Compose 默认还会映射数据库、pgAdmin 和内部服务端口。演示环境也应限制主机入站访问;共享服务器部署前,先调整绑定地址或网络规则,不要将原配置直接作为公网部署方案。

2. 建立一个能人工对账的示例

先启动存储,再在同一实例里创建独立演示库。创建命令用于首次初始化,已有同名数据库时应检查现有内容,不要为了重跑示例直接删库。

bash 复制代码
docker compose up -d --wait postgres

docker compose exec postgres sh -lc \
  'psql -U "$POSTGRES_USER" -d postgres -v ON_ERROR_STOP=1 -c "CREATE DATABASE report_demo;"'

docker compose exec -T postgres sh -lc \
  'psql -U "$POSTGRES_USER" -d report_demo -v ON_ERROR_STOP=1' <<'SQL'
CREATE TABLE demo_orders (
    id INTEGER PRIMARY KEY,
    status TEXT NOT NULL,
    paid_at DATE NOT NULL,
    amount_cents INTEGER NOT NULL
);
INSERT INTO demo_orders VALUES
    (1, 'paid',      '2026-11-01', 10000),
    (2, 'paid',      '2026-11-02', 15000),
    (3, 'cancelled', '2026-11-03', 20000),
    (4, 'paid',      '2026-12-01',  7000);
SQL

docker compose up -d --build

打开本机 3000 端口的 Web 页面,使用所配置的管理员登录。确认数据源已摄取,在 Settings → Semantic Compilation 开启语义编译,等待可用。检查生成的术语是否保留了 amount_cents 的分单位,以及 paid_at 的时间含义,必要时修订描述。

可以问:"按 paid_at 统计 2026 年 11 月 status 为 paid 的订单总金额,保留分单位,不包含 cancelled 订单。"这段明确口径也适合放入 API 的 evidence。固定样例的对账 SQL 是:

sql 复制代码
SELECT SUM(amount_cents) AS revenue_cents,
       COUNT(*) AS paid_orders
FROM demo_orders
WHERE status = 'paid'
  AND paid_at >= '2026-11-01'
  AND paid_at < '2026-12-01';

这份数据的参考值是 revenue_cents=25000、paid_orders=2。它是样例数据的确定结果,不是承诺模型每次生成相同 SQL。故意换成"净收入""已退款金额"等没有定义的口径,再观察系统怎样澄清或拒绝,会比只测一句顺利的问题更有价值。

3. 用 SSE 客户端取回独立字段

在 Web 用户菜单的 API Tokens 中创建自己的 token,通过环境变量提供给客户端。API 请求走 3000 端口网关,不直接绕过它调用内部 FastAPI。下面脚本使用 Python 3.12 标准库,不需要安装 HTTP SDK。

python 复制代码
import json
import os
import urllib.request

payload = {
    "question": "2026年11月已支付订单的总金额和订单数是多少?",
    "evidence": "仅统计status='paid';时间按paid_at;金额保留分单位。",
}
request = urllib.request.Request(
    "http://localhost:3000/api/chat/completions",
    data=json.dumps(payload, ensure_ascii=False).encode("utf-8"),
    headers={
        "Content-Type": "application/json",
        "Accept": "text/event-stream",
        "x-api-key": os.environ["AUTO_ONTOLOGY_API_TOKEN"],
    },
    method="POST",
)
with urllib.request.urlopen(request, timeout=900) as response:
    for raw_line in response:
        line = raw_line.decode("utf-8").strip()
        if not line.startswith("data:"):
            continue
        data = line.removeprefix("data:").strip()
        if not data:
            continue
        if data == "[DONE]":
            break
        event = json.loads(data)
        if event.get("type") == "error":
            raise RuntimeError(event.get("message", "query failed"))
        if event.get("type") == "result":
            answer = event["answer"]
            print(answer.get("response"))
            print(answer.get("sql_code"))
            print(answer.get("sql_response_from_db"))

sql_response_from_db 可能带有 JSON 字符串包装,业务客户端应按约定解析,不要从回答正文正则提取数字。SSE 的 [DONE] 不是 JSON;直接把整个响应交给 .json() 会用错协议。HTTP 成功也不代表流里没有 error 事件。

需要连续对话时,传入并复用一个 UUID 格式的 conversation_id,等待前一轮流结束再发下一轮。同一会话重叠运行会返回 409。只做上面的单次查询,可以省略会话标识,不必为了拿一个统计值创建聊天历史。

接口字段与流格式见 Conversation API。

4. Agent 通过独立 MCP 包连接

已有完整部署后,在另一个终端进入同一仓库目录,安装本地 MCP 子项目并启动:

bash 复制代码
cd mcp
uv sync
AUTO_ONTOLOGY_API_URL=http://localhost:3000 uv run auto-ontology-mcp

这条命令要求另行安装 uv。服务使用 HTTP transport,客户端连接本机 3003 端口的 /mcp,并按登录流程以用户身份授权。MCP 不代替前面的数据库接入和语义编译。

ask_question 的返回层最多提供 100 行,同时带 row_count 和 truncated。这是传给 Agent 的响应截断,不是保证数据库最多计算或读取 100 行。详细分析或大规模导出,不应依赖一个受上下文预算约束的工具返回值。

📊 增长速度与社区热度

2026-11-05 的日榜快照里,Auto Ontology 位于第 1,仓库累计 5,898 Stars、461 Forks。进入 Trending 表明它在这个时间窗口受到关注,但这些累计数不能推导出单日新增量,更不能当成真实部署数量。

对准备接入的团队,更有判断价值的是边界问题能否得到持续维护:连接错误会不会无限重构,认证定义怎样进入生成上下文,模型异常时哪些检查会降级,以及数据库迁移是否与应用启动保持一致。星数可以帮助发现项目,不能代替这些评估。

当日日榜如下:

排名 仓库
1 NVIDIA/auto-ontology
2 max-sixty/worktrunk
3 mattpocock/skills
4 floci-io/floci
5 thedotmack/claude-mem
6 vercel-labs/agent-desktop
7 github/spec-kit
8 The-PR-Agent/pr-agent
9 harry0703/MoneyPrinterTurbo
10 garrytan/gstack
11 FareedKhan-dev/kimi-k3-in-c
12 JiaFeiMiao-K-Cat/DiscImageStudio
13 MichaelCade/90DaysOfDevOps
14 D4Vinci/Scrapling
15 Thysrael/Horizon
16 scenario-labs/skills

🎯 适用场景

有共享指标口径的数据问答

如果团队里不同工具都在回答收入、库存、客户等问题,维护一份能映射到列和 SQL 表达式的语义层,有机会减少重复解释。前提是有负责人管理定义,数据表和关系也足够稳定。

初次接入可以从十几个关键指标、少量视图和受限账号开始。把标准问题与已知 SQL 结果做成回归集,覆盖取消、退款、跨月边界、空结果和连接失败,不要只收集看起来流畅的聊天截图。

编码 Agent 的结构化数据入口

需要让 Agent 先查业务术语,再提问取数时,MCP 的分工比较清楚。先区分部署未就绪与问题超范围,能减少把配置故障反复当成提示词问题修复的浪费。

但这仍是有成本的数据查询。多次串行模型调用、数据库探测和完整流程重试都会增加延迟与端点费用。应按自己的请求分布看调用次数、失败分类和数据库负载,不能只看一次首轮 SQL 生成的时间。

不适合直接承担不可逆业务决策

需要严格证明口径、自动执行资金动作或零容忍错误的流程,不应直接消费聊天回答。模型意图验证存在放行降级,fallback 还可能返回未执行的候选 SQL。必要的审批、对账和只读数据库权限,需要由业务系统单独建立。

只有几张结构固定的表、几个固定看板时,写好 SQL 和视图往往更省维护成本。引入本体的收益应来自多入口复用和复杂语义,而不是为了把一个固定查询改成自然语言。

💡 总结

Auto Ontology 的可取之处,是把业务术语、列映射、连接路径和 SQL 属性放进一条可观察的查询链,而不是指望模型仅凭 schema 就理解企业。

它也清楚地暴露了这条路线的难题:语义初稿需要人维护,检索与目录可能分步成功,验证有降级,返回候选不等于完成执行。把它作为需要治理的语义数据服务来接入,比把它当成开箱即用、永不出错的 SQL 助手更合适。

相关推荐
一条Go2 小时前
两套权限目录,三重闸门:Alioth 的多租户 RBAC 为什么这么拧
后端·程序员·github
imythu2 小时前
让 Claude Code 在家跑,我在外面用手机盯着:做一个开源工具踩过的几个坑
人工智能·github
艾迪的技术之路3 小时前
16k 条慢查询,128 张分表,我这么解决
后端·github
lsc_blog3 小时前
年底还在招的岗位,先确认缺口和到岗要求
git·缓存·ci/cd·职场和发展·github
GitHub实测笔记3 小时前
bat 本机实测:6.7MB JS 开语法高亮要 12.4s,cat 只要 1.2ms;关颜色约慢 24 倍
github
徐小夕7 小时前
下载量超 30 万的开源智能体 Prime Agent:它把自己从 TypeScript 重写成了 Rust
前端·vue.js·github
miofly9 小时前
GitHub 日榜趋势速报 | 2026-11-05
开源·github
wflynn18 小时前
GitHub 日榜趋势速报 | 2026-11-03
开源·github
u13013019 小时前
GitHub 热榜项目:日榜(2026-11-03)
人工智能·github