An agent-oriented programming framework for Python based on asyncio (async/await). Built using aioreactive and ZeroMQ (pyzmq).
Agent programming is based on the idea of autonomous agents interacting with each other in order to perform tasks. Such an agent is provided with inputs from the 'environment', which are often called percepts (i.e. perceptual, or sensory information), and produces outputs which are called actions: procedures that effect the environment in some way. This creates a kind of feedback loop, an example of which is shown in the picture below:
From Wikipedia.
Such an agent is often described as a function f, taking a sequence of percepts (P*) to an action (A):
However, a more appropriate description of an agent in Ajay is a function g which takes a sequence of percepts (P*) to a sequence of actions (A*):
To read some more about agent programming in general, see the paper by Shoham (1993) in the repository, or the book by Russell and Norvig.
These ideas about agents are implemented in Ajay: an agent is a function which takes in percepts and produces actions. The library defines which percepts and actions exist, runs the agent function, provides it with percepts, and execute the actions that it performs. A simple agent might look like this:
async def bounce(percepts, act):
await print("Started agent.")
await print("Waiting for messages...")
async for msg in percepts:
if isinstance(msg, MessagePercept):
await print(f"Received message: {msg.content}")
await print(f"Sending contents back to {msg.sender}")
await send(msg.sender, msg.content)
Two kinds of actions are performed here: print
(which prints to the console with some extra info) and send
(which sends a message to another agent). These actions look like regular functions here, but they actually push an [...]Action
object into a stream internally, so the agent is really transforming a sequence of percepts into a sequence of action objects. As you can see, all library code is asynchronous: each action is prefaced by await, and the loop over the percepts is an async for
. The only kind of percepts that this agent deals with are messages (MessagePercept
s), and when it receives a message it just echoes it back to the sender.
Note that the normal print function is shadowed here: this is of course very edgy and not required ;) Instead of
print(x)
you can also doact(Print(x))
oract(PrintAction(x))
, depending on the level of verbosity you want.
When strictly following this paradigm, all interaction with the 'outside world' should go through this mechanism of percepts and actions. This also applies to things like local file IO. The reason is that we would like to stick to this idea of an agent being a deterministic function from percepts to actions: if we take an action based on the contents of a file, the function becomes unpredictable. The solution is to make the file contents into one of the percepts as well. To accomplish this, the act
function can be used to perform built-in actions, but it can also be used to transform any async
function call into an action, and then provide its result as a percept. For an example, see tests/test_agent_fileio.py
.
The main advantage of such an approach is reproducibility. In some sense, the observable behavior of an agent is entirely determined by the actions it performs, and so can be rigorously studied and even executed in a different context than the one in which the agent originally ran. Moreover, the actions the agent produces are in turn entirely determined by the percepts that it receives. That means if one has a record of the percepts, one can 'replay' an agent's execution in a sandbox state, where it will produce the same actions but these actions do not have to affect the environment. For an example of this, see tests/test_agent_message.py
.
The agents commuincate with each other via message passing. Currently, no special constraints are placed on these messages, but there is actually a base on which these messages can be built, the FIPA-ACL (Agent Communication Language) standard, which is in turn based on the theory of speech acts. Other Python libraries have the ability to produce messages confirming to this specification, and it is also a goal to include a (simple) implementation of it here.
These ACL messages provide one with a lot of metadata, but content-wise they are actually quite flexible. That is because for any application, the relevant terms one may use to describe the things the agents need to communicate will differ. Here is where the concept of ontology comes in: an ontology is a structure which defines a set of terms, their properties and their relationships to each other. Basically, it is a way for agents to construct a shared basis so they both know what they are talking about. There are also standards for constructing these ontologies, such as OWL. OWL ontologies are heavily based on mathematical logic: generally, an OWL ontology is just a collection of propositions (or facts) about a set of terms. This is somewhat similar to an object hierarchy (e.g. a proposition might be "a Car is a Vehicle"), but with some important differences. The most important is the possibility for automated reasoning, where the facts in the ontology can be logically combined to produce new facts. (I believe there may be a link here to the reactive programming paradigm as in MobX.)
Currently, there is no support for specific types of messages, nor integration with any kind of ontologies, but these are interesting possibilities for the future. However, the aim is also to make all of these features optional: if you just want a function which runs continously and use your own methods for state management and communication, that's fine too. No need to adjust all your logic to awkwardly use our preferred data structures if you don't want to ;)
Things which I would like to work on next:
- Translating/designing more/better examples of agent programming which could be done with this framework. Both for illustration of use cases and inspiration for further developments.
- More comments and (API) documentation ;)
- Support for constructing and processing ACL messages easily
- Run tests in virtual time (inspiration from aioreactive)
- Think about a natural form of state management. Probably using some kind of ontology, but how to design an interface which works well in the paradigm?