Skip to content

Commit c18bc90

Browse files
Add E2B code interpreter 🥳
1 parent 7b0b01d commit c18bc90

24 files changed

+400
-243
lines changed

.gitignore

+6-1
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ sdist/
3636
var/
3737
wheels/
3838
share/python-wheels/
39+
node_modules/
3940
*.egg-info/
4041
.installed.cfg
4142
*.egg
@@ -156,4 +157,8 @@ dmypy.json
156157
cython_debug/
157158

158159
# PyCharm
159-
#.idea/
160+
#.idea/
161+
162+
# Archive
163+
archive/
164+
savedir/

Dockerfile

+1-3
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,12 @@
11
# Base Python image
2-
FROM python:3.9-slim
2+
FROM python:3.12-slim
33

44
# Set working directory
55
WORKDIR /app
66

77
# Install build dependencies
88
RUN apt-get update && apt-get install -y \
99
build-essential \
10-
gcc \
11-
g++ \
1210
zlib1g-dev \
1311
libjpeg-dev \
1412
libpng-dev \

e2b.Dockerfile

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# You can use most Debian-based base images
2+
FROM e2bdev/code-interpreter:latest
3+
4+
# Install dependencies and customize sandbox
5+
RUN pip install git+https://github.com/huggingface/agents.git

e2b.toml

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# This is a config for E2B sandbox template.
2+
# You can use template ID (qywp2ctmu2q7jzprcf4j) to create a sandbox:
3+
4+
# Python SDK
5+
# from e2b import Sandbox, AsyncSandbox
6+
# sandbox = Sandbox("qywp2ctmu2q7jzprcf4j") # Sync sandbox
7+
# sandbox = await AsyncSandbox.create("qywp2ctmu2q7jzprcf4j") # Async sandbox
8+
9+
# JS SDK
10+
# import { Sandbox } from 'e2b'
11+
# const sandbox = await Sandbox.create('qywp2ctmu2q7jzprcf4j')
12+
13+
team_id = "f8776d3a-df2f-4a1d-af48-68c2e13b3b87"
14+
start_cmd = "/root/.jupyter/start-up.sh"
15+
dockerfile = "e2b.Dockerfile"
16+
template_id = "qywp2ctmu2q7jzprcf4j"

examples/docker_example.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
from agents.tools.search import DuckDuckGoSearchTool
1+
from agents.default_tools.search import DuckDuckGoSearchTool
22
from agents.docker_alternative import DockerPythonInterpreter
33

44

5-
from agents.tool import Tool
5+
from agents.tools import Tool
66

77
class DummyTool(Tool):
88
name = "echo"

examples/dummytool.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from agents.tool import Tool
1+
from agents.tools import Tool
22

33

44
class DummyTool(Tool):

examples/e2b_example.py

+44
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
from agents import Tool, CodeAgent
2+
from agents.default_tools.search import VisitWebpageTool
3+
from dotenv import load_dotenv
4+
load_dotenv()
5+
6+
LAUNCH_GRADIO = False
7+
8+
class GetCatImageTool(Tool):
9+
name="get_cat_image"
10+
description = "Get a cat image"
11+
inputs = {}
12+
output_type = "image"
13+
14+
def __init__(self):
15+
super().__init__()
16+
self.url = "https://em-content.zobj.net/source/twitter/53/robot-face_1f916.png"
17+
18+
def forward(self):
19+
from PIL import Image
20+
import requests
21+
from io import BytesIO
22+
23+
response = requests.get(self.url)
24+
25+
return Image.open(BytesIO(response.content))
26+
27+
get_cat_image = GetCatImageTool()
28+
29+
30+
agent = CodeAgent(
31+
tools = [get_cat_image, VisitWebpageTool()],
32+
additional_authorized_imports=["Pillow", "requests", "markdownify"], # "duckduckgo-search",
33+
use_e2b_executor=False
34+
)
35+
36+
if LAUNCH_GRADIO:
37+
from agents.gradio_ui import GradioUI
38+
39+
GradioUI(agent).launch()
40+
else:
41+
agent.run(
42+
"Return me an image of Lincoln's preferred pet",
43+
additional_context="Here is a webpage about US presidents and pets: https://www.9lives.com/blog/a-history-of-cats-in-the-white-house/"
44+
)

