Package: reactive-deliberative
reactive_deliberativeIntroductionThe reactive_deliberative project allows creating applications
with a reactive-deliberative architecture.The hybrid deliberative-reactive architecture arose as a result of the recognition
that there is a use of symbolic knowledge in describing the behavior of an intelligent agent.Such an architecture allows an agent with reactive behavior to flexibly adjust
its behavior due to a high-level scheduler in accordance with the current task,
while at the same time taking into account the state of the environment. For
the agent, which is a kind of intelligent system, it will provide an opportunity
to respond to unplanned environmental changes.In simple words, the agent follows the behavior described in the rules, interrupting for reactive actions.This system is built using one the implementation of the Rete algorithms -py_rete.InstallationThis package is installable via pip with the following command:pip install -U reactive_deliberative.The BasicsThe two high-level structures to support reasoning with py_rete arefactsandproductions.FactsFacts represent the basic units of knowledge that the productions match over.
Here are a few examples of facts and how they work.Factsare a subclass of dict, so you can treat them similar to dictionaries.>>>f=Fact(a=1,b=2)>>>f['a']1Factsextend dictionaries, so they also support positional values without
keys. These values are assigned numerical indices based on their position.>>>f=Fact('a','b','c')>>>f[0]'a'Factscan support mixed positional and named arguments, but positional
must come before named and named arguments do not get positional references.>>>f=Fact('a','b',c=3,d=4)>>>f[0]'a'>>>f['c']3Factssupport nesting with other facts.>>>f=Fact(subfact=Fact())Fact(subfact=Fact())Note that there will be issues if facts contain other data structures that
contain facts (they will not be properly added to the rete network or to
productions).ProductionsProductionshave two components:Conditions, which are essentially facts that can contain pattern matching
variables.A Function, which is executed for each rule match, with the arguments to the
function being passed the bindings from pattern matching variables.Here is an example of a simpleProductionsthat binds with allFactsthat
have the color red and prints 'I found something red' for each one:@Production(Fact(color='red'))defalert_something_red():print("I found something red")Productions also support logical operators to express more complex conditions.@Production(AND(OR(Fact(color='red'),Fact(color='blue')),NOT(Fact(color='green'))))defalert_something_complex():print("I found something red or blue without any green present")Bitwise logical operators can be used as shorthand to make composing complex conditions easier.@Production((Fact(color='red')|Fact(color='blue'))&~Fact(color='green'))defalert_something_complex2():print("I found something red or blue without any green present")In addition to matching simple facts, pattern matching variables can be used to
match values from Facts. Matching ensures that variable bindings are consistent
across conditions. Additionally, variables are passed to arguments in the function
with the same name during matching. For example, the following production finds
a Fact with a lastname attribute.  For each Fact it finds, it prints "I found a
fact with a lastname attribute:<lastname>".  Note, theV('lastname')corresponds to a variable named lastname that can bind with values from Facts
during matching.  Additionally the variable (V('lastname')) and the function
argumentlastnamematch have the same name, which enables the matcher to the
variable bindings into the function.@Production(Fact(lastname=V('lastname')))deffound_relatives(lastname):print("I found a fact with a lastname:{}".format(lastname))It is also possible to employ functional tests (lambdas or functions) usingFilterconditions. Like the function that is being decorated, Filter
conditions pass variable bindings to their equivelently named function
arguments. It is important to note that positive facts that bind with these
variables need to be listed in the production before the tests that use them.@Production(Fact(value=V('a'))&Fact(value=V('b'))&Filter(lambdaa,b:a>b)&Fact(value=V('c'))&Filter(lambdab,c:b>c))defthree_values(a,b,c):print("{}is greater than{}is greater than{}".format(a,b,c))It is also possible to bindfactsto variables as well, using the bitshift
operator.@Production(V('name_fact')<<Fact(name=V('name')))deffound_name(name_fact):print("I found a name fact{}".format(name_fact))Productions can have priority and execution timeout:@Production(V('fact')<<Fact(state="transform"),priority=2,timeout=1)asyncdeftransform(net,fact):...Productions can be asynchronous:@Production(V('name_fact')<<Fact(name=V('name')))asyncdeffound_name(name_fact):print("I found a name fact{}and going to sleep".format(name_fact))awaitasyncio.sleep(10)ReactiveDeliberativeHere is how you create an engine:rd=ReactiveDeliberative()Once an engine has been created, then facts can be added to it.f1=Fact(light_color="red")rd.add_fact(f1)Note, facts added to the network cannot contain any variables or they will
trigger an exception when added. Additionally, once a fact has been added to
network it is assigned a unique internal identifier.This makes it possible to update the fact.f1['light_color']="green"net.update_fact(f1)It also make it possible to remove the fact.rd.remove_fact(f1)When updating a fact, note that it is not updated in the network until
theupdate_factmethod is called on it. An update essentially equates to
removing and re-adding the fact.Productions can also be added to the network. Productions also can make use of
thenetvariable, which is automatically bound to the Rete network the
production has been added to. This makes it possible for productions to update
the contents of the network when they are fired. For example, the following functions
have an argument callednetthat is bound to the rete network even though there is
no variable by that name in the production conditions.>>>f1=Fact(light_color="red")>>>>>>@Production(V('fact')<<Fact(light_color="red"))>>>defmake_green(net,fact):>>>print('making green')>>>fact['light_color']='green'>>>net.update_fact(fact)>>>>>>@Production(V('fact')<<Fact(light_color="green"))>>>defmake_red(net,fact):>>>print('making red')>>>fact['light_color']='red'>>>net.update_fact(fact)>>>>>>rd=ReactiveDeliberative()>>>rd.add_fact(f1)>>>rd.add_production(make_green)>>>rd.add_production(make_red)Once the above fact and productions have been added the network can be run in the infinite loop.>>>rd.run()makinggreenmakingredmakinggreenmakingredmakinggreen...Reactive predicates and actionsPredicate functions and action functions must be asynchronous.The predicate function can contain any calls and calculations,
but it must return a Boolean value. If the predicate is executed,
a reactive action will be performed. For example:asyncdefexternal_upload_predicate():returnawaitdumper.external_upload_predicate()asyncdefexternal_upload_action():returnawaitdumper.external_upload_action()After declaring the function, you can add them to the engine:rd.add_reactive_action(external_upload_predicate,external_upload_action)By default, predicate trigger stops the deliberative loop for the duration of the reactive action.
To disable this future passforce=Falseargument inadd_reactive_action:rd.add_reactive_action(external_upload_predicate,external_upload_action,force=False)