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 13 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
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366
  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_agent_type_register_instance() -> None:
  68. runtime = SingleThreadedAgentRuntime()
  69. agent1_id = AgentId(type="name", key="default")
  70. agent2_id = AgentId(type="name", key="notdefault")
  71. agent1 = NoopAgent()
  72. agent1_dup = NoopAgent()
  73. agent2 = NoopAgent()
  74. await agent1.register_instance(runtime=runtime, agent_id=agent1_id)
  75. await agent2.register_instance(runtime=runtime, agent_id=agent2_id)
  76. assert await runtime.try_get_underlying_agent_instance(agent1_id, type=NoopAgent) == agent1
  77. assert await runtime.try_get_underlying_agent_instance(agent2_id, type=NoopAgent) == agent2
  78. with pytest.raises(ValueError):
  79. await agent1_dup.register_instance(runtime=runtime, agent_id=agent1_id)
  80. @pytest.mark.asyncio
  81. async def test_agent_type_register_instance_different_types() -> None:
  82. runtime = SingleThreadedAgentRuntime()
  83. agent_id1 = AgentId(type="name", key="noop")
  84. agent_id2 = AgentId(type="name", key="loopback")
  85. agent1 = NoopAgent()
  86. agent2 = LoopbackAgent()
  87. await agent1.register_instance(runtime=runtime, agent_id=agent_id1)
  88. with pytest.raises(ValueError):
  89. await agent2.register_instance(runtime=runtime, agent_id=agent_id2)
  90. @pytest.mark.asyncio
  91. async def test_agent_type_register_instance_publish_new_source() -> None:
  92. runtime = SingleThreadedAgentRuntime(ignore_unhandled_exceptions=False)
  93. agent_id = AgentId(type="name", key="default")
  94. agent1 = LoopbackAgent()
  95. await agent1.register_instance(runtime=runtime, agent_id=agent_id)
  96. await runtime.add_subscription(TypeSubscription("notdefault", "name"))
  97. runtime.start()
  98. with pytest.raises(RuntimeError):
  99. await runtime.publish_message(MessageType(), TopicId("notdefault", "notdefault"))
  100. await runtime.stop_when_idle()
  101. await runtime.close()
  102. @pytest.mark.asyncio
  103. async def test_register_instance_factory() -> None:
  104. runtime = SingleThreadedAgentRuntime()
  105. agent1_id = AgentId(type="name", key="default")
  106. agent1 = NoopAgent()
  107. await agent1.register_instance(runtime=runtime, agent_id=agent1_id)
  108. with pytest.raises(ValueError):
  109. await NoopAgent.register(runtime, "name", lambda: NoopAgent())
  110. @pytest.mark.asyncio
  111. async def test_register_receives_publish(tracer_provider: TracerProvider) -> None:
  112. runtime = SingleThreadedAgentRuntime(tracer_provider=tracer_provider)
  113. runtime.add_message_serializer(try_get_known_serializers_for_type(MessageType))
  114. await runtime.register_factory(
  115. type=AgentType("name"), agent_factory=lambda: LoopbackAgent(), expected_class=LoopbackAgent
  116. )
  117. await runtime.add_subscription(TypeSubscription("default", "name"))
  118. runtime.start()
  119. await runtime.publish_message(MessageType(), topic_id=TopicId("default", "default"))
  120. await runtime.stop_when_idle()
  121. # Agent in default namespace should have received the message
  122. long_running_agent = await runtime.try_get_underlying_agent_instance(AgentId("name", "default"), type=LoopbackAgent)
  123. assert long_running_agent.num_calls == 1
  124. # Agent in other namespace should not have received the message
  125. other_long_running_agent: LoopbackAgent = await runtime.try_get_underlying_agent_instance(
  126. AgentId("name", key="other"), type=LoopbackAgent
  127. )
  128. assert other_long_running_agent.num_calls == 0
  129. exported_spans = test_exporter.get_exported_spans()
  130. assert len(exported_spans) == 3
  131. span_names = [span.name for span in exported_spans]
  132. assert span_names == [
  133. "autogen create default.(default)-T",
  134. "autogen process name.(default)-A",
  135. "autogen publish default.(default)-T",
  136. ]
  137. await runtime.close()
  138. @pytest.mark.asyncio
  139. async def test_register_receives_publish_with_construction(caplog: pytest.LogCaptureFixture) -> None:
  140. runtime = SingleThreadedAgentRuntime()
  141. runtime.add_message_serializer(try_get_known_serializers_for_type(MessageType))
  142. async def agent_factory() -> LoopbackAgent:
  143. raise ValueError("test")
  144. await runtime.register_factory(type=AgentType("name"), agent_factory=agent_factory, expected_class=LoopbackAgent)
  145. await runtime.add_subscription(TypeSubscription("default", "name"))
  146. with caplog.at_level(logging.ERROR):
  147. runtime.start()
  148. await runtime.publish_message(MessageType(), topic_id=TopicId("default", "default"))
  149. await runtime.stop_when_idle()
  150. # Check if logger has the exception.
  151. assert any("Error constructing agent" in e.message for e in caplog.records)
  152. await runtime.close()
  153. @pytest.mark.asyncio
  154. async def test_register_receives_publish_cascade() -> None:
  155. num_agents = 5
  156. num_initial_messages = 5
  157. max_rounds = 5
  158. total_num_calls_expected = 0
  159. for i in range(0, max_rounds):
  160. total_num_calls_expected += num_initial_messages * ((num_agents - 1) ** i)
  161. runtime = SingleThreadedAgentRuntime()
  162. # Register agents
  163. for i in range(num_agents):
  164. await CascadingAgent.register(runtime, f"name{i}", lambda: CascadingAgent(max_rounds))
  165. runtime.start()
  166. # Publish messages
  167. for _ in range(num_initial_messages):
  168. await runtime.publish_message(CascadingMessageType(round=1), DefaultTopicId())
  169. # Process until idle.
  170. await runtime.stop_when_idle()
  171. # Check that each agent received the correct number of messages.
  172. for i in range(num_agents):
  173. agent = await runtime.try_get_underlying_agent_instance(AgentId(f"name{i}", "default"), CascadingAgent)
  174. assert agent.num_calls == total_num_calls_expected
  175. await runtime.close()
  176. @pytest.mark.asyncio
  177. async def test_register_factory_explicit_name() -> None:
  178. runtime = SingleThreadedAgentRuntime()
  179. await LoopbackAgent.register(runtime, "name", LoopbackAgent)
  180. await runtime.add_subscription(TypeSubscription("default", "name"))
  181. runtime.start()
  182. agent_id = AgentId("name", key="default")
  183. topic_id = TopicId("default", "default")
  184. await runtime.publish_message(MessageType(), topic_id=topic_id)
  185. await runtime.stop_when_idle()
  186. # Agent in default namespace should have received the message
  187. long_running_agent = await runtime.try_get_underlying_agent_instance(agent_id, type=LoopbackAgent)
  188. assert long_running_agent.num_calls == 1
  189. # Agent in other namespace should not have received the message
  190. other_long_running_agent: LoopbackAgent = await runtime.try_get_underlying_agent_instance(
  191. AgentId("name", key="other"), type=LoopbackAgent
  192. )
  193. assert other_long_running_agent.num_calls == 0
  194. await runtime.close()
  195. @pytest.mark.asyncio
  196. async def test_default_subscription() -> None:
  197. runtime = SingleThreadedAgentRuntime()
  198. runtime.start()
  199. await LoopbackAgentWithDefaultSubscription.register(runtime, "name", LoopbackAgentWithDefaultSubscription)
  200. agent_id = AgentId("name", key="default")
  201. await runtime.publish_message(MessageType(), topic_id=DefaultTopicId())
  202. await runtime.stop_when_idle()
  203. long_running_agent = await runtime.try_get_underlying_agent_instance(
  204. agent_id, type=LoopbackAgentWithDefaultSubscription
  205. )
  206. assert long_running_agent.num_calls == 1
  207. other_long_running_agent = await runtime.try_get_underlying_agent_instance(
  208. AgentId("name", key="other"), type=LoopbackAgentWithDefaultSubscription
  209. )
  210. assert other_long_running_agent.num_calls == 0
  211. await runtime.close()
  212. @pytest.mark.asyncio
  213. async def test_type_subscription() -> None:
  214. runtime = SingleThreadedAgentRuntime()
  215. runtime.start()
  216. @type_subscription(topic_type="Other")
  217. class LoopbackAgentWithSubscription(LoopbackAgent): ...
  218. await LoopbackAgentWithSubscription.register(runtime, "name", LoopbackAgentWithSubscription)
  219. agent_id = AgentId("name", key="default")
  220. await runtime.publish_message(MessageType(), topic_id=TopicId("Other", "default"))
  221. await runtime.stop_when_idle()
  222. long_running_agent = await runtime.try_get_underlying_agent_instance(agent_id, type=LoopbackAgentWithSubscription)
  223. assert long_running_agent.num_calls == 1
  224. other_long_running_agent = await runtime.try_get_underlying_agent_instance(
  225. AgentId("name", key="other"), type=LoopbackAgentWithSubscription
  226. )
  227. assert other_long_running_agent.num_calls == 0
  228. await runtime.close()
  229. @pytest.mark.asyncio
  230. async def test_default_subscription_publish_to_other_source() -> None:
  231. runtime = SingleThreadedAgentRuntime()
  232. runtime.start()
  233. await LoopbackAgentWithDefaultSubscription.register(runtime, "name", LoopbackAgentWithDefaultSubscription)
  234. agent_id = AgentId("name", key="default")
  235. await runtime.publish_message(MessageType(), topic_id=DefaultTopicId(source="other"))
  236. await runtime.stop_when_idle()
  237. long_running_agent = await runtime.try_get_underlying_agent_instance(
  238. agent_id, type=LoopbackAgentWithDefaultSubscription
  239. )
  240. assert long_running_agent.num_calls == 0
  241. other_long_running_agent = await runtime.try_get_underlying_agent_instance(
  242. AgentId("name", key="other"), type=LoopbackAgentWithDefaultSubscription
  243. )
  244. assert other_long_running_agent.num_calls == 1
  245. await runtime.close()
  246. @default_subscription
  247. class FailingAgent(RoutedAgent):
  248. def __init__(self) -> None:
  249. super().__init__("A failing agent.")
  250. @event
  251. async def on_new_message_event(self, message: MessageType, ctx: MessageContext) -> None:
  252. raise ValueError("Test exception")
  253. @pytest.mark.asyncio
  254. async def test_event_handler_exception_propogates() -> None:
  255. runtime = SingleThreadedAgentRuntime(ignore_unhandled_exceptions=False)
  256. await FailingAgent.register(runtime, "name", FailingAgent)
  257. with pytest.raises(ValueError, match="Test exception"):
  258. runtime.start()
  259. await runtime.publish_message(MessageType(), topic_id=DefaultTopicId())
  260. await runtime.stop_when_idle()
  261. await runtime.close()
  262. @pytest.mark.asyncio
  263. async def test_event_handler_exception_multi_message() -> None:
  264. runtime = SingleThreadedAgentRuntime(ignore_unhandled_exceptions=False)
  265. await FailingAgent.register(runtime, "name", FailingAgent)
  266. with pytest.raises(ValueError, match="Test exception"):
  267. runtime.start()
  268. await runtime.publish_message(MessageType(), topic_id=DefaultTopicId())
  269. await runtime.publish_message(MessageType(), topic_id=DefaultTopicId())
  270. await runtime.publish_message(MessageType(), topic_id=DefaultTopicId())
  271. await runtime.stop_when_idle()
  272. await runtime.close()