本体如何参与 Agent 的决策
从一顿晚餐,看懂本体、事实、行动规则与模型怎样共同决定下一步。
文章目录 · 7 章 · 约 42 分钟
“朋友今晚来吃饭,只用植物食材,帮我安排菜单,再看看还要买什么。”
AI 很容易列出几道菜。但一道“蘑菇汤”可能用了牛奶,合适的菜也未必有足够食材。程序要查清菜谱、食材和库存之间的关系,才能给出可用的购物清单。

这篇文章讲的是:把对象及其关系明确写出来之后,Agent 怎样利用它们决定下一步。 这套关于对象、类别和关系的定义,称为“本体”。它会参与资料查询和动作筛选,而模型仍负责理解请求、处理疑问、选择下一步操作。
一、先把关系说清楚
三元组是表达格式,本体提供语义定义,实例是被描述的具体对象,知识图谱把定义和事实组织起来。 下面用晚餐系统中的同一套对象,逐步写出这些定义和事实。
1.1 先确定:我们管理什么对象
晚餐系统管理的是食材目录中的条目。牛奶条目和豆腐条目各有固定标识,菜谱和库存都引用这些标识。“牛奶条目”表示目录里的一个食材种类;库存记录保存它当前有多少。
这套模型有三种基本类别:菜谱、食材种类、库存记录。对应的实例分别是配方A、牛奶条目、库存K。
为了按来源筛选目录条目,我们再定义植物来源食材种类、动物来源食材种类;后者包含乳制品种类。这些名称都指一组食材目录条目。下文的“乳制品种类”始终是类,“牛奶条目”始终是实例。
1.2 都是三元组,含义有什么不同
采用 RDF 时,可以把知识写成“主语—谓词—宾语”。下面使用简化的中文标识,保留标准谓词名称;每一行都是一条三元组。
先写本体定义:有哪些类,它们怎样包含彼此。
食材种类 rdf:type owl:Class
动物来源食材种类 rdfs:subClassOf 食材种类
乳制品种类 rdfs:subClassOf 动物来源食材种类
植物来源食材种类 rdfs:subClassOf 食材种类owl:Class表示类;rdfs:subClassOf表示子类关系。“乳制品种类是动物来源食材种类的子类”,意思是:归入前者的每个目录条目,也归入后者。
再写实例事实:具体有哪些条目,哪份菜谱引用了它们。
牛奶条目 rdf:type 乳制品种类
豆腐条目 rdf:type 植物来源食材种类
配方A rdf:type 菜谱
配方A 所需食材种类 牛奶条目
库存K rdf:type 库存记录
库存K 对应食材种类 豆腐条目
库存K 可用数量 0
库存K 计量单位 "盒"牛奶条目 rdf:type 乳制品种类把一个实例归入一个类;它与上一组的“类包含于类”不同。结合两组数据,程序可以推导:
牛奶条目 rdf:type 动物来源食材种类这里的推导来自明确的类别关系。rdf:type也用于前面的类声明,所以不能仅凭出现了这个谓词,就把整条语句判为业务数据;要看它是在声明类,还是给业务对象归类。RDF Schema 对类型和子类的定义说明了这两种关系的语义。
本体还要定义所需食材种类这个关系的含义:从一份菜谱指向它使用的食材目录条目。配方A 所需食材种类 牛奶条目则是对这份配方的具体陈述。两者分别负责定义关系和使用关系记录事实。
关系定义也可以写成三元组
下面继续使用相同的对象粒度,省略前缀声明。domain说明关系主语的类别,range说明宾语的类别。
菜谱 rdf:type owl:Class
库存记录 rdf:type owl:Class
所需食材种类 rdf:type owl:ObjectProperty
所需食材种类 rdfs:domain 菜谱
所需食材种类 rdfs:range 食材种类
对应食材种类 rdf:type owl:ObjectProperty
对应食材种类 rdfs:domain 库存记录
对应食材种类 rdfs:range 食材种类RDFS 的 domain 和 range 用于推导类型。输入是否缺字段、数量是否有效,由数据校验程序检查并决定接受或拒绝。
1.3 本体和实例,怎样组成知识图谱
把这些三元组连接起来,就能从配方A找到牛奶条目,再找到它所属的类及上级类;也能从豆腐条目找到对应的库存记录。这种按对象及其关系组织知识的方式,就是这里所说的知识图谱。
知识图谱可以同时包含本体定义和实例事实,本体文件中也可以写实例声明。 阅读三元组时,要看它在定义类别与关系,还是记录具体对象的事实。工程上常把定义与业务数据分开维护,便于分别更新和核验。OWL 2 入门文档也说明本体可以包含具体对象的知识。
在本例中,开发者和领域人员核对类别及关系定义;菜谱资料提供配方事实;库存查询提供数量、时间和版本。模型可以帮助提取信息,来源与归类仍需核实。
两层之间,最常见的连接是实例归类,但也可以有其他业务关联。 为了保存用户要求,我们增加饮食要求这个类,并创建本次饮食要求这个实例:
本次饮食要求 rdf:type 饮食要求
本次饮食要求 禁止类别 动物来源食材种类第一条回答“这个对象是什么”;第二条回答“这个要求针对哪一类食材”。禁止类别是本文自定义的业务关系,宾语引用一个类别标识。程序据此找到要检查的类别范围。
规则程序读取这项配置,查找菜谱所需条目是否归入被禁止的类,包括经子类关系推导出的归类。牛奶条目属于乳制品种类,而后者是动物来源食材种类的子类,因此配方 A 会被排除。这里的两个步骤是记录禁止哪类,再按配置执行检查。
因此,读跨层关系时要看箭头上的谓词:rdf:type给实例归类;禁止类别指定检查范围。业务对象还可以引用类别来表达适用范围或检查目标,并由对应的查询和规则处理。
实现时,怎样处理“实例引用类别”?
上面的第二条是应用层建模示意。普通实现可以把目标类的标识保存在配置里,由查询程序读取并查找该类的实例。筛选时需要包括子类推导,不能只做字符串相等比较。
若采用 OWL 2 DL,对象属性连接的是个体。可以用 punning,让同一个标识分别作为类和个体使用;但这两个视角在直接语义下分别解释。禁止类别指向其个体视角,不会自动把类的全部成员变成被禁止的对象。仍需应用查询或明确的规则把配置与类别成员联系起来,见 OWL 2 的元建模说明。
1.4 加上任务规则,才能决定选不选
三元组告诉我们配方 A 需要牛奶条目,并能推导它属于动物来源食材种类。是否排除这份配方,还取决于用户这次的要求。
针对“只用植物食材”,我们写出三个分支:
- 已核实某个所需条目不符合植物食材要求,就排除配方。
- 配料表不完整,或某个条目的来源分类未核实,就继续查询,暂不通过。
- 配料表完整,且所有所需条目都已核实符合要求,才加入候选菜单。
因此,排除蘑菇汤的过程是:查到配方 A 需要牛奶条目 → 根据类关系推导它是动物来源 → 按本次任务规则排除。 若用户接受蛋奶,筛选条件可以改变,牛奶条目的来源分类无需改变。
这三条是任务规则,可以由普通代码执行。前面的子类推导属于语义推理;保存清单、下单等动作还需要各自的执行条件。
1.5 实操方法:什么时候建成类
“一个概念会被多个对象当作标签使用”,可以帮助发现候选类。下一步要检查:这个标签是否有明确的归类含义,以及系统是否需要利用这种归类。 单个类只是本体的一部分;本体还包括关系定义和逻辑约定。
可以按下面四步决定。本例的取舍参考了 Ontology Development 101先确定用途与问题、再组织类和实例的做法。
- 先写要回答的问题。 例如“配方 A 用了哪些食材种类”“哪些不符合本次要求”“每种还缺多少”。这些问题决定需要什么信息。
- 确定独立标识代表什么。 本例一个食材标识代表一个目录条目。若以后管理每盒食材的保质期,需要另建实物对象,为每盒实物分配独立标识,再关联到原来的目录条目。
- 检查归类和继承是否成立。 乳制品种类可以容纳牛奶、奶酪等条目,并定义为动物来源食材种类的子类。检验子类时,问“前者的每个实例,是否必然也是后者的实例”。
- 放入真实样例,核对查询结果。 加入牛奶、豆腐及来源未知的条目,检查系统能否分别排除、通过来源检查和要求补查。不能因为没有动物来源记录,就自动判为植物来源。
例如,“本周推荐”也会贴在很多菜谱上,但本例只需要显示它,可以保存为标签;可用数量是属性,0是属性值;牛奶条目需要独立标识并被菜谱引用,是实例。如果推荐资格后来有明确条件且参与推理,也可以重新考虑建模。
类可以暂时没有实例,或只有一个实例;实例数量不是硬性门槛。分类含义、对象身份和查询需求,才是本文采用的判断依据。
如果把“牛奶”建成类,可以吗?
可以。若系统追踪每盒实物,可以定义牛奶实物类,让某盒牛奶成为它的实例。那是另一种对象粒度。本文选择食材目录方案:牛奶条目是食材种类的实例,库存记录统计它的可用数量。两种方案的名字、标识和关系需要分别定义。
小型实现可以用配置文件维护概念定义,用数据库或 JSON 保存目录和库存,再用代码完成查询和筛选;采用 RDF 的系统则可将定义与事实分别放在文件或命名图中。
1.6 把定义、事实和规则放在一起
这张图按知识关系布局,上部是本体中的类,下部是具体实例。沿箭头可以读出“主语—关系—宾语”。蓝色实线是子类关系,绿色虚线是实例归类,绿色实线连接具体事实;橙色线单独表示对类别的业务引用。

