Flesh out agent docs more (#71)

This commit is contained in:
Jack Gerrits 2024-06-12 17:10:17 -04:00 committed by GitHub
parent 92d413e877
commit c36ea487e0
7 changed files with 120 additions and 15 deletions

View File

@ -50,3 +50,9 @@ html_theme_options = {
"source_branch": "main",
"source_directory": "docs/src/",
}
autodoc_default_options = {
"members": True,
"undoc-members": True,
"private-members": True
}

View File

@ -2,23 +2,67 @@
An agent in AGNext is an entity that can react to, send, and publish
messages. Messages are the only means through which agents can communicate
with each others.
with each other.
## TypeRoutedAgent
Examples of agents include:
{py:class}`agnext.components.TypeRoutedAgent`
is a base class for building custom agents. It provides
a simple API for associating message types with message handlers.
Here is an example of simple agent that reacts to `TextMessage`:
- A chat completion agent that makes requests to an LLM in response to receiving messages.
```python
from agnext.chat.types import TextMessage
from agnext.components import TypeRoutedAgent, message_handler
from agnext.core import AgentRuntime, CancellationToken
## Messages
class MyAgent(TypeRoutedAgent):
Messages are typed, and serializable (to JSON) objects that agents use to communicate. The type of a message is used to determine which agents a message should be delivered to, if an agent can handle a message and the handler that should be invoked when the message is received by an agent. If an agent is invoked with a message it is not able to handle, it must raise {py:class}`~agnext.core.exceptions.CantHandleException`.
@message_handler()
async def on_text_message(self, message: TextMessage, cancellation_token: CancellationToken)) -> None:
await self._publish(TextMessage(content=f"I received this message: ({message.content}) from {message.source}"))
Generally, messages are one of:
- A subclass of Pydantic's {py:class}`pydantic.BaseModel`
- A dataclass
Messages are purely data, and should not contain any logic.
### Required Message Types
At the core framework level there is *no requirement* of which message types are handled by an agent. However, some behavior patterns require agents understand certain message types. For an agent to participate in these patterns, it must understand any such required message types.
For example, the chat layer in AGNext has the following required message types:
- {py:class}`agnext.chat.types.PublishNow`
- {py:class}`agnext.chat.types.Reset`
These are purely behavioral messages that are used to control the behavior of agents in the chat layer and do not represent any content.
Agents should document which message types they can handle. Orchestrating agents should document which message types they require.
```{tip}
An important part of designing an agent or choosing which agents to use is understanding which message types are required by the agents you are using.
```
## Communication
There are two forms of communication in AGNext:
- **Direct communication**: An agent sends a message to another agent.
- **Broadcast communication**: An agent publishes a message to all agents.
### Message Handling
When an agent receives a message the runtime will invoke the agent's message handler ({py:meth}`agnext.core.Agent.on_message`) which should implement the agents message handling logic. If this message cannot be handled by the agent, the agent should raise a {py:class}`~agnext.core.exceptions.CantHandleException`. For the majority of custom agent's {py:meth}`agnext.core.Agent.on_message` will not be directly implemented, but rather the agent will use the {py:class}`~agnext.components.TypeRoutedAgent` base class which provides a simple API for associating message types with message handlers.
### Direct Communication
Direct communication is effectively an RPC call directly to another agent. When sending a direct message to another agent, the receiving agent can respond to the message with another message, or simply return `None`. To send a message to another agent, within a message handler use the {py:meth}`agnext.core.BaseAgent._send_message` method. Awaiting this call will return the response of the invoked agent. If the receiving agent raises an exception, this will be propagated back to the sending agent.
To send a message to an agent outside of agent handling a message the message should be sent via the runtime with the {py:meth}`agnext.core.AgentRuntime.send_message` method. This is often how an application might "start" a workflow or conversation.
### Broadcast Communication
As part of the agent's implementation it must advertise the message types that it would like to receive when published ({py:attr}`agnext.core.Agent.subscriptions`). If one of these messages is published, the agent's message handler will be invoked. The key difference between direct and broadcast communication is that broadcast communication is not a request/response pattern. When an agent publishes a message it is one way, it is not expecting a response from any other agent. In fact, they cannot respond to the message.
To publish a message to all agents, use the {py:meth}`agnext.core.BaseAgent._publish_message` method. This call must still be awaited to allow the runtime to deliver the message to all agents, but it will always return `None`. If an agent raises an exception while handling a published message, this will be logged but will not be propagated back to the publishing agent.
To publish a message to all agents outside of an agent handling a message, the message should be published via the runtime with the {py:meth}`agnext.core.AgentRuntime.publish_message` method.
If an agent publishes a message type for which it is subscribed it will not receive the message it published. This is to prevent infinite loops.
```{note}
Currently an agent does not know if it is handling a published or direct message. So, if a response is given to a published message, it will be thrown away.
```

View File

@ -0,0 +1 @@
# Cancellation

View File

@ -0,0 +1 @@
# Logging

View File

@ -0,0 +1,50 @@
# Using Type Routed Agent
To make it easier to implement agents that respond to certain message types there is a base class called {py:class}`~agnext.components.TypeRoutedAgent`. This class provides a simple decorator pattern for associating message types with message handlers.
The decorator {py:func}`agnext.components.message_handler` should be added to functions in the class that are intended to handle messages. These functions have a specific signature that needs to be followed for it to be recognized as a message handler.
- The function must be an `async` function.
- The function must be decorated with the `message_handler` decorator.
- The function must have exactly 3 arguments.
- `self`
- `message`: The message to be handled, this must be type hinted with the message type that it is intended to handle.
- `cancellation_token`: A {py:class}`agnext.core.CancellationToken` object
- The function must be type hinted with what message types it can return.
```{tip}
Handlers can handle more than one message type by accepting a Union of the message types. It can also return more than one message type by returning a Union of the message types.
```
## Example
The following is an example of a simple agent that broadcasts the fact it received messages, and resets its internal counter when it receives a reset message.
One important thing to point out is that when an agent is constructed it must be passed a runtime object. This allows the agent to communicate with other agents via the runtime.
```python
from agnext.chat.types import MultiModalMessage, Reset, TextMessage
from agnext.components import TypeRoutedAgent, message_handler
from agnext.core import AgentRuntime, CancellationToken
class MyAgent(TypeRoutedAgent):
def __init__(self, name: str, runtime: AgentRuntime):
super().__init__(name, "I am a demo agent", runtime)
self._received_count = 0
@message_handler()
async def on_text_message(
self, message: TextMessage | MultiModalMessage, cancellation_token: CancellationToken
) -> None:
await self._publish_message(
TextMessage(
content=f"I received a message from {message.source}. Message received #{self._received_count}",
source=self.name,
)
)
@message_handler()
async def on_reset(self, message: Reset, cancellation_token: CancellationToken) -> None:
self._received_count = 0
```

View File

@ -27,12 +27,15 @@ common uses, such as chat completion agents, but also allows for fully custom ag
core-concepts/patterns
core-concepts/memory
core-concepts/tools
core-concepts/cancellation
core-concepts/logging
.. toctree::
:caption: Guides
:hidden:
guides/azure_openai_with_aad_auth
guides/type-routed-agent
guides/azure-openai-with-aad-auth
.. toctree::