当本体遇上 Agent:不只是推理,更是企业语义基础设施
开始前我们依然强调同一个问题:本体不是另一个数据库,它是 Agent 与企业系统之间的业务语义层。
数据仍然在数据库、ERP、CRM、物流系统里;本体负责告诉 Agent:这些数据在业务上叫什么、有什么属性、彼此是什么关系、有哪些业务规则、如何与真实数据映射等。
上篇介绍的是“Agent 如何按规矩判断”;本篇更侧重“Agent 如何沿着正确的业务关系和数据结构行动”。前者偏推理,后者更偏语义基础设施。
01 多关系传递:让 Agent 看见隐含影响链
让我们假设一个企业客户问题。业务人员问 Agent:
“张总集团有哪些关联公司?如果集团本身有逾期风险,哪些子公司也要纳入审查?”
这个问题听起来像查组织树。但一个是:控制关系可以穿透,担保关系却不能随便传递。如果这两类关系混在一起,结论很容易错。
控制关系传递:张总芯片没有直接风险标记,但上层集团有风险,因此进入集团风险关注名单。
担保关系不传递:张总物流担保客户,但不能推出“张总科技也担保外部客户 A”这类错误结论。
关系类型 | 是否传递 | 业务含义 |
|---|---|---|
controlledBy | 是 | 子公司受母公司控制,孙公司也受集团控制。 |
belongsToGroup | 是 | 公司归属集团,集团层级可以继续向上归并。 |
guaranteesFor | 否 | A 担保 B,B 担保 C,不代表 A 自动担保 C。 |
本体的做法是把关系语义直接声明出来。Agent 不需要自己猜,推理机也不会把不该传递的关系错误传递:
# 控制关系:可传递 classcontrolledBy(ObjectProperty, TransitiveProperty): domain = [Organization] range = [Organization] # 集团归属:可传递 classbelongsToGroup(ObjectProperty, TransitiveProperty): domain = [Organization] range = [Organization] # 担保关系:不可传递,故意不加 TransitiveProperty classguaranteesFor(ObjectProperty): domain = [Organization] range = [Organization]更关键的是:分类规则也可以依赖这种传递关系。
比如“集团风险主体”不一定是自己有逾期的公司。某家子公司本身没有风险标记,但它受一个高风险集团控制,也应该进入授信审查范围。定义这种分类规则:
class GroupRiskEntity(Organization): equivalent_to = [Organization & ( hasRiskFlag.value(True) | controlledBy.some(hasRiskFlag.value(True)) | belongsToGroup.some(hasRiskFlag.value(True)) )]你可以对此规则进行代码验证,具体方法参考上一篇的推理机分类。
本体推理和普通递归查询的关键差异在于:递归 SQL 更像告诉数据库“按这个过程查下去”,如果关系复杂,需要各种JOIN和拼接;本体则是告诉系统“这些关系在业务上具有什么性质”。当分类规则还依赖传递结果时,本体的表达会更自然。
变化 | 递归 SQL | 本体推理 |
|---|---|---|
新增可传递关系 | 新增递归查询 | 声明 TransitiveProperty |
区分不可传递关系 | 额外排除,容易遗漏 | 默认不传递 |
分类依赖闭包 | 递归、JOIN、CASE 拼接 | 一条 equivalent_to 定义 |
02 Schema 映射层:消除硬编码表名列名
再看一个企业级的系统问题:数据库 Schema 变了。
例如客户等级原来叫tier,后来 DBA 规范命名,改成customer_level。如果 Agent 工具里写死 SQL,所有引用这个列名的地方都会出错或者遗漏修改。
本体可以在业务概念和数据库实现之间增加一层映射:Agent 只说业务语言 — “客户等级”,不直接碰数据库列名。
在本体中定义:
# 在本体中声明映射关系(AnnotationProperty) with onto: classmapsToTable(AnnotationProperty):pass # 类 → 数据库表 classmapsToColumn(AnnotationProperty):pass # 属性 → 数据库列 # Customer 类映射到 onto_customers 表 Customer.mapsToTable = ["onto_customers"] customerTier.mapsToColumn = ["tier"] # 业务概念 → 实际列名 customerRegion.mapsToColumn = ["region"]查询引擎读取这些注解,把业务概念翻译成真实表名和列名。
def build_mapped_query(class_name, property_name, value): cls = onto.search_one(iri=f"*{class_name}") prop = onto.search_one(iri=f"*{property_name}") table = cls.mapsToTable[0] column = prop.mapsToColumn[0] returnf"SELECT * FROM {table} WHERE {column} = %s", [value]这样一来,Agent 代码里出现的是customerTier,而不是tier。
明天列名从tier改成customer_level,只需要改本体注解,Agent 工具不用改。
当然,你可能会说,我现在系统也有很完善的数据层的映射方案,足够应付这样的schema变更情况。
但这个模式的价值并不仅限于防改名。当你有 10 个 Agent 访问同一个数据库,或者同一个 Agent 需要对接多个数据源时,本体作为中间翻译层的价值会放大 — 把"内部实现"和"业务契约"彻底解耦。
进一步:跨系统客户全视图
Schema 映射解决的是改名的问题。不过,企业里还有一个更麻烦的问题:同一个客户散落在多个系统里。
比如“张总”这个客户,在 CRM、ERP、物流系统中可能有不同表、不同字段、不同名称表达:
系统 | 表名 | 名称字段 |
|---|---|---|
CRM | onto_customers | name |
ERP | onto_erp_contracts | legal_name |
物流 | onto_logistics_recipients | recipient_name |
当业务人员说"帮我查一下张总的信息",他可能期望的是看到所有系统中跟"张总"有关的完整信息。但传统做法每接入一个新系统,就要改代码。
这本质上还是一个"知识该存在哪里"的问题 — "客户名称在 CRM 系统的 name 列、在 ERP 系统的 legal_name 列"这个知识,应该写在代码里吗?
本体方式是让每个系统在本体中“自报家门”:
查询引擎的逻辑变成了:扫描本体中所有标注了mapsToNameColumn的类,对每个类查对应的表,用名称列做模糊匹配。Agent 代码不需要知道有几个系统、每个系统的表叫什么、名称字段叫什么 —— 这些全部由本体告诉它。
这个模式的价值不只是能查多个系统,重要的是:新增系统时,改的是本体上的注册信息,而不是 Agent 代码。
把 Schema 映射和跨系统全视图放在一起看:
前者解决的是"列名变了怎么办"(纵向解耦),后者解决的是"新系统来了怎么办"(横向扩展)。两者都依赖同一套本体注解机制(AnnotationProperty),组合起来就构成了一个完整的抽象层— Agent 代码只跟业务概念打交道,不接触底层的实体表名、列名和系统边界。
03 语义查询引擎:从 N 个工具到一个引擎
最后一个场景,是很多企业 Agent 很快会遇到的问题:工具(Tools)爆炸。
“查 VIP 客户”写一个工具,“查待处理订单”写一个工具,“查华东区域处理中订单”再写一个工具。业务需求越多,工具越多,提示词越来越长,维护越来越难。
本体的另一种用法,是把查询逻辑声明在本体里。Agent 只调用一个语义查询引擎,由引擎读取本体概念,再生成查询动作。
本体中定义查询相关概念:
...with onto: class queryFilter(AnnotationProperty):pass # 过滤条件 class queryJoin(AnnotationProperty):pass # 跨表关联 # 定义"VIP 客户"概念 class VIPCustomer(Thing): queryFilter = ["customerTier=VIP"] # 过滤: tier = 'VIP' # 定义"待处理订单"概念 class PendingOrder(Thing): queryFilter = ["orderStatus=pending"] # 定义"VIP 客户的待处理订单"—— 跨表组合概念 class VIPPendingOrder(Thing): queryFilter = ["orderStatus=pending", "customerTier=VIP"] queryJoin = ["Customer:orderCustomerId=customerId"] # JOIN 条件这里的VIPPendingOrder等不是一个新工具函数,而是一个本体概念。它声明了两个过滤条件和一个关联关系。
然后只需一个通用的语义查询引擎,读取概念的注解,“翻译”生成对应的 SQL:
def build_semantic_query(self, concept_name: str) -> tuple[str, list]: """读取本体概念的注解 → 自动构建 SQL""" concept = self.onto.search_one(iri=f"*{concept_name}") filters = concept.queryFilter # 从注解读取过滤条件 joins = concept.queryJoin # 从注解读取 JOIN 条件 # 通过 Schema 映射解析真实列名(复用案例三的能力) # ... 自动构建 WHERE 子句和 JOIN 子句 return sql, params现在,新增查询类型时,传统方式需要写代码、测试、部署;语义方式只需在本体中添加一个类 + 几行注解,引擎自动识别并执行。
很显然,Agent 的工具箱变薄了,但语义层变厚了,这正是本体的意义 — 当需求增长时,传统方式可能是增长代码,而现在主要是增长本体模型。
04 三种能力放在一起看
如果把上下篇连起来看,本体在 Agent 系统中已经有多种不同用法。上篇讲的是规则判断与智能分类;下篇的三类能力更像语义基础设施。
所以,正如上篇我们说过 — 本体并不等于“每次都启动推理机”。
在企业系统中,规则和多关系传递使用推理;映射、注册、查询概念则更多是轻量查阅。本体是统一语义层,推理机只是其中一类使用方式。
05 系列最后:本体不是万能神器
到这里,“企业级本体应用入门“这个小系列可先告一段落。
第一篇我们理解了本体的概念和核心积木;第二篇用 Protégé 构建了一个业务本体;第三篇分上下篇,把本体放进 Agent 系统,看它如何参与规则判断、关系推理、数据映射和语义查询。
如果只记住一句话,可以是这句:本体不是替代数据库,也不是替代大模型,而是让 Agent 能够按企业自己的业务语言行动。
本体落地,可以从哪里开始?
更现实的路径不是先做一个“大而全”的企业本体,而是挑一个边界清楚、收益可验证的本地场景:比如订单规则判定、客户跨系统全视图、供应链影响链分析,或者一个高频语义查询入口。
最后必须说的是:本体不是万能神器。
因为我们也听到一些大型企业的客户在推动或者尝试本体。本体很强大,但实施难度也很大。难点不在 OWL 语法,而在业务建模:概念口径谁来定?谁来梳理这些复杂的关系与规则?规则冲突谁来裁决?更具体一点的问题比如,Schema 变更谁来维护映射?推理性能如何控制?本体版本如何治理?等等。
这些问题没有解决前,一拥而上会把本体做成另一套没人敢动的“复杂资产”。
最后的建议
从一个小闭环开始:一个业务问题、一套有限概念、一个可运行 Agent、一个能被业务验证的结果。先证明本体能带来可解释性、可维护性和跨系统复用,再逐步扩展。
本体的见效不会特别快,也不适合所有场景。但在那些规则复杂、系统割裂、语义混乱、解释性要求高的企业 AI 场景里,本体确实值得认真研究与对待。