规则如何使用这张网:
- 排除:某个所需条目已核实归入禁止类别,就排除对应配方;图中的牛奶条目会使配方 A 被排除。
- 补查:配料表不完整或来源分类未知,继续查询。没有命中禁止类别,只能说明当前没有查到禁止项。
- 通过:完整配料表中的每个条目都已核实符合本次植物食材要求,才加入候选菜单。配方 B 的豆腐条目已符合要求,其余配料也要逐项核实。
二、本体怎样参与一次决策
现在让 Agent 处理开头的请求。这里的 Agent 是能够选择下一步、调用查询或写入功能,并根据结果继续操作的程序。
2.1 把人的话对应到明确的要求
用户说“只用植物食材”,系统将它记录为这次菜单的要求:每道菜所需的食材都必须符合这个范围。若用户只说“素一点”,含义就不够清楚,需要先询问是否接受蛋奶。
模型负责从语言中提出对应的概念,本体提供可使用的类别与关系。模型识别出的结果先作为待确认信息;有歧义时向用户询问,确认后写入任务要求。
2.2 沿着关系查完整资料
Agent 找到蘑菇汤配方,沿“所需食材种类”查询目录条目,再根据条目的类型及子类关系检查来源。配方 A 引用了牛奶条目,该条目可推导为动物来源食材种类的实例,因此不符合本次要求。
这里增加本体的意义,是让“查菜谱”“核对食材类别”“解释排除原因”使用同一组明确关系。即使资料里有“鲜奶”和“牛乳”等不同称呼,也可以先通过核实过的名称映射,找到同一个食材目录条目。
如果菜谱只列出主料,Agent 就继续查询调味料。配料齐全并逐项核实后,才能判断这道菜是否符合要求。
2.3 根据规则形成候选菜单
本例采用一条明确的任务规则:只有配料完整、各食材条目的来源分类已核实且均符合要求的菜谱,才进入候选菜单。
本体定义食材种类及来源分类的含义,实例数据记录菜谱引用了哪个条目、条目归入哪个类,任务规则检查它们是否满足当前要求。
筛选后仍可能有多个合适菜谱。模型可以结合人数、口味和准备时间提出方案。涉及数量的计算交给程序处理;用户未提供的偏好保留为未知,必要时询问。
2.4 查库存,生成购物清单
选定番茄豆腐配方 B 后,Agent 沿“所需食材种类”查到豆腐等条目,再读取各配料的两人份用量,并按条目标识查库存。关系本身只说明需要哪种食材,用量另存在这份配方的配料明细中。单位换算和缺口计算由程序完成。本例中,其他配料已备齐,只有豆腐不足,于是清单中列出豆腐及缺少的数量。
“配方 B 所需食材种类 豆腐条目”是实例之间的事实;“将豆腐加入清单”是动作。关系说明对象怎样关联,动作改变任务状态。
清单保存后,本次任务完成。若要继续购买,系统需要另行取得用户授权。
三、把各部分接成架构
本体在这次任务中参与了四项工作:理解用户用语、寻找关联资料、检查参数类别,以及为模型挑选当前需要的事实。
具体实现时,可以让模型通过类型明确的查询工具访问这些信息。规则程序先筛出适用的动作,再交给模型选择。下面按晚餐案例区分各部分的工作。
表 1:晚餐 Agent 中各部分的职责。 同一份菜谱会经过多个部分处理;每一行承担的判断不同。
| 部分 | 在这个例子中负责什么 |
|---|---|
| 本体 | 定义菜谱、食材种类、库存记录这些类及关系含义 |
| 实例事实与任务状态 | 保存条目类型、配方配料、库存、来源及当前方案 |
| 行动模型与规则 | 规定资料齐全后才能生成清单,下单还需另外授权 |
| 规划器 | 选择先补查哪项资料、何时请求用户确认 |
| 执行器 | 检查参数和权限,调用查询或清单写入功能 |
| 结果检查器 | 核对清单数量、要求及实际保存结果 |
这些部分组成一个循环:读取状态 → 查询关系和事实 → 筛选候选动作 → 选择并执行 → 核对结果 → 更新状态。
以菜谱配料不全为例,当前状态记着“调味料未核实”。规则程序允许查询完整配方或请求补充,暂不允许提交菜单。规划器选择查询配方;工具返回结果后,程序记录来源与版本,再重新筛选动作。

