Python 的特定领域语言和规则引擎
项目描述
抱歉,后面会有更好的自述文件。
智力
- 信息:
Intellect 是 Python 的特定领域语言和规则引擎。
- 作者:
迈克尔·约瑟夫·沃尔什
1.什么是智力
Intellect 是我为 Python 编写的 DSL(“特定领域语言”)和规则引擎,用于表达策略以协调和控制 MITRE 公司创新计划正在研究的动态网络防御网络安全平台。
规则引擎提供了一种智力,一种人工智能形式,一种对工作记忆进行客观推理和理解的能力。内存保留了与系统相关的知识,以及在 DSL 中编写的一组规则,这些规则描述了实现某个目标的必要行为。每个规则都有一个可选条件和一组一个或多个操作。这些动作或者进一步指导系统的行为,和/或进一步通知系统。引擎从一些事实开始,即关于过去或现在情况的真相,并使用规则来推断更多事实。这些事实触发了更多的规则,从而推断出更多的事实等等。
对于创新计划中的平台,网络防御者使用 DSL 来授予策略,平台如何响应安装在隐蔽网络通道上的网络事件,但语言和规则引擎与网络安全没有直接联系因此,整个系统可以更广泛地用于其他领域。
2.安装
要通过setuptools安装,请使用easy_install -U Intellect
要通过pip安装,请使用pip install Intellect
要通过pypm安装,请使用pypm install intellect
或者从Master下载最新的源代码或最新的标记版本Tagged Releases,解压并运行python setup.py install
3. 依赖关系
Python 本身,如果你还没有的话。我在 Python 2.7.1、2.7.2、2.7.3 上测试了代码。
4. 贡献
源代码在 BSD 4 条款许可下可用。如果您有想要贡献的想法、代码、错误报告或修复,请这样做。
可以在Github提交错误和功能请求。
5. 背景
许多生产规则系统实现已经开源,例如 JBoss Drools、Rools、Jess、Lisa 等等。如果您熟悉 Drools 语法,那么 Intellect 的语法应该看起来很熟悉。(我并不是说它是基于它的,因为它并不完全,但我发现当我使用 Drools 的语法时,我会检查它,如果朝着 Drools 的方向推进是有意义的,这就是我所做的。 ) 上述实现可用于其他语言来表达生产规则,但我认为 Python 的代表性不足,因此我认为语言和规则引擎可以从开源中受益,所以我放了一个请求进来。
MITRE Corporation 于 2011 年 8 月 4 日批准发布。
因此,将领域特定语言 (DSL) 和规则引擎发布到开源,希望这样做可以扩大其使用范围并增加其可能采用的机会,同时使项目成熟,吸引更多感兴趣的眼球.
开始时,最初假设上述平台将与可用于 Python 的最佳开源规则引擎集成,因为 Ruby、Java 和 Perl 有无数的实现,但令人惊讶的是,我发现没有一个适合项目的需求。这导致了发明一个的想法。只需在 Google 中输入关键字“python 规则引擎”,虽然会返回给您“不要发明另一种规则语言”的建议,但建议您“只用 Python 编写规则,导入它们,然后执行它们”。这个建议的基础可以归结为这样做,否则不符合“Python哲学”。当时,我不相信这是真的,也没有完全根据上下文进行分析,但不可否认,我还没有编写过一行 Python 代码(是的,你正在查看我的第一个 Python 程序。所以,请让我休息一下。)在此之前也没有使用过 ANTLR3。回顾过去,我坚信发明规则引擎并将其抽象为描述和阐明特定领域的命名法的行为是在上述平台的情况下网络防御者思考问题的最佳方式。就像我说的,尽管 DSL 和规则引擎可以用于任何需要“生产规则系统”的东西。
由于没有可用于 Python 的规则引擎来满足平台的需求,因此从头开始构建了一种策略语言和简单的正向链接规则引擎。策略语言的语法基于 Python 语言语法的一个子集。在 ANTLR3 Parse Generator and Runtime for Python 的帮助下对策略 DSL 进行解析和词法分析。
6. 事实(数据被推理)
解释器、规则引擎和代码的其余部分,例如用于赋予离散网络条件的对象,称为“事实”,也是用 Python 编写的。Python 的面向对象编程范式(其中对象由数据字段和方法组成)的方法并不适合描述“事实”。因为在语法上称为“属性”的 Python 对象的数据字段可以而且经常设置在类的实例上,所以它们在类的实例化之前不会存在。为了让规则引擎工作,它必须能够完全自省表示条件的对象实例。这被证明是非常困难的,除非采用 Python 2.6 中引入的具有两个属性“getter”和“setter”的属性装饰器并正式用于创作这些对象。巧合的是,
因此,您需要将您的事实编写为 Python 对象的 who 属性被正式表示为属性,就像您想要推理的属性一样:
class ClassA(object): ''' An example fact ''' def __init__(self, property0 = None, property1 = None): ''' ClassA initializer ''' self._property0 = property0 @property def property0(self): return self._property0 @property0.setter def property0(self, value): self._property0 = value
7. 政策 DSL
可以在路径Intellect/src/intellect/examples中找到带有策略文件的示例。策略文件必须遵循Intellect/src/intellect/grammar/Policy.g中定义的策略语法。本节的其余部分记录了特定于策略域的语言的语法。
7.1 导入语句(ImportStmts)
导入语句基本上遵循 Python 的,但有一些限制。例如,不支持通配符形式的导入,原因在此详述 ,请遵循 Python 2.7.2 语法。根据语法, ImportStmt语句仅存在于ruleStmt语句的同一级别,并且通常位于策略文件的顶部,但不限于。事实上,如果您将策略分解为多个文件,最后作为类或模块导入的文件将作为被命名的文件获胜。
7.2 属性语句(attribute)
<图>attributeStmt的语法图。
</figcaption> </figure>attributeStmt语句是用于创建策略属性的表达式,一种全局变量形式,可从规则访问。
例如,可以编写一个策略:
import logging first_sum = 0 second_sum = 0 rule "set both first_sum and second_sum to 1": agenda-group "test_d" then: attribute (first_sum, second_sum) = (1,1) log("first_sum is {0}".format(first_sum), "example", logging.DEBUG) log("second_sum is {0}".format(second_sum), "example", logging.DEBUG) rule "add 2": agenda-group "test_d" then: attribute first_sum += 2 attribute second_sum += 2 log("first_sum is {0}".format(first_sum), "example", logging.DEBUG) log("second_sum is {0}".format(second_sum), "example", logging.DEBUG) rule "add 3": agenda-group "test_d" then: attribute first_sum += 3 attribute second_sum += 3 log("first_sum is {0}".format(first_sum), "example", logging.DEBUG) log("second_sum is {0}".format(second_sum), "example", logging.DEBUG) rule "add 4": agenda-group "test_d" then: attribute first_sum += 4 attribute second_sum += 4 log("first_sum is {0}".format(first_sum), "example", logging.DEBUG) log("second_sum is {0}".format(second_sum), "example", logging.DEBUG) halt rule "should never get here": agenda-group "test_d" then: log("Then how did I get here?", "example", logging.DEBUG)
包含两个atributeStmt语句:
first_sum = 0 second_sum = 0
以下规则将使用attributeAction 语句增加这两个属性。
执行此策略的代码如下所示:
class MyIntellect(Intellect): pass if __name__ == "__main__": # set up logging for the example logger = logging.getLogger('example') logger.setLevel(logging.DEBUG) consoleHandler = logging.StreamHandler(stream=sys.stdout) consoleHandler.setFormatter(logging.Formatter('%(asctime)s %(name)-12s %(levelname)-8s%(message)s')) logger.addHandler(consoleHandler) myIntellect = MyIntellect() policy_d = myIntellect.learn(Intellect.local_file_uri("./rulesets/test_d.policy")) myIntellect.reason(["test_d"])
并且执行上述操作的日志输出将是:
2011-10-04 23:56:51,681 example DEBUG __main__.MyIntellect :: first_sum is 1 2011-10-04 23:56:51,682 example DEBUG __main__.MyIntellect :: second_sum is 1 2011-10-04 23:56:51,683 example DEBUG __main__.MyIntellect :: first_sum is 3 2011-10-04 23:56:51,683 example DEBUG __main__.MyIntellect :: second_sum is 3 2011-10-04 23:56:51,685 example DEBUG __main__.MyIntellect :: first_sum is 6 2011-10-04 23:56:51,685 example DEBUG __main__.MyIntellect :: second_sum is 6 2011-10-04 23:56:51,687 example DEBUG __main__.MyIntellect :: first_sum is 10 2011-10-04 23:56:51,687 example DEBUG __main__.MyIntellect :: second_sum is 10
有关另一个示例,请参见第7.3.3.1.2节 attributeAction。
7.3 规则语句(ruleStmt)
<图>ruleStmt的语法图。
</figcaption> </figure>最简单的规则语句如下所示:
rule "print": then: print("hello world!!!!")
规则“打印”将始终激活并输出hello world!!!!到 sys.stdout。
遵循 Python 的命名和字符串约定,规则将始终具有NAME或STRING令牌形式的标识符 ( id ) 。
通常,规则将同时具有包含规则条件的when部分,截至目前为ruleCondition,以及由then部分描述 的操作。在 Python 术语中,动作可以被认为是具有更具体的一组一个或多个动作。
根据对条件的评估,知识中的事实将被匹配,然后在规则的作用中进行操作。
例如在规则“删除那些不匹配的”中,在该规则的作用下,所有属于ClassD类型且property1值为1或2或3的事实 都将被删除。
from intellect.testing.ClassCandD import ClassD rule "delete those that don't match": when: not $bar := ClassD(property1 in [1,2,3]) then: delete $bar
7.3.1议程组规则属性
<图>议程组的语法图。
</figcaption> </figure>可选地,一个规则可能有一个议程组属性,允许它被分组到议程组中,并在议程上触发。
有关使用此属性的示例,请参阅第7.2节 属性和第 7.3.3.1.2节属性操作。
7.3.2 何时
<图>when的语法图。
</figcaption> </figure>如果存在于规则中,它定义了激活规则的条件。
7.3.2.1 规则条件(condition)
<图>条件的语法图。
</figcaption> </figure>规则可以有一个可选条件,一个布尔评估,关于由类约束 ( classConstraint ) 定义的知识中的对象的状态,并且可以可选地预先添加如下存在:
rule rule_c: when: exists $classB := ClassB(property1.startswith("apple") and property2>5 and test.greaterThanTen(property2) and aMethod() == "a") then: print( "matches" + " exist" ) a = 1 b = 2 c = a + b print(c) test.helloworld() # call MyIntellect's bar method as it is decorated as callable bar()
因此,如果内存中有任何符合条件的对象,则该操作将被调用一次。如果在classContraint前面存在,则动作语句modify和delete可能不会在动作中使用。
目前,DSL 仅支持一个classConstraint,但支持多个的工作正在进行中。
7.3.2.1.1 类约束(classConstraint)
<图>classConsraint的语法图。
</figcaption> </figure>classContraint定义如何匹配知识中的对象。它定义了一个 OBJECTBINDING ,对象类的 Python 名称以及 对象将在知识中匹配的可选约束。
OBJECTBINDING是一个遵循Python 命名约定的NAME标记,前面带有美元符号 ( $ )。
与规则条件示例的情况一样:
exists $classB := ClassB(property1.startswith("apple") and property2>5 and test.greaterThanTen(property2) and aMethod() == "a")
$classB是OBJECTBINDING ,它在与约束匹配的知识中绑定ClassB类型的事实的 匹配。
可以在规则的操作中进一步使用OBJECTBINDING,但不能像示例中那样在条件前面加上存在的情况下使用。
7.3.2.1.2 约束
约束遵循 Python 遵循的相同基本的and、or和not语法。
与规则条件示例的情况一样:
exists $classB := ClassB(property1.startswith("apple") and property2>5 and test.greaterThanTen(property2) and aMethod() == "a")
所有ClassB类型的事实都在具有以apple开头的property1属性和大于5的property2属性的知识中匹配,然后再使用存在语句进行评估。有关约束的其余部分的更多信息,请参见以下部分。
7.3.2.1.2.1 使用正则表达式
您也可以在约束中使用正则表达式,只需直接从 Python 导入正则表达式库,然后在规则条件示例中使用 like so:
$classB := ClassB( re.search(r"\bapple\b", property1)!=None and property2>5 and test.greaterThanTen(property2) and aMethod() == "a")
正则表达式r"\bapple\b"搜索是在知识中ClassB类型的对象的property1上执行的。
7.3.2.1.2.2 使用方法
重写一个复杂的约束:
如果您正在编写一个非常复杂的约束,请考虑将约束所需的评估转移到一种经过推理的事实方法中,以提高可读性。
与规则条件示例的情况一样,它可以重写为:
$classB := ClassB(property1ContainsTheStrApple() and property2>5 and test.greaterThanTen(property2) and aMethod() == "a")
如果要将方法添加到 ClassB:
def property1ContainsTheStrApple() return re.search(r"\bapple\b", property1) != None
类和/或实例的:
此示例还演示了如何将测试模块函数greaterThanTen发送 到实例的property2属性和评估函数的返回值,并且可以评估对实例的aMethod方法的调用以返回"a"。
7.3.3 然后
<图>then的语法图。
</figcaption> </figure>当规则被激活时,用于定义一组一个或多个操作语句以调用触发规则。
7.3.3.1 规则动作(动作套件)
<图>动作的语法图。
</figcaption> </figure>规则可能有一套在做某事的过程中使用的一个或多个动作,通常是为了实现一个目标。
7.3.3.1.1 简单语句(simpleStmt)
<图>simpleStmt的语法图。
</figcaption> </figure>simpleStmts是规则的受支持操作,因此可以执行以下操作:
规则规则_c: 什么时候: exists $classB := ClassB(property1.startswith("apple") and property2>5 and test.greaterThanTen(property2) and aMethod() == "a") 然后: 打印(“匹配”+“存在”) a = 1 b = 2 c = a + b 打印(c) test.helloworld() 酒吧()
如果存在与条件匹配的知识中的任何事实,则将执行操作中的simpleStmt 。
为了防止策略文件变成另一个 Python 脚本,您需要在操作套件中保留尽可能少的代码,因此策略文件是可能的……您需要专注于使用modify、delete、 insert、halt之前大量使用使用大量的简单语句。这就是为什么action支持有限的 Python 语法。 不支持if、for、while等,仅支持 Python 的expressionStmt语句。
7.3.3.1.2属性动作
<图>attributeStmt的语法图。
</figcaption> </figure>attributeAction操作用于创建、删除或修改策略属性。
例如:
i = 0 rule rule_e: agenda-group "1" then: attribute i = i + 1 print i rule rule_f: agenda-group "2" then: attribute i = i + 1 print i rule rule_g: agenda-group "3" then: attribute i = i + 1 print i rule rule_h: agenda-group "4" then: # the 'i' variable is scoped to then portion of the rule i = 0 print i rule rule_i: agenda-group "5" then: attribute i += 1 print i # the 'i' variable is scoped to then portion of the rule i = 0 rule rule_j: agenda-group "6" then: attribute i += 1 print i
如果指示规则引擎按照 Python 列表 [“1”、“2”、“3”、“4”、“5”、“6”]描述的顺序推理寻求激活议程上的规则,如下所示:
class MyIntellect(Intellect): pass if __name__ == "__main__": myIntellect = MyIntellect() policy_c = myIntellect.learn(Intellect.local_file_uri"./rulesets/test_c.policy")) myIntellect.reason(["1", "2", "3", "4", "5", "6"])
将产生以下输出:
1 2 3 0 4 5
触发rule_e时,策略属性i将增加 1 的值,并打印1,与rule_f和rule_g相同,但rule_h 打印 0。原因是i变量的范围仅限于规则的一部分。Rule_i进一步说明作用域:策略属性i 进一步增加1并打印,然后变量i作用于 初始化为0的规则部分,但这对策略属性没有影响i当执行rule_j操作触发规则时,将打印6的值。
7.3.3.1.3学习动作
<图>learnAction的语法图。
</figcaption> </figure>一条名为“是时候买新羊了?”的规则。可能如下所示:
rule "Time to buy new sheep?": when: $buyOrder := BuyOrder( ) then: print( "Buying a new sheep." ) modify $buyOrder: count = $buyOrder.count - 1 learn BlackSheep()
上面的规则说明了使用学习操作来学习/插入BlackSheep事实。同样的规则也可以使用insert编写如下:
rule "Time to buy new sheep?": when: $buyOrder := BuyOrder( ) then: print( "Buying a new sheep." ) modify $buyOrder: count = $buyOrder.count - 1 insert BlackSheep()
7.3.3.1.4忘记动作
<图>forgetAction的语法图。
</figcaption> </figure>名为“删除空买单”的规则可能如下所示:
rule "Remove empty buy orders": when: $buyOrder := BuyOrder( count == 0 ) then: forget $buyOrder
上面的规则说明了使用忘记操作来忘记/删除规则条件返回的每个匹配项。同样的规则也可以使用delete编写如下:
rule "Remove empty buy orders": when: $buyOrder := BuyOrder( count == 0 ) then: delete $buyOrder
注意:不能与存在结合使用。
7.3.3.1.5修改动作
<图>modifyAction的语法图。
</figcaption> </figure>以下规则:
rule "Time to buy new sheep?": when: $buyOrder := BuyOrder( ) then: print( "Buying a new sheep." ) modify $buyOrder: count = $buyOrder.count - 1 learn BlackSheep()
说明使用修改操作来修改规则条件返回的每个BuyOrder匹配项。不能与存在 规则条件一起使用。修改操作也可用于链接规则,您所做的是修改事实(切换布尔属性、设置属性值等),然后使用此属性在后续规则中进行评估。
7.3.3.1.6停止动作
<图>haltAction的语法图。
</figcaption> </figure>以下规则:
rule "End policy": then: log("Finished reasoning over policy.", "example", logging.DEBUG) halt
说明了使用停止动作来告诉规则引擎停止对策略的推理。
8. 使用单一策略创建和使用规则引擎
最简单的规则引擎可以像这样创建和使用:
import sys, logging from intellect.Intellect import Intellect from intellect.Intellect import Callable # set up logging logging.basicConfig(level=logging.DEBUG, format='%(asctime)s %(name)-12s%(levelname)-8s%(message)s', stream=sys.stdout) intellect = Intellect() policy_a = intellect.learn(Intellect.local_file_uri("../rulesets/test_a.policy")) intellect.reason() intellect.forget_all()
您最好将intellect.Intellect.Intellect类子类化,以便添加@Callable修饰方法,从而允许从规则的操作中调用这些方法。
例如,MyIntellect被创建为Intellect的子类:
import sys, logging from intellect.Intellect import Intellect from intellect.Intellect import Callable class MyIntellect(Intellect): @Callable def bar(self): self.log(logging.DEBUG, ">>>>>>>>>>>>>> called MyIntellect's bar method as it was decorated as callable.") if __name__ == "__main__": # set up logging logging.basicConfig(level=logging.DEBUG, format='%(asctime)s %(name)-12s%(levelname)-8s%(message)s', #filename="rules.log") stream=sys.stdout) print "*"*80 print """create an instance of MyIntellect extending Intellect, create some facts, and exercise the MyIntellect's ability to learn and forget""" print "*"*80 myIntellect = MyIntellect() policy_a = myIntellect.learn(Intellect.local_file_uri("../rulesets/test_a.policy")) myIntellect.reason() myIntellect.forget_all()
然后可以编写策略,调用 MyIntellect类的bar方法以匹配规则条件,如下所示:
from intellect.testing.subModule.ClassB import ClassB import intellect.testing.Test as Test import logging fruits_of_interest = ["apple", "grape", "mellon", "pear"] count = 5 rule rule_a: agenda-group test_a when: $classB := ClassB( property1 in fruits_of_interest and property2>count ) then: # mark the 'ClassB' matches in memory as modified modify $classB: property1 = $classB.property1 + " pie" modified = True # increment the match's 'property2' value by 1000 property2 = $classB.property2 + 1000 attribute count = $classB.property2 print "count = {0}".format( count ) # call MyIntellect's bar method as it is decorated as callable bar() log(logging.DEBUG, "rule_a fired")