专栏 · Agent 工程手艺 › 第 210 期 · AGENT 工程 · 收录于 2026 年 9 月 14 日
本体治理:构建健壮的本体——Chad Wahlquist、Jonas Lang(Palantir)
CW
Chad Wahlquist Jonas Lang · Palantir
TL;DR · 三句话
- 一本体是按业务「怎么说话、怎么运转」建模,不是把数仓表、系统表搬进来:Palantir 的四条根本原则——领域驱动设计、不要重复自己、开闭原则、组合优于深层级——每条都对着一个一线最常见的反模式:一张 order data 表一比一建成「上帝对象」、销售 / 支持 / 计费三个「客户」各自为政、往已经上生产的对象上加字段、把建筑挂成一条父子长链。→ 详细
- 二治理的核心是「做加法,别做修改」:对象先 experimental 后 active,active 了就对修改关闭,新需求(如设备认证)就新建对象、用 interface 挂上去——因为即便有 global branching 和变更管理,删一个字段照样连锁反应、代价极高;Chad 的原话是「如果我想预先定义一套层级,那它在我发布的那一刻就已经过时了」。→ 详细
- 三名字和描述是 agent 的输入,不是文档洁癖:Jonas 把 misnomer(「物品」「实物资产」这种读完不知道是什么的名字)列为最糟的反模式之一——人得去问同事,agent 会做出更差的决策;Chad 的结论是「LLM 加上本体,才是真正的魔力所在」,因为大模型没用你公司的数据训练过,只能靠本体把事实表达好。→ 详细
01
开场定调:本体是给「业务怎么说话」建模,不是把数仓搬进来
- 两位讲者:Chad Wahlquist,Palantir 架构师;Jonas Lang(片中只称 Jonas),Palantir 的 FDE(forward deployed engineer,前置部署工程师:驻在客户那边、亲手替客户把系统搭起来的工程师)。两人都花大量时间跟客户聊本体怎么建模,最常被问的就是:「我是不是该把数据仓库一股脑塞进本体?是不是把系统表直接搬过来?」→ 详细
- Chad 先定框架:本体要按真实世界实际存在的样子建模,「用你的业务真实运转时所用的那套术语、那些名词和动词」,而不是为了让报表工具能处理、塞进数仓时的那种样子——所以它「跟大家习惯的那一套比起来会显得有点怪」。本期既讲正确做法,也讲一线见过的反模式(anti-pattern:看起来顺手、实际反复坑人的常见错误做法)。→ 详细
- Jonas 补出处:这套原则和反模式一半植根于软件工程理论,一半来自一线「现实中什么管用」;服务两拨人——给业务带来价值,也让维护本体的团队能把它建得健壮、经得起未来考验。Palantir 为此更新了一批新文档(链接随视频放出)。→ 详细
02
四条根本设计原则总览,以及原则①领域驱动设计:先懂领域,源数据放最后一步
- Jonas 给出四条「最根本的最佳实践模式」:①领域驱动设计(domain-driven design,DDD:先把业务领域本身搞懂,按业务的概念来切分和建模);②不要重复自己(Don't Repeat Yourself,DRY);③开闭原则(open/closed principle:对扩展开放、对修改关闭);④组合优于深层级(composition over deep hierarchies)。后面整期基本按这四条逐条展开,每条配反例和正例(速查见下表,各节逐条细讲)。→ 详细
| 原则 |
反例(片中点名的反模式) |
正例 |
细讲 |
| ① 领域驱动设计 |
order data 表一比一建模 → 上帝对象 + 大杂烩;把维度数仓的事实表 / 维度表直接硬塞成对象 |
先看领域,拆成 Order / Customer / Product 三个对象,再映射源数据 |
三、四、五 |
| ② 不要重复自己 |
销售 / 支持 / 计费三个「客户」→ 部门孤岛、系统孤岛、action 泛滥;金锤子(源系统维护的字段也拿 action 改) |
只建一个 Customer;或 interface + 改名为销售线索 / 支持联系人 / 计费账户再链回 Customer;一个「更新客户联系信息」action;不常变的字段走 pipeline |
六~十 |
| ③ 开闭原则 |
往已 active 的设备对象上直接加认证字段 → 过载,长回上帝对象 |
对象先 experimental 后 active;新建「设备认证」对象 + interface 挂上去,将来卡车也能挂 |
十一~十三 |
| ④ 组合优于深层级 |
物品 / 实物资产 → 建筑 → 可排期建筑 → 场馆的父子长链;链头命名失当 |
building interface 共享地址、面积与排期行为,一个 action 管三种建筑;BOM 用对象描述、按需展开 |
十五~十七 |
- 领域驱动设计的做法是「在真正动手构建任何东西之前」分三步走:第一步思考并理解你面对的业务领域;第二步设计本体——object type(对象类型:本体里的一类「东西」,如订单、客户)、link(链接:对象之间的关系)、action(动作:作用在对象上的业务操作);第三步最后才把源数据和逻辑映射到这个本体上。顺序本身就是要点:大多数人的反模式恰恰是从源表出发。→ 详细
- 一句话总结这条原则:「你是在给现实建模,而不只是照搬别的软件逼你采用的那套结构。」→ 详细
03
反例:把一张 order data 表一比一建进本体——同时踩中「上帝对象」和「大杂烩」
- 例子:源系统里有一张叫 order data 的表。一比一直接建进本体,这个对象里就混着好几种实体——客户姓名、客户邮箱,还有
CM extracted at、CM batch ID 这类抽数时留下的技术字段,Jonas 的评价是「其实没什么业务价值」。→ 详细
- 反模式一 God object(上帝对象:一个对象什么都管、什么都往里塞,软件工程里的经典坏味道),Jonas 点了它的两个症状:① 用某个团队专属的、或技术性的 property(属性)污染 object type;② 一个 object type 同时代表着好几个截然不同的实体(订单、客户、产品挤在一张表里)。→ 详细
- 反模式二 kitchen sink(大杂烩:连厨房水槽都塞进去):不管需不需要,把源表所有列一比一全映射进 object type。代价落在后来人身上——有人想基于它改点东西,或拿去做 Workshop 应用(Palantir 里搭业务界面的工具)和其他前端,看着一堆属性就犯迷糊:「这个我真的需要吗?它到底是什么意思?」解法:让 object type 保持聚焦、一个对象只对应现实里一种实体,业务人员和技术开发者都更好懂。→ 详细
- Chad 补了一层更根本的理由:本体「对应的是我们人实际上怎么交流,而不一定是你的开发人员怎么做报表」。batch ID 这类技术标记常常「是被原样搬了进来,而不是体现这些东西的功能、或者大家到底想拿它们去做什么」,结果就是「持续不断的摩擦:我得不停地给业务用户来回翻译,因为我说的是另一种语言」。→ 详细
04
正例:拆成 Order / Customer / Product 三个对象——新工作流「挂上去」,而不是推倒重建
- 做对的样子:先不看 order data 表,先看领域。Jonas 的推理是「如果我处理的是订单,那我需要订单本身;我关心这笔订单的客户,也关心它的产品」——于是得出三个 object type:订单 Order、客户 Customer、产品 Product;然后才回到源数据里梳理这三个对象各要哪些属性、彼此什么关系。他的判断标准是:这样「大家说的才是同一件事」,「本体的威力就在于每个人都从同一个基础出发」。→ 详细
- 拆对了的两个回报:① 看对象、顺着 link 看关系,理解起来更容易;② 未来的工作流能直接在这些实体上长出来——比如订单工作流之后又来一个客户支持工作流,「你直接把它挂到 Customer 上」,不用「仅仅因为想加一个用例,就大费周章地重新建模你的本体」。Jonas 的结论:「一旦你按最好的设计原则把它搭对了,本体也更容易产生复利效应」,后面的工作流搭得更快、更稳。→ 详细
05
陷阱:订单像事实表、客户产品像维度——别把维度数仓的表直接硬塞成本体对象
- Chad 马上指出这个例子的危险:它「让我强烈联想到维度建模(dimensional modeling:数仓里把「发生的事」做成事实表、把「描述它的东西」做成维度表的星型结构),而这在这里可能算是个陷阱」。订单就是事实表,客户和产品就是维度,所以人们很容易把维度数仓的表直接硬塞成本体对象——他承认「在某些场景下确实行得通」。→ 详细
- 但一旦涉及跨度量、跨领域的关系,它就「成了瓶颈,因为我是在一些别扭的领域划分里建的模,这些领域之间不一定能顺畅地打通」。图上演示的是多对一,现实里常是跨不同度量、不同粒度的多对多;传统做法是用「非常静态、或者说预先定义好的方式」把这些情况预先处理掉。→ 详细
- 本体给的答案是反过来:「干脆把所有这些复杂性都建模出来」,让本体去负责扩展、性能、安全,从而能跨这些相互交织的领域运转——因为目标是在整个业务流程上构建数字孪生。这一段与 207 老冯「本体论 = 数据库建模换皮」的反方论证正面相撞:Palantir 自己也承认简单例子里两者「非常像」,分歧只落在跨领域、多粒度的多对多上(本体 vs 语义层的另一种切法见 ep99)。→ 详细
06
原则②不要重复自己:销售客户 / 支持客户 / 计费客户「三胞胎」,改成一个 Customer 或一个 interface
- 规则本身:同样的事做了三遍——比如三个非常相似、几乎一模一样的 object type——就该重构;但 Jonas 强调刚开始建模时就要记着这条,别等长出三胞胎再收拾。→ 详细
- 反例:sales customer(销售客户)、support customer(支持客户)、billing customer(计费客户)三个 object type,看起来基本是一回事。它在现实中是这么长出来的:先做销售用例,接着做支持用例,某天又来个计费用例。正确的起点是:做销售用例时就「不该建一个『销售客户』,而是应该直接从『客户』开始」,并预想将来会有更多用例、更多团队来用这个 Customer——因为「现实中,一家企业里会有多个团队、多个部门跟客户这个实体打交道」。→ 详细
- 反例的代价很具体:三个对象属性相同、得有人分别维护;在一个对象里改了姓名,却没在第三个对象里同步改,「本体某一侧的数据就过时了」。Chad 说这正是「没有单一事实版本(single version of the truth)」的由来——「我有销售客户、支持客户,这种情况在各家的数据版图里随处可见」。→ 详细
- 两种正解:① 真正只建一个 Customer 对象;② 如果业务运营上确实必须把它们分成不同的 object type,就用 interface(接口:给几类对象约定一组共同的「形状」,比如都有姓名 / 邮箱 / 电话,动作只认这组形状)——这样只要一个基于 interface 的 action 就能更新姓名、邮箱、电话,而不是每个对象各做一个、一共三个。Chad 的总结:这两种做法既给了单一事实版本,又给了跨用例的互操作性,「我始终只有那一个对象,大家都在它的基础上构建」,本体「应该是全公司通用的」。→ 详细
07
违反 DRY 会长出两种孤岛:部门孤岛与系统孤岛——有的客户光 ERP 就有 150 套
- 部门孤岛(department silos):分别建销售、支持、计费三种客户,等于承认销售部门、CSS 团队(转写原词)、计费团队「都各自活在自己的世界里」。一旦要协作——比如一起看某个客户的一个 case——手里关于这个客户的信息「各不相同、甚至互相矛盾」,因为信息是在各自孤岛里长出来的。→ 详细
- 系统孤岛(system silos):把实体按它在源系统里的样子几乎一比一映射过来。Jonas 的判词是:这样「你只是把现有系统版图里的孤岛原样复制了一遍」,CRM、helpdesk(工单客服系统)、ERP 之间依旧不通——而「本体恰恰非常擅长做集成,打破现有系统版图里的这些孤岛」。→ 详细
- Chad 给了个量级让人清醒:大多数客户都有 N 套 ERP,「有的客户光 ERP 系统就有 70 套、上百套,甚至 150 套,每一套里都有客户」。每套 ERP 各建一个客户对象,「既没法规模化,也没多大用处」。→ 详细
- 但合并不是终点。Chad 说关键是既建统一视图、又能回写(write back,把本体里做的改动写回源系统)——这是「双向」的一环:「我得能搞清楚这条数据是从哪儿来的、来自哪个系统、这个 ERP 属于哪个地区、哪个区域、哪个业务单元」。所以合并的同时必须保住血缘(lineage,数据一路怎么流过来的)和出处(provenance,最初来自哪里),数据才真正能驱动行动(actionable)。→ 详细
08
action 层的两个反模式:action 泛滥(一个字段一个动作)与金锤子(什么都拿 action 改)
- 对象重复,动作就跟着成倍重复:三个都代表客户的 object type,每个上面都要一个或三个 action 去改姓名、邮箱、电话,「这就让要建的资源成倍增加」。Jonas 把这叫 action sprawl(action 泛滥),分两个角度:① 同一实体上 action 被重复建好几份,仅仅因为 object type 本身是重复的;② 一个属性一个 action——一个只改姓名、一个只改邮箱、一个只改电话。正例:合成一个 「更新客户联系信息」,三项一起改。→ 详细
- golden hammer(金锤子)反模式,出自「手里只有锤子,看什么都像钉子」:放在这个例子里,你可能根本不该用 action 去更新联系信息——这些信息不常变,而且「是在源系统里维护的,源头就在源系统」,更合适的是用一条 pipeline(数据管道,定时把源系统数据自动灌进本体),而不是每次一有变化就去执行一次 action。客户身上别的属性,才是真该用 action 去改的。→ 详细
09
为什么 action 必须建对:建模的是业务流程,而且 agent 要跟人一起在本体里干活
- Chad 借此纠正一个普遍误解:「很多人一想到建模就以为是数据建模,但我们建模的其实是业务流程:数据、逻辑,还有 action。」 action 这块之所以重要,「不光是为了人这一侧」——在 agentic(由 AI agent 自主执行任务)的世界里,「我也需要 agent 能在恰当的层面上去理解」,以及「我到底要怎么真正把事办成」。→ 详细
- 他把问题落到一句话上:要把 action 做对、映射对,「我就得去想:agent 在这个环境里、在本体里,到底怎么跟人类并肩工作?」——「我得有这些说得通的 action,agent 才能替我去干活。」(Palantir 内部怎么把本体动作开放给 agent,见同组 208 Ontology MCP;agent 为什么需要本体见 ep98。)→ 详细
- Jonas 补的是「让人看得懂」这一面:为了驱动决策,action 的名字要让人一看就明白它干什么——「更新客户联系信息」谁都懂;如果某个 action 只改一个属性、而那个属性名字还挺晦涩,「那最好给它写一段真正清楚的描述」,让所有人一看就懂。→ 详细
10
interface 用对的样子:既然工作流真不同,就改名成「销售线索 / 支持联系人 / 计费账户」,再链回同一个 Customer
- Chad 讲 interface 的价值:它让销售、支持、计费「各自去实现『客户』」——既有灵活性,又保留了「客户是什么」的统一标准视图。这对「在整个本体里做到高复用」很关键,因为「本体正是会产生复利的资产」,「要从本体里拿到最大价值,靠的就是不重复自己」,这样「下一个用例只需要往上做加法」。→ 详细
- Jonas 随即给了个很细、很实用的修正:如果真的因为围绕这些对象的工作流根本不同而决定走 interface,那三个对象就不该还叫「某某客户」,而大概应该叫 sales lead(销售线索)、support contact(支持联系人)、billing account(计费账户),然后另外建一个 Customer 对象,跟它们链接起来——理由是这样的名字「更能说明它在工作流里的用途」。换句话说:名字里带「客户」却不是客户本体,本身就是信号不对。→ 详细
- Chad 对这一来一回的评价是「一个很好、很丰富的反馈回路」:能看出你得怎么真正把这些东西拆解开,看清现实世界在这里面究竟是怎么运转的。→ 详细
11
原则③开闭原则:新对象先是 experimental,上了生产才设成 active——active 就「对修改关闭」
- 开闭原则的原意是「对扩展开放,对修改关闭」。落到本体上,Jonas 讲的第一个机制是对象的生命周期状态:新建的 object type 默认是 experimental(实验),准备好上生产了,再把它设成 active。→ 详细
- 这个状态是本体里的一个标记,同时承担两件事:① 声明「这个 object type 已经在生产环境里被使用了」;② 给开发者一个信号——「这里我得小心,不能说改就改」。它和「对修改关闭」配合起来就是一条设计要求:既然已经有了现成的东西,设计时就应该让 active 状态的 object type 不需要太多改动。→ 详细
12
开闭原则的反例与正例:设备要做认证——别往设备对象上加属性,新建「设备认证」对象挂上去
- 例子:已有一个 equipment(设备) 对象,来了个新用例「需要给设备做认证」。反例是图省事——把认证信息拿过来,当成新属性直接加到设备对象上(等于去改一个已经 active 的对象)。→ 详细
- 正例是新建一个 equipment certification(设备认证) object type,链接到设备对象上,再配一个 interface。Jonas 顺着问「说到设备,它还可能是什么?」——比如一辆卡车,某天也需要认证,那这个认证对象将来也能链接到一个(假想的)卡车对象上。通过 interface,他就能设计自己的 action(他也管这部分叫 kinetics,即让本体「动起来」的那一层)去真正处理认证、驱动决策,「而不是去改动一个已经在生产环境里、已经『关闭』的 object type」。→ 详细
- Chad 补的是治理代价:理念就是对组件做加法,用新的 object type 去扩展。「当然,我们有 global branching(全局分支:像写代码开分支一样,先在分支上改、确认无误再合回主线),有各种安全护栏和变更管理机制」——但即便如此,从本体的某个类型里删掉一个指标或字段,还是会产生连锁反应,「你可以这么干,但无论从时间还是额外开销来看,代价都会非常高」。所以要让新能力以加法叠上去,「大家可以逐步用起来,而不是我把现有生产对象上的一切都搞坏」。(分支-评审式治理的系统讲法见同组 211。)→ 详细
13
不守开闭原则的下场:设备对象越扩越胖,又长回上帝对象
- Jonas:如果不新建认证对象,而是往 object type 里塞进多个实体——所谓「过载」(overload)——就又回到上帝对象反模式。设备对象本来只代表一台设备,「可随着用例越来越多、你对本体扩展得越来越多、往单个对象上加的信息越来越多,我们就会得到一个过载的大 object type」。解药绕回第一条原则:用领域驱动设计让一切保持聚焦、易懂,「link 和 interface 在这方面能帮上大忙」。→ 详细
- Chad 说这个反模式他「每次聊到都忍不住笑,因为实在见得太多了」:「就是不停往里堆新东西,因为这是把东西加进去最省事的办法。结果很快,非常快,就变得难以为继。」——上帝对象很少是一次设计失误,而是一次次「图省事」的加字段累积出来的。→ 详细
14
Chad 的自我定位:本体设计原则落在「维度数仓」和「面向对象」之间的中间地带
- Chad 注意到这些原则「很多都跟面向对象的原则非常像」(面向对象:软件工程里把数据和操作它的方法打包成「类」的思路),他在这里看到的是一种融合——既有维度建模数仓那一套,也有面向对象那一套。→ 详细
- 本体驱动设计「差不多就落在两者中间」:一半是设计现实世界如何运转的数据和结构(数仓那半),一半是「围绕 action 和其他组件,为我想在对象上做的事去设计方法和类」(面向对象那半),并让它们「在各处都能复用、可持续」——这帮他理清「什么时候该拆开,什么时候该复用」。这等于正面回应了 207 老冯「数据库建模换皮」的指控:Palantir 自认是数仓 + 面向对象的混血,而不是纯数据建模。→ 详细
15
原则④组合优于深层级:场馆 / 办公楼 / 餐厅别挂成一条父子长链,用一个 building interface 共享行为
- 什么叫深层级:多个互相链接的 object type,「实际上构成的是一种父子关系,而且这条链非常长」。图例是 Arena(场馆)、Office Building(办公楼)、Restaurant(餐厅)三个对象,上面挂一个 Building(建筑),再往上还有别的实体。代价有两条:① 实际用的时候「还得穿过一大堆 link」;② 想改点什么,「很可能迟早得把整条链都改一遍」。→ 详细
- 正例是用 interface 替代长链。场景:想给各种建筑排期(schedule),但行为因建筑而异——场馆排的是活动档期,一般也就一两天;办公楼也能排期,但按租期来,时间长得多。这些实体共享着各种属性,而且「从根本上说,它们可以继承同一套行为」,interface 就能做到,而且「不需要为此建太多不同的 action」。→ 详细
- Jonas 给了最简版本:三种建筑实际用法大概各不相同,但通过 building interface 共享地址、面积(square footage)这类属性,于是能对三种类型都用 action,「我们只需要一个作用于这个 interface 的 action 就行」。→ 详细
- Chad 点出这背后的治理含义:「想要长期治理好、管好这套东西,就意味着我得真正把这些原则用起来」——要是每个对象都单独建 action,「这种泛滥很快就会失控,我觉得这也正是大家在企业里通常会看到的情况」。→ 详细
16
Chad 的 BOM 类比:预先定义好的层级,「在我发布的那一刻就已经过时了」
- Chad 由此联想到制造业天天在做的 BOM(bill of materials,物料清单:造一个产品要用哪些部件、每个部件又由哪些子件组成的层级树),抛出一个建模选择题:「我到底该不该把物料清单、把生产这个产品所需的各级层级,全都明明白白地列出来?」→ 详细
- 两条路:A,把「我 BOM 树里本质上需要搭建出来的全部层级」一层层铺成对象;B,「用一个对象来描述它,让我能把这种 BOM 展开(explode,把层级树按需逐级拆开),然后以一种不预先定义的方式去用」。他站 B:组合「特别说得通」,相比之下预先定义好的深层级「更新很难管理,长期用起来也费劲」。→ 详细
- 本期最硬的一句:「如果我想预先定义一套层级,那它在我发布的那一刻就已经过时了。」而靠组合、靠搭建 object type 的方式,才能「随着时间推移保持灵活」。Jonas 当场接:「对,对,就是这样。」→ 详细
17
深层级链上最糟的反模式 misnomer(命名失当):「物品」「实物资产」——读完名字也不知道它是什么
- 反例链:Item(物品)/ Physical Asset(实物资产)→ Building(建筑)→ Schedulable Building(可排期建筑)→ Arena(场馆),一条从抽象到具体的父子关系链。链头暴露的问题叫 misnomer(命名失当):「光读这个名字,我其实根本不知道它到底是什么,因为它可以是任何东西:可以是设备,可以是库存,也可以是一栋建筑」——所以「需要好的命名规范」。→ 详细
- Jonas 明确说「在我看来,命名失当是最糟糕的反模式之一」,理由分两层:① 对人——「用它的人会产生错误的理解,还得去找其他同事收集一大堆信息,才能用好这些 object type」;② 对 agent——「如果命名规范很差,property 或 object type 又缺少描述,agent 就没法理解,做出的决策也会更差」。→ 详细
- 落到生产:无论是在 object type 之上搭一个好用的 agent,还是在平台内部让 AI FDE(转写听作 AI FTE;片中指 Palantir 在平台里协助构建的 AI)来帮着搭,「这些描述、以及把所有东西都命名对,在生产中都大有帮助,能提供正确的上下文,而这正是本体的一大差异化优势」。换句话说:名字和描述不是文档洁癖,是 agent 的输入。→ 详细
18
LLM + 本体才是魔力所在:大模型没用你公司的数据训练过,本体负责把「事实」表达好
- Chad:「我们常说 LLM 加上本体,才是真正的魔力所在。」理由很朴素:「这些 LLM 并不是拿你们企业的数据训练出来的」,所以得由本体把「事情实际是怎么运转的」这层上下文表达出来,模型「才能在此基础上有效地推理」。→ 详细
- 反面是报表思维留下的遗产:「我自己造出一套伪系统式的层级语言,只为了让一个能层层下钻的报表结构跑起来——那其实根本不是真实世界的运转方式」,而且它更绕,「反而会让人和 agent 都更难拿到正确答案」。正面是「一套恰当组合起来的层级和关系」——人能拿到答案,agent 也能理解本体、「真正在本体里导航」。→ 详细
- Chad 用一问一答收束全期:「我要怎么确保 LLM 和 agent 是锚定在事实之上的?——那我就得用最好的方式把事实表达出来。」他说「这正是我们今天在聊的东西」。→ 详细
20
收尾|四条原则只是「起步材料」,怎么治理还得去翻新文档里的例子
- Chad 盘点:四种设计模式、一堆反模式、不少具体例子;官网刚更新了新文档,会放链接,可能再附上 Palantir 的 ontology explainer(本体讲解)。他说这期对每个来问「本体该怎么建模?又该怎么治理?」的人都会有帮助,但「这些只是帮大家起步的一些入门材料」。→ 详细
- Jonas 收尾时专门推荐文档:「这方面的文档写得非常好,对理解这些内容很有帮助」,而且「里面还有很多例子」。→ 详细