如果多个查询都能补齐资料,模型可以选择更有希望解决当前疑问的那个。步骤固定时也可以用普通工作流;目标、动作和成本都明确时,可以用传统规划算法。这些决策方式都可以查询本体及实例数据。
3.1 状态里要保留什么
本体的类别和关系定义相对稳定;库存、菜谱版本和任务进度会变化,需要单独保存。
状态至少应能回答:用户确认了什么要求,哪些资料已核实,哪些还未知,当前选择哪个方案,执行过什么操作。查询记录附带来源、时间和版本,使用时根据任务要求检查是否需要更新。
“尚未查库存”“确认库存为零”“查询失败”是三种不同情况。它们分别对应查询、计算购买量、等待或处理错误。如果都写成“没有”,Agent 就无法选对下一步。
每次发给模型的上下文,从状态中按当前问题选取。当前要确认豆腐用量,就提供对应菜谱、份数、库存及未决问题;原始资料保留引用,按需读取。
四、从关系到动作,还要定义什么
本体定义“对应食材种类”连接库存记录与食材目录条目。执行动作还需要更具体的约定:收什么参数,满足什么条件才允许执行,执行后应该发生什么。
例如,save_shopping_list 接收菜单方案、份数和库存版本。执行前,程序检查配方是否完整、数量单位是否一致、饮食要求是否已核实,以及调用者是否有权保存这份清单。执行后,读取已保存的清单,核对它与计算结果一致。
这类行动模型可以借鉴 PDDL 对参数、前置条件和效果的描述。实际工具接口还要实现身份认证、失败返回和并发控制。
行动模型的配置示意
下面用一份自定义配置表示这些约定:
action: save_shopping_list
parameters:
menu_plan: MenuPlanId
servings: PositiveInteger
inventory_version: Version
preconditions:
- ingredients_complete
- dietary_requirement_verified
- quantities_and_units_valid
- caller_can_save_list
expected_effect:
- shopping_list_saved
verification:
- read_back_saved_items
on_unknown_result:
- query_original_request参数类型引用本体中的对象类型;执行器检查调用权限和数据版本,并通过数据库事务完成写入。
4.1 执行结果如何返回状态
保存清单时,应先持久化请求编号及参数,再发送请求。如果回复丢失,先查询原请求是否成功,而不是直接创建另一份清单。重试去重需要接收请求的系统支持,并约定编号的有效期与使用范围。
库存也可能在计算期间发生变化。需要按最新库存生成清单时,保存操作要校验对应版本;版本冲突则重新查询和计算。仅在发送请求前读一次版本,仍可能遗漏读取和写入之间的修改。

