Skip to content

Commit bf4b659

Browse files
JINO-ROHITSaedbhatifengju0213
authored
feat: add user dep packages in terminal toolkit (#3421)
Co-authored-by: Saed Bhati <[email protected]> Co-authored-by: Tao Sun <[email protected]>
1 parent 33427e1 commit bf4b659

File tree

2 files changed

+117
-0
lines changed

2 files changed

+117
-0
lines changed

camel/toolkits/terminal_toolkit/terminal_toolkit.py

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,8 @@ class TerminalToolkit(BaseToolkit):
8484
when safe_mode is True. If None, uses default safety rules.
8585
clone_current_env (bool): Whether to clone the current Python
8686
environment for local execution. Defaults to False.
87+
install_dependencies (List): A list of user specified libraries
88+
to install.
8789
"""
8890

8991
def __init__(
@@ -96,6 +98,7 @@ def __init__(
9698
safe_mode: bool = True,
9799
allowed_commands: Optional[List[str]] = None,
98100
clone_current_env: bool = False,
101+
install_dependencies: Optional[List[str]] = None,
99102
):
100103
self.use_docker_backend = use_docker_backend
101104
self.timeout = timeout
@@ -145,6 +148,7 @@ def __init__(
145148
self.cloned_env_path: Optional[str] = None
146149
self.initial_env_path: Optional[str] = None
147150
self.python_executable = sys.executable
151+
self.install_dependencies = install_dependencies or []
148152

149153
self.log_dir = os.path.abspath(
150154
session_logs_dir or os.path.join(self.working_dir, "terminal_logs")
@@ -228,6 +232,10 @@ def __init__(
228232
"- container is already isolated"
229233
)
230234

235+
# Install dependencies
236+
if self.install_dependencies:
237+
self._install_dependencies()
238+
231239
def _setup_cloned_environment(self):
232240
r"""Set up a cloned Python environment."""
233241
self.cloned_env_path = os.path.join(self.working_dir, ".venv")
@@ -255,6 +263,80 @@ def update_callback(msg: str):
255263
"using system Python"
256264
)
257265

266+
def _install_dependencies(self):
267+
r"""Install user specified dependencies in the current environment."""
268+
if not self.install_dependencies:
269+
return
270+
271+
logger.info("Installing dependencies...")
272+
273+
if self.use_docker_backend:
274+
pkg_str = " ".join(
275+
shlex.quote(p) for p in self.install_dependencies
276+
)
277+
install_cmd = f'sh -lc "pip install {pkg_str}"'
278+
279+
try:
280+
exec_id = self.docker_api_client.exec_create(
281+
self.container.id, install_cmd
282+
)["Id"]
283+
log = self.docker_api_client.exec_start(exec_id)
284+
logger.info(f"Package installation output:\n{log}")
285+
286+
# Check exit code to ensure installation succeeded
287+
exec_info = self.docker_api_client.exec_inspect(exec_id)
288+
if exec_info['ExitCode'] != 0:
289+
error_msg = (
290+
f"Failed to install dependencies in Docker: "
291+
f"{log.decode('utf-8', errors='ignore')}"
292+
)
293+
logger.error(error_msg)
294+
raise RuntimeError(error_msg)
295+
296+
logger.info(
297+
"Successfully installed all dependencies in Docker."
298+
)
299+
except Exception as e:
300+
if not isinstance(e, RuntimeError):
301+
logger.error(f"Docker dependency installation error: {e}")
302+
raise RuntimeError(
303+
f"Docker dependency installation error: {e}"
304+
) from e
305+
raise
306+
307+
else:
308+
pip_cmd = [
309+
self.python_executable,
310+
"-m",
311+
"pip",
312+
"install",
313+
"--upgrade",
314+
*self.install_dependencies,
315+
]
316+
317+
try:
318+
subprocess.run(
319+
pip_cmd,
320+
check=True,
321+
cwd=self.working_dir,
322+
capture_output=True,
323+
text=True,
324+
timeout=300, # 5 minutes timeout for installation
325+
)
326+
logger.info("Successfully installed all dependencies.")
327+
except subprocess.CalledProcessError as e:
328+
logger.error(f"Failed to install dependencies: {e.stderr}")
329+
raise RuntimeError(
330+
f"Failed to install dependencies: {e.stderr}"
331+
) from e
332+
except subprocess.TimeoutExpired:
333+
logger.error(
334+
"Dependency installation timed out after 5 minutes"
335+
)
336+
raise RuntimeError(
337+
"Dependency installation timed out after 5 minutes"
338+
)
339+
258340
def _setup_initial_environment(self):
259341
r"""Set up an initial environment with Python 3.10."""
260342
self.initial_env_path = os.path.join(self.working_dir, ".initial_env")

examples/toolkits/terminal_toolkit.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -281,6 +281,41 @@
281281
===============================================================================
282282
"""
283283

284+
tools = TerminalToolkit(
285+
working_directory=workspace_dir, install_dependencies=['numpy']
286+
).get_tools()
287+
288+
model_config_dict = ChatGPTConfig(
289+
temperature=0.0,
290+
).as_dict()
291+
292+
model = ModelFactory.create(
293+
model_platform=ModelPlatformType.DEFAULT,
294+
model_type=ModelType.DEFAULT,
295+
model_config_dict=model_config_dict,
296+
)
297+
camel_agent = ChatAgent(
298+
system_message=sys_msg,
299+
model=model,
300+
tools=tools,
301+
)
302+
camel_agent.reset()
303+
304+
usr_msg = "check my numpy version"
305+
306+
# Get response information
307+
response = camel_agent.step(usr_msg)
308+
print(str(response.info['tool_calls'])[:1000])
309+
310+
"""
311+
===============================================================================
312+
[ToolCallingRecord(tool_name='shell_exec', args={'id': 'check_numpy_version_1',
313+
'command': 'python3 -c "import numpy; print(numpy.__version__)"',
314+
'block': True}, result='2.2.6', tool_call_id='call_UuL6YGIMv7I4GSOjA8es65aW',
315+
images=None)]
316+
===============================================================================
317+
"""
318+
284319
# ------Docker backend------
285320
tools = TerminalToolkit(
286321
use_docker_backend=True,

0 commit comments

Comments
 (0)