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.

_agents.py 8.4 kB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214
  1. import asyncio
  2. import random
  3. from typing import Awaitable, Callable, List
  4. from uuid import uuid4
  5. from _types import GroupChatMessage, MessageChunk, RequestToSpeak, UIAgentConfig
  6. from autogen_core import DefaultTopicId, MessageContext, RoutedAgent, message_handler
  7. from autogen_core.models import (
  8. AssistantMessage,
  9. ChatCompletionClient,
  10. LLMMessage,
  11. SystemMessage,
  12. UserMessage,
  13. )
  14. from autogen_ext.runtimes.grpc import GrpcWorkerAgentRuntime
  15. from rich.console import Console
  16. from rich.markdown import Markdown
  17. class BaseGroupChatAgent(RoutedAgent):
  18. """A group chat participant using an LLM."""
  19. def __init__(
  20. self,
  21. description: str,
  22. group_chat_topic_type: str,
  23. model_client: ChatCompletionClient,
  24. system_message: str,
  25. ui_config: UIAgentConfig,
  26. ) -> None:
  27. super().__init__(description=description)
  28. self._group_chat_topic_type = group_chat_topic_type
  29. self._model_client = model_client
  30. self._system_message = SystemMessage(content=system_message)
  31. self._chat_history: List[LLMMessage] = []
  32. self._ui_config = ui_config
  33. self.console = Console()
  34. @message_handler
  35. async def handle_message(self, message: GroupChatMessage, ctx: MessageContext) -> None:
  36. self._chat_history.extend(
  37. [
  38. UserMessage(content=f"Transferred to {message.body.source}", source="system"), # type: ignore[union-attr]
  39. message.body,
  40. ]
  41. )
  42. @message_handler
  43. async def handle_request_to_speak(self, message: RequestToSpeak, ctx: MessageContext) -> None:
  44. self._chat_history.append(
  45. UserMessage(content=f"Transferred to {self.id.type}, adopt the persona immediately.", source="system")
  46. )
  47. completion = await self._model_client.create([self._system_message] + self._chat_history)
  48. assert isinstance(completion.content, str)
  49. self._chat_history.append(AssistantMessage(content=completion.content, source=self.id.type))
  50. console_message = f"\n{'-'*80}\n**{self.id.type}**: {completion.content}"
  51. self.console.print(Markdown(console_message))
  52. await publish_message_to_ui_and_backend(
  53. runtime=self,
  54. source=self.id.type,
  55. user_message=completion.content,
  56. ui_config=self._ui_config,
  57. group_chat_topic_type=self._group_chat_topic_type,
  58. )
  59. class GroupChatManager(RoutedAgent):
  60. def __init__(
  61. self,
  62. model_client: ChatCompletionClient,
  63. participant_topic_types: List[str],
  64. participant_descriptions: List[str],
  65. ui_config: UIAgentConfig,
  66. max_rounds: int = 3,
  67. ) -> None:
  68. super().__init__("Group chat manager")
  69. self._model_client = model_client
  70. self._num_rounds = 0
  71. self._participant_topic_types = participant_topic_types
  72. self._chat_history: List[GroupChatMessage] = []
  73. self._max_rounds = max_rounds
  74. self.console = Console()
  75. self._participant_descriptions = participant_descriptions
  76. self._previous_participant_topic_type: str | None = None
  77. self._ui_config = ui_config
  78. @message_handler
  79. async def handle_message(self, message: GroupChatMessage, ctx: MessageContext) -> None:
  80. assert isinstance(message.body, UserMessage)
  81. self._chat_history.append(message.body) # type: ignore[reportargumenttype,arg-type]
  82. # Format message history.
  83. messages: List[str] = []
  84. for msg in self._chat_history:
  85. if isinstance(msg.content, str): # type: ignore[attr-defined]
  86. messages.append(f"{msg.source}: {msg.content}") # type: ignore[attr-defined]
  87. elif isinstance(msg.content, list): # type: ignore[attr-defined]
  88. messages.append(f"{msg.source}: {', '.join(msg.content)}") # type: ignore[attr-defined,reportUnknownArgumentType]
  89. history = "\n".join(messages)
  90. # Format roles.
  91. roles = "\n".join(
  92. [
  93. f"{topic_type}: {description}".strip()
  94. for topic_type, description in zip(
  95. self._participant_topic_types, self._participant_descriptions, strict=True
  96. )
  97. if topic_type != self._previous_participant_topic_type
  98. ]
  99. )
  100. participants = str(
  101. [
  102. topic_type
  103. for topic_type in self._participant_topic_types
  104. if topic_type != self._previous_participant_topic_type
  105. ]
  106. )
  107. selector_prompt = f"""You are in a role play game. The following roles are available:
  108. {roles}.
  109. Read the following conversation. Then select the next role from {participants} to play. Only return the role.
  110. {history}
  111. Read the above conversation. Then select the next role from {participants} to play. if you think it's enough talking (for example they have talked for {self._max_rounds} rounds), return 'FINISH'.
  112. """
  113. system_message = SystemMessage(content=selector_prompt)
  114. completion = await self._model_client.create([system_message], cancellation_token=ctx.cancellation_token)
  115. assert isinstance(
  116. completion.content, str
  117. ), f"Completion content must be a string, but is: {type(completion.content)}"
  118. if completion.content.upper() == "FINISH":
  119. finish_msg = "I think it's enough iterations on the story! Thanks for collaborating!"
  120. manager_message = f"\n{'-'*80}\n Manager ({id(self)}): {finish_msg}"
  121. await publish_message_to_ui(
  122. runtime=self, source=self.id.type, user_message=finish_msg, ui_config=self._ui_config
  123. )
  124. self.console.print(Markdown(manager_message))
  125. return
  126. selected_topic_type: str
  127. for topic_type in self._participant_topic_types:
  128. if topic_type.lower() in completion.content.lower():
  129. selected_topic_type = topic_type
  130. self._previous_participant_topic_type = selected_topic_type
  131. self.console.print(
  132. Markdown(f"\n{'-'*80}\n Manager ({id(self)}): Asking `{selected_topic_type}` to speak")
  133. )
  134. await self.publish_message(RequestToSpeak(), DefaultTopicId(type=selected_topic_type))
  135. return
  136. raise ValueError(f"Invalid role selected: {completion.content}")
  137. class UIAgent(RoutedAgent):
  138. """Handles UI-related tasks and message processing for the distributed group chat system."""
  139. def __init__(self, on_message_chunk_func: Callable[[MessageChunk], Awaitable[None]]) -> None:
  140. super().__init__("UI Agent")
  141. self._on_message_chunk_func = on_message_chunk_func
  142. @message_handler
  143. async def handle_message_chunk(self, message: MessageChunk, ctx: MessageContext) -> None:
  144. await self._on_message_chunk_func(message)
  145. async def publish_message_to_ui(
  146. runtime: RoutedAgent | GrpcWorkerAgentRuntime,
  147. source: str,
  148. user_message: str,
  149. ui_config: UIAgentConfig,
  150. ) -> None:
  151. message_id = str(uuid4())
  152. # Stream the message to UI
  153. message_chunks = (
  154. MessageChunk(message_id=message_id, text=token + " ", author=source, finished=False)
  155. for token in user_message.split()
  156. )
  157. for chunk in message_chunks:
  158. await runtime.publish_message(
  159. chunk,
  160. DefaultTopicId(type=ui_config.topic_type),
  161. )
  162. await asyncio.sleep(random.uniform(ui_config.min_delay, ui_config.max_delay))
  163. await runtime.publish_message(
  164. MessageChunk(message_id=message_id, text=" ", author=source, finished=True),
  165. DefaultTopicId(type=ui_config.topic_type),
  166. )
  167. async def publish_message_to_ui_and_backend(
  168. runtime: RoutedAgent | GrpcWorkerAgentRuntime,
  169. source: str,
  170. user_message: str,
  171. ui_config: UIAgentConfig,
  172. group_chat_topic_type: str,
  173. ) -> None:
  174. # Publish messages for ui
  175. await publish_message_to_ui(
  176. runtime=runtime,
  177. source=source,
  178. user_message=user_message,
  179. ui_config=ui_config,
  180. )
  181. # Publish message to backend
  182. await runtime.publish_message(
  183. GroupChatMessage(body=UserMessage(content=user_message, source=source)),
  184. topic_id=DefaultTopicId(type=group_chat_topic_type),
  185. )