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.

test_runtime.py 11 kB

1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312
  1. import logging
  2. import pytest
  3. from autogen_core import (
  4. AgentId,
  5. AgentInstantiationContext,
  6. AgentType,
  7. DefaultTopicId,
  8. MessageContext,
  9. RoutedAgent,
  10. SingleThreadedAgentRuntime,
  11. TopicId,
  12. TypeSubscription,
  13. event,
  14. try_get_known_serializers_for_type,
  15. type_subscription,
  16. )
  17. from autogen_core._default_subscription import default_subscription
  18. from autogen_test_utils import (
  19. CascadingAgent,
  20. CascadingMessageType,
  21. LoopbackAgent,
  22. LoopbackAgentWithDefaultSubscription,
  23. MessageType,
  24. NoopAgent,
  25. )
  26. from autogen_test_utils.telemetry_test_utils import MyTestExporter, get_test_tracer_provider
  27. from opentelemetry.sdk.trace import TracerProvider
  28. test_exporter = MyTestExporter()
  29. @pytest.fixture
  30. def tracer_provider() -> TracerProvider:
  31. test_exporter.clear()
  32. return get_test_tracer_provider(test_exporter)
  33. @pytest.mark.asyncio
  34. async def test_agent_type_register_factory() -> None:
  35. runtime = SingleThreadedAgentRuntime()
  36. def agent_factory() -> NoopAgent:
  37. id = AgentInstantiationContext.current_agent_id()
  38. assert id == AgentId("name1", "default")
  39. agent = NoopAgent()
  40. assert agent.id == id
  41. return agent
  42. await runtime.register_factory(type=AgentType("name1"), agent_factory=agent_factory, expected_class=NoopAgent)
  43. with pytest.raises(ValueError):
  44. # This should fail because the expected class does not match the actual class.
  45. await runtime.register_factory(
  46. type=AgentType("name1"),
  47. agent_factory=agent_factory, # type: ignore
  48. expected_class=CascadingAgent,
  49. )
  50. # Without expected_class, no error.
  51. await runtime.register_factory(type=AgentType("name2"), agent_factory=agent_factory)
  52. @pytest.mark.asyncio
  53. async def test_agent_type_must_be_unique() -> None:
  54. runtime = SingleThreadedAgentRuntime()
  55. def agent_factory() -> NoopAgent:
  56. id = AgentInstantiationContext.current_agent_id()
  57. assert id == AgentId("name1", "default")
  58. agent = NoopAgent()
  59. assert agent.id == id
  60. return agent
  61. await NoopAgent.register(runtime, "name1", agent_factory)
  62. # await runtime.register_factory(type=AgentType("name1"), agent_factory=agent_factory, expected_class=NoopAgent)
  63. with pytest.raises(ValueError):
  64. await runtime.register_factory(type=AgentType("name1"), agent_factory=agent_factory, expected_class=NoopAgent)
  65. await runtime.register_factory(type=AgentType("name2"), agent_factory=agent_factory, expected_class=NoopAgent)
  66. @pytest.mark.asyncio
  67. async def test_register_receives_publish(tracer_provider: TracerProvider) -> None:
  68. runtime = SingleThreadedAgentRuntime(tracer_provider=tracer_provider)
  69. runtime.add_message_serializer(try_get_known_serializers_for_type(MessageType))
  70. await runtime.register_factory(
  71. type=AgentType("name"), agent_factory=lambda: LoopbackAgent(), expected_class=LoopbackAgent
  72. )
  73. await runtime.add_subscription(TypeSubscription("default", "name"))
  74. runtime.start()
  75. await runtime.publish_message(MessageType(), topic_id=TopicId("default", "default"))
  76. await runtime.stop_when_idle()
  77. # Agent in default namespace should have received the message
  78. long_running_agent = await runtime.try_get_underlying_agent_instance(AgentId("name", "default"), type=LoopbackAgent)
  79. assert long_running_agent.num_calls == 1
  80. # Agent in other namespace should not have received the message
  81. other_long_running_agent: LoopbackAgent = await runtime.try_get_underlying_agent_instance(
  82. AgentId("name", key="other"), type=LoopbackAgent
  83. )
  84. assert other_long_running_agent.num_calls == 0
  85. exported_spans = test_exporter.get_exported_spans()
  86. assert len(exported_spans) == 3
  87. span_names = [span.name for span in exported_spans]
  88. assert span_names == [
  89. "autogen create default.(default)-T",
  90. "autogen process name.(default)-A",
  91. "autogen publish default.(default)-T",
  92. ]
  93. await runtime.close()
  94. @pytest.mark.asyncio
  95. async def test_register_receives_publish_with_construction(caplog: pytest.LogCaptureFixture) -> None:
  96. runtime = SingleThreadedAgentRuntime()
  97. runtime.add_message_serializer(try_get_known_serializers_for_type(MessageType))
  98. async def agent_factory() -> LoopbackAgent:
  99. raise ValueError("test")
  100. await runtime.register_factory(type=AgentType("name"), agent_factory=agent_factory, expected_class=LoopbackAgent)
  101. await runtime.add_subscription(TypeSubscription("default", "name"))
  102. with caplog.at_level(logging.ERROR):
  103. runtime.start()
  104. await runtime.publish_message(MessageType(), topic_id=TopicId("default", "default"))
  105. await runtime.stop_when_idle()
  106. # Check if logger has the exception.
  107. assert any("Error constructing agent" in e.message for e in caplog.records)
  108. await runtime.close()
  109. @pytest.mark.asyncio
  110. async def test_register_receives_publish_cascade() -> None:
  111. num_agents = 5
  112. num_initial_messages = 5
  113. max_rounds = 5
  114. total_num_calls_expected = 0
  115. for i in range(0, max_rounds):
  116. total_num_calls_expected += num_initial_messages * ((num_agents - 1) ** i)
  117. runtime = SingleThreadedAgentRuntime()
  118. # Register agents
  119. for i in range(num_agents):
  120. await CascadingAgent.register(runtime, f"name{i}", lambda: CascadingAgent(max_rounds))
  121. runtime.start()
  122. # Publish messages
  123. for _ in range(num_initial_messages):
  124. await runtime.publish_message(CascadingMessageType(round=1), DefaultTopicId())
  125. # Process until idle.
  126. await runtime.stop_when_idle()
  127. # Check that each agent received the correct number of messages.
  128. for i in range(num_agents):
  129. agent = await runtime.try_get_underlying_agent_instance(AgentId(f"name{i}", "default"), CascadingAgent)
  130. assert agent.num_calls == total_num_calls_expected
  131. await runtime.close()
  132. @pytest.mark.asyncio
  133. async def test_register_factory_explicit_name() -> None:
  134. runtime = SingleThreadedAgentRuntime()
  135. await LoopbackAgent.register(runtime, "name", LoopbackAgent)
  136. await runtime.add_subscription(TypeSubscription("default", "name"))
  137. runtime.start()
  138. agent_id = AgentId("name", key="default")
  139. topic_id = TopicId("default", "default")
  140. await runtime.publish_message(MessageType(), topic_id=topic_id)
  141. await runtime.stop_when_idle()
  142. # Agent in default namespace should have received the message
  143. long_running_agent = await runtime.try_get_underlying_agent_instance(agent_id, type=LoopbackAgent)
  144. assert long_running_agent.num_calls == 1
  145. # Agent in other namespace should not have received the message
  146. other_long_running_agent: LoopbackAgent = await runtime.try_get_underlying_agent_instance(
  147. AgentId("name", key="other"), type=LoopbackAgent
  148. )
  149. assert other_long_running_agent.num_calls == 0
  150. await runtime.close()
  151. @pytest.mark.asyncio
  152. async def test_default_subscription() -> None:
  153. runtime = SingleThreadedAgentRuntime()
  154. runtime.start()
  155. await LoopbackAgentWithDefaultSubscription.register(runtime, "name", LoopbackAgentWithDefaultSubscription)
  156. agent_id = AgentId("name", key="default")
  157. await runtime.publish_message(MessageType(), topic_id=DefaultTopicId())
  158. await runtime.stop_when_idle()
  159. long_running_agent = await runtime.try_get_underlying_agent_instance(
  160. agent_id, type=LoopbackAgentWithDefaultSubscription
  161. )
  162. assert long_running_agent.num_calls == 1
  163. other_long_running_agent = await runtime.try_get_underlying_agent_instance(
  164. AgentId("name", key="other"), type=LoopbackAgentWithDefaultSubscription
  165. )
  166. assert other_long_running_agent.num_calls == 0
  167. await runtime.close()
  168. @pytest.mark.asyncio
  169. async def test_type_subscription() -> None:
  170. runtime = SingleThreadedAgentRuntime()
  171. runtime.start()
  172. @type_subscription(topic_type="Other")
  173. class LoopbackAgentWithSubscription(LoopbackAgent): ...
  174. await LoopbackAgentWithSubscription.register(runtime, "name", LoopbackAgentWithSubscription)
  175. agent_id = AgentId("name", key="default")
  176. await runtime.publish_message(MessageType(), topic_id=TopicId("Other", "default"))
  177. await runtime.stop_when_idle()
  178. long_running_agent = await runtime.try_get_underlying_agent_instance(agent_id, type=LoopbackAgentWithSubscription)
  179. assert long_running_agent.num_calls == 1
  180. other_long_running_agent = await runtime.try_get_underlying_agent_instance(
  181. AgentId("name", key="other"), type=LoopbackAgentWithSubscription
  182. )
  183. assert other_long_running_agent.num_calls == 0
  184. await runtime.close()
  185. @pytest.mark.asyncio
  186. async def test_default_subscription_publish_to_other_source() -> None:
  187. runtime = SingleThreadedAgentRuntime()
  188. runtime.start()
  189. await LoopbackAgentWithDefaultSubscription.register(runtime, "name", LoopbackAgentWithDefaultSubscription)
  190. agent_id = AgentId("name", key="default")
  191. await runtime.publish_message(MessageType(), topic_id=DefaultTopicId(source="other"))
  192. await runtime.stop_when_idle()
  193. long_running_agent = await runtime.try_get_underlying_agent_instance(
  194. agent_id, type=LoopbackAgentWithDefaultSubscription
  195. )
  196. assert long_running_agent.num_calls == 0
  197. other_long_running_agent = await runtime.try_get_underlying_agent_instance(
  198. AgentId("name", key="other"), type=LoopbackAgentWithDefaultSubscription
  199. )
  200. assert other_long_running_agent.num_calls == 1
  201. await runtime.close()
  202. @default_subscription
  203. class FailingAgent(RoutedAgent):
  204. def __init__(self) -> None:
  205. super().__init__("A failing agent.")
  206. @event
  207. async def on_new_message_event(self, message: MessageType, ctx: MessageContext) -> None:
  208. raise ValueError("Test exception")
  209. @pytest.mark.asyncio
  210. async def test_event_handler_exception_propogates() -> None:
  211. runtime = SingleThreadedAgentRuntime(ignore_unhandled_exceptions=False)
  212. await FailingAgent.register(runtime, "name", FailingAgent)
  213. with pytest.raises(ValueError, match="Test exception"):
  214. runtime.start()
  215. await runtime.publish_message(MessageType(), topic_id=DefaultTopicId())
  216. await runtime.stop_when_idle()
  217. await runtime.close()
  218. @pytest.mark.asyncio
  219. async def test_event_handler_exception_multi_message() -> None:
  220. runtime = SingleThreadedAgentRuntime(ignore_unhandled_exceptions=False)
  221. await FailingAgent.register(runtime, "name", FailingAgent)
  222. with pytest.raises(ValueError, match="Test exception"):
  223. runtime.start()
  224. await runtime.publish_message(MessageType(), topic_id=DefaultTopicId())
  225. await runtime.publish_message(MessageType(), topic_id=DefaultTopicId())
  226. await runtime.publish_message(MessageType(), topic_id=DefaultTopicId())
  227. await runtime.stop_when_idle()
  228. await runtime.close()