autogen/examples/round_robin_chat.py
Jack Gerrits 5afbadbe43 Add support for task cancellation (#7)
* Add support for task cancellation

* add tests to CI

* matrix for python testing
2024-05-20 15:32:08 -04:00

124 lines
4.7 KiB
Python

import asyncio
import random
from dataclasses import dataclass
from typing import List
from agnext.agent_components.type_routed_agent import TypeRoutedAgent, message_handler
from agnext.application_components.single_threaded_agent_runtime import SingleThreadedAgentRuntime
from agnext.core.agent_runtime import AgentRuntime
# TODO: a runtime should be able to handle multiple types of messages
# TODO: allow request and response to be different message types
# should support this in handlers.
@dataclass
class GroupChatMessage:
body: str
sender: str
require_response: bool
class GroupChatParticipant(TypeRoutedAgent[GroupChatMessage]):
def __init__(self, name: str, runtime: AgentRuntime[GroupChatMessage]) -> None:
super().__init__(name, runtime)
@message_handler(GroupChatMessage)
async def on_new_message(self, message: GroupChatMessage) -> GroupChatMessage:
print(f"{self.name} received message from {message.sender}: {message.body}")
if not message.require_response:
return GroupChatMessage(body="OK", sender=self.name, require_response=False)
# Generate a random response.
response_body = random.choice(
[
"Hello!",
"Hi!",
"Hey!",
"How are you?",
"What's up?",
"Good day!",
"Good morning!",
"Good evening!",
"Good afternoon!",
"Good night!",
"Good bye!",
"Bye!",
"See you later!",
"See you soon!",
"See you!",
]
)
return GroupChatMessage(body=response_body, sender=self.name, require_response=False)
class RoundRobinChat(TypeRoutedAgent[GroupChatMessage]):
def __init__(
self, name: str, runtime: AgentRuntime[GroupChatMessage], agents: List[GroupChatParticipant], num_rounds: int
) -> None:
super().__init__(name, runtime)
self._agents = agents
self._num_rounds = num_rounds
@message_handler(GroupChatMessage)
async def on_new_message(self, message: GroupChatMessage) -> GroupChatMessage:
print(f"{self.name} received task request from {message.sender}: {message.body}")
history = [message]
previous_speaker: TypeRoutedAgent[GroupChatMessage] | None = None
round = 0
while round < self._num_rounds:
# 1. Select speaker.
speaker = self._agents[round % len(self._agents)]
# 2. Send the last message to non-speaking agents.
for agent in self._agents:
if agent is not previous_speaker and agent is not speaker:
# TODO: should support a separate message type for just passing on a message.
_ = await self._send_message(
GroupChatMessage(body=history[-1].body, sender=history[-1].sender, require_response=False),
agent,
)
# 3. Send the last message to the speaking agent and ask to speak.
if previous_speaker is not speaker:
response = await self._send_message(
GroupChatMessage(body=history[-1].body, sender=history[-1].sender, require_response=True), speaker
)
else:
# The same speaker is speaking again.
# TODO: should support a separate message type for request to speak only.
response = await self._send_message(
GroupChatMessage(body="", sender=self.name, require_response=True), speaker
)
print(f"Speaker {speaker.name} responded with: {response.body}")
# 4. Append the response to the history.
history.append(response)
# 5. Update the previous speaker.
previous_speaker = speaker
# 6. Increment the round.
round += 1
# Construct the final response.
response_body = "\n".join([f"{message.sender}: {message.body}" for message in history])
return GroupChatMessage(body=response_body, sender=self.name, require_response=False)
async def main() -> None:
runtime = SingleThreadedAgentRuntime[GroupChatMessage]()
participants = [GroupChatParticipant(f"participant_{i}", runtime) for i in range(3)]
chat = RoundRobinChat("chat_room", runtime, participants, num_rounds=10)
response = runtime.send_message(GroupChatMessage(body="Hello!", sender="external", require_response=True), chat)
while not response.done():
await runtime.process_next()
print((await response).body)
if __name__ == "__main__":
asyncio.run(main())