工具返回的资料可能含有“直接购买”等文字,它们只作为资料处理。权限和动作范围由程序检查。用户取消、调用预算用完或多次查询仍无结果时,停止新增操作,并保留未确认请求供核对或人工接手。
一个包含本体查询的执行循环
这里的函数名表示职责。权限、事务和调度机制需要在各模块内实现。
def advance(task_id):
state = store.load(task_id)
if state.pending_request:
return reconcile_or_handoff(state) # 有次数与时间限制
if state.cancelled or budget.exhausted(state):
return handoff_or_stop(state)
facts = observe_and_record(state)
context = semantic_layer.query(
ontology=ontology,
facts=facts,
goal=state.goal,
)
candidates = action_model.allowed(context, state.principal)
action = planner.select(state.goal, context, candidates)
if action is None:
return ask_wait_or_stop(state)
result = executor.validate_and_run(action, state)
state = store.record_result(state, result)
return completed(state) if verifier.goal_reached(state) else schedule(state)semantic_layer.query 返回相关对象、关系、来源和未知项;allowed 依据明确规则筛选动作;validate_and_run 使用执行时的权威记录再次检查。新结果先作为观察记录,核实后再用于事实更新,避免把模型猜测直接写成已知事实。
实际运行还需防止同一任务被多个进程同时推进,并保证保存状态后仍能触发后续执行。
五、本体能带来什么,什么时候值得用
本体定义可供不同任务复用;核实后的名称映射也是可复用的数据。例如将“鲜奶”“牛乳”映射到同一个牛奶条目后,菜谱和库存便能通过条目标识关联。以后增加早餐计划或多人聚餐,不必为每个入口重新解释一遍食材和库存的关系。
本体也使错误更容易定位。菜单不合适时,可以分别检查:用户要求是否识别错误,菜谱资料是否缺失,类别定义或条目归类是否错误,还是筛选规则漏掉了条件。
但关系定义和事实来源都需要维护。如果菜谱只有几条、字段固定,一份类型定义加规则代码可能已经足够。跨资料源名称不一致、对象关系多、同一知识需要服务多个任务时,显式本体才更值得考虑。
5.1 本体语言、数据校验和业务规则
OWL 2可以描述类别、属性、个体与逻辑关系。在本文模型中,牛奶条目是乳制品种类的实例;借助子类关系,可推导它也是动物来源食材种类的实例。它采用开放世界语义:没有记录某个断言,不等于这个断言为假。因此不能因为图中没写某条目的动物来源类型,就确认它属于植物来源食材种类。
本例要求“完整配料已核实”,是额外的任务规则。使用 RDF 图保存数据时,SHACL可检查声明的数据约束;JSON Schema、关系数据库约束或普通规则代码也能承担各自适合的检查。通过格式与结构检查,仍需要可靠来源支持事实本身。
工程上应分别决定:哪些关系需要本体表达,哪些事实由数据源提供,哪些条件由规则检查,哪些动作由执行器授权。
六、如何判断本体有没有帮助
先验证一次完整任务。给出含牛奶的候选配方,系统应指出具体配料并排除;缺少配料表,应继续查询而不是通过;库存单位不一致,应先换算或要求补充;没有购买授权,生成清单后就结束。
再加入名称不同但指向同一食材条目、错误归类、过时库存、重复请求、进程中断等案例。分别记录模型的错误建议、数据映射错误、规则判断错误和实际执行错误,才能知道问题出在哪里。
要评估本体本身的作用,可以比较两个版本:两者都有相同的状态存储、工具和执行检查,其中一个使用显式本体,另一个使用原有字段与规则。保持模型、任务、事实、权限及上下文预算尽量一致;需要调整检索和提示时,将调整记录下来。
除任务完成率外,还要比较资料缺失时是否正确暂停、解释能否追溯到来源、延迟、人工介入和维护成本。例如接入一份新的库存表,是否减少了重复映射工作。
从这个例子开始实现,先定义少量对象与关系,接入可核实的数据,再写筛选规则和工具条件。让一份菜单从需求走到购物清单,并能解释每一次选择,然后再增加更多任务。
本体与状态空间有什么区别?
本体描述对象及其含义,状态空间描述任务可能处于的情况,以及动作怎样使情况变化。
在完全可观测、动作结果确定的简化模型中,可写成:
是状态集合, 是动作集合, 描述变化, 是初始状态, 判断任务完成, 表示约束。菜谱已核实、库存已查询、清单已保存,是不同的任务状态。
现实中只能掌握部分信息,规划器使用带来源和时间的运行记录。严格 POMDP 中的信念状态是对隐藏状态的概率分布,要采用这种模型,需要另外定义隐藏状态及其概率更新方式,背景见 Kaelbling 等人的论文。
本体可以帮助组织状态中的对象、关系和查询;行动模型规定前置条件与效果;规划器在候选动作中选择。三者共同参与运行,但各自解决不同问题。
七、参考资料
- W3C:RDF Schema:类型、子类与关系定义。
- Noy 与 McGuinness:Ontology Development 101:从用途和问题出发组织类、属性与实例。
- W3C:OWL 2 Primer:类别、关系、个体及开放世界语义。
- W3C:SHACL:对 RDF 数据图声明并检查约束。
- PDDL 1.2:行动模型中的参数、前置条件与效果。
- Anthropic:Building effective agents:工作流与动态选择动作的 Agent。
- AWS:Making retries safe with idempotent APIs:请求去重与重试。
晚餐流程与模块组合为本文的教学设计。漫画与知识图谱插图由 AI 生成。