Summary of Changes
This release brings multiprocessing support and a few nice API improvements. Notably, I've added an implementation of synchronous messaging with the new `request()` method, and I've introduced logging support for development and debugging.
There are a number of breaking API changes in this release. Please read through the following summary to update your codebase.
Since this update required a rather large internal change, please beware of bugs and report any issues that you find. ❤️
`Space` Class Changes
`Space.add()`
`Space.add()` no longer accepts an `Agent` instance as an argument. Spaces will now create the `Agent` instance for you when adding them to the space.
An example of the updated syntax:
space.add(MyAgent, "my_agent_id")
Note how it accepts the `Agent` type. Any remaining parameters starting with the `id` are passed to the type's constructor when creating the agent.
`MultiprocessSpace` added
The new `MultiprocessSpace` class allows for better parallelism on a single host. Adding this option was the initial focus of this update. It uses the multiprocessing library for both concurrency and communication.
`NativeSpace` renamed to `ThreadSpace`
The `NativeSpace` class has been renamed to `ThreadSpace` for clarity.
`AMQPSpace` updated to use multiprocessing for local concurrency
Since multiprocessing is now supported, it made sense to use it rather than threading with the `AMQPSpace` class for local concurrency. This means that you do not need to define separate applications to achieve parallelism on a single host when using AMQP.
`Agent` Class Changes
Synchronous messaging support added with `Agent.request()`
To allow calling actions synchronously, the `Agent` class now includes a `request()` method.
For example:
python
try:
return_value = self.request({
"to": "ExampleAgent",
"action": {
"name": "example_action",
"args": {
"content": "hello"
}
}
}, timeout=5)
except ActionError as e:
print(e.message)
The `Agent.request()` method is a synchronous version of the `send()` method that allows you to call an action and receive its return value or exception synchronously.
`response()` and `error()` renamed to `handle_action_value()` and `handle_action_error()`
Additionally, these methods are no longer implemented as actions. You should remove the `action` decorator from these methods when you update them.
To clarify moving forward, an action (a method decorated by `action`) should be considered a public method available to other agents. Callbacks are called upon certain events, rather than being directly invoked by an agent.
In the cases of `handle_action_value()` and `handle_action_error()`, these callbacks are invoked when a previously sent action returns a value or raises an exception.
Also note that their signatures have removed the `original_message_id` argument and simplified to:
python
def handle_action_value(self, value):
and
python
def handle_action_error(self, error: ActionError):
See the `original_message()` method for how the original message may now be accessed.
`Agent.current_message()` is now a method
`Agent.current_message()` may be called during any action or action related callback to inspect the full message being processed.
`Agent.original_message()` added
Similar to `current_message()`, `original_message()` may be called during the `handle_action_value()` and `handle_action_error()` callbacks to inspect the original message that the current message is responding to.
The original message must have defined the `meta.id` field for this method to return it. Otherwise, this method will return None.
Message schema changes
Moved `id` field to `meta.id`
The `id` field has been moved within the `meta` field to be treated similarly as any other metadata.
If set, the `id` field may be used to correlate response messages (internal messages that return a value or exception) with the original message they correspond to. See the `original_message()` method for more details.
`action.args` is now optional
`action.args` can be omitted when calling an action that takes no arguments. For example, given the following action:
python
action
def example_action(self):
...
The following message is valid:
python
self.send({
"to": "ExampleAgent",
"action": {
"name": "example_action"
}
})
Beware that this may cause errors if your code currently inspects messages and expects the `action.args` field to be present.
Additional Changes
Added Logging Support
Some basic logging has been added. Set the `LOGLEVEL` environment variable to see the logs.
The default setting is `warning`. `info` will show messages as they are being sent. `debug` shows more fine grained handling of messages as they are received and processed.
If you'd like to add more logging or make changes, please open a PR or issue.
Demo updates
* A `multiprocess` example has been added.
Run `demo run multiprocess` to try it out
* The `native` service has been renamed to `threaded`
* The `request_permission` callback on `Host` class has been bypassed temporarily.
Asking for terminal input in a multiprocess application doesn't work well yet. A future update should address this. So for the time being, all actions on the host will be allowed.
* Unfortunately gradio live reloading also had to be disabled. You'll have to restart the app for code changes to take effect. If either of the above changes cause problems for you, please open an issue.