mirror of
https://github.com/microsoft/autogen.git
synced 2025-08-20 06:31:54 +00:00
420 lines
14 KiB
Plaintext
420 lines
14 KiB
Plaintext
{
|
|
"cells": [
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"# Message and Communication\n",
|
|
"\n",
|
|
"An agent in AGNext can react to, send, and publish messages,\n",
|
|
"and messages are the only means through which agents can communicate\n",
|
|
"with each other."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Messages\n",
|
|
"\n",
|
|
"Messages are serializable objects, they can be defined using:\n",
|
|
"\n",
|
|
"- A subclass of Pydantic's {py:class}`pydantic.BaseModel`, or\n",
|
|
"- A dataclass\n",
|
|
"\n",
|
|
"For example:"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 2,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"from dataclasses import dataclass\n",
|
|
"\n",
|
|
"\n",
|
|
"@dataclass\n",
|
|
"class TextMessage:\n",
|
|
" content: str\n",
|
|
" source: str\n",
|
|
"\n",
|
|
"\n",
|
|
"@dataclass\n",
|
|
"class ImageMessage:\n",
|
|
" url: str\n",
|
|
" source: str"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"```{note}\n",
|
|
"Messages are purely data, and should not contain any logic.\n",
|
|
"```"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"### Message Handlers\n",
|
|
"\n",
|
|
"When an agent receives a message the runtime will invoke the agent's message handler\n",
|
|
"({py:meth}`~agnext.core.Agent.on_message`) which should implement the agents message handling logic.\n",
|
|
"If this message cannot be handled by the agent, the agent should raise a\n",
|
|
"{py:class}`~agnext.core.exceptions.CantHandleException`.\n",
|
|
"\n",
|
|
"For convenience, the {py:class}`~agnext.components.RoutedAgent` base class\n",
|
|
"provides the {py:meth}`~agnext.components.message_handler` decorator\n",
|
|
"for associating message types with message handlers,\n",
|
|
"so developers do not need to implement the {py:meth}`~agnext.core.Agent.on_message` method.\n",
|
|
"\n",
|
|
"For example, the following type-routed agent responds to `TextMessage` and `ImageMessage`\n",
|
|
"using different message handlers:"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 4,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"from agnext.application import SingleThreadedAgentRuntime\n",
|
|
"from agnext.components import RoutedAgent, message_handler\n",
|
|
"from agnext.core import AgentId, MessageContext\n",
|
|
"\n",
|
|
"\n",
|
|
"class MyAgent(RoutedAgent):\n",
|
|
" @message_handler\n",
|
|
" async def on_text_message(self, message: TextMessage, ctx: MessageContext) -> None:\n",
|
|
" print(f\"Hello, {message.source}, you said {message.content}!\")\n",
|
|
"\n",
|
|
" @message_handler\n",
|
|
" async def on_image_message(self, message: ImageMessage, ctx: MessageContext) -> None:\n",
|
|
" print(f\"Hello, {message.source}, you sent me {message.url}!\")"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"Create the agent runtime and register the agent (see [Agent and Agent Runtime](agent-and-agent-runtime.ipynb)):"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 6,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"runtime = SingleThreadedAgentRuntime()\n",
|
|
"await runtime.register(\"my_agent\", lambda: MyAgent(\"My Agent\"))\n",
|
|
"agent = AgentId(\"my_agent\", \"default\")"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"Test this agent with `TextMessage` and `ImageMessage`."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 7,
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"Hello, User, you said Hello, World!!\n",
|
|
"Hello, User, you sent me https://example.com/image.jpg!\n"
|
|
]
|
|
}
|
|
],
|
|
"source": [
|
|
"runtime.start()\n",
|
|
"await runtime.send_message(TextMessage(content=\"Hello, World!\", source=\"User\"), agent)\n",
|
|
"await runtime.send_message(ImageMessage(url=\"https://example.com/image.jpg\", source=\"User\"), agent)\n",
|
|
"await runtime.stop_when_idle()"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Communication\n",
|
|
"\n",
|
|
"There are two types of communication in AGNext:\n",
|
|
"\n",
|
|
"- **Direct communication**: An agent sends a direct message to another agent.\n",
|
|
"- **Broadcast communication**: An agent publishes a message to all agents in the same namespace.\n",
|
|
"\n",
|
|
"### Direct Communication\n",
|
|
"\n",
|
|
"To send a direct message to another agent, within a message handler use\n",
|
|
"the {py:meth}`agnext.core.BaseAgent.send_message` method,\n",
|
|
"from the runtime use the {py:meth}`agnext.core.AgentRuntime.send_message` method.\n",
|
|
"Awaiting calls to these methods will return the return value of the\n",
|
|
"receiving agent's message handler.\n",
|
|
"\n",
|
|
"```{note}\n",
|
|
"If the invoked agent raises an exception while the sender is awaiting,\n",
|
|
"the exception will be propagated back to the sender.\n",
|
|
"```\n",
|
|
"\n",
|
|
"#### Request/Response\n",
|
|
"\n",
|
|
"Direct communication can be used for request/response scenarios,\n",
|
|
"where the sender expects a response from the receiver.\n",
|
|
"The receiver can respond to the message by returning a value from its message handler.\n",
|
|
"You can think of this as a function call between agents.\n",
|
|
"\n",
|
|
"For example, consider the following type-routed agent:"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 17,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"from dataclasses import dataclass\n",
|
|
"\n",
|
|
"from agnext.application import SingleThreadedAgentRuntime\n",
|
|
"from agnext.components import RoutedAgent, message_handler\n",
|
|
"from agnext.core import MessageContext\n",
|
|
"\n",
|
|
"\n",
|
|
"@dataclass\n",
|
|
"class Message:\n",
|
|
" content: str\n",
|
|
"\n",
|
|
"\n",
|
|
"class InnerAgent(RoutedAgent):\n",
|
|
" @message_handler\n",
|
|
" async def on_my_message(self, message: Message, ctx: MessageContext) -> Message:\n",
|
|
" return Message(content=f\"Hello from inner, {message.content}\")\n",
|
|
"\n",
|
|
"\n",
|
|
"class OuterAgent(RoutedAgent):\n",
|
|
" def __init__(self, description: str, inner_agent_type: str):\n",
|
|
" super().__init__(description)\n",
|
|
" self.inner_agent_id = AgentId(inner_agent_type, self.id.key)\n",
|
|
"\n",
|
|
" @message_handler\n",
|
|
" async def on_my_message(self, message: Message, ctx: MessageContext) -> None:\n",
|
|
" print(f\"Received message: {message.content}\")\n",
|
|
" # Send a direct message to the inner agent and receves a response.\n",
|
|
" response = await self.send_message(Message(f\"Hello from outer, {message.content}\"), self.inner_agent_id)\n",
|
|
" print(f\"Received inner response: {response.content}\")"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"Upone receving a message, the `OuterAgent` sends a direct message to the `InnerAgent` and receives\n",
|
|
"a message in response.\n",
|
|
"\n",
|
|
"We can test these agents by sending a `Message` to the `OuterAgent`."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 18,
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"Received message: Hello, World!\n",
|
|
"Received inner response: Hello from inner, Hello from outer, Hello, World!\n"
|
|
]
|
|
}
|
|
],
|
|
"source": [
|
|
"runtime = SingleThreadedAgentRuntime()\n",
|
|
"await runtime.register(\"inner_agent\", lambda: InnerAgent(\"InnerAgent\"))\n",
|
|
"await runtime.register(\"outer_agent\", lambda: OuterAgent(\"OuterAgent\", \"InnerAgent\"))\n",
|
|
"runtime.start()\n",
|
|
"outer = AgentId(\"outer_agent\", \"default\")\n",
|
|
"await runtime.send_message(Message(content=\"Hello, World!\"), outer)\n",
|
|
"await runtime.stop_when_idle()"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"Both outputs are produced by the `OuterAgent`'s message handler, however the second output is based on the response from the `InnerAgent`."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"### Broadcast Communication\n",
|
|
"\n",
|
|
"Broadcast communication is effectively the publish/subscribe model.\n",
|
|
"As part of the base agent ({py:class}`~agnext.core.BaseAgent`) implementation,\n",
|
|
"it must advertise the message types that\n",
|
|
"it would like to receive when published ({py:attr}`~agnext.core.AgentMetadata.subscriptions`).\n",
|
|
"If one of these messages is published, the agent's message handler will be invoked.\n",
|
|
"\n",
|
|
"The key difference between direct and broadcast communication is that broadcast\n",
|
|
"communication cannot be used for request/response scenarios.\n",
|
|
"When an agent publishes a message it is one way only, it cannot receive a response\n",
|
|
"from any other agent, even if a receiving agent sends a response.\n",
|
|
"\n",
|
|
"```{note}\n",
|
|
"An agent receiving a message does not know if it is handling a published or direct message.\n",
|
|
"So, if a response is given to a published message, it will be thrown away.\n",
|
|
"```\n",
|
|
"\n",
|
|
"To publish a message to all agents in the same namespace,\n",
|
|
"use the {py:meth}`agnext.core.BaseAgent.publish_message` method.\n",
|
|
"This call must still be awaited to allow the runtime to deliver the message to all agents,\n",
|
|
"but it will always return `None`.\n",
|
|
"If an agent raises an exception while handling a published message,\n",
|
|
"this will be logged but will not be propagated back to the publishing agent.\n",
|
|
"\n",
|
|
"The following example shows a `BroadcastingAgent` that publishes a message\n",
|
|
"upong receiving a message. A `ReceivingAgent` that prints the message\n",
|
|
"it receives."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 20,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"from agnext.application import SingleThreadedAgentRuntime\n",
|
|
"from agnext.components import DefaultSubscription, DefaultTopicId, RoutedAgent, message_handler\n",
|
|
"from agnext.core import MessageContext\n",
|
|
"\n",
|
|
"\n",
|
|
"class BroadcastingAgent(RoutedAgent):\n",
|
|
" @message_handler\n",
|
|
" async def on_my_message(self, message: Message, ctx: MessageContext) -> None:\n",
|
|
" # Publish a message to all agents in the same namespace.\n",
|
|
" await self.publish_message(Message(f\"Publishing a message: {message.content}!\"), topic_id=DefaultTopicId())\n",
|
|
"\n",
|
|
"\n",
|
|
"class ReceivingAgent(RoutedAgent):\n",
|
|
" @message_handler\n",
|
|
" async def on_my_message(self, message: Message, ctx: MessageContext) -> None:\n",
|
|
" print(f\"Received a message: {message.content}\")"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"Sending a direct message to the `BroadcastingAgent` will result in a message being published by\n",
|
|
"the `BroadcastingAgent` and received by the `ReceivingAgent`."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 21,
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"Received a message: Publishing a message: Hello, World!!\n"
|
|
]
|
|
}
|
|
],
|
|
"source": [
|
|
"runtime = SingleThreadedAgentRuntime()\n",
|
|
"await runtime.register(\n",
|
|
" \"broadcasting_agent\", lambda: BroadcastingAgent(\"Broadcasting Agent\"), lambda: [DefaultSubscription()]\n",
|
|
")\n",
|
|
"await runtime.register(\"receiving_agent\", lambda: ReceivingAgent(\"Receiving Agent\"), lambda: [DefaultSubscription()])\n",
|
|
"runtime.start()\n",
|
|
"await runtime.send_message(Message(\"Hello, World!\"), AgentId(\"broadcasting_agent\", \"default\"))\n",
|
|
"await runtime.stop()"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"To publish a message to all agents outside of an agent handling a message,\n",
|
|
"the message should be published via the runtime with the\n",
|
|
"{py:meth}`agnext.core.AgentRuntime.publish_message` method."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 22,
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"Received a message: Hello, World! From the runtime!\n",
|
|
"Received a message: Publishing a message: Hello, World! From the runtime!!\n"
|
|
]
|
|
}
|
|
],
|
|
"source": [
|
|
"# Replace send_message with publish_message in the above example.\n",
|
|
"\n",
|
|
"runtime = SingleThreadedAgentRuntime()\n",
|
|
"await runtime.register(\n",
|
|
" \"broadcasting_agent\", lambda: BroadcastingAgent(\"Broadcasting Agent\"), lambda: [DefaultSubscription()]\n",
|
|
")\n",
|
|
"await runtime.register(\"receiving_agent\", lambda: ReceivingAgent(\"Receiving Agent\"), lambda: [DefaultSubscription()])\n",
|
|
"runtime.start()\n",
|
|
"await runtime.publish_message(Message(\"Hello, World! From the runtime!\"), topic_id=DefaultTopicId())\n",
|
|
"await runtime.stop_when_idle()"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"The first output is from the `ReceivingAgent` that received a message published\n",
|
|
"by the runtime. The second output is from the `ReceivingAgent` that received\n",
|
|
"a message published by the `BroadcastingAgent`.\n",
|
|
"\n",
|
|
"```{note}\n",
|
|
"If an agent publishes a message type for which it is subscribed it will not\n",
|
|
"receive the message it published. This is to prevent infinite loops.\n",
|
|
"```"
|
|
]
|
|
}
|
|
],
|
|
"metadata": {
|
|
"kernelspec": {
|
|
"display_name": "agnext",
|
|
"language": "python",
|
|
"name": "python3"
|
|
},
|
|
"language_info": {
|
|
"codemirror_mode": {
|
|
"name": "ipython",
|
|
"version": 3
|
|
},
|
|
"file_extension": ".py",
|
|
"mimetype": "text/x-python",
|
|
"name": "python",
|
|
"nbconvert_exporter": "python",
|
|
"pygments_lexer": "ipython3",
|
|
"version": "3.11.9"
|
|
}
|
|
},
|
|
"nbformat": 4,
|
|
"nbformat_minor": 2
|
|
}
|