Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add mode for python execution #3713

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions .env.template
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,17 @@ OPENAI_API_KEY=your-openai-api-key
# GOOGLE_API_KEY=your-google-api-key
# CUSTOM_SEARCH_ENGINE_ID=your-custom-search-engine-id

################################################################################
### PYTHON CODE EXECUTION
################################################################################
## PYTHON EXECUTION - Sets the python execution environment to excute python script (default: Docker).
## Note: set this to either 'Docker' or 'Local' depending on your current environment. Local is use virtual environment(venv).
# PYTHON_EXECUTION=Docker

### DOCKER
## DOCKER_IMAGE - Docker image to use for python execution (default: python:3-alpine)
# DOCKER_IMAGE=python:3-alpine

################################################################################
### TTS PROVIDER
################################################################################
Expand Down
137 changes: 87 additions & 50 deletions autogpt/commands/execute_code.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
"""Execute code in a Docker container"""
import os
import pathlib
import subprocess
import venv
from pathlib import Path

import docker
Expand All @@ -15,8 +17,7 @@

@command("execute_python_file", "Execute Python File", '"filename": "<filename>"')
def execute_python_file(filename: str) -> str:
"""Execute a Python file in a Docker container and return the output

"""Execute a Python file in a Docker container or Venv and return the output
Args:
filename (str): The name of the file to execute

Expand All @@ -39,62 +40,98 @@ def execute_python_file(filename: str) -> str:
return result.stdout
else:
return f"Error: {result.stderr}"
if CFG.python_execution == "Docker":
try:
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 = CFG.docker_image
try:
client.images.get(image_name)
logger.warn(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 = client.containers.run(
image_name,
f"python {Path(filename).relative_to(CFG.workspace_path)}",
volumes={
CFG.workspace_path: {
"bind": "/workspace",
"mode": "ro",
}
},
working_dir="/workspace",
stderr=True,
stdout=True,
detach=True,
)

container.wait()
logs = container.logs().decode("utf-8")
container.remove()

try:
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"
return logs
except docker.errors.DockerException as e:
logger.warn(
"Could not run the script in a container. If you haven't already, please install Docker https://docs.docker.com/get-docker/"
)
return f"Error: {str(e)}"
except Exception as e:
return f"Error: {str(e)}"

elif CFG.python_execution == "Local":
try:
client.images.get(image_name)
logger.warn(f"Image '{image_name}' found locally")
except ImageNotFound:
logger.info(
f"Image '{image_name}' not found locally, pulling from Docker Hub"
# Set the desired virtual environment directory
venv_dir = CFG.venv_path
if pathlib.Path(venv_dir).exists():
print("Virtual environment already exists.")
else:
# Create the virtual environment
venv.create(venv_dir, with_pip=True)
print("Virtual environment created.")

# Define the path to the Python interpreter within the virtual environment
venv_python = f"{venv_dir}/Scripts/python.exe"

os.chdir(CFG.workspace_path)

filepath = Path(filename)

script_process = subprocess.Popen(
[venv_python, filepath], stdout=subprocess.PIPE, stderr=subprocess.PIPE
)
# 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 = client.containers.run(
image_name,
f"python {Path(filename).relative_to(CFG.workspace_path)}",
volumes={
CFG.workspace_path: {
"bind": "/workspace",
"mode": "ro",
}
},
working_dir="/workspace",
stderr=True,
stdout=True,
detach=True,
)
script_stdout, script_stderr = script_process.communicate()

container.wait()
logs = container.logs().decode("utf-8")
container.remove()
# Wait for the script to complete
script_process.wait()

# print(f"Execution complete. Output: {output}")
# print(f"Logs: {logs}")
script_process.terminate()

return logs
logs = ""

except docker.errors.DockerException as e:
logger.warn(
"Could not run the script in a container. If you haven't already, please install Docker https://docs.docker.com/get-docker/"
)
return f"Error: {str(e)}"
# Capture the script output and append it to the logs
if script_stdout:
logs += "Script Output:\n" + script_stdout.decode("utf-8") + "\n"
if script_stderr:
logs += "Script Error:\n" + script_stderr.decode("utf-8") + "\n"

return logs

except Exception as e:
return f"Error: {str(e)}"
except Exception as e:
return f"Error: {str(e)}"


@command(
Expand Down
5 changes: 5 additions & 0 deletions autogpt/config/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ class Config(metaclass=Singleton):
def __init__(self) -> None:
"""Initialize the Config class"""
self.workspace_path = None
self.venv_path = None
self.file_logger_path = None

self.debug_mode = False
Expand Down Expand Up @@ -159,6 +160,10 @@ def __init__(self) -> None:
else:
self.plugins_denylist = []

self.python_execution = os.getenv("PYTHON_EXECUTION", "Docker")
if self.python_execution == "Docker":
self.docker_image = os.getenv("DOCKER_IMAGE", "python:3-alpine")

def get_azure_deployment_id_for_model(self, model: str) -> str:
"""
Returns the relevant deployment id for the model specified.
Expand Down
1 change: 1 addition & 0 deletions autogpt/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ def run_auto_gpt(
if install_plugin_deps:
install_plugin_dependencies()

cfg.venv_path = Path(__file__).parent / "venv"
# TODO: have this directory live outside the repository (e.g. in a user's
# home directory) and have it come in as a command line argument or part of
# the env file.
Expand Down