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_cancellation.py 5.2 kB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154
  1. import asyncio
  2. from dataclasses import dataclass
  3. import pytest
  4. from agnext.application import SingleThreadedAgentRuntime
  5. from agnext.components import RoutedAgent, message_handler
  6. from agnext.core import AgentId, CancellationToken
  7. from agnext.core import MessageContext
  8. from agnext.core import AgentInstantiationContext
  9. @dataclass
  10. class MessageType: ...
  11. # Note for future reader:
  12. # To do cancellation, only the token should be interacted with as a user
  13. # If you cancel a future, it may not work as you expect.
  14. class LongRunningAgent(RoutedAgent):
  15. def __init__(self) -> None:
  16. super().__init__("A long running agent")
  17. self.called = False
  18. self.cancelled = False
  19. @message_handler
  20. async def on_new_message(self, message: MessageType, ctx: MessageContext) -> MessageType:
  21. self.called = True
  22. sleep = asyncio.ensure_future(asyncio.sleep(100))
  23. ctx.cancellation_token.link_future(sleep)
  24. try:
  25. await sleep
  26. return MessageType()
  27. except asyncio.CancelledError:
  28. self.cancelled = True
  29. raise
  30. class NestingLongRunningAgent(RoutedAgent):
  31. def __init__(self, nested_agent: AgentId) -> None:
  32. super().__init__("A nesting long running agent")
  33. self.called = False
  34. self.cancelled = False
  35. self._nested_agent = nested_agent
  36. @message_handler
  37. async def on_new_message(self, message: MessageType, ctx: MessageContext) -> MessageType:
  38. self.called = True
  39. response = self.send_message(message, self._nested_agent, cancellation_token=ctx.cancellation_token)
  40. try:
  41. val = await response
  42. assert isinstance(val, MessageType)
  43. return val
  44. except asyncio.CancelledError:
  45. self.cancelled = True
  46. raise
  47. @pytest.mark.asyncio
  48. async def test_cancellation_with_token() -> None:
  49. runtime = SingleThreadedAgentRuntime()
  50. await runtime.register("long_running", LongRunningAgent)
  51. agent_id = AgentId("long_running", key="default")
  52. token = CancellationToken()
  53. response = asyncio.create_task(runtime.send_message(MessageType(), recipient=agent_id, cancellation_token=token))
  54. assert not response.done()
  55. while len(runtime.unprocessed_messages) == 0:
  56. await asyncio.sleep(0.01)
  57. await runtime.process_next()
  58. token.cancel()
  59. with pytest.raises(asyncio.CancelledError):
  60. await response
  61. assert response.done()
  62. long_running_agent = await runtime.try_get_underlying_agent_instance(agent_id, type=LongRunningAgent)
  63. assert long_running_agent.called
  64. assert long_running_agent.cancelled
  65. @pytest.mark.asyncio
  66. async def test_nested_cancellation_only_outer_called() -> None:
  67. runtime = SingleThreadedAgentRuntime()
  68. await runtime.register("long_running", LongRunningAgent)
  69. await runtime.register(
  70. "nested",
  71. lambda: NestingLongRunningAgent(AgentId("long_running", key=AgentInstantiationContext.current_agent_id().key)),
  72. )
  73. long_running_id = AgentId("long_running", key="default")
  74. nested_id = AgentId("nested", key="default")
  75. token = CancellationToken()
  76. response = asyncio.create_task(runtime.send_message(MessageType(), nested_id, cancellation_token=token))
  77. assert not response.done()
  78. while len(runtime.unprocessed_messages) == 0:
  79. await asyncio.sleep(0.01)
  80. await runtime.process_next()
  81. token.cancel()
  82. with pytest.raises(asyncio.CancelledError):
  83. await response
  84. assert response.done()
  85. nested_agent = await runtime.try_get_underlying_agent_instance(nested_id, type=NestingLongRunningAgent)
  86. assert nested_agent.called
  87. assert nested_agent.cancelled
  88. long_running_agent = await runtime.try_get_underlying_agent_instance(long_running_id, type=LongRunningAgent)
  89. assert long_running_agent.called is False
  90. assert long_running_agent.cancelled is False
  91. @pytest.mark.asyncio
  92. async def test_nested_cancellation_inner_called() -> None:
  93. runtime = SingleThreadedAgentRuntime()
  94. await runtime.register("long_running", LongRunningAgent)
  95. await runtime.register(
  96. "nested",
  97. lambda: NestingLongRunningAgent(AgentId("long_running", key=AgentInstantiationContext.current_agent_id().key)),
  98. )
  99. long_running_id = AgentId("long_running", key="default")
  100. nested_id = AgentId("nested", key="default")
  101. token = CancellationToken()
  102. response = asyncio.create_task(runtime.send_message(MessageType(), nested_id, cancellation_token=token))
  103. assert not response.done()
  104. while len(runtime.unprocessed_messages) == 0:
  105. await asyncio.sleep(0.01)
  106. await runtime.process_next()
  107. # allow the inner agent to process
  108. await runtime.process_next()
  109. token.cancel()
  110. with pytest.raises(asyncio.CancelledError):
  111. await response
  112. assert response.done()
  113. nested_agent = await runtime.try_get_underlying_agent_instance(nested_id, type=NestingLongRunningAgent)
  114. assert nested_agent.called
  115. assert nested_agent.cancelled
  116. long_running_agent = await runtime.try_get_underlying_agent_instance(long_running_id, type=LongRunningAgent)
  117. assert long_running_agent.called
  118. assert long_running_agent.cancelled