Summary of Changes
This release brings many API and internal improvements. I've incorporated a lot of the great feedback I've been getting and improved multiple areas in terms of clarity, organization, and implementation.
What I'm most excited about is that the API feels like it's beginning to reach a relatively clean and extensible form, enabling more possibilities to follow.
A number of syntactic and semantic changes have been made. I've summarized the changes here, but for full details also refer to the updated [docs/](./docs/) or [readme](./README.md).
As always please let me know what you think, especially if you find any issues!
Syntax and functional changes
* Introduced a new `action` decorator based syntax for defining actions and their metadata, replacing the `access_policy` decorator. For example:
python
action
def my_action(self):
...
An example showing how the `access_policy` is now declared:
python
action(access_policy=ACCESS_PERMITTED)
def my_action(self):
...
Additionally, the `_action__` prefix is no longer needed on action method names.
* Agent `id`'s are now enforced to be unique.
Allowing duplicate `id`'s creates more problems than it solves. If one needs to duplicate or distribute messages to multiple consumers, that can be achieved by simply sending multiple messages from the agent.
* Leading underscores removed from several agent methods and callbacks.
Moving forward, methods with leading underscores should be treated as internal API methods and should generally not be referenced in except when overriding methods or extending a class. To follow this convention, the following methods have been renamed:
`Agent` class methods:
* `_send` is now `send`
* `_before_add` is now `before_add`
* `_after_add` is now `after_add`
* `_before_remove` is now `before_remove`
* `_after_remove` is now `after_remove`
* `_request_permission` is now `request_permission`
* Additionally, the special action `return` is now renamed to `response`.
* The signatures for `response`(formerly `return`) and `error` have changed to the following:
python
action
def response(self, data, original_message_id: str):
...
action
def error(self, error: str, original_message_id: str):
...
Note that the `original_message` argument is replaced with the `original_message_id` parameter. This parameter is discussed with the message schema changes below.
* Updated `help` functionality
The `help` action now returns a new schema for describing actions. The data structure returned is now a dictionary similar to the following example:
python
{
"say": {
"description": "Say something to this agent",
"args": {
"content": {
"type": "string"
"description": "The content to say"
}
},
"returns": {
"type": "string"
"description": "A response"
}
},
...
}
The help structure above is automatically generated from the method's docstring and signature. Additionally, you can provide a custom help schema using the `action` decorator.
See the ["Defining Actions"](./docs/defining-actions) section for more details on the help schema.
* `Space.remove_all()` method added for removing all agents who were added through the receiving space instance. In other words, in an AMQP distributed system, only the locally added agents would be removed.
Message schema changes
* Added an optional `id` field. This field is automatically provided with `response` or `error` messages for you to correlate with the original message.
* Added an optional `meta` field. This field is an optional dictionary of key-value pairs for you to attach metadata to a message. For example, you may use this to store timestamps, or a "thoughts" field for recording reasoning.
* The `to` field is now required. To send a broadcast use the special address `*`.
* The `action` field is now an object containing the `name` and `args` fields.
The full schema is summarized with this example:
python
{
"id": "optional string id",
"meta": {
"an": "optional",
"field": ["for", "metadata"]
},
"to": "an agent id", or '*' to broadcast
"from": "sender's id",
"action": {
"name": "my_action",
"args": {
"the": "args"
}
}
}
See the docs ["Schema"](./docs/schema) section for more details.
Message routing changes
* Broadcasts are now self received by default. This is the normal semantics for broadcasts.
This behavior is now an option on the `Agent` class: `receive_own_broadcasts: bool = True`.
Setting it to `False` will restore the previous behavior.
* Messages sent to a non-existent agent will now be silently ignored. Previously this would result in an error message being returned. Note that calling a non-existent _action_ on an existent _agent_ will still result in an error message.
This change brings us more in line with the Actor model. Reliability options may be added at the Agent level in the future.
Internal Improvements
* Removed `colorama` dependency
* Removed use of `id_queue` in `AMQPSpace` for checking agent existence.
* `Space._route()` no longer sets the `from` field on messages. The entire
message must be populated within the Agent instance before calling `_route()`.
* Threading improvements
Threading code has been greatly cleaned up and improved. The updated implementation should make it easy to support additional forms of multi-processing like green threads and the multiprocessing module. I'm really happy about this change in particular.
* Space classes (`NativeSpace` and `AMQPSpace`) moved to the `agency.spaces` namespace.
* Tests have been better organized into separate files.
* The `Space` class abstract methods have been changed. `Space` subclasses now implement the following methods:
* `_connect()`
* `_disconnect()`
* `_deliver()`
* `_consume()`
This is a change from the previous interface. See the [`Space`](./agency/space.py) class for more details.
Demo improvements
* `OpenAIFunctionAgent` now supplies argument descriptions to the OpenAI function calling API, enabled by the new `help` information schema.
* Agent mixins have been refactored to better separate behavior.
* Updated slash syntax in the UI. Slash commands now follow this example:
/agent_id.action_name arg1:value1 arg2:value2 ...
The `agent_id` above defines the `to` field of the message and is required.
To broadcast an action, use the syntax:
/*.action_name arg1:value1 arg2:value2 ...
Note that unformatted text is still considered a broadcasted `say` action.
That's all for now!