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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432
  1. import pytest
  2. from autogen_agentchat.agents import CodeExecutorAgent
  3. from autogen_agentchat.base import Response
  4. from autogen_agentchat.messages import (
  5. CodeExecutionEvent,
  6. CodeGenerationEvent,
  7. TextMessage,
  8. )
  9. from autogen_core import CancellationToken
  10. from autogen_core.models import ModelFamily, ModelInfo
  11. from autogen_ext.code_executors.local import LocalCommandLineCodeExecutor
  12. from autogen_ext.models.replay import ReplayChatCompletionClient
  13. @pytest.mark.asyncio
  14. async def test_basic_code_execution() -> None:
  15. """Test basic code execution"""
  16. agent = CodeExecutorAgent(name="code_executor", code_executor=LocalCommandLineCodeExecutor())
  17. messages = [
  18. TextMessage(
  19. content="""
  20. ```python
  21. import math
  22. number = 42
  23. square_root = math.sqrt(number)
  24. print("%0.3f" % (square_root,))
  25. ```
  26. """.strip(),
  27. source="assistant",
  28. )
  29. ]
  30. response = await agent.on_messages(messages, CancellationToken())
  31. assert isinstance(response, Response)
  32. assert isinstance(response.chat_message, TextMessage)
  33. assert response.chat_message.content.strip() == "6.481"
  34. assert response.chat_message.source == "code_executor"
  35. @pytest.mark.asyncio
  36. async def test_code_generation_and_execution_with_model_client() -> None:
  37. """
  38. Tests the code generation, execution and reflection pipeline using a model client.
  39. """
  40. language = "python"
  41. code = 'import math\n\nnumber = 42\nsquare_root = math.sqrt(number)\nprint("%0.3f" % (square_root,))'
  42. model_client = ReplayChatCompletionClient(
  43. [f"Here is the code to calculate the square root of 42:\n```{language}\n{code}```".strip(), "TERMINATE"]
  44. )
  45. agent = CodeExecutorAgent(
  46. name="code_executor_agent", code_executor=LocalCommandLineCodeExecutor(), model_client=model_client
  47. )
  48. messages = [
  49. TextMessage(
  50. content="Generate python code to calculate the square root of 42",
  51. source="assistant",
  52. )
  53. ]
  54. code_generation_event: CodeGenerationEvent | None = None
  55. code_execution_event: CodeExecutionEvent | None = None
  56. response: Response | None = None
  57. async for message in agent.on_messages_stream(messages, CancellationToken()):
  58. if isinstance(message, CodeGenerationEvent):
  59. code_block = message.code_blocks[0]
  60. assert code_block.code == code, "Code block does not match"
  61. assert code_block.language == language, "Language does not match"
  62. code_generation_event = message
  63. elif isinstance(message, CodeExecutionEvent):
  64. assert message.to_text().strip() == "6.481", f"Expected '6.481', got: {message.to_text().strip()}"
  65. code_execution_event = message
  66. elif isinstance(message, Response):
  67. assert isinstance(
  68. message.chat_message, TextMessage
  69. ), f"Expected TextMessage, got: {type(message.chat_message)}"
  70. assert (
  71. message.chat_message.source == "code_executor_agent"
  72. ), f"Expected source 'code_executor_agent', got: {message.chat_message.source}"
  73. response = message
  74. else:
  75. raise AssertionError(f"Unexpected message type: {type(message)}")
  76. assert code_generation_event is not None, "Code generation event was not received"
  77. assert code_execution_event is not None, "Code execution event was not received"
  78. assert response is not None, "Response was not received"
  79. @pytest.mark.asyncio
  80. async def test_no_code_response_with_model_client() -> None:
  81. """
  82. Tests agent behavior when the model client responds with non-code content.
  83. """
  84. model_client = ReplayChatCompletionClient(["The capital of France is Paris.", "TERMINATE"])
  85. agent = CodeExecutorAgent(
  86. name="code_executor_agent", code_executor=LocalCommandLineCodeExecutor(), model_client=model_client
  87. )
  88. messages = [
  89. TextMessage(
  90. content="What is the capital of France?",
  91. source="assistant",
  92. )
  93. ]
  94. response: Response | None = None
  95. async for message in agent.on_messages_stream(messages, CancellationToken()):
  96. if isinstance(message, Response):
  97. assert isinstance(
  98. message.chat_message, TextMessage
  99. ), f"Expected TextMessage, got: {type(message.chat_message)}"
  100. assert (
  101. message.chat_message.source == "code_executor_agent"
  102. ), f"Expected source 'code_executor_agent', got: {message.chat_message.source}"
  103. assert (
  104. message.chat_message.content.strip() == "The capital of France is Paris."
  105. ), f"Expected 'The capital of France is Paris.', got: {message.chat_message.content.strip()}"
  106. response = message
  107. else:
  108. raise AssertionError(f"Unexpected message type: {type(message)}")
  109. assert response is not None, "Response was not received"
  110. @pytest.mark.asyncio
  111. async def test_self_debugging_loop() -> None:
  112. """
  113. Tests self debugging loop when the model client responds with incorrect code.
  114. """
  115. language = "python"
  116. incorrect_code_block = """
  117. numbers = [10, 20, 30, 40, 50]
  118. mean = sum(numbers) / len(numbers
  119. print("The mean is:", mean)
  120. """.strip()
  121. incorrect_code_result = """
  122. mean = sum(numbers) / len(numbers
  123. ^
  124. SyntaxError: '(' was never closed
  125. """.strip()
  126. correct_code_block = """
  127. numbers = [10, 20, 30, 40, 50]
  128. mean = sum(numbers) / len(numbers)
  129. print("The mean is:", mean)
  130. """.strip()
  131. correct_code_result = """
  132. The mean is: 30.0
  133. """.strip()
  134. model_client = ReplayChatCompletionClient(
  135. [
  136. f"""
  137. Here is the code to calculate the mean of 10, 20, 30, 40, 50
  138. ```{language}
  139. {incorrect_code_block}
  140. ```
  141. """,
  142. """{"retry": "true", "reason": "Retry 1: It is a test environment"}""",
  143. f"""
  144. Here is the updated code to calculate the mean of 10, 20, 30, 40, 50
  145. ```{language}
  146. {correct_code_block}
  147. ```""",
  148. "Final Response",
  149. "TERMINATE",
  150. ],
  151. model_info=ModelInfo(
  152. vision=False,
  153. function_calling=False,
  154. json_output=True,
  155. family=ModelFamily.UNKNOWN,
  156. structured_output=True,
  157. ),
  158. )
  159. agent = CodeExecutorAgent(
  160. name="code_executor_agent",
  161. code_executor=LocalCommandLineCodeExecutor(),
  162. model_client=model_client,
  163. max_retries_on_error=1,
  164. )
  165. messages = [
  166. TextMessage(
  167. content="Calculate the mean of 10, 20, 30, 40, 50.",
  168. source="assistant",
  169. )
  170. ]
  171. incorrect_code_generation_event: CodeGenerationEvent | None = None
  172. correct_code_generation_event: CodeGenerationEvent | None = None
  173. retry_decision_event: CodeGenerationEvent | None = None
  174. incorrect_code_execution_event: CodeExecutionEvent | None = None
  175. correct_code_execution_event: CodeExecutionEvent | None = None
  176. response: Response | None = None
  177. message_id: int = 0
  178. async for message in agent.on_messages_stream(messages, CancellationToken()):
  179. if isinstance(message, CodeGenerationEvent) and message_id == 0:
  180. # Step 1: First code generation
  181. code_block = message.code_blocks[0]
  182. assert code_block.code.strip() == incorrect_code_block, "Incorrect code block does not match"
  183. assert code_block.language == language, "Language does not match"
  184. incorrect_code_generation_event = message
  185. elif isinstance(message, CodeExecutionEvent) and message_id == 1:
  186. # Step 2: First code execution
  187. assert (
  188. incorrect_code_result in message.to_text().strip()
  189. ), f"Expected {incorrect_code_result} in execution result, got: {message.to_text().strip()}"
  190. incorrect_code_execution_event = message
  191. elif isinstance(message, CodeGenerationEvent) and message_id == 2:
  192. # Step 3: Retry generation with proposed correction
  193. retry_response = "Attempt number: 1\nProposed correction: Retry 1: It is a test environment"
  194. assert (
  195. message.to_text().strip() == retry_response
  196. ), f"Expected {retry_response}, got: {message.to_text().strip()}"
  197. retry_decision_event = message
  198. elif isinstance(message, CodeGenerationEvent) and message_id == 3:
  199. # Step 4: Second retry code generation
  200. code_block = message.code_blocks[0]
  201. assert code_block.code.strip() == correct_code_block, "Correct code block does not match"
  202. assert code_block.language == language, "Language does not match"
  203. correct_code_generation_event = message
  204. elif isinstance(message, CodeExecutionEvent) and message_id == 4:
  205. # Step 5: Second retry code execution
  206. assert (
  207. message.to_text().strip() == correct_code_result
  208. ), f"Expected {correct_code_result} in execution result, got: {message.to_text().strip()}"
  209. correct_code_execution_event = message
  210. elif isinstance(message, Response) and message_id == 5:
  211. # Step 6: Final response
  212. assert isinstance(
  213. message.chat_message, TextMessage
  214. ), f"Expected TextMessage, got: {type(message.chat_message)}"
  215. assert (
  216. message.chat_message.source == "code_executor_agent"
  217. ), f"Expected source 'code_executor_agent', got: {message.chat_message.source}"
  218. response = message
  219. else:
  220. raise AssertionError(f"Unexpected message type: {type(message)}")
  221. message_id += 1
  222. assert incorrect_code_generation_event is not None, "Incorrect code generation event was not received"
  223. assert incorrect_code_execution_event is not None, "Incorrect code execution event was not received"
  224. assert retry_decision_event is not None, "Retry decision event was not received"
  225. assert correct_code_generation_event is not None, "Correct code generation event was not received"
  226. assert correct_code_execution_event is not None, "Correct code execution event was not received"
  227. assert response is not None, "Response was not received"
  228. @pytest.mark.asyncio
  229. async def test_code_execution_error() -> None:
  230. """Test basic code execution"""
  231. agent = CodeExecutorAgent(name="code_executor", code_executor=LocalCommandLineCodeExecutor())
  232. messages = [
  233. TextMessage(
  234. content="""
  235. ```python
  236. import math
  237. number = -1.0
  238. square_root = math.sqrt(number)
  239. print("%0.3f" % (square_root,))
  240. ```
  241. """.strip(),
  242. source="assistant",
  243. )
  244. ]
  245. response = await agent.on_messages(messages, CancellationToken())
  246. assert isinstance(response, Response)
  247. assert isinstance(response.chat_message, TextMessage)
  248. assert "The script ran, then exited with an error (POSIX exit code: 1)" in response.chat_message.content
  249. assert "ValueError: math domain error" in response.chat_message.content
  250. @pytest.mark.asyncio
  251. async def test_code_execution_no_output() -> None:
  252. """Test basic code execution"""
  253. agent = CodeExecutorAgent(name="code_executor", code_executor=LocalCommandLineCodeExecutor())
  254. messages = [
  255. TextMessage(
  256. content="""
  257. ```python
  258. import math
  259. number = 42
  260. square_root = math.sqrt(number)
  261. ```
  262. """.strip(),
  263. source="assistant",
  264. )
  265. ]
  266. response = await agent.on_messages(messages, CancellationToken())
  267. assert isinstance(response, Response)
  268. assert isinstance(response.chat_message, TextMessage)
  269. assert (
  270. "The script ran but produced no output to console. The POSIX exit code was: 0. If you were expecting output, consider revising the script to ensure content is printed to stdout."
  271. in response.chat_message.content
  272. )
  273. @pytest.mark.asyncio
  274. async def test_code_execution_no_block() -> None:
  275. """Test basic code execution"""
  276. agent = CodeExecutorAgent(name="code_executor", code_executor=LocalCommandLineCodeExecutor())
  277. messages = [
  278. TextMessage(
  279. content="""
  280. import math
  281. number = 42
  282. square_root = math.sqrt(number)
  283. """.strip(),
  284. source="assistant",
  285. )
  286. ]
  287. response = await agent.on_messages(messages, CancellationToken())
  288. assert isinstance(response, Response)
  289. assert isinstance(response.chat_message, TextMessage)
  290. assert (
  291. "No code blocks found in the thread. Please provide at least one markdown-encoded code block"
  292. in response.chat_message.content
  293. )
  294. @pytest.mark.asyncio
  295. async def test_code_execution_multiple_blocks() -> None:
  296. """Test basic code execution"""
  297. agent = CodeExecutorAgent(name="code_executor", code_executor=LocalCommandLineCodeExecutor())
  298. messages = [
  299. TextMessage(
  300. content="""
  301. ```python
  302. import math
  303. number = 42
  304. square_root = math.sqrt(number)
  305. print("%0.3f" % (square_root,))
  306. ```
  307. And also:
  308. ```python
  309. import time
  310. print(f"The current time is: {time.time()}")
  311. ```
  312. And this should result in an error:
  313. ```python
  314. import math
  315. number = -1.0
  316. square_root = math.sqrt(number)
  317. print("%0.3f" % (square_root,))
  318. ```
  319. """.strip(),
  320. source="assistant",
  321. )
  322. ]
  323. response = await agent.on_messages(messages, CancellationToken())
  324. assert isinstance(response, Response)
  325. assert isinstance(response.chat_message, TextMessage)
  326. assert "6.481" in response.chat_message.content
  327. assert "The current time is:" in response.chat_message.content
  328. assert "The script ran, then exited with an error (POSIX exit code: 1)" in response.chat_message.content
  329. assert "ValueError: math domain error" in response.chat_message.content
  330. @pytest.mark.asyncio
  331. async def test_code_execution_agent_serialization() -> None:
  332. """Test agent config serialization"""
  333. agent = CodeExecutorAgent(name="code_executor", code_executor=LocalCommandLineCodeExecutor())
  334. # Serialize and deserialize the agent
  335. serialized_agent = agent.dump_component()
  336. deserialized_agent = CodeExecutorAgent.load_component(serialized_agent)
  337. assert isinstance(deserialized_agent, CodeExecutorAgent)
  338. assert deserialized_agent.name == "code_executor"
  339. @pytest.mark.asyncio
  340. async def test_code_execution_agent_serialization_with_model_client() -> None:
  341. """Test agent config serialization"""
  342. model_client = ReplayChatCompletionClient(["The capital of France is Paris.", "TERMINATE"])
  343. agent = CodeExecutorAgent(
  344. name="code_executor_agent", code_executor=LocalCommandLineCodeExecutor(), model_client=model_client
  345. )
  346. # Serialize and deserialize the agent
  347. serialized_agent = agent.dump_component()
  348. deserialized_agent = CodeExecutorAgent.load_component(serialized_agent)
  349. assert isinstance(deserialized_agent, CodeExecutorAgent)
  350. assert deserialized_agent.name == "code_executor_agent"
  351. assert deserialized_agent._model_client is not None # type: ignore