You can not select more than 25 topics Topics must start with a chinese character,a letter or number, can include dashes ('-') and can be up to 35 characters long.

group_chat.py 5.8 kB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173
  1. """
  2. This example shows how to use publish-subscribe to implement
  3. a simple round-robin group chat among multiple agents:
  4. each agent in the group chat takes turns speaking in a round-robin fashion.
  5. The conversation ends after a specified number of rounds.
  6. 1. Upon receiving a message, the group chat manager selects the next speaker
  7. in a round-robin fashion and sends a request to speak message to the selected speaker.
  8. 2. Upon receiving a request to speak message, the speaker generates a response
  9. to the last message in the memory and publishes the response.
  10. 3. The conversation continues until the specified number of rounds is reached.
  11. """
  12. import asyncio
  13. import os
  14. import sys
  15. from dataclasses import dataclass
  16. from typing import List
  17. from agnext.application import SingleThreadedAgentRuntime
  18. from agnext.components import DefaultTopicId, RoutedAgent, message_handler
  19. from agnext.components.models import (
  20. AssistantMessage,
  21. ChatCompletionClient,
  22. LLMMessage,
  23. SystemMessage,
  24. UserMessage,
  25. )
  26. from agnext.core import AgentId, AgentInstantiationContext, TopicId
  27. sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
  28. from agnext.core import MessageContext
  29. from common.utils import get_chat_completion_client_from_envs
  30. @dataclass
  31. class Message:
  32. source: str
  33. content: str
  34. @dataclass
  35. class RequestToSpeak:
  36. pass
  37. @dataclass
  38. class Termination:
  39. pass
  40. class RoundRobinGroupChatManager(RoutedAgent):
  41. def __init__(
  42. self,
  43. description: str,
  44. participants: List[AgentId],
  45. num_rounds: int,
  46. ) -> None:
  47. super().__init__(description)
  48. self._participants = participants
  49. self._num_rounds = num_rounds
  50. self._round_count = 0
  51. @message_handler
  52. async def handle_message(self, message: Message, ctx: MessageContext) -> None:
  53. # Select the next speaker in a round-robin fashion
  54. speaker = self._participants[self._round_count % len(self._participants)]
  55. self._round_count += 1
  56. if self._round_count > self._num_rounds * len(self._participants):
  57. # End the conversation after the specified number of rounds.
  58. await self.publish_message(Termination(), DefaultTopicId())
  59. return
  60. # Send a request to speak message to the selected speaker.
  61. await self.send_message(RequestToSpeak(), speaker)
  62. class GroupChatParticipant(RoutedAgent):
  63. def __init__(
  64. self,
  65. description: str,
  66. system_messages: List[SystemMessage],
  67. model_client: ChatCompletionClient,
  68. ) -> None:
  69. super().__init__(description)
  70. self._system_messages = system_messages
  71. self._model_client = model_client
  72. self._memory: List[Message] = []
  73. @message_handler
  74. async def handle_message(self, message: Message, ctx: MessageContext) -> None:
  75. self._memory.append(message)
  76. @message_handler
  77. async def handle_request_to_speak(self, message: RequestToSpeak, ctx: MessageContext) -> None:
  78. # Generate a response to the last message in the memory
  79. if not self._memory:
  80. return
  81. llm_messages: List[LLMMessage] = []
  82. for m in self._memory[-10:]:
  83. if m.source == self.metadata["type"]:
  84. llm_messages.append(AssistantMessage(content=m.content, source=self.metadata["type"]))
  85. else:
  86. llm_messages.append(UserMessage(content=m.content, source=m.source))
  87. response = await self._model_client.create(self._system_messages + llm_messages)
  88. assert isinstance(response.content, str)
  89. speech = Message(content=response.content, source=self.metadata["type"])
  90. self._memory.append(speech)
  91. await self.publish_message(speech, topic_id=DefaultTopicId())
  92. async def main() -> None:
  93. # Create the runtime.
  94. runtime = SingleThreadedAgentRuntime()
  95. # Register the participants.
  96. await runtime.register(
  97. "DataScientist",
  98. lambda: GroupChatParticipant(
  99. description="A data scientist",
  100. system_messages=[SystemMessage("You are a data scientist.")],
  101. model_client=get_chat_completion_client_from_envs(model="gpt-4o-mini"),
  102. ),
  103. )
  104. await runtime.register(
  105. "Engineer",
  106. lambda: GroupChatParticipant(
  107. description="An engineer",
  108. system_messages=[SystemMessage("You are an engineer.")],
  109. model_client=get_chat_completion_client_from_envs(model="gpt-4o-mini"),
  110. ),
  111. )
  112. await runtime.register(
  113. "Artist",
  114. lambda: GroupChatParticipant(
  115. description="An artist",
  116. system_messages=[SystemMessage("You are an artist.")],
  117. model_client=get_chat_completion_client_from_envs(model="gpt-4o-mini"),
  118. ),
  119. )
  120. # Register the group chat manager.
  121. await runtime.register(
  122. "GroupChatManager",
  123. lambda: RoundRobinGroupChatManager(
  124. description="A group chat manager",
  125. participants=[
  126. AgentId("DataScientist", AgentInstantiationContext.current_agent_id().key),
  127. AgentId("Engineer", AgentInstantiationContext.current_agent_id().key),
  128. AgentId("Artist", AgentInstantiationContext.current_agent_id().key),
  129. ],
  130. num_rounds=3,
  131. ),
  132. )
  133. # Start the runtime.
  134. runtime.start()
  135. # Start the conversation.
  136. await runtime.publish_message(
  137. Message(content="Hello, everyone!", source="Moderator"), topic_id=TopicId("default", "default")
  138. )
  139. await runtime.stop_when_idle()
  140. if __name__ == "__main__":
  141. import logging
  142. logging.basicConfig(level=logging.WARNING)
  143. logging.getLogger("agnext").setLevel(logging.DEBUG)
  144. asyncio.run(main())