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 10 kB

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