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