mirror of
https://github.com/microsoft/autogen.git
synced 2025-08-20 06:31:54 +00:00
Refactor documentation pages and Add core concept page. (#140)
This commit is contained in:
parent
ebed669231
commit
4023f13b5f
@ -1,16 +1,4 @@
|
||||
# Memory
|
||||
|
||||
Memory is a collection of data corresponding to the conversation history
|
||||
of an agent.
|
||||
Data in meory can be just a simple list of all messages,
|
||||
or one which provides a view of the last N messages.
|
||||
|
||||
To create a custom memory implementation, you need to subclass the
|
||||
{py:class}`agnext.components.memory.ChatMemory` protocol class and implement
|
||||
all its methods.
|
||||
For example, you can use [LLMLingua](https://github.com/microsoft/LLMLingua)
|
||||
to create a custom memory implementation that provides a compressed
|
||||
view of the conversation history.
|
||||
# Buffered Memory
|
||||
|
||||
Here is an example of a custom memory implementation that keeps a view of the
|
||||
last N messages:
|
@ -1,69 +0,0 @@
|
||||
# Agent
|
||||
|
||||
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 other.
|
||||
|
||||
## Messages
|
||||
|
||||
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`.
|
||||
|
||||
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.
|
||||
|
||||
```{tip}
|
||||
It is *strongly* recommended that messages are Pydantic models. This allows for easy serialization and deserialization of messages, and provides a clear schema for the message.
|
||||
```
|
||||
|
||||
<!-- ### 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 direct message to another agent.
|
||||
- **Broadcast communication**: An agent publishes a message to all agents in the same namespace.
|
||||
|
||||
### 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
|
||||
|
||||
Broadcast communication is effectively the publish-subscribe model.
|
||||
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 model. 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.
|
||||
```
|
@ -1 +0,0 @@
|
||||
# Cancellation
|
@ -1,19 +0,0 @@
|
||||
# Namespace
|
||||
|
||||
Namespace allow for defining logical boundaries between agents.
|
||||
|
||||
Namespaces are strings, and the default is `default`.
|
||||
|
||||
Two possible use cases of agents are:
|
||||
|
||||
- Creating a multi-tenant system where each tenant has its own namespace. For
|
||||
example, a chat system where each tenant has its own set of agents.
|
||||
- Security boundaries between agent groups. For example, a chat system where
|
||||
agents in the `admin` namespace can communicate with agents in the `user`
|
||||
namespace, but not the other way around.
|
||||
|
||||
The {py:class}`agnext.core.AgentId` is used to address an agent, it is the combination of the agent's namespace and its name.
|
||||
|
||||
When getting an agent reference ({py:meth}`agnext.core.AgentRuntime.get`) or proxy ({py:meth}`agnext.core.AgentRuntime.get_proxy`) from the runtime the namespace can be specified. Agents have an ID property ({py:attr}`agnext.core.Agent.id`) that returns the agent's id. Additionally, the register method takes a factory that can optionally accept the ID as an argument ({py:meth}`agnext.core.AgentRuntime.register`).
|
||||
|
||||
By default, there are no restrictions and are left to the application to enforce. The runtime will however automatically create agents in a namespace if it does not exist.
|
@ -1,36 +0,0 @@
|
||||
# Agent Runtime
|
||||
|
||||
Agent runtime is the execution environment for agents in AGNext.
|
||||
Similar to the runtime environment of a programming language, the
|
||||
agent runtime provides the necessary infrastructure to facilitate communication
|
||||
between agents, manage agent states, and provide API for monitoring and
|
||||
debugging multi-agent interactions.
|
||||
|
||||
Further readings:
|
||||
|
||||
1. {py:class}`agnext.core.AgentRuntime`
|
||||
2. {py:class}`agnext.application.SingleThreadedAgentRuntime`
|
||||
|
||||
## Agent Registration
|
||||
|
||||
Agents are registered with the runtime using the
|
||||
{py:meth}`agnext.core.AgentRuntime.register` method. The process of registration
|
||||
associates some name, which is the `type` of the agent with a factory function
|
||||
that is able to create an instance of the agent in a given namespace. The reason
|
||||
for the factory function is to allow automatic creation of agents when they are
|
||||
needed, including automatic creation of agents for not yet existing namespaces.
|
||||
|
||||
Once an agent is registered, a reference to the agent can be retrieved by
|
||||
calling {py:meth}`agnext.core.AgentRuntime.get` or
|
||||
{py:meth}`agnext.core.AgentRuntime.get_proxy`. There is a convenience method
|
||||
{py:meth}`agnext.core.AgentRuntime.register_and_get` that both registers a type
|
||||
and gets a reference.
|
||||
|
||||
A byproduct of this process of `register` + `get` is that
|
||||
{py:class}`agnext.core.Agent` interface is a purely implementation contract. All
|
||||
agents must be communicated with via the runtime. This is a key design decision
|
||||
that allows the runtime to manage the lifecycle of agents, and to provide a
|
||||
consistent API for interacting with agents. Therefore, to communicate with
|
||||
another agent the {py:class}`agnext.core.AgentId` must be used. There is a
|
||||
convenience class {py:meth}`agnext.core.AgentProxy` that bundles an ID and a
|
||||
runtime together.
|
@ -1,8 +0,0 @@
|
||||
# Tools
|
||||
|
||||
Tools can be used together with agents powered by the OpenAI's ChatCompletion or the Assistant API.
|
||||
AGNext provides the {py:mod}`agnext.components.tools` module with a suite of built-in
|
||||
tools and utilities for creating and running custom tools.
|
||||
|
||||
See [examples](https://github.com/microsoft/agnext/tree/main/python/examples#tool-use-examples)
|
||||
for how to use the built-in code execution tool and creating custom tools.
|
456
python/docs/src/getting-started/core-concepts.md
Normal file
456
python/docs/src/getting-started/core-concepts.md
Normal file
@ -0,0 +1,456 @@
|
||||
# Core Concepts
|
||||
|
||||
## What is Multi-Agent Application?
|
||||
|
||||
A wide variety of software applications can be modeled as a collection of independent
|
||||
agents that communicate with each other through messages:
|
||||
sensors on a factory floor,
|
||||
distributed services powering web applications,
|
||||
business workflows involving multiple stakeholders,
|
||||
and more recently, generative artificial intelligence (AI) models (e.g., GPT-4) that can write code and interact with
|
||||
other software systems.
|
||||
We refer to them as multi-agent applications.
|
||||
|
||||
In a multi-agent application, agents can live in the same process, on the same machine,
|
||||
or on different machines and across organizational boundaries.
|
||||
They can be implemented using different AI models, instructions, and programming languages.
|
||||
They can collaborate and work toward a common goal.
|
||||
|
||||
Each agent is a self-contained unit:
|
||||
developers can build, test and deploy it independently, and reuse it for different scenarios.
|
||||
Agents are composable: simple agents can form complex applications.
|
||||
|
||||
## AGNext Overview
|
||||
|
||||
AGNext is a framework for building multi-agent applications.
|
||||
It provides a runtime envionment to facilitate communication between agents,
|
||||
manage their identities and lifecycles, and enforce boundaries.
|
||||
It also provides a set of common patterns and components to help developers build
|
||||
AI agents that can work together.
|
||||
|
||||
AGNext is designed to be unopinionated and extensible.
|
||||
It does not prescribe an abstraction for agents or messages, rather, it provides
|
||||
a minimal base layer that can be extended to suit the application's needs.
|
||||
Developers can build agents quickly by using the provided components including
|
||||
type-routed agent, AI model clients, tools for AI models, code execution sandboxes,
|
||||
memory stores, and more.
|
||||
Developers can also make use of the provided multi-agent patterns to build
|
||||
orchestrated workflows, group chat systems, and more.
|
||||
|
||||
The API consists of the following modules:
|
||||
|
||||
- {py:mod}`agnext.core` - The core interfaces that defines agent and runtime.
|
||||
- {py:mod}`agnext.application` - Implementations of the runtime and other modules (e.g., logging) for building applications.
|
||||
- {py:mod}`agnext.components` - Independent agent-building components: agents, models, memory, and tools.
|
||||
|
||||
## Agent and Agent Runtime
|
||||
|
||||
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 other.
|
||||
|
||||
An agent runtime is the execution environment for agents in AGNext.
|
||||
Similar to the runtime environment of a programming language, the
|
||||
agent runtime provides the necessary infrastructure to facilitate communication
|
||||
between agents, manage agent lifecycles, enforce security boundaries, and support monitoring and
|
||||
debugging.
|
||||
For local development, developers can use {py:class}`~agnext.application.SingleThreadedAgentRuntime`,
|
||||
which can be embedded in a Python application.
|
||||
|
||||
```{note}
|
||||
Agents are not directly instantiated and managed by application code.
|
||||
Instead, they are created by the runtime when needed and managed by the runtime.
|
||||
```
|
||||
|
||||
### Implementing an Agent
|
||||
|
||||
To implement an agent, developer must subclass the {py:class}`~agnext.core.BaseAgent` class,
|
||||
declare the message types it can handle in the {py:attr}`~agnext.core.AgentMetadata.subscriptions` metadata,
|
||||
and implement the {py:meth}`~agnext.core.BaseAgent.on_message` method.
|
||||
This method is invoked when the agent receives a message. For example,
|
||||
the following agent handles string messages and simply prints message it receives:
|
||||
|
||||
```python
|
||||
from agnext.core import BaseAgent, CancellationToken
|
||||
|
||||
class MyAgent(BaseAgent):
|
||||
def __init__(self):
|
||||
super().__init__("MyAgent", subscriptions=[str])
|
||||
|
||||
async def on_message(self, message: str, cancellation_token: CancellationToken) -> None:
|
||||
print(f"Received message: {message}")
|
||||
```
|
||||
|
||||
For convenience, developers can subclass the {py:class}`~agnext.components.TypeRoutedAgent` class
|
||||
which provides an easy-to use API to implement different message handlers for different message types.
|
||||
See the section on message handlers below.
|
||||
|
||||
### Registering Agents
|
||||
|
||||
To make an agent available to the runtime, developers can use the
|
||||
{py:meth}`~agnext.core.AgentRuntime.register` method.
|
||||
The process of registration associates a name and a factory function
|
||||
that creates an instance of the agent in a given namespace.
|
||||
The factory function is used to allow automatic creation of agents when they are needed.
|
||||
|
||||
For example, to register an agent with the {py:class}`~agnext.application.SingleThreadedAgentRuntime`,
|
||||
the following code can be used:
|
||||
|
||||
```python
|
||||
from agnext.application import SingleThreadedAgentRuntime
|
||||
|
||||
runtime = SingleThreadedAgentRuntime()
|
||||
runtime.register("my_agent", lambda: MyAgent())
|
||||
```
|
||||
|
||||
Once an agent is registered, a reference to the agent can be retrieved by
|
||||
calling {py:meth}`~agnext.core.AgentRuntime.get` or
|
||||
{py:meth}`~agnext.core.AgentRuntime.get_proxy`. For example, to
|
||||
send a message to the agent we just registered:
|
||||
|
||||
```python
|
||||
agent = runtime.get("my_agent")
|
||||
await runtime.send_message("Hello, World!", agent)
|
||||
```
|
||||
|
||||
There is a convenience method
|
||||
{py:meth}`~agnext.core.AgentRuntime.register_and_get` that both registers an agent
|
||||
and gets a reference.
|
||||
|
||||
```{note}
|
||||
Because the runtime manages the lifecycle of agents, a reference to an agent,
|
||||
whether it is {py:class}`~agnext.core.AgentId` or {py:class}`~agnext.core.AgentProxy`,
|
||||
is only used to communicate with the agent or retrieve its metadata (e.g., description).
|
||||
```
|
||||
|
||||
### Running the Agent Runtime
|
||||
|
||||
The above code snippets will not actually produce any output because the
|
||||
runtime is not running.
|
||||
The local embedded runtime {py:class}`~agnext.application.SingleThreadedAgentRuntime`
|
||||
can be called to process messages until there are no more messages to process.
|
||||
|
||||
```python
|
||||
await runtime.process_until_idle()
|
||||
```
|
||||
|
||||
It can also be called to process a single message:
|
||||
|
||||
```python
|
||||
await runtime.process_next()
|
||||
```
|
||||
|
||||
Other runtime implementations will have their own way of running the runtime.
|
||||
|
||||
## Messages
|
||||
|
||||
Agents communicate with each other via messages.
|
||||
Messages are serializable objects, they can be defined using:
|
||||
|
||||
- A subclass of Pydantic's {py:class}`pydantic.BaseModel`, or
|
||||
- A dataclass
|
||||
- A built-in serializable Python type (e.g., `str`).
|
||||
|
||||
For example:
|
||||
|
||||
```python
|
||||
from dataclasses import dataclass
|
||||
|
||||
@dataclass
|
||||
class TextMessage:
|
||||
content: str
|
||||
source: str
|
||||
|
||||
@dataclass
|
||||
class ImageMessage:
|
||||
url: str
|
||||
source: str
|
||||
```
|
||||
|
||||
```{note}
|
||||
Messages are purely data, and should not contain any logic.
|
||||
```
|
||||
|
||||
### Message Handlers
|
||||
|
||||
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 convenience, the {py:class}`~agnext.components.TypeRoutedAgent` base class
|
||||
provides a simple API for associating message types with message handlers,
|
||||
so developers do not need to implement the {py:meth}`~agnext.core.Agent.on_message` method.
|
||||
|
||||
For example, the following type-routed agent responds to `TextMessage` and `ImageMessage`
|
||||
using different message handlers:
|
||||
|
||||
```python
|
||||
from agnext.application import SingleThreadedAgentRuntime
|
||||
from agnext.components import TypeRoutedAgent, message_handler
|
||||
from agnext.core import CancellationToken
|
||||
|
||||
class MyAgent(TypeRoutedAgent):
|
||||
@message_handler
|
||||
async def on_text_message(self, message: TextMessage, cancellation_token: CancellationToken) -> None:
|
||||
print(f"Hello, {message.source}, you said {message.content}!")
|
||||
|
||||
@message_handler
|
||||
async def on_image_message(self, message: ImageMessage, cancellation_token: CancellationToken) -> None:
|
||||
print(f"Hello, {message.source}, you sent me {message.url}!")
|
||||
|
||||
async def main() -> None:
|
||||
runtime = SingleThreadedAgentRuntime()
|
||||
agent = runtime.register_and_get("my_agent", lambda: MyAgent("My Agent"))
|
||||
await runtime.send_message(TextMessage(content="Hello, World!", source="User"), agent)
|
||||
await runtime.send_message(ImageMessage(url="https://example.com/image.jpg", source="User"), agent)
|
||||
runtime.process_until_idle()
|
||||
|
||||
import asyncio
|
||||
asyncio.run(main())
|
||||
```
|
||||
|
||||
## Communication
|
||||
|
||||
There are two types of communication in AGNext:
|
||||
|
||||
- **Direct communication**: An agent sends a direct message to another agent.
|
||||
- **Broadcast communication**: An agent publishes a message to all agents in the same namespace.
|
||||
|
||||
### Direct Communication
|
||||
|
||||
To send a direct message to another agent, within a message handler use
|
||||
the {py:meth}`agnext.core.BaseAgent.send_message` method,
|
||||
from the runtime use the {py:meth}`agnext.core.AgentRuntime.send_message` method.
|
||||
|
||||
Awaiting this method call will return the a `Future[T]` object where `T` is the type
|
||||
of response of the invoked agent.
|
||||
The future object can be awaited to get the actual response.
|
||||
|
||||
```{note}
|
||||
If the invoked agent raises an exception while the sender is awaiting on
|
||||
the future, the exception will be propagated back to the sender.
|
||||
```
|
||||
|
||||
#### Request/Response
|
||||
|
||||
Direct communication can be used for request/response scenarios,
|
||||
where the sender expects a response from the receiver.
|
||||
The receiver can respond to the message by returning a value from its message handler.
|
||||
You can think of this as a function call between agents.
|
||||
|
||||
For example, consider the following type-routed agents:
|
||||
|
||||
```python
|
||||
from agnext.application import SingleThreadedAgentRuntime
|
||||
from agnext.components import TypeRoutedAgent, message_handler
|
||||
from agnext.core import CancellationToken, AgentId
|
||||
|
||||
class InnerAgent(TypeRoutedAgent):
|
||||
@message_handler
|
||||
async def on_str_message(self, message: str, cancellation_token: CancellationToken) -> str:
|
||||
return f"Hello from inner, {message}"
|
||||
|
||||
class OuterAgent(TypeRoutedAgent):
|
||||
def __init__(self, inner_agent_id: AgentId):
|
||||
super().__init__("OuterAgent")
|
||||
self.inner_agent_id = inner_agent_id
|
||||
|
||||
@message_handler
|
||||
async def on_str_message(self, message: str, cancellation_token: CancellationToken) -> None:
|
||||
print(f"Received message: {message}")
|
||||
# Send a direct message to the inner agent and receves a response future.
|
||||
response_future = await self.send_message(f"Hello from outer, {message}", self.inner_agent_id)
|
||||
# Wait for the response to be ready.
|
||||
response = await response_future
|
||||
print(f"Received inner response: {response}")
|
||||
|
||||
async def main() -> None:
|
||||
runtime = SingleThreadedAgentRuntime()
|
||||
inner = runtime.register_and_get("inner_agent", lambda: InnerAgent("InnerAgent"))
|
||||
outer = runtime.register_and_get("outer_agent", lambda: OuterAgent("OuterAgent", inner))
|
||||
await runtime.send_message("Hello, World!", outer)
|
||||
runtime.process_until_idle()
|
||||
|
||||
import asyncio
|
||||
asyncio.run(main())
|
||||
```
|
||||
|
||||
In the above example, upone receving a message,
|
||||
the `OuterAgent` sends a direct string message to the `InnerAgent` and receives
|
||||
a string message in response. The following output will be produced:
|
||||
|
||||
```text
|
||||
Received message: Hello, World!
|
||||
Received inner response: Hello from inner, Hello from outer, Hello, World!
|
||||
```
|
||||
|
||||
```{note}
|
||||
To get the response after sending a message, the sender must await on the
|
||||
response future. So you can also write `response = await await self.send_message(...)`.
|
||||
```
|
||||
|
||||
#### Send, No Reply
|
||||
|
||||
In many scenarios, the sender does not need a response from the receiver.
|
||||
In this case, the sender does not need to await on the response future,
|
||||
and the receiver does not need to return a value from the message handler.
|
||||
In the following example, the `InnerAgent` does not return a value,
|
||||
and the `OuterAgent` does not await on the response future:
|
||||
|
||||
```python
|
||||
from agnext.application import SingleThreadedAgentRuntime
|
||||
from agnext.components import TypeRoutedAgent, message_handler
|
||||
from agnext.core import CancellationToken, AgentId
|
||||
|
||||
class InnerAgent(TypeRoutedAgent):
|
||||
@message_handler
|
||||
async def on_str_message(self, message: str, cancellation_token: CancellationToken) -> None:
|
||||
# Just print the message.
|
||||
print(f"Hello from inner, {message}")
|
||||
|
||||
class OuterAgent(TypeRoutedAgent):
|
||||
def __init__(self, inner_agent_id: AgentId):
|
||||
super().__init__("OuterAgent")
|
||||
self.inner_agent_id = inner_agent_id
|
||||
|
||||
@message_handler
|
||||
async def on_str_message(self, message: str, cancellation_token: CancellationToken) -> None:
|
||||
print(f"Received message: {message}")
|
||||
# Send a direct message to the inner agent and move on.
|
||||
await self.send_message(f"Hello from outer, {message}", self.inner_agent_id)
|
||||
# No need to wait for the response, just do other things.
|
||||
|
||||
async def main() -> None:
|
||||
runtime = SingleThreadedAgentRuntime()
|
||||
inner = runtime.register_and_get("inner_agent", lambda: InnerAgent("InnerAgent"))
|
||||
outer = runtime.register_and_get("outer_agent", lambda: OuterAgent("OuterAgent", inner))
|
||||
await runtime.send_message("Hello, World!", outer)
|
||||
runtime.process_until_idle()
|
||||
|
||||
import asyncio
|
||||
asyncio.run(main())
|
||||
```
|
||||
|
||||
In the above example, the `OuterAgent` sends a direct string message to the `InnerAgent`
|
||||
but does not await on the response future. The following output will be produced:
|
||||
|
||||
```text
|
||||
Received message: Hello, World!
|
||||
Hello from inner, Hello from outer, Hello, World!
|
||||
```
|
||||
|
||||
### Broadcast Communication
|
||||
|
||||
Broadcast communication is effectively the publish/subscribe model.
|
||||
As part of the base agent ({py:class}`~agnext.core.BaseAgent`) implementation,
|
||||
it must advertise the message types that
|
||||
it would like to receive when published ({py:attr}`~agnext.core.AgentMetadata.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 cannot be used for request/response scenarios.
|
||||
When an agent publishes a message it is one way only, it cannot receive a response
|
||||
from any other agent, even if a receiving agent sends a response.
|
||||
|
||||
```{note}
|
||||
An agent receiving a message 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.
|
||||
```
|
||||
|
||||
To publish a message to all agents in the same namespace,
|
||||
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.
|
||||
|
||||
The following example shows a `BroadcastingAgent` that publishes a message
|
||||
upong receiving a string message. A `ReceivingAgent` that prints the message
|
||||
it receives.
|
||||
|
||||
```python
|
||||
from agnext.application import SingleThreadedAgentRuntime
|
||||
from agnext.components import TypeRoutedAgent, message_handler
|
||||
from agnext.core import CancellationToken
|
||||
|
||||
class BroadcastingAgent(TypeRoutedAgent):
|
||||
@message_handler
|
||||
async def on_str_message(self, message: str, cancellation_token: CancellationToken) -> None:
|
||||
# Publish a message to all agents in the same namespace.
|
||||
await self.publish_message(f"Publishing a message: {message}!")
|
||||
|
||||
class ReceivingAgent(TypeRoutedAgent):
|
||||
@message_handler
|
||||
async def on_str_message(self, message: str, cancellation_token: CancellationToken) -> None:
|
||||
print(f"Received a message: {message}")
|
||||
|
||||
async def main() -> None:
|
||||
runtime = SingleThreadedAgentRuntime()
|
||||
broadcaster = runtime.register_and_get("broadcasting_agent", lambda: BroadcastingAgent("Broadcasting Agent"))
|
||||
runtime.register("receiving_agent", lambda: ReceivingAgent("Receiving Agent"))
|
||||
await runtime.send_message("Hello, World!", broadcaster)
|
||||
runtime.process_until_idle()
|
||||
|
||||
import asyncio
|
||||
asyncio.run(main())
|
||||
```
|
||||
|
||||
Running the above code will produce the following output produced by the `ReceivingAgent`:
|
||||
|
||||
```text
|
||||
Received a message: Publishing a message: Hello, World!
|
||||
```
|
||||
|
||||
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.
|
||||
|
||||
```python
|
||||
await runtime.publish_message("Hello, World! From the runtime!", namespace="default")
|
||||
runtime.process_until_idle()
|
||||
```
|
||||
|
||||
Running the above code will produce the following output:
|
||||
|
||||
```text
|
||||
Received a message: Hello, World! From the runtime!
|
||||
Received a message: Publishing a message: Hello, World! From the runtime!
|
||||
```
|
||||
|
||||
The first output is from the `ReceivingAgent` that received a message published
|
||||
by the runtime. The second output is from the `ReceivingAgent` that received
|
||||
a message published by the `BroadcastingAgent`.
|
||||
|
||||
```{note}
|
||||
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.
|
||||
```
|
||||
|
||||
## Namespace
|
||||
|
||||
Namespace allow for defining logical boundaries between agents.
|
||||
|
||||
Namespaces are strings, and the default is `default`.
|
||||
|
||||
Two possible use cases of agents are:
|
||||
|
||||
- Creating a multi-tenant system where each tenant has its own namespace. For
|
||||
example, a chat system where each tenant has its own set of agents.
|
||||
- Security boundaries between agent groups. For example, a chat system where
|
||||
agents in the `admin` namespace can communicate with agents in the `user`
|
||||
namespace, but not the other way around.
|
||||
|
||||
The {py:class}`~agnext.core.AgentId` is used to address an agent,
|
||||
it is the combination of the agent's namespace and its name.
|
||||
|
||||
When getting an agent reference ({py:meth}`agnext.core.AgentRuntime.get`) or
|
||||
proxy ({py:meth}`agnext.core.AgentRuntime.get_proxy`) from the runtime the
|
||||
namespace can be specified.
|
||||
Agents have an ID property ({py:attr}`agnext.core.Agent.id`) that returns the
|
||||
agent's id.
|
||||
Additionally, the register method takes a factory that can optionally accept
|
||||
the ID as an argument ({py:meth}`agnext.core.AgentRuntime.register`).
|
||||
|
||||
By default, there are no restrictions and are left to the application to
|
||||
enforce. The runtime will however automatically create agents in a
|
||||
namespace if it does not exist.
|
@ -1 +0,0 @@
|
||||
# Tutorial
|
37
python/docs/src/guides/components.md
Normal file
37
python/docs/src/guides/components.md
Normal file
@ -0,0 +1,37 @@
|
||||
# Agent Components
|
||||
|
||||
AGNext provides a suite of components to help developers build agents.
|
||||
|
||||
## Type-Routed Agent
|
||||
|
||||
The {py:class}`~agnext.components.TypeRoutedAgent` base class provides
|
||||
developer with a simple decorator {py:meth}`~agnext.components.message_handler`
|
||||
for associating message types with message handlers.
|
||||
|
||||
## Model Clients
|
||||
|
||||
AGNext provides the {py:mod}`agnext.components.models` module with a suite of built-in
|
||||
model clients for using ChatCompletion API.
|
||||
|
||||
## Tools
|
||||
|
||||
Tools can be used together with agents powered by the OpenAI's ChatCompletion or the Assistant API.
|
||||
AGNext provides the {py:mod}`agnext.components.tools` module with a suite of built-in
|
||||
tools and utilities for creating and running custom tools.
|
||||
|
||||
See [examples](https://github.com/microsoft/agnext/tree/main/python/examples#tool-use-examples)
|
||||
for how to use the built-in code execution tool and creating custom tools.
|
||||
|
||||
## Memory
|
||||
|
||||
Memory is a collection of data corresponding to the conversation history
|
||||
of an agent.
|
||||
Data in meory can be just a simple list of all messages,
|
||||
or one which provides a view of the last N messages.
|
||||
|
||||
To create a custom memory implementation, you need to subclass the
|
||||
{py:class}`agnext.components.memory.ChatMemory` protocol class and implement
|
||||
all its methods.
|
||||
For example, you can use [LLMLingua](https://github.com/microsoft/LLMLingua)
|
||||
to create a custom memory implementation that provides a compressed
|
||||
view of the conversation history.
|
@ -8,7 +8,6 @@ multi-agent systems out-performing single agent systems at complex tasks
|
||||
like software development.
|
||||
|
||||
You can implement any multi-agent pattern using AGNext agents, which
|
||||
communicate with each other using messages through the agent runtime
|
||||
(see {doc}`/core-concepts/runtime` and {doc}`/core-concepts/agent`).
|
||||
communicate with each other using messages through the agent runtime.
|
||||
See [examples](https://github.com/microsoft/agnext/tree/main/python/examples#pattern-examples)
|
||||
for how to implement patterns like reflection and group chat.
|
@ -3,18 +3,16 @@ AGNext
|
||||
|
||||
AGNext is a framework for building multi-agent applications.
|
||||
|
||||
At a high level, it provides a framework for inter-agent communication and a
|
||||
suite of independent components for building and managing agents. It models agents as
|
||||
independent actors communicating via messages. You can implement agents in
|
||||
different languages and run them on different machines across organizational boundaries.
|
||||
At a high level, it provides a framework for inter-agent communication and a
|
||||
suite of independent components for building and managing agents.
|
||||
You can implement agents in
|
||||
different programming languages and run them on different machines across organizational boundaries.
|
||||
You can also implement agents using other agent frameworks and run them in AGNext.
|
||||
|
||||
:doc:`Agents <core-concepts/agent>` are hosted by and managed by a :doc:`runtime <core-concepts/runtime>`.
|
||||
AGNext supports both RPC-like direct messaging and event based
|
||||
communication between agents, allowing for a :doc:`diverse set of agent patterns
|
||||
<core-concepts/patterns>`.
|
||||
Please read :doc:`Core Concepts <getting-started/core-concepts>` for
|
||||
a detailed overview of AGNext's architecture and design.
|
||||
|
||||
AGNext's developer API consists of the following layers:
|
||||
AGNext's API consists of the following modules:
|
||||
|
||||
- :doc:`core <reference/agnext.core>` - The core interfaces that defines agent and runtime.
|
||||
- :doc:`application <reference/agnext.application>` - Implementations of the runtime and other modules (e.g., logging) for building applications.
|
||||
@ -29,30 +27,26 @@ that demonstrate how to use AGNext.
|
||||
:hidden:
|
||||
|
||||
getting-started/installation
|
||||
getting-started/tutorial
|
||||
|
||||
.. toctree::
|
||||
:caption: Core Concepts
|
||||
:hidden:
|
||||
|
||||
core-concepts/runtime
|
||||
core-concepts/agent
|
||||
core-concepts/patterns
|
||||
core-concepts/memory
|
||||
core-concepts/tools
|
||||
core-concepts/cancellation
|
||||
core-concepts/logging
|
||||
core-concepts/namespace
|
||||
core-concepts/worker_protocol
|
||||
getting-started/core-concepts
|
||||
|
||||
.. toctree::
|
||||
:caption: Guides
|
||||
:hidden:
|
||||
|
||||
guides/type-routed-agent
|
||||
guides/azure-openai-with-aad-auth
|
||||
guides/termination-with-intervention
|
||||
guides/extracting-results-with-an-agent
|
||||
guides/components
|
||||
guides/patterns
|
||||
guides/logging
|
||||
guides/worker-protocol
|
||||
|
||||
.. toctree::
|
||||
:caption: Cookbook
|
||||
:hidden:
|
||||
|
||||
cookbook/type-routed-agent
|
||||
cookbook/azure-openai-with-aad-auth
|
||||
cookbook/termination-with-intervention
|
||||
cookbook/buffered-memory
|
||||
cookbook/extracting-results-with-an-agent
|
||||
|
||||
|
||||
.. toctree::
|
||||
|
Loading…
x
Reference in New Issue
Block a user