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_worker_runtime.py 27 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
1 year ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718
  1. import asyncio
  2. import logging
  3. import os
  4. from typing import Any, List
  5. import pytest
  6. from autogen_core import (
  7. PROTOBUF_DATA_CONTENT_TYPE,
  8. AgentId,
  9. AgentType,
  10. DefaultSubscription,
  11. DefaultTopicId,
  12. MessageContext,
  13. RoutedAgent,
  14. Subscription,
  15. TopicId,
  16. TypeSubscription,
  17. default_subscription,
  18. event,
  19. try_get_known_serializers_for_type,
  20. type_subscription,
  21. )
  22. from autogen_ext.runtimes.grpc import GrpcWorkerAgentRuntime, GrpcWorkerAgentRuntimeHost
  23. from autogen_test_utils import (
  24. CascadingAgent,
  25. CascadingMessageType,
  26. ContentMessage,
  27. LoopbackAgent,
  28. LoopbackAgentWithDefaultSubscription,
  29. MessageType,
  30. NoopAgent,
  31. )
  32. from .protos.serialization_test_pb2 import ProtoMessage
  33. @pytest.mark.grpc
  34. @pytest.mark.asyncio
  35. async def test_agent_types_must_be_unique_single_worker() -> None:
  36. host_address = "localhost:50051"
  37. host = GrpcWorkerAgentRuntimeHost(address=host_address)
  38. host.start()
  39. worker = GrpcWorkerAgentRuntime(host_address=host_address)
  40. await worker.start()
  41. await worker.register_factory(type=AgentType("name1"), agent_factory=lambda: NoopAgent(), expected_class=NoopAgent)
  42. with pytest.raises(ValueError):
  43. await worker.register_factory(
  44. type=AgentType("name1"), agent_factory=lambda: NoopAgent(), expected_class=NoopAgent
  45. )
  46. await worker.register_factory(type=AgentType("name4"), agent_factory=lambda: NoopAgent(), expected_class=NoopAgent)
  47. await worker.register_factory(type=AgentType("name5"), agent_factory=lambda: NoopAgent())
  48. await worker.stop()
  49. await host.stop()
  50. @pytest.mark.grpc
  51. @pytest.mark.asyncio
  52. async def test_agent_types_must_be_unique_multiple_workers() -> None:
  53. host_address = "localhost:50052"
  54. host = GrpcWorkerAgentRuntimeHost(address=host_address)
  55. host.start()
  56. worker1 = GrpcWorkerAgentRuntime(host_address=host_address)
  57. await worker1.start()
  58. worker2 = GrpcWorkerAgentRuntime(host_address=host_address)
  59. await worker2.start()
  60. await worker1.register_factory(type=AgentType("name1"), agent_factory=lambda: NoopAgent(), expected_class=NoopAgent)
  61. with pytest.raises(Exception, match="Agent type name1 already registered"):
  62. await worker2.register_factory(
  63. type=AgentType("name1"), agent_factory=lambda: NoopAgent(), expected_class=NoopAgent
  64. )
  65. await worker2.register_factory(type=AgentType("name4"), agent_factory=lambda: NoopAgent(), expected_class=NoopAgent)
  66. await worker1.stop()
  67. await worker2.stop()
  68. await host.stop()
  69. @pytest.mark.grpc
  70. @pytest.mark.asyncio
  71. async def test_register_receives_publish() -> None:
  72. host_address = "localhost:50053"
  73. host = GrpcWorkerAgentRuntimeHost(address=host_address)
  74. host.start()
  75. worker1 = GrpcWorkerAgentRuntime(host_address=host_address)
  76. await worker1.start()
  77. worker1.add_message_serializer(try_get_known_serializers_for_type(MessageType))
  78. await worker1.register_factory(
  79. type=AgentType("name1"), agent_factory=lambda: LoopbackAgent(), expected_class=LoopbackAgent
  80. )
  81. await worker1.add_subscription(TypeSubscription("default", "name1"))
  82. worker2 = GrpcWorkerAgentRuntime(host_address=host_address)
  83. await worker2.start()
  84. worker2.add_message_serializer(try_get_known_serializers_for_type(MessageType))
  85. await worker2.register_factory(
  86. type=AgentType("name2"), agent_factory=lambda: LoopbackAgent(), expected_class=LoopbackAgent
  87. )
  88. await worker2.add_subscription(TypeSubscription("default", "name2"))
  89. # Publish message from worker1
  90. await worker1.publish_message(MessageType(), topic_id=TopicId("default", "default"))
  91. # Let the agent run for a bit.
  92. await asyncio.sleep(2)
  93. # Agents in default topic source should have received the message.
  94. worker1_agent = await worker1.try_get_underlying_agent_instance(AgentId("name1", "default"), LoopbackAgent)
  95. assert worker1_agent.num_calls == 1
  96. worker2_agent = await worker2.try_get_underlying_agent_instance(AgentId("name2", "default"), LoopbackAgent)
  97. assert worker2_agent.num_calls == 1
  98. # Agents in other topic source should not have received the message.
  99. worker1_agent = await worker1.try_get_underlying_agent_instance(AgentId("name1", "other"), LoopbackAgent)
  100. assert worker1_agent.num_calls == 0
  101. worker2_agent = await worker2.try_get_underlying_agent_instance(AgentId("name2", "other"), LoopbackAgent)
  102. assert worker2_agent.num_calls == 0
  103. await worker1.stop()
  104. await worker2.stop()
  105. await host.stop()
  106. @pytest.mark.grpc
  107. @pytest.mark.asyncio
  108. async def test_register_doesnt_receive_after_removing_subscription() -> None:
  109. host_address = "localhost:50053"
  110. host = GrpcWorkerAgentRuntimeHost(address=host_address)
  111. host.start()
  112. worker1 = GrpcWorkerAgentRuntime(host_address=host_address)
  113. await worker1.start()
  114. worker1.add_message_serializer(try_get_known_serializers_for_type(MessageType))
  115. await worker1.register_factory(
  116. type=AgentType("name1"), agent_factory=lambda: LoopbackAgent(), expected_class=LoopbackAgent
  117. )
  118. sub = DefaultSubscription(agent_type="name1")
  119. await worker1.add_subscription(sub)
  120. agent_1_instance = await worker1.try_get_underlying_agent_instance(AgentId("name1", "default"), LoopbackAgent)
  121. # Publish message from worker1
  122. await worker1.publish_message(MessageType(), topic_id=DefaultTopicId())
  123. # Let the agent run for a bit.
  124. await agent_1_instance.event.wait()
  125. agent_1_instance.event.clear()
  126. # Agents in default topic source should have received the message.
  127. assert agent_1_instance.num_calls == 1
  128. await worker1.remove_subscription(sub.id)
  129. # Publish message from worker1
  130. await worker1.publish_message(MessageType(), topic_id=DefaultTopicId())
  131. # Let the agent run for a bit.
  132. await asyncio.sleep(2)
  133. # Agent should not have received the message.
  134. assert agent_1_instance.num_calls == 1
  135. await worker1.stop()
  136. await host.stop()
  137. @pytest.mark.asyncio
  138. async def test_register_receives_publish_cascade_single_worker() -> None:
  139. host_address = "localhost:50054"
  140. host = GrpcWorkerAgentRuntimeHost(address=host_address)
  141. host.start()
  142. runtime = GrpcWorkerAgentRuntime(host_address=host_address)
  143. await runtime.start()
  144. num_agents = 5
  145. num_initial_messages = 5
  146. max_rounds = 5
  147. total_num_calls_expected = 0
  148. for i in range(0, max_rounds):
  149. total_num_calls_expected += num_initial_messages * ((num_agents - 1) ** i)
  150. # Register agents
  151. for i in range(num_agents):
  152. await CascadingAgent.register(runtime, f"name{i}", lambda: CascadingAgent(max_rounds))
  153. # Publish messages
  154. for _ in range(num_initial_messages):
  155. await runtime.publish_message(CascadingMessageType(round=1), topic_id=DefaultTopicId())
  156. # Wait for all agents to finish.
  157. await asyncio.sleep(10)
  158. # Check that each agent received the correct number of messages.
  159. for i in range(num_agents):
  160. agent = await runtime.try_get_underlying_agent_instance(AgentId(f"name{i}", "default"), CascadingAgent)
  161. assert agent.num_calls == total_num_calls_expected
  162. await runtime.stop()
  163. await host.stop()
  164. @pytest.mark.grpc
  165. @pytest.mark.skip(reason="Fix flakiness")
  166. @pytest.mark.asyncio
  167. async def test_register_receives_publish_cascade_multiple_workers() -> None:
  168. logging.basicConfig(level=logging.DEBUG)
  169. host_address = "localhost:50055"
  170. host = GrpcWorkerAgentRuntimeHost(address=host_address)
  171. host.start()
  172. # TODO: Increasing num_initial_messages or max_round to 2 causes the test to fail.
  173. num_agents = 2
  174. num_initial_messages = 1
  175. max_rounds = 1
  176. total_num_calls_expected = 0
  177. for i in range(0, max_rounds):
  178. total_num_calls_expected += num_initial_messages * ((num_agents - 1) ** i)
  179. # Run multiple workers one for each agent.
  180. workers: List[GrpcWorkerAgentRuntime] = []
  181. # Register agents
  182. for i in range(num_agents):
  183. runtime = GrpcWorkerAgentRuntime(host_address=host_address)
  184. await runtime.start()
  185. await CascadingAgent.register(runtime, f"name{i}", lambda: CascadingAgent(max_rounds))
  186. workers.append(runtime)
  187. # Publish messages
  188. publisher = GrpcWorkerAgentRuntime(host_address=host_address)
  189. publisher.add_message_serializer(try_get_known_serializers_for_type(CascadingMessageType))
  190. await publisher.start()
  191. for _ in range(num_initial_messages):
  192. await publisher.publish_message(CascadingMessageType(round=1), topic_id=DefaultTopicId())
  193. await asyncio.sleep(20)
  194. # Check that each agent received the correct number of messages.
  195. for i in range(num_agents):
  196. agent = await workers[i].try_get_underlying_agent_instance(AgentId(f"name{i}", "default"), CascadingAgent)
  197. assert agent.num_calls == total_num_calls_expected
  198. for worker in workers:
  199. await worker.stop()
  200. await publisher.stop()
  201. await host.stop()
  202. @pytest.mark.grpc
  203. @pytest.mark.asyncio
  204. async def test_default_subscription() -> None:
  205. host_address = "localhost:50056"
  206. host = GrpcWorkerAgentRuntimeHost(address=host_address)
  207. host.start()
  208. worker = GrpcWorkerAgentRuntime(host_address=host_address)
  209. await worker.start()
  210. publisher = GrpcWorkerAgentRuntime(host_address=host_address)
  211. publisher.add_message_serializer(try_get_known_serializers_for_type(MessageType))
  212. await publisher.start()
  213. await LoopbackAgentWithDefaultSubscription.register(worker, "name", lambda: LoopbackAgentWithDefaultSubscription())
  214. await publisher.publish_message(MessageType(), topic_id=DefaultTopicId())
  215. await asyncio.sleep(2)
  216. # Agent in default topic source should have received the message.
  217. long_running_agent = await worker.try_get_underlying_agent_instance(
  218. AgentId("name", "default"), type=LoopbackAgentWithDefaultSubscription
  219. )
  220. assert long_running_agent.num_calls == 1
  221. # Agent in other topic source should not have received the message.
  222. other_long_running_agent = await worker.try_get_underlying_agent_instance(
  223. AgentId("name", key="other"), type=LoopbackAgentWithDefaultSubscription
  224. )
  225. assert other_long_running_agent.num_calls == 0
  226. await worker.stop()
  227. await publisher.stop()
  228. await host.stop()
  229. @pytest.mark.grpc
  230. @pytest.mark.asyncio
  231. async def test_default_subscription_other_source() -> None:
  232. host_address = "localhost:50057"
  233. host = GrpcWorkerAgentRuntimeHost(address=host_address)
  234. host.start()
  235. runtime = GrpcWorkerAgentRuntime(host_address=host_address)
  236. await runtime.start()
  237. publisher = GrpcWorkerAgentRuntime(host_address=host_address)
  238. publisher.add_message_serializer(try_get_known_serializers_for_type(MessageType))
  239. await publisher.start()
  240. await LoopbackAgentWithDefaultSubscription.register(runtime, "name", lambda: LoopbackAgentWithDefaultSubscription())
  241. await publisher.publish_message(MessageType(), topic_id=DefaultTopicId(source="other"))
  242. await asyncio.sleep(2)
  243. # Agent in default namespace should have received the message
  244. long_running_agent = await runtime.try_get_underlying_agent_instance(
  245. AgentId("name", "default"), type=LoopbackAgentWithDefaultSubscription
  246. )
  247. assert long_running_agent.num_calls == 0
  248. # Agent in other namespace should not have received the message
  249. other_long_running_agent = await runtime.try_get_underlying_agent_instance(
  250. AgentId("name", key="other"), type=LoopbackAgentWithDefaultSubscription
  251. )
  252. assert other_long_running_agent.num_calls == 1
  253. await runtime.stop()
  254. await publisher.stop()
  255. await host.stop()
  256. @pytest.mark.grpc
  257. @pytest.mark.asyncio
  258. async def test_type_subscription() -> None:
  259. host_address = "localhost:50058"
  260. host = GrpcWorkerAgentRuntimeHost(address=host_address)
  261. host.start()
  262. worker = GrpcWorkerAgentRuntime(host_address=host_address)
  263. await worker.start()
  264. publisher = GrpcWorkerAgentRuntime(host_address=host_address)
  265. publisher.add_message_serializer(try_get_known_serializers_for_type(MessageType))
  266. await publisher.start()
  267. @type_subscription("Other")
  268. class LoopbackAgentWithSubscription(LoopbackAgent): ...
  269. await LoopbackAgentWithSubscription.register(worker, "name", lambda: LoopbackAgentWithSubscription())
  270. await publisher.publish_message(MessageType(), topic_id=TopicId(type="Other", source="default"))
  271. await asyncio.sleep(2)
  272. # Agent in default topic source should have received the message.
  273. long_running_agent = await worker.try_get_underlying_agent_instance(
  274. AgentId("name", "default"), type=LoopbackAgentWithSubscription
  275. )
  276. assert long_running_agent.num_calls == 1
  277. # Agent in other topic source should not have received the message.
  278. other_long_running_agent = await worker.try_get_underlying_agent_instance(
  279. AgentId("name", key="other"), type=LoopbackAgentWithSubscription
  280. )
  281. assert other_long_running_agent.num_calls == 0
  282. await worker.stop()
  283. await publisher.stop()
  284. await host.stop()
  285. @pytest.mark.grpc
  286. @pytest.mark.asyncio
  287. async def test_duplicate_subscription() -> None:
  288. host_address = "localhost:50059"
  289. host = GrpcWorkerAgentRuntimeHost(address=host_address)
  290. worker1 = GrpcWorkerAgentRuntime(host_address=host_address)
  291. worker1_2 = GrpcWorkerAgentRuntime(host_address=host_address)
  292. host.start()
  293. try:
  294. await worker1.start()
  295. await NoopAgent.register(worker1, "worker1", lambda: NoopAgent())
  296. await worker1_2.start()
  297. # Note: This passes because worker1 is still running
  298. with pytest.raises(Exception, match="Agent type worker1 already registered"):
  299. await NoopAgent.register(worker1_2, "worker1", lambda: NoopAgent())
  300. # This is somehow covered in test_disconnected_agent as well as a stop will also disconnect the agent.
  301. # Will keep them both for now as we might replace the way we simulate a disconnect
  302. await worker1.stop()
  303. with pytest.raises(ValueError):
  304. await NoopAgent.register(worker1_2, "worker1", lambda: NoopAgent())
  305. except Exception as ex:
  306. raise ex
  307. finally:
  308. await worker1_2.stop()
  309. await host.stop()
  310. @pytest.mark.grpc
  311. @pytest.mark.asyncio
  312. async def test_disconnected_agent() -> None:
  313. host_address = "localhost:50060"
  314. host = GrpcWorkerAgentRuntimeHost(address=host_address)
  315. host.start()
  316. worker1 = GrpcWorkerAgentRuntime(host_address=host_address)
  317. worker1_2 = GrpcWorkerAgentRuntime(host_address=host_address)
  318. # TODO: Implementing `get_current_subscriptions` and `get_subscribed_recipients` requires access
  319. # to some private properties. This needs to be updated once they are available publicly
  320. def get_current_subscriptions() -> List[Subscription]:
  321. return host._servicer._subscription_manager._subscriptions # type: ignore[reportPrivateUsage]
  322. async def get_subscribed_recipients() -> List[AgentId]:
  323. return await host._servicer._subscription_manager.get_subscribed_recipients(DefaultTopicId()) # type: ignore[reportPrivateUsage]
  324. try:
  325. await worker1.start()
  326. await LoopbackAgentWithDefaultSubscription.register(
  327. worker1, "worker1", lambda: LoopbackAgentWithDefaultSubscription()
  328. )
  329. subscriptions1 = get_current_subscriptions()
  330. assert len(subscriptions1) == 2
  331. recipients1 = await get_subscribed_recipients()
  332. assert AgentId(type="worker1", key="default") in recipients1
  333. first_subscription_id = subscriptions1[0].id
  334. await worker1.publish_message(ContentMessage(content="Hello!"), DefaultTopicId())
  335. # This is a simple simulation of worker disconnct
  336. if worker1._host_connection is not None: # type: ignore[reportPrivateUsage]
  337. try:
  338. await worker1._host_connection.close() # type: ignore[reportPrivateUsage]
  339. except asyncio.CancelledError:
  340. pass
  341. await asyncio.sleep(1)
  342. subscriptions2 = get_current_subscriptions()
  343. assert len(subscriptions2) == 0
  344. recipients2 = await get_subscribed_recipients()
  345. assert len(recipients2) == 0
  346. await asyncio.sleep(1)
  347. await worker1_2.start()
  348. await LoopbackAgentWithDefaultSubscription.register(
  349. worker1_2, "worker1", lambda: LoopbackAgentWithDefaultSubscription()
  350. )
  351. subscriptions3 = get_current_subscriptions()
  352. assert len(subscriptions3) == 2
  353. assert first_subscription_id not in [x.id for x in subscriptions3]
  354. recipients3 = await get_subscribed_recipients()
  355. assert len(set(recipients2)) == len(recipients2) # Make sure there are no duplicates
  356. assert AgentId(type="worker1", key="default") in recipients3
  357. except Exception as ex:
  358. raise ex
  359. finally:
  360. await worker1.stop()
  361. await worker1_2.stop()
  362. @default_subscription
  363. class ProtoReceivingAgent(RoutedAgent):
  364. def __init__(self) -> None:
  365. super().__init__("A loop back agent.")
  366. self.num_calls = 0
  367. self.received_messages: list[Any] = []
  368. @event
  369. async def on_new_message(self, message: ProtoMessage, ctx: MessageContext) -> None: # type: ignore
  370. self.num_calls += 1
  371. self.received_messages.append(message)
  372. @pytest.mark.grpc
  373. @pytest.mark.asyncio
  374. async def test_proto_payloads() -> None:
  375. host_address = "localhost:50057"
  376. host = GrpcWorkerAgentRuntimeHost(address=host_address)
  377. host.start()
  378. receiver_runtime = GrpcWorkerAgentRuntime(
  379. host_address=host_address, payload_serialization_format=PROTOBUF_DATA_CONTENT_TYPE
  380. )
  381. await receiver_runtime.start()
  382. publisher_runtime = GrpcWorkerAgentRuntime(
  383. host_address=host_address, payload_serialization_format=PROTOBUF_DATA_CONTENT_TYPE
  384. )
  385. publisher_runtime.add_message_serializer(try_get_known_serializers_for_type(ProtoMessage))
  386. await publisher_runtime.start()
  387. await ProtoReceivingAgent.register(receiver_runtime, "name", ProtoReceivingAgent)
  388. await publisher_runtime.publish_message(ProtoMessage(message="Hello!"), topic_id=DefaultTopicId())
  389. await asyncio.sleep(2)
  390. # Agent in default namespace should have received the message
  391. long_running_agent = await receiver_runtime.try_get_underlying_agent_instance(
  392. AgentId("name", "default"), type=ProtoReceivingAgent
  393. )
  394. assert long_running_agent.num_calls == 1
  395. assert long_running_agent.received_messages[0].message == "Hello!"
  396. # Agent in other namespace should not have received the message
  397. other_long_running_agent = await receiver_runtime.try_get_underlying_agent_instance(
  398. AgentId("name", key="other"), type=ProtoReceivingAgent
  399. )
  400. assert other_long_running_agent.num_calls == 0
  401. assert len(other_long_running_agent.received_messages) == 0
  402. await receiver_runtime.stop()
  403. await publisher_runtime.stop()
  404. await host.stop()
  405. # TODO add tests for failure to deserialize
  406. @pytest.mark.grpc
  407. @pytest.mark.asyncio
  408. @pytest.mark.skip(reason="Fix flakiness")
  409. async def test_grpc_max_message_size() -> None:
  410. default_max_size = 2**22
  411. new_max_size = default_max_size * 2
  412. small_message = ContentMessage(content="small message")
  413. big_message = ContentMessage(content="." * (default_max_size + 1))
  414. extra_grpc_config = [
  415. ("grpc.max_send_message_length", new_max_size),
  416. ("grpc.max_receive_message_length", new_max_size),
  417. ]
  418. host_address = "localhost:50061"
  419. host = GrpcWorkerAgentRuntimeHost(address=host_address, extra_grpc_config=extra_grpc_config)
  420. worker1 = GrpcWorkerAgentRuntime(host_address=host_address, extra_grpc_config=extra_grpc_config)
  421. worker2 = GrpcWorkerAgentRuntime(host_address=host_address)
  422. worker3 = GrpcWorkerAgentRuntime(host_address=host_address, extra_grpc_config=extra_grpc_config)
  423. try:
  424. host.start()
  425. await worker1.start()
  426. await worker2.start()
  427. await worker3.start()
  428. await LoopbackAgentWithDefaultSubscription.register(
  429. worker1, "worker1", lambda: LoopbackAgentWithDefaultSubscription()
  430. )
  431. await LoopbackAgentWithDefaultSubscription.register(
  432. worker2, "worker2", lambda: LoopbackAgentWithDefaultSubscription()
  433. )
  434. await LoopbackAgentWithDefaultSubscription.register(
  435. worker3, "worker3", lambda: LoopbackAgentWithDefaultSubscription()
  436. )
  437. # with pytest.raises(Exception):
  438. await worker1.publish_message(small_message, DefaultTopicId())
  439. # This is a simple simulation of worker disconnct
  440. await asyncio.sleep(1)
  441. agent_instance_2 = await worker2.try_get_underlying_agent_instance(
  442. AgentId("worker2", key="default"), type=LoopbackAgent
  443. )
  444. agent_instance_3 = await worker3.try_get_underlying_agent_instance(
  445. AgentId("worker3", key="default"), type=LoopbackAgent
  446. )
  447. assert agent_instance_2.num_calls == 1
  448. assert agent_instance_3.num_calls == 1
  449. await worker1.publish_message(big_message, DefaultTopicId())
  450. await asyncio.sleep(2)
  451. assert agent_instance_2.num_calls == 1 # Worker 2 won't receive the big message
  452. assert agent_instance_3.num_calls == 2 # Worker 3 will receive the big message as has increased message length
  453. except Exception as e:
  454. raise e
  455. finally:
  456. await worker1.stop()
  457. # await worker2.stop() # Worker 2 somehow breaks can can not be stopped.
  458. await worker3.stop()
  459. await host.stop()
  460. @pytest.mark.grpc
  461. @pytest.mark.asyncio
  462. async def test_agent_type_register_instance() -> None:
  463. host_address = "localhost:50051"
  464. agent1_id = AgentId(type="name", key="default")
  465. agentdup_id = AgentId(type="name", key="default")
  466. agent2_id = AgentId(type="name", key="notdefault")
  467. host = GrpcWorkerAgentRuntimeHost(address=host_address)
  468. host.start()
  469. worker = GrpcWorkerAgentRuntime(host_address=host_address)
  470. agent1 = NoopAgent()
  471. agent2 = NoopAgent()
  472. agentdup = NoopAgent()
  473. await worker.start()
  474. await worker.register_agent_instance(agent1, agent_id=agent1_id)
  475. await worker.register_agent_instance(agent2, agent_id=agent2_id)
  476. with pytest.raises(ValueError):
  477. await worker.register_agent_instance(agentdup, agent_id=agentdup_id)
  478. assert await worker.try_get_underlying_agent_instance(agent1_id, type=NoopAgent) == agent1
  479. assert await worker.try_get_underlying_agent_instance(agent2_id, type=NoopAgent) == agent2
  480. await worker.stop()
  481. await host.stop()
  482. @pytest.mark.grpc
  483. @pytest.mark.asyncio
  484. async def test_agent_type_register_instance_different_types() -> None:
  485. host_address = "localhost:50051"
  486. agent1_id = AgentId(type="name", key="noop")
  487. agent2_id = AgentId(type="name", key="loopback")
  488. host = GrpcWorkerAgentRuntimeHost(address=host_address)
  489. host.start()
  490. worker = GrpcWorkerAgentRuntime(host_address=host_address)
  491. agent1 = NoopAgent()
  492. agent2 = LoopbackAgent()
  493. await worker.start()
  494. await worker.register_agent_instance(agent1, agent_id=agent1_id)
  495. with pytest.raises(ValueError):
  496. await worker.register_agent_instance(agent2, agent_id=agent2_id)
  497. await worker.stop()
  498. await host.stop()
  499. @pytest.mark.grpc
  500. @pytest.mark.asyncio
  501. async def test_register_instance_factory() -> None:
  502. host_address = "localhost:50051"
  503. agent1_id = AgentId(type="name", key="default")
  504. host = GrpcWorkerAgentRuntimeHost(address=host_address)
  505. host.start()
  506. worker = GrpcWorkerAgentRuntime(host_address=host_address)
  507. agent1 = NoopAgent()
  508. await worker.start()
  509. await agent1.register_instance(runtime=worker, agent_id=agent1_id)
  510. with pytest.raises(ValueError):
  511. await NoopAgent.register(runtime=worker, type="name", factory=lambda: NoopAgent())
  512. await worker.stop()
  513. await host.stop()
  514. @pytest.mark.grpc
  515. @pytest.mark.asyncio
  516. async def test_instance_factory_messaging() -> None:
  517. host_address = "localhost:50051"
  518. loopback_agent_id = AgentId(type="dm_agent", key="dm_agent")
  519. cascading_agent_id = AgentId(type="instance_agent", key="instance_agent")
  520. host = GrpcWorkerAgentRuntimeHost(address=host_address)
  521. host.start()
  522. worker = GrpcWorkerAgentRuntime(host_address=host_address)
  523. cascading_agent = CascadingAgent(max_rounds=5)
  524. loopback_agent = LoopbackAgent()
  525. await worker.start()
  526. await loopback_agent.register_instance(worker, agent_id=loopback_agent_id)
  527. resp = await worker.send_message(message=ContentMessage(content="Hello!"), recipient=loopback_agent_id)
  528. assert resp == ContentMessage(content="Hello!")
  529. await cascading_agent.register_instance(worker, agent_id=cascading_agent_id)
  530. await CascadingAgent.register(worker, "factory_agent", lambda: CascadingAgent(max_rounds=5))
  531. # instance_agent will publish a message that factory_agent will pick up
  532. for i in range(5):
  533. await worker.publish_message(
  534. CascadingMessageType(round=i + 1), TopicId(type="instance_agent", source="instance_agent")
  535. )
  536. await asyncio.sleep(2)
  537. agent = await worker.try_get_underlying_agent_instance(AgentId("factory_agent", "default"), CascadingAgent)
  538. assert agent.num_calls == 4
  539. assert cascading_agent.num_calls == 5
  540. await worker.stop()
  541. await host.stop()
  542. # GrpcWorkerAgentRuntimeHost eats exceptions in the main loop
  543. # @pytest.mark.grpc
  544. # @pytest.mark.asyncio
  545. # async def test_agent_type_register_instance_publish_new_source() -> None:
  546. # host_address = "localhost:50056"
  547. # agent_id = AgentId(type="name", key="default")
  548. # agent1 = LoopbackAgent()
  549. # host = GrpcWorkerAgentRuntimeHost(address=host_address)
  550. # host.start()
  551. # worker = GrpcWorkerAgentRuntime(host_address=host_address)
  552. # await worker.start()
  553. # publisher = GrpcWorkerAgentRuntime(host_address=host_address)
  554. # publisher.add_message_serializer(try_get_known_serializers_for_type(MessageType))
  555. # await publisher.start()
  556. # await agent1.register_instance(worker, agent_id=agent_id)
  557. # await worker.add_subscription(TypeSubscription("notdefault", "name"))
  558. # with pytest.raises(RuntimeError):
  559. # await worker.publish_message(MessageType(), TopicId("notdefault", "notdefault"))
  560. # await asyncio.sleep(2)
  561. # await worker.stop()
  562. # await host.stop()
  563. if __name__ == "__main__":
  564. os.environ["GRPC_VERBOSITY"] = "DEBUG"
  565. os.environ["GRPC_TRACE"] = "all"
  566. asyncio.run(test_disconnected_agent())
  567. asyncio.run(test_grpc_max_message_size())