feat(blocks): Add code execution block (#8768)

- Resolves #8766 

Creates a block that executes code in an E2B sandbox.

Demo:


https://github.com/user-attachments/assets/460382c4-5bf7-4f96-a539-88ab263777de

---------

Co-authored-by: Reinier van der Leer <github@pwuts.nl>
Co-authored-by: Reinier van der Leer <pwuts@agpt.co>
This commit is contained in:
Abhimanyu Yadav 2024-12-06 06:46:19 +05:30 committed by GitHub
parent 0272d87af3
commit 227806aef9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 232 additions and 1 deletions

View File

@ -0,0 +1,191 @@
from enum import Enum
from typing import Literal
from e2b_code_interpreter import Sandbox
from pydantic import SecretStr
from backend.data.block import Block, BlockCategory, BlockOutput, BlockSchema
from backend.data.model import (
APIKeyCredentials,
CredentialsField,
CredentialsMetaInput,
SchemaField,
)
TEST_CREDENTIALS = APIKeyCredentials(
id="01234567-89ab-cdef-0123-456789abcdef",
provider="e2b",
api_key=SecretStr("mock-e2b-api-key"),
title="Mock E2B API key",
expires_at=None,
)
TEST_CREDENTIALS_INPUT = {
"provider": TEST_CREDENTIALS.provider,
"id": TEST_CREDENTIALS.id,
"type": TEST_CREDENTIALS.type,
"title": TEST_CREDENTIALS.type,
}
class ProgrammingLanguage(Enum):
PYTHON = "python"
JAVASCRIPT = "js"
BASH = "bash"
R = "r"
JAVA = "java"
class CodeExecutionBlock(Block):
# TODO : Add support to upload and download files
# Currently, You can customized the CPU and Memory, only by creating a pre customized sandbox template
class Input(BlockSchema):
credentials: CredentialsMetaInput[Literal["e2b"], Literal["api_key"]] = (
CredentialsField(
provider="e2b",
supported_credential_types={"api_key"},
description="Enter your api key for the E2B Sandbox. You can get it in here - https://e2b.dev/docs",
)
)
# Todo : Option to run commond in background
setup_commands: list[str] = SchemaField(
description=(
"Shell commands to set up the sandbox before running the code. "
"You can use `curl` or `git` to install your desired Debian based "
"package manager. `pip` and `npm` are pre-installed.\n\n"
"These commands are executed with `sh`, in the foreground."
),
placeholder="pip install cowsay",
default=[],
advanced=False,
)
code: str = SchemaField(
description="Code to execute in the sandbox",
placeholder="print('Hello, World!')",
default="",
advanced=False,
)
language: ProgrammingLanguage = SchemaField(
description="Programming language to execute",
default=ProgrammingLanguage.PYTHON,
advanced=False,
)
timeout: int = SchemaField(
description="Execution timeout in seconds", default=300
)
template_id: str = SchemaField(
description=(
"You can use an E2B sandbox template by entering its ID here. "
"Check out the E2B docs for more details: "
"[E2B - Sandbox template](https://e2b.dev/docs/sandbox-template)"
),
default="",
advanced=True,
)
class Output(BlockSchema):
response: str = SchemaField(description="Response from code execution")
stdout_logs: str = SchemaField(
description="Standard output logs from execution"
)
stderr_logs: str = SchemaField(description="Standard error logs from execution")
error: str = SchemaField(description="Error message if execution failed")
def __init__(self):
super().__init__(
id="0b02b072-abe7-11ef-8372-fb5d162dd712",
description="Executes code in an isolated sandbox environment with internet access.",
categories={BlockCategory.DEVELOPER_TOOLS},
input_schema=CodeExecutionBlock.Input,
output_schema=CodeExecutionBlock.Output,
test_credentials=TEST_CREDENTIALS,
test_input={
"credentials": TEST_CREDENTIALS_INPUT,
"code": "print('Hello World')",
"language": ProgrammingLanguage.PYTHON.value,
"setup_commands": [],
"timeout": 300,
"template_id": "",
},
test_output=[
("response", "Hello World"),
("stdout_logs", "Hello World\n"),
],
test_mock={
"execute_code": lambda code, language, setup_commands, timeout, api_key, template_id: (
"Hello World",
"Hello World\n",
"",
),
},
)
def execute_code(
self,
code: str,
language: ProgrammingLanguage,
setup_commands: list[str],
timeout: int,
api_key: str,
template_id: str,
):
try:
sandbox = None
if template_id:
sandbox = Sandbox(
template=template_id, api_key=api_key, timeout=timeout
)
else:
sandbox = Sandbox(api_key=api_key, timeout=timeout)
if not sandbox:
raise Exception("Sandbox not created")
# Running setup commands
for cmd in setup_commands:
sandbox.commands.run(cmd)
# Executing the code
execution = sandbox.run_code(
code,
language=language.value,
on_error=lambda e: sandbox.kill(), # Kill the sandbox if there is an error
)
if execution.error:
raise Exception(execution.error)
response = execution.text
stdout_logs = "".join(execution.logs.stdout)
stderr_logs = "".join(execution.logs.stderr)
return response, stdout_logs, stderr_logs
except Exception as e:
raise e
def run(
self, input_data: Input, *, credentials: APIKeyCredentials, **kwargs
) -> BlockOutput:
try:
response, stdout_logs, stderr_logs = self.execute_code(
input_data.code,
input_data.language,
input_data.setup_commands,
input_data.timeout,
credentials.api_key.get_secret_value(),
input_data.template_id,
)
if response:
yield "response", response
if stdout_logs:
yield "stdout_logs", stdout_logs
if stderr_logs:
yield "stderr_logs", stderr_logs
except Exception as e:
yield "error", str(e)

View File

@ -704,6 +704,42 @@ files = [
{file = "distro-1.9.0.tar.gz", hash = "sha256:2fa77c6fd8940f116ee1d6b94a2f90b13b5ea8d019b98bc8bafdcabcdd9bdbed"},
]
[[package]]
name = "e2b"
version = "1.0.4"
description = "E2B SDK that give agents cloud environments"
optional = false
python-versions = "<4.0,>=3.8"
files = [
{file = "e2b-1.0.4-py3-none-any.whl", hash = "sha256:1a9c765eb1b2cc291c5ebd3f2e268f8fba9471a12f470f4651395b5753730170"},
{file = "e2b-1.0.4.tar.gz", hash = "sha256:5ed3db4f984e52cf3aabb717725493ff060a8374b7c878b31bceeff46a0b5648"},
]
[package.dependencies]
attrs = ">=23.2.0"
httpcore = ">=1.0.5,<2.0.0"
httpx = ">=0.27.0,<0.28.0"
packaging = ">=24.1"
protobuf = ">=3.20.0,<6.0.0"
python-dateutil = ">=2.8.2"
typing-extensions = ">=4.1.0"
[[package]]
name = "e2b-code-interpreter"
version = "1.0.1"
description = "E2B Code Interpreter - Stateful code execution"
optional = false
python-versions = "<4.0,>=3.8"
files = [
{file = "e2b_code_interpreter-1.0.1-py3-none-any.whl", hash = "sha256:e27c40174ba7daac4942388611a73e1ac58300227f0ba6c0555ee54507d4944c"},
{file = "e2b_code_interpreter-1.0.1.tar.gz", hash = "sha256:b0c061e41315d21514affe78f80052be335b687204e669dd7ca852b59eeaaea2"},
]
[package.dependencies]
attrs = ">=21.3.0"
e2b = ">=1.0.0,<2.0.0"
httpx = ">=0.20.0,<0.28.0"
[[package]]
name = "exceptiongroup"
version = "1.2.2"
@ -4028,4 +4064,4 @@ type = ["pytest-mypy"]
[metadata]
lock-version = "2.0"
python-versions = "^3.10"
content-hash = "5bc61641d782791b76f39b18625560a6652b02f3ad788f110e0258293032c34a"
content-hash = "94dbe280c8215cd4ceef47c14b681092cb03964aa081b6cdb978da7bbf818593"

View File

@ -16,6 +16,7 @@ autogpt-libs = { path = "../autogpt_libs", develop = true }
click = "^8.1.7"
croniter = "^5.0.1"
discord-py = "^2.4.0"
e2b-code-interpreter = "^1.0.1"
fastapi = "^0.115.5"
feedparser = "^6.0.11"
flake8 = "^7.0.0"

View File

@ -48,6 +48,7 @@ export const providerIcons: Record<
React.FC<{ className?: string }>
> = {
anthropic: fallbackIcon,
e2b: fallbackIcon,
github: FaGithub,
google: FaGoogle,
groq: fallbackIcon,

View File

@ -24,6 +24,7 @@ const providerDisplayNames: Record<CredentialsProviderName, string> = {
anthropic: "Anthropic",
discord: "Discord",
d_id: "D-ID",
e2b: "E2B",
github: "GitHub",
google: "Google",
google_maps: "Google Maps",

View File

@ -102,6 +102,7 @@ export const PROVIDER_NAMES = {
ANTHROPIC: "anthropic",
D_ID: "d_id",
DISCORD: "discord",
E2B: "e2b",
GITHUB: "github",
GOOGLE: "google",
GOOGLE_MAPS: "google_maps",