Getting Started¶
Installation¶
You can install Academy with pip
or from source.
We suggest installing within a virtual environment (e.g., venv
or Conda).
Option 1: Install from PyPI:
Option 2: Install from source:
A Basic Example¶
The following script defines, initializes, and launches a simple agent that performs a single action.
Click on the plus (+
) signs to learn more.
- Running agents implement a
Behavior
. - Behavior methods decorated with
@action
can be invoked remotely by clients and other agents. An agent can call action methods on itself as normal methods. - The
Manager
is a high-level interface that reduces boilerplate code when launching and managing agents. It will also manage clean up of resources and shutting down agents when the context manager exits. - The
ThreadExchange
manages message passing between clients and agents running in different threads of a single process. - The
ThreadLauncher
launches agents in threads of the current process. - An instantiated behavior (here,
ExampleAgent
) can be launched withManager.launch()
, returning a handle to the remote agent. - Interact with running agents via a
RemoteHandle
. Invoking an action returns a future to the result. - Agents can be shutdown via a handle or the manager.
Running this script with logging enabled produces the following output:
$ python example.py
INFO (root) Configured logger (stdout-level=INFO, logfile=None, logfile-level=None)
INFO (academy.manager) Initialized manager (ClientID<6e890226>; ThreadExchange<4401447664>)
INFO (academy.manager) Launched agent (AgentID<ad6faf7e>; Behavior<ExampleAgent>)
INFO (academy.agent) Running agent (AgentID<ad6faf7e>; Behavior<ExampleAgent>)
INFO (academy.agent) Shutdown agent (AgentID<ad6faf7e>; Behavior<ExampleAgent>)
INFO (academy.manager) Closed manager (ClientID<6e890226>)
Control Loops¶
Control loops define the autonomous behavior of a running agent and are created by decorating a method with @loop
.
import threading
import time
from academy.behavior import loop
class ExampleAgent(Behavior):
@loop
def counter(self, shutdown: threading.Event) -> None:
count = 0
while not shutdown.is_set():
print(f'Count: {count}')
count += 1
time.sleep(1)
All control loops are started in separate threads when an agent is executed, and run until the control loop exits or the agent is shut down, as indicated by the shutdown
event.
Agent to Agent Interaction¶
Agent handles can be passed to other agents to facilitate agent-to-agent interaction.
Here, a Coordinator
is initialized with handles to two other agents implementing the Lowerer
and Reverser
behaviors, respectively.
from academy.behavior import action
from academy.behavior import Behavior
from academy.handle import Handle
class Coordinator(Behavior):
def __init__(
self,
lowerer: Handle[Lowerer],
reverser: Handle[Reverser],
) -> None:
self.lowerer = lowerer
self.reverser = reverser
@action
def process(self, text: str) -> str:
text = self.lowerer.action('lower', text).result()
text = self.reverser.action('reverse', text).result()
return text
class Lowerer(Behavior):
@action
def lower(self, text: str) -> str:
return text.lower()
class Reverser(Behavior):
@action
def reverse(self, text: str) -> str:
return text[::-1]
After launching the Lowerer
and Reverser
, the respective handles can be used to initialize the Coordinator
before launching it.
from academy.exchange.thread import ThreadExchange
from academy.launcher import ThreadLauncher
from academy.logging import init_logging
from academy.manager import Manager
def main() -> None:
init_logging('INFO')
with Manager(
exchange=ThreadExchange(),
launcher=ThreadLauncher(),
) as manager:
lowerer = manager.launch(Lowerer())
reverser = manager.launch(Reverser())
coordinator = manager.launch(Coordinator(lowerer, reverser))
text = 'DEADBEEF'
expected = 'feebdaed'
future = coordinator.process(text)
assert future.result() == expected
if __name__ == '__main__':
main()
Distributed Execution¶
The prior examples have launched agent in threads of the main process, but in practice agents are launched in different processes, possibly on the same node or remote nodes.
The prior example can be executed in a distributed fashion by changing the launcher and exchange to implementations which support distributed execution.
Below, a Redis server server (via the RedisExchange
) is used to support messaging between distributed agents executed with a ProcessPoolExecutor
(via the Launcher
).
from concurrent.futures import ProcessPoolExecutor
from academy.exchange.redis import RedisExchange
from academy.launcher import Launcher
def main() -> None:
process_pool = ProcessPoolExecutor(max_processes=4)
with Manager(
exchange=RedisExchange('<REDIS HOST>', port=6379),
launcher=Launcher(process_pool),
) as manager:
...
The Launcher
is compatible with any concurrent.futures.Executor
.