人们希望拥有确定性的系统,而我们正在将旧有的理念带回大型语言模型(LLM)时代。
现存的最大问题之一是如何对系统验证和推理进入系统的信息设置约束。随着我们通过随机性的大型语言模型消费越来越多的非结构化数据,强制执行规则和护栏的能力变得愈发重要。
我们构建了一个基于Python的推理和验证框架,该框架受Pydantic启发,使开发人员和非技术领域的专家能够轻松构建复杂的规则引擎。这个过程对开发人员来说极易扩展,我们已经微调了一个模型,该模型有助于从自然语言指令中自动构建规则。虽然每种问题都需要一定程度的定制,但许多商业问题可以使用类似的框架(例如,物流->食品配送)。这个符号推理和验证框架在你想将标准操作程序(SOPs)和其他业务护栏转化为可执行的代码时非常有用。从后文的过河谜题中可以看出,它还可以用于构建问题解决的推理框架。
这些规则引擎与图数据结构配合得很好,但并不需要图数据库来运行。在本例中,我们的图数据结构存储在JSON文件中。无论你的数据存储在图数据库、关系数据库还是NoSQL数据库中,它都旨在提供灵活性。我们选择使用Python,因为语义工具和语言(如SHACL和RDF)对于Python生态系统中的大多数开发人员来说难以访问且不熟悉。此外,我们可以利用像Pydantic这样的熟悉框架,为现有工作流程带来验证和基本推理功能。这意味着可以在同一个代码文件中构建完整的、有状态的程序。反正我们也无法在RDF中构建状态机。
该系统由5个部分组成:
这是什么意思呢?让我们通过类比将这个系统比作国际象棋来解释。如果我们将推理引擎比作国际象棋,那么:
我们将介绍两种系统——验证引擎和推理引擎。验证引擎是推理引擎的子集,因为它们仅根据已述规则检查某事物是否有效。验证引擎通常没有状态机、代理或推理框架,因为它们只是将数据结构与现有的规则系统进行验证。
推理引擎有助于理解和“导航”问题集。这涉及在规则约束下做出决策,并基于潜在选择进行决策。由于涉及决策,推理引擎需要状态机。
验证与推理案例研究
我们构建了一个流程,旨在简化非技术运营/业务人员对企业级规则引擎的贡献,同时让开发人员参与其中。
该流程如下:
这是前端流程的可视化快照(这是下文过河谜题示例中的)。
开发时间分配详解
对于每个问题陈述,开发过程大约需要5个小时;其中大部分时间用于理解用例、创建能够反映问题特定上下文的数据结构和推理框架。一旦我们有了最佳结构的概念,就会利用模型来进一步完善规则、结构和推理框架的所有细节。
该过程主要包括:
接下来,我们将深入探讨几个用例。
验证引擎(简单,无状态)
我们现在来看一个匿名的案例研究。一家矿业公司希望验证其现有员工(以及潜在员工)的资格和课程,以确保他们能够安全地完成工作,而这些工作可能因地区而异。对于不同年龄、不同人员以及不同类型的车辆,都有相应的资格要求和规则限制。
以下是一个数据样本:
{
"employees": [
{
"name": "Sarah",
"age": 25,
"country": { "name": "Australia" },
“role”: “Manager”,
"documents": [{ "type": "safe_handling_at_work" }, { "type": "heavy_lifting" }],
},
{`
"name": "John",
"age": 17,
“role”: “Laborer”,
"country": { "name": "Australia" },
"documents": [{ "type": "heavy_lifting" }],
},
{
"name": "Alice",
"age": 30,
“role”: “Dozer Operator”,
"country": { "name": "Brazil" },
"documents": [{ "type": "heavy_lifting" }],
}
]
}
诸如“员工必须年满18岁”之类的规则也可以用同样的方式表示:
{
"rules": [
{
"type": "min_age",
"parameters": { "min_age": 18 }
},
{
"type": "dozer_operator",
"parameters": {
"country": "Australia",
“role”: “dozer_operator”,
"document_type": "dozer_qualification"
}
},
]
}
然后,使用基本的Python推理引擎运行它,我们可以得到以下输出。
Graph does not conform to rules:
Minimum_age must be 18
Role "Dozer_operator" must have document_type: "Dozer_qualification"
推理引擎(具有代理性,有状态)
解谜
在这个例子中,我们解决了著名的过河谜题,这类谜题通常依赖大型语言模型(LLM)的死记硬背来解决。
一个农夫从市场回来,他买了一只山羊、一棵卷心菜和一只狼。在回家的路上,他必须过一条河。他的船很小,装不下超过一件他买的东西。他不能把山羊和卷心菜单独留在一起(因为山羊会吃掉卷心菜),也不能把山羊和狼单独留在一起(因为山羊会被狼吃掉)。在这个过河谜题中,农夫如何才能把所有东西都运到对岸呢?
尽管最新版本的大型语言模型(截至24年10月)似乎已经在足够的数据上进行了充分训练,能够泛化并特别适用于解决过河谜题,但这个经典例子表明,要将大型语言模型用作企业内部用例的推理或验证引擎,还需要大量工作。同时,目前尚不清楚,如果数据集中没有包含某个足够独特的规则,大型语言模型是否会因此而出错。
对于此过程,我们采取以下步骤:
以下视频演示了将自然语言指令转化为规则的具体步骤,这些规则会自动进行验证、检查和接受,同时包含非技术领域专家和开发人员的人工参与环节。
开发者指南
对于技术团队,我们还添加了一些代码示例来展示我们所做的工作。基本流程的输出如下所示。鉴于这是一个符号验证引擎,它的运行时间为0.0003秒。实际上,它还可以更快。
Initial state:
Left bank: Wolf, Goat, Cabbage, Farmer
Right bank: empty
Solution:
Step 0:
Left bank: Wolf, Goat, Cabbage, Farmer
Right bank: empty
Step 1:
Left bank: Wolf, Cabbage
Right bank: Goat, Farmer
Step 2:
Left bank: Wolf, Cabbage, Farmer
Right bank: Goat
Step 3:
Left bank: Cabbage
Right bank: Wolf, Goat, Farmer
Step 4:
Left bank: Goat, Cabbage, Farmer
Right bank: Wolf
Step 5:
Left bank: Goat
Right bank: Wolf, Cabbage, Farmer
Step 6:
Left bank: Goat, Farmer
Right bank: Wolf, Cabbage
Step 7:
Left bank: empty
Right bank: Wolf, Goat, Cabbage, Farmer
状态机可以用图形方式表示,如下面的WhyHow图所示:
可以像下面这样简单地添加规则:
class GoatCabbageRule(Rule):
def evaluate(self, state: WolfGoatCabbageState) -> bool:
return not (state.goat == state.cabbage and state.farmer != state.goat)
def get_description(self) -> str:
return "Goat cannot be left alone with cabbage"
所以,要添加一条新规则,比如“狼不能和鸡单独留在一起”,我们可以添加如下规则:
class ChickenWolfRule(Rule):
def evaluate(self, state: StateModel) -> bool:
return not (state.chicken == state.wolf and state.farmer != state.chicken)
def get_description(self) -> str:
return "Wolf cannot be left alone with chicken"
然后将该规则添加到引擎中:
class RulesEngine:
def __init__(self):
self.rules: List[Rule] = [
WolfGoatRule(),
GoatCabbageRule(),
ChickenWolfRule()
]
def validate_state(self, state: StateModel) -> Tuple[bool, List[str]]:
violations = []
for rule in self.rules:
if not rule.evaluate(state):
violations.append(rule.get_description())
return len(violations) == 0, violations
然后我们得到以下输出:
Initial state:
Left bank: Wolf, Goat, Cabbage, Farmer
Right bank: empty
Rule violations: Farmer can only carry one item at a time
Solution found in 0.0003 seconds
然后我们可以调整代码,使得答案如下:
Initial state:
Left bank: Wolf, Goat, Cabbage, Chicken, Farmer
Right bank: empty
All rules satisfied
Searching for solution…
Debug: Invalid - Wolf would eat goat
Debug: Invalid - Goat would eat cabbage
Debug: Invalid - Wolf would eat chicken
Debug: Invalid - Wolf would eat goat
Debug: Invalid - Wolf would eat goat
Debug: No valid moves found!
Current state: wolf='left' goat='left' cabbage='left' chicken='left' farmer='left'
Attempted moves were invalid due to rule violations
Current state: Left bank: Wolf, Goat, Cabbage, Chicken, Farmer
Right bank: empty
Found 0 possible moves
No solution found!
如上所示,添加ChickenWolfRule规则后创建了一个无解的不可能情况。这种失败是有价值的,因为它让我们了解了数据和系统的限制。然而,为了创建一个可解的扩展,我们可以进一步添加一条规则,即“农夫可以同时携带两件物品”,如下所示:
class CarryingCapacityRule(Rule):
def evaluate(self, state: StateModel) -> bool:
"""Check that farmer isn't carrying more than two items at once"""
# Get previous state from visited states (if it exists)
# Count how many items moved with the farmer
items_moved = 0
items = ['wolf', 'goat', 'cabbage', 'chicken']
# For each item, check if it moved to the same side as the farmer
for item in items:
item_side = getattr(state, item)
if item_side == state.farmer:
items_moved += 1
# Farmer can carry up to two items
return items_moved <= 2
def get_description(self) -> str:
return "Farmer can carry up to two items at a time"
现在,我们得到了以下解决方案:
Initial state:
Left bank: Wolf, Goat, Cabbage, Chicken, Farmer
Right bank: empty
All rules satisfied
Searching for solution...
Current state: Left bank: Wolf, Goat, Cabbage, Chicken, Farmer
Right bank: empty
Solution found in 0.0007 seconds:
Step 0:
Left bank: Wolf, Goat, Cabbage, Chicken, Farmer
Right bank: empty
All rules satisfied
Step 1:
Left bank: Goat, Chicken
Right bank: Wolf, Cabbage, Farmer
All rules satisfied
Step 2:
Left bank: Goat, Chicken, Farmer
Right bank: Wolf, Cabbage
All rules satisfied
Step 3:
Left bank: empty
Right bank: Wolf, Goat, Cabbage, Chicken, Farmer
All rules satisfied
它解决了这个谜题!而且,它只用了3步,而不是原来的7步。