diff --git a/autogpts/autogpt/autogpt/commands/execute_code.py b/autogpts/autogpt/autogpt/commands/execute_code.py index f9d55885..a5b48a7f 100644 --- a/autogpts/autogpt/autogpt/commands/execute_code.py +++ b/autogpts/autogpt/autogpt/commands/execute_code.py @@ -10,7 +10,7 @@ from pathlib import Path from tempfile import NamedTemporaryFile import docker -from docker.errors import DockerException, ImageNotFound +from docker.errors import DockerException, ImageNotFound, NotFound from docker.models.containers import Container as DockerContainer from autogpt.agents.agent import Agent @@ -135,58 +135,75 @@ def execute_python_file( logger.debug("AutoGPT is not running in a Docker container") try: + assert agent.state.agent_id, "Need Agent ID to attach Docker container" + client = docker.from_env() # You can replace this with the desired Python image/version # You can find available Python images on Docker Hub: # https://hub.docker.com/_/python image_name = "python:3-alpine" + container_is_fresh = False + container_name = f"{agent.state.agent_id}_sandbox" try: - client.images.get(image_name) - logger.debug(f"Image '{image_name}' found locally") - except ImageNotFound: - logger.info( - f"Image '{image_name}' not found locally, pulling from Docker Hub..." - ) - # Use the low-level API to stream the pull response - low_level_client = docker.APIClient() - for line in low_level_client.pull(image_name, stream=True, decode=True): - # Print the status and progress, if available - status = line.get("status") - progress = line.get("progress") - if status and progress: - logger.info(f"{status}: {progress}") - elif status: - logger.info(status) + container: DockerContainer = client.containers.get(container_name) # type: ignore + except NotFound: + try: + client.images.get(image_name) + logger.debug(f"Image '{image_name}' found locally") + except ImageNotFound: + logger.info( + f"Image '{image_name}' not found locally, pulling from Docker Hub..." + ) + # Use the low-level API to stream the pull response + low_level_client = docker.APIClient() + for line in low_level_client.pull(image_name, stream=True, decode=True): + # Print the status and progress, if available + status = line.get("status") + progress = line.get("progress") + if status and progress: + logger.info(f"{status}: {progress}") + elif status: + logger.info(status) - logger.debug(f"Running {file_path} in a {image_name} container...") - container: DockerContainer = client.containers.run( - image_name, + logger.debug(f"Creating new {image_name} container...") + container: DockerContainer = client.containers.run( + image_name, + ["sleep", "60"], # Max 60 seconds to prevent permanent hangs + volumes={ + str(agent.workspace.root): { + "bind": "/workspace", + "mode": "rw", + } + }, + working_dir="/workspace", + stderr=True, + stdout=True, + detach=True, + name=container_name, + ) # type: ignore + container_is_fresh = True + + if not container.status == "running": + container.start() + elif not container_is_fresh: + container.restart() + + logger.debug(f"Running {file_path} in container {container.name}...") + exec_result = container.exec_run( [ "python", "-B", file_path.relative_to(agent.workspace.root).as_posix(), ] + args, - volumes={ - str(agent.workspace.root): { - "bind": "/workspace", - "mode": "rw", - } - }, - working_dir="/workspace", stderr=True, stdout=True, - detach=True, - ) # type: ignore + ) - container.wait() - logs = container.logs().decode("utf-8") - container.remove() + if exec_result.exit_code != 0: + raise CodeExecutionError(exec_result.output.decode("utf-8")) - # print(f"Execution complete. Output: {output}") - # print(f"Logs: {logs}") - - return logs + return exec_result.output.decode("utf-8") except DockerException as e: logger.warn(