我对"本体"这个词有条件反射式的戒备。几年前参与过一个元数据治理项目,第一阶段的目标就是"建立企业统一本体"。一个小组花了好几个月讨论"客户"和"用户"是不是同一个类,"账户"下面挂几层子类,评审会开了一轮又一轮,等类型体系终于定稿,上游业务系统已经改过两次表结构。那套本体到头来成了一份很漂亮的文档,查询链路里一次也没用上。
所以这次在云栖分论坛听到 PPT 上出现 Context Ontology,我的第一反应是:又来了。
分论坛上关于本体的那几分钟
这部分在整场演讲里占的时间不长,夹在"怎么组织数据"那一段里,我是边听边在笔记里画问号记下来的。
讲法大致是:Agent Context 内部有多条解析管线,txt、Markdown、PDF、PPT 这些文档解析之后,和模型配合建知识图谱;在每个知识库上可以一键开启本体抽取,配置一些本体约束规则,这样建出来的图谱他们称为"增强的知识图谱",用讲者的话说,能离业务走得更近一点。产出的知识单元包括两部分,按 chunk 切分的图谱内容,以及存在 OSS 里的原始文档。Agent 获取知识时可以先通过实体找到关联实体,需要的话再下钻到原文,实现渐进式的加载。
PPT 上对 Context Ontology 的定义是一句话:连接业务对象、数据关系与行动规则。另一页画的是语义建模的流程,领域本体负责定义实体类型与关系、统一领域语义,之后是实体识别和关系构建,输出知识单元和实体关系图谱。示例用的是三份材料:一份 PDF 产品手册、一份 PPT 方案介绍、一份 Excel 参数清单,围绕同一个产品把组件、参数和应用场景关联起来,来源能追溯到原始文档。
听完我划掉了笔记上的一个问号。我担心的是那种"先建全企业统一本体"的路子,台上讲的粒度小得多:本体是挂在单个知识库上的开关,约束规则可配,原文另存。
"先找实体、再下钻原文"这个加载顺序我也记了一笔。做数据的人对它不陌生,和先查维表、再按主键去事实表取明细是一个思路。对 Agent 来说,好处是不必一上来把整篇文档塞进上下文:先拿到一小块实体和关系,判断方向对了,再去取需要核对的那一段原文。前提是实体和关系本身靠得住,否则先走的那一步就把方向带偏了。这也是我后面实验里最想检查的地方。
剩下的问号是:这东西在我自己的文档上到底能抽出什么,缺的部分要花多少力气补。
检索那一段讲者也提到了图。Context Engine 把向量检索、全文检索和图关联查询组合在一起,对 Agent 只暴露 search 入口。三路各管一类问题:向量找含义相近的内容,全文精确命中名称、编号和术语,图沿实体和关系找上下游。前两路我在自己的项目里都用过,能解决大部分"找得到"的问题。本体如果有价值,只可能体现在第三路上,也就是那些靠相似度找不到、要靠关系才能连起来的问题。
让我愿意试一试的那类问题
回来的高铁上我想了一个自己工作里的例子,用来判断本体到底有没有用武之地。
"工作区 W 属于团队 T"记在资产表里,"团队 T 的生产环境变更需要二次审批"写在制度文档里,"二次审批通过某个流程发起"又在一份操作手册里。用户问"我能不能直接在 W 上执行这个变更",三段内容和问题的语义距离都不近,向量检索排序时很可能输给一篇泛泛讨论变更规范的文章。全文检索能命中"W",但命中的只是资产表里的那一行。
人读这三份文档时会在脑子里自动补上关系。Agent 拿到三段相似度都不低的片段,往往只是并排放进上下文,指望模型自己推断。推断错了,错误还带着"有出处"的外观,更难被发现。
这类多跳问题需要的连接,我归成三种:
- 业务对象:人、团队、工作区、数据集、规则、任务、工具;
- 数据关系:归属、依赖、引用、负责、适用、替代;
- 行动规则:在什么条件下可以做什么,需要什么权限,应该走哪个流程或用哪个 Skill。
只有对象没有关系,本体就退化成分类目录。有了关系却没有约束,Agent 知道两件事有关联,却不知道这层关联在当前任务中是否成立。至于行动规则,直接写进提示词的话,规则就丢了来源、版本和权限,改一次规则要同步改一批提示词。Context Ontology 那句"连接业务对象、数据关系与行动规则",至少在字面上是把这三块都列进去了。
回来后的实验
我挑了三份和上面那个例子结构一样的内部文档:一份资产归属表导出的 Markdown,一份变更管理制度,一份审批流程的操作手册。都是我自己维护的,内容熟悉,抽得对不对我一眼能看出来。在控制台建了一个测试知识库,开了本体抽取,约束规则这一轮先没配,想看看不加约束时它自己会抽出什么。
上传和检索用的是帮助文档里的命令:
ctxdb kb upload-file onto-test ./docs/asset-ownership.md --wait ctxdb kb upload-file onto-test ./docs/change-policy.md --wait ctxdb kb upload-file onto-test ./docs/approval-runbook.md --wait
处理完之后,我用三个命令从不同角度看结果:ctxdb kb graph 看整体图,ctxdb kb entities 列实体,ctxdb kb relations 列关系。再挑几个关键实体用 ctxdb kb entity-get 看详情,它会带出完整的来源文档,能确认某个实体是从哪份文件里抽出来的。
在这三份文档上,我看到的情况大致是这样:
- 文档内部的对象和关系抽得比我预期的多,资产表里工作区和团队的归属、制度文档里规则和适用范围之间的联系,基本都在;
- 我最想要的那条跨文档的边,"二次审批这条规则适用于团队 T 的工作区",在关系列表里没有找到。制度文档写的是"核心业务团队",资产表里写的是团队的具体名称,两者之间的对应关系本来就只存在于我们几个人的脑子里;
- 有两条关系在我看来表达的是同一件事,名字不一样。这在开放抽取里很常见,没配约束规则时更明显。
缺的那条边我用 ctxdb kb entity-create 先补了一个"核心业务团队"的实体,再用 ctxdb kb relation-create 把它和具体团队、和那条审批规则连起来。做完之后再搜:
ctxdb kb search "能不能直接在工作区 W 上执行生产变更" --kb=onto-test --top-k=6 --verbose
返回的 chunks 每条带 doc_name、kb_id、doc_id,能顺着找回原文。有时还会多一段 graph_context,里面能看到沿关系展开的实体。这一段带着 citation_eligible: false,意思是它只能帮助理解,不能直接当引用证据。我一开始觉得这个限制有点保守,想了一下又认为是对的:我手工补的那条边,本身就没有一份原始文档能证明它,它反映的是我的理解。图谱帮 Agent 想到"该
去看审批规则",依据还得是 chunks 里的原文。
我还用 ctxdb kb entity-chunks 看了几个实体分别挂在哪些 chunk 上。这个视角对我判断切分是否合理很有帮助:在我这份资产表上,一个工作区对应的 chunk 同时带着好几个工作区的行,说明表格被切得比较粗,以后这类文档我会考虑先按工作区分成几份再传。这算是本体实验的一个副产品。
和自建方案放在一起比
做数据的人很自然会想,这套东西我自己能不能搭。能搭,常见的组合是向量库加一张实体与关系的元数据表,或者直接引入图数据库。我以前做过前一种,实体表靠规则从文档标题和表头里抽,关系基本靠手填。
和 Agent Context 这套比,存储选型反倒是次要的。我在意的差别有两处:抽取有没有约束,原文能不能回溯。自建方案里,约束一般写死在抽取脚本里,改一次 schema 要重跑整批文档;原文回溯要自己维护 chunk 到文件的映射,文件更新以后映射很容易错位。托管方案把约束做成知识库上的配置,原文放在 OSS、通过 kb entity-get 能追回来源文档,这两件事省掉的是我最不想写、也最容易写错的那部分代码。代价是抽取逻辑对我不透明,抽错了我只能补和改,很难去调它的抽取过程。
建模成本,我自己算的账
这次实验让我对"本体"的成本有了具体的感受,记在这里,免得以后被自己的乐观骗了。
三份文档,自动抽取花的是机器时间,我花的时间主要在两件事上:读实体和关系列表,判断哪些对、哪些重复、哪些缺;再就是想清楚那条缺失的边应该怎么建,"核心业务团队"该是一个实体还是团队的一个属性。后者花得更久,而且和工具无关,是我们自己的业务定义从来没写清楚过。三份文档我前后用了一个晚上,其中大半时间在和一个同事确认"核心业务团队"到底包括哪几个团队,他给的名单和制度文档里的描述还对不上,暂时以他的说法为准,制度文档那边我记了一个待修订。
如果扩到几百份文档,读列表这件事就不可能逐条做了。我能想到的办法是只盯几类高频任务:先围绕它们定一个很小的 schema,用约束规则限定抽取的实体类型和关系名称,schema 之外的内容暂时当普通文本,靠向量和全文检索去找。这和知识图谱领域常说的两种起点是一个折中。先定 schema 再抽取,质量稳但启动慢,我那次元数据项目就死在这里;先开放抽取再归并,启动快,但关系名会五花八门,我这次已经看到苗头。
关系的定义我打算给自己定几条规矩:数量少而稳定,名字要能回答一个明确的问题,比如"由谁负责""适用于哪个范围";同义关系要归并,方向写清楚。"负责"和"归属"如果被两次抽取分别拿来表达同一件事,图上就会有两条含义重叠的边,后面谁也说不清该信哪条。
还有一个判断建模是否过重的信号:每新增一个业务字段,都要改一批类型和关系,多半就是建得太细了。业务系统一年改几次字段,过细的类型体系很快会大量失配,维护本体本身成了新的负担。
怀疑还剩多少
剩下的部分,我分成能接受的边界和还没解决的问题。
能接受的边界有两条。本体能表达行动规则,但不负责执行,事务、补偿、权限和业务校验还在编排层和业务系统里,这一点讲者和材料都没有越界。关系图也消除不了事实冲突,两个来源对同一个对象给出不同状态时,应该保留双方来源和版本,走评审流程判断;帮助文档里写了知识冲突不会自动覆盖,这和我的期望一致。
还没解决的问题主要是手工补的部分。我建的实体和关系,来源该怎么记录,过半年谁还记得这条边是我在某个周四晚上根据口头约定加的?自动抽取的实体能通过 kb entity-get 追到原始文档,手工建的就只能靠我自己写清楚。我想到的办法是专门写一份"团队与业务范围对照"文档上传进去,让这条关系也有一份原文可以追溯。这样做的副作用是又多了一份要维护的文档。
另外,约束规则这一轮我没有配,配了之后抽取结果会规整到什么程度,那两条同义关系会不会合并,我还不知道。
权限是另一个没验证的点。知识库有 kb acl 一组命令管理访问权限,我关心的是关系层面:用户对制度文档没有权限时,沿着图走到那条审批规则的路径是否也随之不可见。从数据工程的习惯看,权限只控制到文档、不控制到派生出来的实体和关系,是最容易出现的漏洞。我打算下一轮建一个只读用户,收掉它对制度文档的权限,再用同一个问题去搜,看 graph_context 里还会不会出现那条规则。
接下来先看错误样本
我不打算马上在团队的正式知识库上开本体。判断要不要建,我想先看错误样本。也有一些知识库我已经确定不需要:团队那个部署 FAQ 库,条目之间几乎互不相关,问题基本是单跳查询,答案在一段文本里就能找全,全文加向量检索、再加上来源和版本这些元数据已经够用。在那里开本体抽取,多出来的抽取和维护成本换不回相应的召回收益。
接下来两周,我会把团队内部问答 Agent 答错或答得不完整的问题收集起来,逐条判断失败原因属于哪一类。如果失败主要集中在"相关材料都找到了,却没能连起来",本体就该投入,而且应当从错得最多的那两三类任务开始,先配约束规则,再看图上还缺哪些边。如果失败主要是材料缺失或者过时,那该补的是数据源和同步链路,本体帮不上忙。
验证有没有起作用,我也不打算看图上有多少节点和边。我会用几个能直接检验的问题:给定一个工作区,Agent 能不能找到适用的规则;能不能说出这层关系来自哪份文档;规则文档被删除或修订之后,挂在它上面的上下文能不能被识别出来。图上如果出现大量没有任何边的孤立实体,我会先回头检查关系定义有没有覆盖任务关心的联系,再去考虑换模型或调参数。
至于"本体"这个词,我还是不太喜欢。但这次的版本至少允许我从三份文档、一个开关、两条手工补的边开始。