src/agents/__init__.py

+11-5
Original file line numberDiff line numberDiff line change
@@ -24,22 +24,28 @@
2424

2525
if TYPE_CHECKING:
2626
from .agents import *
27-
from .default_tools import *
27+
from .default_tools.base import *
28+
from .default_tools.search import *
2829
from .gradio_ui import *
2930
from .llm_engines import *
3031
from .local_python_executor import *
3132
from .monitoring import *
3233
from .prompts import *
33-
from .tools.search import *
34-
from .tool import *
34+
from .tools import *
3535
from .types import *
3636
from .utils import *
37+
from .default_tools.search import *
3738

3839

3940
else:
4041
import sys
41-
4242
_file = globals()["__file__"]
43+
import_structure = define_import_structure(_file)
44+
import_structure[""]= {"__version__": __version__}
4345
sys.modules[__name__] = _LazyModule(
44-
__name__, _file, define_import_structure(_file), module_spec=__spec__
46+
__name__,
47+
_file,
48+
import_structure,
49+
module_spec=__spec__,
50+
extra_objects={"__version__": __version__}
4551
)

src/agents/agents.py

+21-44
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727

2828
from .utils import console, parse_code_blob, parse_json_tool_call, truncate_content
2929
from .types import AgentAudio, AgentImage
30-
from .default_tools import BASE_PYTHON_TOOLS, FinalAnswerTool
30+
from .default_tools.base import FinalAnswerTool
3131
from .llm_engines import HfApiEngine, MessageRole
3232
from .monitoring import Monitor
3333
from .prompts import (
@@ -42,8 +42,9 @@
4242
SYSTEM_PROMPT_PLAN_UPDATE,
4343
SYSTEM_PROMPT_PLAN,
4444
)
45-
from .local_python_executor import LIST_SAFE_MODULES, evaluate_python_code
46-
from .tool import (
45+
from .local_python_executor import BASE_BUILTIN_MODULES, LocalPythonExecutor
46+
from .e2b_executor import E2BExecutor
47+
from .tools import (
4748
DEFAULT_TOOL_DESCRIPTION_TEMPLATE,
4849
Tool,
4950
get_tool_description_with_args,
@@ -169,17 +170,6 @@ def format_prompt_with_managed_agents_descriptions(
169170
else:
170171
return prompt_template.replace(agent_descriptions_placeholder, "")
171172

172-
173-
def format_prompt_with_imports(
174-
prompt_template: str, authorized_imports: List[str]
175-
) -> str:
176-
if "<<authorized_imports>>" not in prompt_template:
177-
raise AgentError(
178-
"Tag '<<authorized_imports>>' should be provided in the prompt."
179-
)
180-
return prompt_template.replace("<<authorized_imports>>", str(authorized_imports))
181-
182-
183173
class BaseAgent:
184174
def __init__(
185175
self,
@@ -264,11 +254,6 @@ def initialize_system_prompt(self):
264254
self.system_prompt = format_prompt_with_managed_agents_descriptions(
265255
self.system_prompt, self.managed_agents
266256
)
267-
if hasattr(self, "authorized_imports"):
268-
self.system_prompt = format_prompt_with_imports(
269-
self.system_prompt,
270-
list(set(LIST_SAFE_MODULES) | set(getattr(self, "authorized_imports"))),
271-
)
272257

273258
return self.system_prompt
274259

@@ -439,9 +424,7 @@ def execute_tool_call(self, tool_name: str, arguments: Dict[str, str]) -> Any:
439424
tool_name (`str`): Name of the Tool to execute (should be one from self.toolbox).
440425
arguments (Dict[str, str]): Arguments passed to the Tool.
441426
"""
442-
available_tools = self.toolbox.tools
443-
if self.managed_agents is not None:
444-
available_tools = {**available_tools, **self.managed_agents}
427+
available_tools = {**self.toolbox.tools, **self.managed_agents}
445428
if tool_name not in available_tools:
446429
error_msg = f"Error: unknown tool {tool_name}, should be instead one of {list(available_tools.keys())}."
447430
console.print(f"[bold red]{error_msg}")
@@ -674,8 +657,6 @@ def planning_step(self, task, is_first_step: bool, iteration: int):
674657
),
675658
managed_agents_descriptions=(
676659
show_agents_descriptions(self.managed_agents)
677-
if self.managed_agents is not None
678-
else ""
679660
),
680661
answer_facts=answer_facts,
681662
),
@@ -729,8 +710,6 @@ def planning_step(self, task, is_first_step: bool, iteration: int):
729710
),
730711
managed_agents_descriptions=(
731712
show_agents_descriptions(self.managed_agents)
732-
if self.managed_agents is not None
733-
else ""
734713
),
735714
facts_update=facts_update,
736715
remaining_steps=(self.max_iterations - iteration),
@@ -891,6 +870,7 @@ def __init__(
891870
grammar: Optional[Dict[str, str]] = None,
892871
additional_authorized_imports: Optional[List[str]] = None,
893872
planning_interval: Optional[int] = None,
873+
use_e2b_executor: bool = False,
894874
**kwargs,
895875
):
896876
if llm_engine is None:
@@ -909,17 +889,24 @@ def __init__(
909889
**kwargs,
910890
)
911891

912-
self.python_evaluator = evaluate_python_code
913892
self.additional_authorized_imports = (
914893
additional_authorized_imports if additional_authorized_imports else []
915894
)
895+
all_tools = {**self.toolbox.tools, **self.managed_agents}
896+
if use_e2b_executor:
897+
self.python_executor = E2BExecutor(self.additional_authorized_imports, list(all_tools.values()))
898+
else:
899+
self.python_executor = LocalPythonExecutor(self.additional_authorized_imports, all_tools)
916900
self.authorized_imports = list(
917-
set(LIST_SAFE_MODULES) | set(self.additional_authorized_imports)
901+
set(BASE_BUILTIN_MODULES) | set(self.additional_authorized_imports)
918902
)
903+
if "{{authorized_imports}}" not in self.system_prompt:
904+
raise AgentError(
905+
"Tag '{{authorized_imports}}' should be provided in the prompt."
906+
)
919907
self.system_prompt = self.system_prompt.replace(
920908
"{{authorized_imports}}", str(self.authorized_imports)
921909
)
922-
self.custom_tools = {}
923910

924911
def step(self, log_entry: ActionStep) -> Union[None, Any]:
925912
"""
@@ -991,22 +978,12 @@ def step(self, log_entry: ActionStep) -> Union[None, Any]:
991978
)
992979

993980
try:
994-
static_tools = {
995-
**BASE_PYTHON_TOOLS.copy(),
996-
**self.toolbox.tools,
997-
}
998-
if self.managed_agents is not None:
999-
static_tools = {**static_tools, **self.managed_agents}
1000-
output = self.python_evaluator(
981+
output, execution_logs = self.python_executor(
1001982
code_action,
1002-
static_tools=static_tools,
1003-
custom_tools=self.custom_tools,
1004-
state=self.state,
1005-
authorized_imports=self.authorized_imports,
1006983
)
1007-
if len(self.state["print_outputs"]) > 0:
1008-
console.print(Group(Text("Print outputs:", style="bold"), Text(self.state["print_outputs"])))
1009-
observation = "Print outputs:\n" + self.state["print_outputs"]
984+
if len(execution_logs) > 0:
985+
console.print(Group(Text("Execution logs:", style="bold"), Text(execution_logs)))
986+
observation = "Execution logs:\n" + execution_logs
1010987
if output is not None:
1011988
truncated_output = truncate_content(
1012989
str(output)
@@ -1026,7 +1003,7 @@ def step(self, log_entry: ActionStep) -> Union[None, Any]:
10261003
console.print(Group(Text("Final answer:", style="bold"), Text(str(output), style="bold green")))
10271004
log_entry.action_output = output
10281005
return output
1029-
return None
1006+
10301007

10311008

10321009
class ManagedAgent:
File renamed without changes.

src/agents/default_tools.py src/agents/default_tools/base.py

+4-65
Original file line numberDiff line numberDiff line change
@@ -15,75 +15,14 @@
1515
# See the License for the specific language governing permissions and
1616
# limitations under the License.
1717
import json
18-
import math
1918
from dataclasses import dataclass
20-
from math import sqrt
2119
from typing import Dict
2220

2321
from huggingface_hub import hf_hub_download, list_spaces
2422

2523
from transformers.utils import is_offline_mode
26-
from .local_python_executor import LIST_SAFE_MODULES, evaluate_python_code
27-
from .tool import TOOL_CONFIG_FILE, Tool
28-
29-
30-
def custom_print(*args):
31-
return None
32-
33-
34-
BASE_PYTHON_TOOLS = {
35-
"print": custom_print,
36-
"isinstance": isinstance,
37-
"range": range,
38-
"float": float,
39-
"int": int,
40-
"bool": bool,
41-
"str": str,
42-
"set": set,
43-
"list": list,
44-
"dict": dict,
45-
"tuple": tuple,
46-
"round": round,
47-
"ceil": math.ceil,
48-
"floor": math.floor,
49-
"log": math.log,
50-
"exp": math.exp,
51-
"sin": math.sin,
52-
"cos": math.cos,
53-
"tan": math.tan,
54-
"asin": math.asin,
55-
"acos": math.acos,
56-
"atan": math.atan,
57-
"atan2": math.atan2,
58-
"degrees": math.degrees,
59-
"radians": math.radians,
60-
"pow": math.pow,
61-
"sqrt": sqrt,
62-
"len": len,
63-
"sum": sum,
64-
"max": max,
65-
"min": min,
66-
"abs": abs,
67-
"enumerate": enumerate,
68-
"zip": zip,
69-
"reversed": reversed,
70-
"sorted": sorted,
71-
"all": all,
72-
"any": any,
73-
"map": map,
74-
"filter": filter,
75-
"ord": ord,
76-
"chr": chr,
77-
"next": next,
78-
"iter": iter,
79-
"divmod": divmod,
80-
"callable": callable,
81-
"getattr": getattr,
82-
"hasattr": hasattr,
83-
"setattr": setattr,
84-
"issubclass": issubclass,
85-
"type": type,
86-
}
24+
from ..local_python_executor import BASE_BUILTIN_MODULES, BASE_PYTHON_TOOLS, evaluate_python_code
25+
from ..tools import TOOL_CONFIG_FILE, Tool
8726

8827

8928
@dataclass
@@ -136,10 +75,10 @@ class PythonInterpreterTool(Tool):
13675

13776
def __init__(self, *args, authorized_imports=None, **kwargs):
13877
if authorized_imports is None:
139-
self.authorized_imports = list(set(LIST_SAFE_MODULES))
78+
self.authorized_imports = list(set(BASE_BUILTIN_MODULES))
14079
else:
14180
self.authorized_imports = list(
142-
set(LIST_SAFE_MODULES) | set(authorized_imports)
81+
set(BASE_BUILTIN_MODULES) | set(authorized_imports)
14382
)
14483
self.inputs = {
14584
"code": {

0 commit comments

Comments
 (0)