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_termination_condition.py 15 kB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433
  1. import asyncio
  2. from typing import Sequence
  3. import pytest
  4. from autogen_agentchat.base import TerminatedException
  5. from autogen_agentchat.conditions import (
  6. ExternalTermination,
  7. FunctionalTermination,
  8. FunctionCallTermination,
  9. HandoffTermination,
  10. MaxMessageTermination,
  11. SourceMatchTermination,
  12. StopMessageTermination,
  13. TextMentionTermination,
  14. TextMessageTermination,
  15. TimeoutTermination,
  16. TokenUsageTermination,
  17. )
  18. from autogen_agentchat.messages import (
  19. BaseAgentEvent,
  20. BaseChatMessage,
  21. HandoffMessage,
  22. StopMessage,
  23. StructuredMessage,
  24. TextMessage,
  25. ToolCallExecutionEvent,
  26. UserInputRequestedEvent,
  27. )
  28. from autogen_core.models import FunctionExecutionResult, RequestUsage
  29. from pydantic import BaseModel
  30. @pytest.mark.asyncio
  31. async def test_handoff_termination() -> None:
  32. termination = HandoffTermination("target")
  33. assert await termination([]) is None
  34. await termination.reset()
  35. assert await termination([TextMessage(content="Hello", source="user")]) is None
  36. await termination.reset()
  37. assert await termination([HandoffMessage(target="target", source="user", content="Hello")]) is not None
  38. assert termination.terminated
  39. await termination.reset()
  40. assert await termination([HandoffMessage(target="another", source="user", content="Hello")]) is None
  41. assert not termination.terminated
  42. await termination.reset()
  43. assert (
  44. await termination(
  45. [
  46. TextMessage(content="Hello", source="user"),
  47. HandoffMessage(target="target", source="user", content="Hello"),
  48. ]
  49. )
  50. is not None
  51. )
  52. assert termination.terminated
  53. await termination.reset()
  54. @pytest.mark.asyncio
  55. async def test_stop_message_termination() -> None:
  56. termination = StopMessageTermination()
  57. assert await termination([]) is None
  58. await termination.reset()
  59. assert await termination([TextMessage(content="Hello", source="user")]) is None
  60. await termination.reset()
  61. assert await termination([StopMessage(content="Stop", source="user")]) is not None
  62. await termination.reset()
  63. assert (
  64. await termination([TextMessage(content="Hello", source="user"), TextMessage(content="World", source="agent")])
  65. is None
  66. )
  67. await termination.reset()
  68. assert (
  69. await termination([TextMessage(content="Hello", source="user"), StopMessage(content="Stop", source="user")])
  70. is not None
  71. )
  72. @pytest.mark.asyncio
  73. async def test_text_message_termination() -> None:
  74. termination = TextMessageTermination()
  75. assert await termination([]) is None
  76. await termination.reset()
  77. assert await termination([StopMessage(content="Hello", source="user")]) is None
  78. await termination.reset()
  79. assert await termination([TextMessage(content="Hello", source="user")]) is not None
  80. assert termination.terminated
  81. await termination.reset()
  82. assert (
  83. await termination([StopMessage(content="Hello", source="user"), TextMessage(content="World", source="agent")])
  84. is not None
  85. )
  86. assert termination.terminated
  87. with pytest.raises(TerminatedException):
  88. await termination([TextMessage(content="Hello", source="user")])
  89. termination = TextMessageTermination(source="user")
  90. assert await termination([]) is None
  91. await termination.reset()
  92. assert await termination([TextMessage(content="Hello", source="user")]) is not None
  93. assert termination.terminated
  94. await termination.reset()
  95. termination = TextMessageTermination(source="agent")
  96. assert await termination([]) is None
  97. await termination.reset()
  98. assert await termination([TextMessage(content="Hello", source="user")]) is None
  99. await termination.reset()
  100. assert await termination([TextMessage(content="Hello", source="agent")]) is not None
  101. assert termination.terminated
  102. @pytest.mark.asyncio
  103. async def test_max_message_termination() -> None:
  104. termination = MaxMessageTermination(2)
  105. assert await termination([]) is None
  106. await termination.reset()
  107. assert await termination([TextMessage(content="Hello", source="user")]) is None
  108. await termination.reset()
  109. assert (
  110. await termination([TextMessage(content="Hello", source="user"), TextMessage(content="World", source="agent")])
  111. is not None
  112. )
  113. termination = MaxMessageTermination(2, include_agent_event=True)
  114. assert await termination([]) is None
  115. await termination.reset()
  116. assert await termination([TextMessage(content="Hello", source="user")]) is None
  117. await termination.reset()
  118. assert (
  119. await termination(
  120. [TextMessage(content="Hello", source="user"), UserInputRequestedEvent(request_id="1", source="agent")]
  121. )
  122. is not None
  123. )
  124. @pytest.mark.asyncio
  125. async def test_mention_termination() -> None:
  126. termination = TextMentionTermination("stop")
  127. assert await termination([]) is None
  128. await termination.reset()
  129. assert await termination([TextMessage(content="Hello", source="user")]) is None
  130. await termination.reset()
  131. assert await termination([TextMessage(content="stop", source="user")]) is not None
  132. await termination.reset()
  133. assert (
  134. await termination([TextMessage(content="Hello", source="user"), TextMessage(content="stop", source="user")])
  135. is not None
  136. )
  137. termination = TextMentionTermination("stop", sources=["agent"])
  138. assert await termination([TextMessage(content="stop", source="user")]) is None
  139. await termination.reset()
  140. assert (
  141. await termination([TextMessage(content="stop", source="user"), TextMessage(content="stop", source="agent")])
  142. is not None
  143. )
  144. @pytest.mark.asyncio
  145. async def test_token_usage_termination() -> None:
  146. termination = TokenUsageTermination(max_total_token=10)
  147. assert await termination([]) is None
  148. await termination.reset()
  149. assert (
  150. await termination(
  151. [
  152. TextMessage(
  153. content="Hello", source="user", models_usage=RequestUsage(prompt_tokens=10, completion_tokens=10)
  154. )
  155. ]
  156. )
  157. is not None
  158. )
  159. await termination.reset()
  160. assert (
  161. await termination(
  162. [
  163. TextMessage(
  164. content="Hello", source="user", models_usage=RequestUsage(prompt_tokens=1, completion_tokens=1)
  165. ),
  166. TextMessage(
  167. content="World", source="agent", models_usage=RequestUsage(prompt_tokens=1, completion_tokens=1)
  168. ),
  169. ]
  170. )
  171. is None
  172. )
  173. await termination.reset()
  174. assert (
  175. await termination(
  176. [
  177. TextMessage(
  178. content="Hello", source="user", models_usage=RequestUsage(prompt_tokens=5, completion_tokens=0)
  179. ),
  180. TextMessage(
  181. content="stop", source="user", models_usage=RequestUsage(prompt_tokens=0, completion_tokens=5)
  182. ),
  183. ]
  184. )
  185. is not None
  186. )
  187. @pytest.mark.asyncio
  188. async def test_and_termination() -> None:
  189. termination = MaxMessageTermination(2) & TextMentionTermination("stop")
  190. assert await termination([]) is None
  191. await termination.reset()
  192. assert await termination([TextMessage(content="Hello", source="user")]) is None
  193. await termination.reset()
  194. assert (
  195. await termination([TextMessage(content="Hello", source="user"), TextMessage(content="World", source="agent")])
  196. is None
  197. )
  198. await termination.reset()
  199. assert (
  200. await termination([TextMessage(content="Hello", source="user"), TextMessage(content="stop", source="user")])
  201. is not None
  202. )
  203. @pytest.mark.asyncio
  204. async def test_or_termination() -> None:
  205. termination = MaxMessageTermination(3) | TextMentionTermination("stop")
  206. assert await termination([]) is None
  207. await termination.reset()
  208. assert await termination([TextMessage(content="Hello", source="user")]) is None
  209. await termination.reset()
  210. assert (
  211. await termination([TextMessage(content="Hello", source="user"), TextMessage(content="World", source="agent")])
  212. is None
  213. )
  214. await termination.reset()
  215. assert (
  216. await termination([TextMessage(content="Hello", source="user"), TextMessage(content="stop", source="user")])
  217. is not None
  218. )
  219. await termination.reset()
  220. assert (
  221. await termination([TextMessage(content="Hello", source="user"), TextMessage(content="Hello", source="user")])
  222. is None
  223. )
  224. await termination.reset()
  225. assert (
  226. await termination(
  227. [
  228. TextMessage(content="Hello", source="user"),
  229. TextMessage(content="Hello", source="user"),
  230. TextMessage(content="Hello", source="user"),
  231. ]
  232. )
  233. is not None
  234. )
  235. await termination.reset()
  236. assert (
  237. await termination(
  238. [
  239. TextMessage(content="Hello", source="user"),
  240. TextMessage(content="Hello", source="user"),
  241. TextMessage(content="stop", source="user"),
  242. ]
  243. )
  244. is not None
  245. )
  246. await termination.reset()
  247. assert (
  248. await termination(
  249. [
  250. TextMessage(content="Hello", source="user"),
  251. TextMessage(content="Hello", source="user"),
  252. TextMessage(content="Hello", source="user"),
  253. TextMessage(content="stop", source="user"),
  254. ]
  255. )
  256. is not None
  257. )
  258. @pytest.mark.asyncio
  259. async def test_timeout_termination() -> None:
  260. termination = TimeoutTermination(0.1) # 100ms timeout
  261. assert await termination([]) is None
  262. assert not termination.terminated
  263. await asyncio.sleep(0.2)
  264. assert await termination([]) is not None
  265. assert termination.terminated
  266. await termination.reset()
  267. assert not termination.terminated
  268. assert await termination([]) is None
  269. assert await termination([TextMessage(content="Hello", source="user")]) is None
  270. await asyncio.sleep(0.2)
  271. assert await termination([TextMessage(content="World", source="user")]) is not None
  272. @pytest.mark.asyncio
  273. async def test_external_termination() -> None:
  274. termination = ExternalTermination()
  275. assert await termination([]) is None
  276. assert not termination.terminated
  277. termination.set()
  278. assert await termination([]) is not None
  279. assert termination.terminated
  280. await termination.reset()
  281. assert await termination([]) is None
  282. @pytest.mark.asyncio
  283. async def test_source_match_termination() -> None:
  284. termination = SourceMatchTermination(sources=["Assistant"])
  285. assert await termination([]) is None
  286. continue_messages = [TextMessage(content="Hello", source="agent"), TextMessage(content="Hello", source="user")]
  287. assert await termination(continue_messages) is None
  288. terminate_messages = [
  289. TextMessage(content="Hello", source="agent"),
  290. TextMessage(content="Hello", source="Assistant"),
  291. TextMessage(content="Hello", source="user"),
  292. ]
  293. result = await termination(terminate_messages)
  294. assert isinstance(result, StopMessage)
  295. assert termination.terminated
  296. with pytest.raises(TerminatedException):
  297. await termination([])
  298. await termination.reset()
  299. assert not termination.terminated
  300. @pytest.mark.asyncio
  301. async def test_function_call_termination() -> None:
  302. termination = FunctionCallTermination(function_name="test_function")
  303. assert await termination([]) is None
  304. await termination.reset()
  305. assert await termination([TextMessage(content="Hello", source="user")]) is None
  306. await termination.reset()
  307. assert (
  308. await termination(
  309. [TextMessage(content="Hello", source="user"), ToolCallExecutionEvent(content=[], source="assistant")]
  310. )
  311. is None
  312. )
  313. await termination.reset()
  314. assert (
  315. await termination(
  316. [
  317. TextMessage(content="Hello", source="user"),
  318. ToolCallExecutionEvent(
  319. content=[FunctionExecutionResult(content="", name="test_function", call_id="")], source="assistant"
  320. ),
  321. ]
  322. )
  323. is not None
  324. )
  325. assert termination.terminated
  326. await termination.reset()
  327. assert (
  328. await termination(
  329. [
  330. TextMessage(content="Hello", source="user"),
  331. ToolCallExecutionEvent(
  332. content=[FunctionExecutionResult(content="", name="another_function", call_id="")],
  333. source="assistant",
  334. ),
  335. ]
  336. )
  337. is None
  338. )
  339. assert not termination.terminated
  340. await termination.reset()
  341. @pytest.mark.asyncio
  342. async def test_functional_termination() -> None:
  343. async def async_termination_func(messages: Sequence[BaseAgentEvent | BaseChatMessage]) -> bool:
  344. if len(messages) < 1:
  345. return False
  346. if isinstance(messages[-1], TextMessage):
  347. return messages[-1].content == "stop"
  348. return False
  349. termination = FunctionalTermination(async_termination_func)
  350. assert await termination([]) is None
  351. await termination.reset()
  352. assert await termination([TextMessage(content="Hello", source="user")]) is None
  353. await termination.reset()
  354. assert await termination([TextMessage(content="stop", source="user")]) is not None
  355. assert termination.terminated
  356. await termination.reset()
  357. assert await termination([TextMessage(content="Hello", source="user")]) is None
  358. class TestContentType(BaseModel):
  359. content: str
  360. data: str
  361. def sync_termination_func(messages: Sequence[BaseAgentEvent | BaseChatMessage]) -> bool:
  362. if len(messages) < 1:
  363. return False
  364. last_message = messages[-1]
  365. if isinstance(last_message, StructuredMessage) and isinstance(last_message.content, TestContentType): # type: ignore[reportUnknownMemberType]
  366. return last_message.content.data == "stop"
  367. return False
  368. termination = FunctionalTermination(sync_termination_func)
  369. assert await termination([]) is None
  370. await termination.reset()
  371. assert await termination([TextMessage(content="Hello", source="user")]) is None
  372. await termination.reset()
  373. assert (
  374. await termination(
  375. [StructuredMessage[TestContentType](content=TestContentType(content="1", data="stop"), source="user")]
  376. )
  377. is not None
  378. )
  379. assert termination.terminated
  380. await termination.reset()
  381. assert await termination([TextMessage(content="Hello", source="user")]) is None