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