11 月 5 日和 6 日,GitHub 趋势榜 Python 分类的第一名被同一个新项目连续占住:NVIDIA 开源的 auto-ontology,单日进账约 2000 星。它不是又一个向量数据库,也不是又一套 RAG 脚手架,官方 README 给它的定义只有一句------为企业数据构建语义化的图状推理层,让 Agent 在写 SQL 之前先搞清楚每个业务词在这家公司到底指什么。这个方向为什么突然被星标砸中?因为 Agent 做数据分析的最大事故从来不是查得慢,而是口径错了还答得理直气壮。
它解决的不是检索问题,是口径问题
用过 text-to-SQL 演示的人都会有一种错觉:把数据库 schema 贴给模型,问一句自然语言,SQL 就出来了,很漂亮。但只要你把它接进真实的公司数据,同一个问题「上季度营收是多少」很可能在三个系统里得到三个数字,而且每一个数字都有完整的 SQL 作证。
根因不在模型不够聪明,在于模型拿到的是表名和字段名,不是业务定义。营收到底是 extendedprice 直接求和,还是要扣掉退货,还是只统计已付款订单?这些定义散落在 wiki、老员工的记忆里和历代报表的注释里,没有任何一份 schema 会写出来。模型不知道,就只能猜,猜完还显得很有把握。
目前大家应对这件事的路线大体有三条:直接 NL2SQL 拼提示词、用 RAG 把 schema 和文档切片喂给模型、或者建一层语义层统一定义。三条路的实际差别可以摆在一起看。
| 路线 | 口径一致性 | 回答可解释性 | 答不了时会不会拒答 | 主要代价 |
|---|---|---|---|---|
| 直接 NL2SQL | 无保障,每次现场编 | 只有 SQL 文本 | 不会,硬答 | 提示词反复调 |
| RAG 检索 schema 与文档 | 取决于文档质量 | 弱,靠拼上下文 | 不会 | 文档维护繁重 |
| 语义层路线 | 认证后统一 | SQL 与血缘一起返回 | 先验 answerability | 建层加人工认证 |
auto-ontology 走第三条。值得说清楚的是它并不打算替代企业已经有的语义模型,官方措辞是 complement,从现有元数据自动生成本体草稿,把人工认证过的定义通过 MCP、CLI 和 API 暴露给任意 agent harness。定位是补层,不是推倒重来。
本体里只装三类东西
这个项目的本体设计得相当克制,全部定义只有三类。第一类是 term,也就是业务术语,比如 Customer、Invoice Line,每个 term 带一段自然语言描述、一组同义词,以及它映射到哪些表的哪些列。Agent 听到「发票行」和「invoice line」能认出是同一个东西,靠的就是这层同义词和映射。
第二类是 SQL attribute,挂在 term 下面的、经过人工审核的 SQL 表达式,比如毛利率怎么算、滚动三十天总额怎么算。Agent 回答时复用这些表达式,而不是每次现场新写一段,这样同一个指标在任何一次回答里都是同一套算法,口径一致性就是从这里来的。
第三类是 relationship,描述表与表怎么 join,包括桥接表。它不完全依赖外键声明,还会从数据内容本身推断关系,这对那些年久失修、外键残缺的老库特别重要,因为很多真实系统的关联关系从来就没在 DDL 里声明过。
给一个规模直觉:官方用微软的示例销售库 Wide World Importers 跑了一次完整编译,产出三十一个 term。所有定义落在 Postgres 里,对应的 embedding 存在 pgvector 里供检索用,Web UI 里还有一张可点击探索的语义图,选中 Order 能看到它的描述、关联术语、映射列和 SQL attribute。
三类定义在一次提问里是同时工作的:问题里的业务词先靠 term 的同义词表收敛到唯一概念,指标算法靠 SQL attribute 直接取认证过的版本,表与表之间的路由靠 relationship 给出 join 路径。embedding 只负责把候选项捞回来,真正决定答案长什么样的是认证过的定义本身。这个分工很关键:检索是概率性的,定义是确定性的,把确定性的部分从模型手里拿走,幻觉的空间就被压缩到只剩意图理解这一小段,「为什么这样算」也变成可以点开检查的东西,而不是埋在生成概率里。
生成、认证、回答三段流水线
auto-ontology 的工作流分成三个阶段,理解这三段就理解了整个项目。生成阶段由 ingestion 服务完成,动作有四步:读取数据源,把 schema、列的统计画像和采样值抽进 catalog;逐表命名实体并把 term 映射到列;推断表间 join 和桥接表,同时从查询历史里建议候选 SQL attribute;最后把 term、relationship、SQL attribute 写进 Postgres,embedding 写进 pgvector。
认证阶段是人的工作,也是整个设计里最不妥协的一环。机器生成的只是草稿,人用真实问题在 Chat 界面里测试,或者通过 MCP 调 check_answerable 和 ask_question 来测,发现 term 描述不准、SQL attribute 算错,就在 UI 或 API 里修,修到对了就打上认证标记。没认证的草稿,Agent 用起来会打折扣。
回答阶段是 Agent 的运行时:先对问题做意图分类和实体抽取,再用 embedding 检索候选的 term、列和 SQL attribute,然后拿这些候选项对照认证过的定义和血缘做校验,通过之后才生成并执行 SQL,收尾时把答案连同 SQL 和产生它的步骤一起返回。注意校验在生成之前,顺序不能反。这五步每步都可以单独观测,出问题时能定位到具体环节,而不是面对一个黑箱干瞪眼。「这题能不能答」在这里是一个显式判断,不是等 SQL 生成失败才被动的报错。
还有一个学习回路把整条链路闭环:被认证过的修正和实际使用情况会折回本体,共享上下文随着每一次提问变好。这意味着本体不是一次性交付物,而是越用越准的资产,前期认证投入决定后期回答质量的上限。
一次真实问答拆开看
官方 quickstart 里给了一个完整例子,值得逐步拆开。底层数据是 Wide World Importers 销售库,提问是:USB food flash drive - sushi roll 这个商品,2015 年 6 月在宾夕法尼亚州的营收是多少。
返回结果长这样:答案是 588.80 美元,来自三条发票行;同时附上生成的 SQL 和推理步骤。SQL 是真实的五表关联:
sql
SELECT SUM(il.extendedprice) AS revenue, COUNT(*) AS invoice_lines
FROM sales.invoicelines il
JOIN sales.invoices i ON il.invoiceid = i.invoiceid
JOIN warehouse.stockitems si ON il.stockitemid = si.stockitemid
JOIN sales.customers c ON i.customerid = c.customerid
JOIN application.cities ct ON c.deliverycityid = ct.cityid
JOIN application.stateprovinces sp ON ct.stateprovinceid = sp.stateprovinceid
WHERE si.stockitemname = 'USB food flash drive - sushi roll'
AND sp.stateprovincename = 'Pennsylvania'
AND i.invoicedate >= '2015-06-01' AND i.invoicedate < '2015-07-01'
步骤里写着它怎么走的:revenue 被解析到 Invoice Line 这个 term 下认证过的 SQL attribute,商品和州被解析到对应 term,然后沿 relationship 走出发票行到发票到客户到城市到州的 join 链,过滤值先对着数据校验过,然后才执行。每一步都留下了血缘,可以拿回去对源。
比正例更值钱的是官方给的反例。这个库里根本没有广告投放数据,所以当你问「这个商品的广告支出回报率是多少」时,check_answerable 先验返回 coverage 0.5,没有任何 term 覆盖「广告支出」,Agent 会直接明说算不了,并反问你是要看六月营收,还是广告数据在另一个需要接入的源里。它不会编一个数给你,这就是口径层和闷头生成 SQL 的分水岭。
把正反两个例子放在一起看,auto-ontology 真正解决的是信任问题而不是速度问题。一个能随时给出血缘、并且敢于说「这题数据不够」的系统,业务方才敢把它接进正式报表链路。README 里有句话说得很直白:营收指标回答得再快,数字是错的就一文不值,可靠性是 agentic analytics 的入场券。反过来看市面上大多数 NL2SQL 演示,它们没有 coverage 这个概念,任何问题都必定产出一段 SQL,因为流水线里根本没有「不答」这个出口。把拒答做成一等公民,是这套系统和演示级方案最本质的差别。从工程角度看,answerability 先验还能省钱:不该答的问题在模型生成 SQL 之前就被拦下,token 成本和数据库的无效查询一起省掉。
部署形态与组件清单
这是一个全栈项目,五个组件各司其职。前端是 Next.js 16,底下是 React 19、TypeScript 和 Tailwind CSS 4,认证用 Better Auth,ORM 用 Prisma,跑在 3000 端口。后端是 FastAPI 加 LangChain,负责 Chat、NL-to-SQL、catalog 和数据源 API,跑在 3001。独立的 ingestion worker 跑在 3002,负责灌表格数据和写 embedding。
| 组件 | 技术栈 | 职责 | 默认端口 |
|---|---|---|---|
| 前端 | Next.js 16、React 19、Better Auth、Prisma | Web UI、认证、API 网关 | 3000 |
| 后端 | FastAPI、LangChain | Chat 与 NL-to-SQL、catalog、数据源 API | 3001 |
| ingestion worker | FastAPI | 灌入表格数据、写 embedding | 3002 |
| Postgres 加 pgvector | 关系库 | 应用元数据、catalog、本体、向量 | 5432 |
| HashiCorp Vault | 可选 | 连接凭据的安全存储 | 无 |
模型侧全部通过环境变量 triplet 配置,每个角色一组 ENDPOINT、API_KEY、MODEL。默认指向 NVIDIA 托管的 NIM 目录 integrate.api.nvidia.com,LLM 用 nemotron-3.5-lightning-30b-a3b,embedding 用 nemotron-3-embed-1b,rerank 用 llama-nemotron-rerank-vl-1b-v2。REASONING、NON_REASONING、EMBED、RERANK 四个角色可以只覆盖个别字段,没设的部分回落到 DEFAULT_MODELS,再老的 NVIDIA_API_KEY 也兼容。
官方自己点名了一个容易踩的坑:EMBED 模型在 ingest 和 query 两侧必须保持同一个。灌数据时用了一个 embedding 模型,查询时换成另一个,向量空间对不上,检索质量直接塌掉,而且这种坏法不报错,只是答案悄悄变差。
部署给两条路,本地用 Docker Compose,要求是 Compose v2 加 uv;生产用 Helm chart 上 Kubernetes。还有一个文档里明确记录的小坑:UI 重启后如果左边导航不见了,是浏览器拿着过期的会话 cookie,清掉 localhost:3000 的站点数据重新登录就好。
这个组件拆分方式本身就值得抄作业:认证和 API 网关收在前端层,模型编排收在 FastAPI 层,重负载的灌库和 embedding 写入隔离在独立 worker 里,聊天流量和灌库流量互不阻塞。各数据源的连接凭据不进环境变量文件,交给可选的 Vault 托管,轮换和审计都有现成机制。对一个要接生产库的系统来说,这些不是锦上添花,而是能不能过安全评审的硬条件。五个组件全是常见技术栈,没有自研黑盒,二次开发和线上排查的门槛都压得很低,团队里普通后端就能接住。
MCP 接入:server 手里没有任何凭据
auto-ontology 的 MCP server 放在仓库的 mcp 子目录,是个独立组件,用 uvx 直接从 GitHub 子目录安装,不用 clone 主仓。它的架构非常克制:本身只是部署实例公网 API 的一个 HTTP client,向下转发到 Next.js 层,再进 FastAPI 和 Postgres。
设计里最该细看的是凭据策略。这个 server 不持有数据库凭据,不持有模型配置,也不持有任何自己的凭据。认证和权限校验本来就活在 Next.js 那层,server 直接继承,什么都不重新实现。官方对此的概括是:一个 session 能做的,恰好就是登录的那个人能做的,viewer 角色的登录者通过 Agent 也只能拿到 viewer 该看的答案。
OAuth 流程也省了常规麻烦。Auto-Ontology 自己充当授权服务器,MCP server 上没有 client id、client secret 和 redirect URI 需要配置,客户端会自己向 Auto-Ontology 自动注册。注册和授权记录存在 Auto-Ontology 的数据库里,重启不会让任何人掉登录;权限在每次调用时按账号实时取,所以改角色、封禁用户、删除授权全部立即生效,不存在 token 快照里的过期权限。
客户端接入只需要一行 URL,注意 /mcp 后缀是 endpoint 而不是服务根路径,改完配置要重启客户端,因为多数客户端只在启动时读 MCP 配置。Cursor 里会把它显示为需要登录的服务。
json
{
"mcpServers": {
"auto-ontology": {
"url": "https://auto-ontology-mcp.example/mcp"
}
}
}
这个设计对正在搭内部 Agent 平台的团队有直接参考价值:把权限收敛到业务系统既有的认证层,让 Agent 基础设施变成无凭据的薄转发层,审计面会小很多,出事时的爆炸半径也小很多,安全评审时也少一份要单独解释的组件。
上手路径与几条边界
想本地跑起来,路径是固定的:clone 仓库后复制 .env.example 为 .env,填好从 build.nvidia.com 申请的 nvapi key、AUTH_SECRET 和若干密码,先起 Postgres,再用姊妹仓 auto-ontology-eval 里的 seed 脚本灌入 Wide World Importers 示例库,然后整体构建启动。核心命令如下:
bash
git clone https://github.com/NVIDIA/auto-ontology.git && cd auto-ontology
cp .env.example .env
docker compose up -d --wait postgres
docker compose up -d --build
起来之后打开 localhost:3000,用 .env 里设置的管理员账号登录,到 Settings 的 Semantic Compilation 把它打开,ingestion 会在启动时跑,编译完成后界面会报告 ready,然后就能在 Chat 里提问。想认真评估准确率的,可以用官方的 ontology-sql-eval 仓库跑 BIRD 这类 text-to-SQL 基准。
接入自己的库时,建议的顺序是先把只读副本接进来跑生成,让三十来个 term 的草稿先出来,再安排熟悉业务的人做认证,认证一批上线一批,不要等全部认证完才让 Agent 用。学习回路会把认证修正折回本体,早期用得越多,后期答得越稳。如果第一批 term 的命名和你们内部口径对不上,优先改描述和同义词,而不是急着改 SQL attribute,名字对了,后面的指标才有地方挂。
边界也要说清楚。第一条是项目治理:Apache 2.0 开源,但官方明确目前不接受外部 PR,只收 issue,仓库 2026 年 4 月底才创建,到现在零个 release、一个 tag,这是典型的早期形态,生产采用要把维护风险算进去。
第二条是模型耦合:整条链路默认依赖 NVIDIA NIM,要么用托管目录要么自托管 NIM,不是模型无关的设计,切换别家模型需要自己动手改配置层。第三条是最根本的:答案质量的上限由你在本体认证上的投入决定,机器生成草稿而人不认证,等于把幻觉制度化。语义层本身不是新发明,但把它做成开源、自带 MCP、两天冲上趋势榜,说明 Agent 工程的重点正在从「让模型敢答」转向「让系统知道什么时候不该答」。