Skip to content

Commit f4f9e6c

Browse files
authored
Accept path for work_dir in LocalCommandLineCodeExecutor (#1909)
* Accept path in LocalCommandLineCodeExecutor * formatting * fixes
1 parent e9219fe commit f4f9e6c

File tree

2 files changed

+70
-52
lines changed

2 files changed

+70
-52
lines changed

autogen/coding/local_commandline_code_executor.py

+68-50
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
import os
2+
from pathlib import Path
23
import re
34
import uuid
45
import warnings
5-
from typing import Any, ClassVar, List, Optional
6-
from pydantic import BaseModel, Field, field_validator
6+
from typing import ClassVar, List, Optional, Union
7+
from pydantic import Field
78

89
from ..agentchat.agent import LLMAgent
910
from ..code_utils import execute_code
10-
from .base import CodeBlock, CodeExtractor, CodeResult
11+
from .base import CodeBlock, CodeExecutor, CodeExtractor, CodeResult
1112
from .markdown_code_extractor import MarkdownCodeExtractor
1213

1314
__all__ = (
@@ -25,33 +26,7 @@ class CommandLineCodeResult(CodeResult):
2526
)
2627

2728

28-
class LocalCommandLineCodeExecutor(BaseModel):
29-
"""(Experimental) A code executor class that executes code through a local command line
30-
environment.
31-
32-
**This will execute LLM generated code on the local machine.**
33-
34-
Each code block is saved as a file and executed in a separate process in
35-
the working directory, and a unique file is generated and saved in the
36-
working directory for each code block.
37-
The code blocks are executed in the order they are received.
38-
Command line code is sanitized using regular expression match against a list of dangerous commands in order to prevent self-destructive
39-
commands from being executed which may potentially affect the users environment.
40-
Currently the only supported languages is Python and shell scripts.
41-
For Python code, use the language "python" for the code block.
42-
For shell scripts, use the language "bash", "shell", or "sh" for the code
43-
block.
44-
45-
Args:
46-
timeout (int): The timeout for code execution. Default is 60.
47-
work_dir (str): The working directory for the code execution. If None,
48-
a default working directory will be used. The default working
49-
directory is the current directory ".".
50-
system_message_update (str): The system message update for agent that
51-
produces code to run on this executor.
52-
Default is `LocalCommandLineCodeExecutor.DEFAULT_SYSTEM_MESSAGE_UPDATE`.
53-
"""
54-
29+
class LocalCommandLineCodeExecutor(CodeExecutor):
5530
DEFAULT_SYSTEM_MESSAGE_UPDATE: ClassVar[
5631
str
5732
] = """
@@ -64,12 +39,51 @@ class LocalCommandLineCodeExecutor(BaseModel):
6439
If you want the user to save the code in a file before executing it, put # filename: <filename> inside the code block as the first line. Don't include multiple code blocks in one response. Do not ask users to copy and paste the result. Instead, use 'print' function for the output when relevant. Check the execution result returned by the user.
6540
"""
6641

67-
timeout: int = Field(default=60, ge=1, description="The timeout for code execution.")
68-
work_dir: str = Field(default=".", description="The working directory for the code execution.")
69-
system_message_update: str = Field(
70-
default=DEFAULT_SYSTEM_MESSAGE_UPDATE,
71-
description="The system message update for agent that produces code to run on this executor.",
72-
)
42+
def __init__(
43+
self,
44+
timeout: int = 60,
45+
work_dir: Union[Path, str] = Path("."),
46+
system_message_update: str = DEFAULT_SYSTEM_MESSAGE_UPDATE,
47+
):
48+
"""(Experimental) A code executor class that executes code through a local command line
49+
environment.
50+
51+
**This will execute LLM generated code on the local machine.**
52+
53+
Each code block is saved as a file and executed in a separate process in
54+
the working directory, and a unique file is generated and saved in the
55+
working directory for each code block.
56+
The code blocks are executed in the order they are received.
57+
Command line code is sanitized using regular expression match against a list of dangerous commands in order to prevent self-destructive
58+
commands from being executed which may potentially affect the users environment.
59+
Currently the only supported languages is Python and shell scripts.
60+
For Python code, use the language "python" for the code block.
61+
For shell scripts, use the language "bash", "shell", or "sh" for the code
62+
block.
63+
64+
Args:
65+
timeout (int): The timeout for code execution. Default is 60.
66+
work_dir (str): The working directory for the code execution. If None,
67+
a default working directory will be used. The default working
68+
directory is the current directory ".".
69+
system_message_update (str): The system message update for agent that
70+
produces code to run on this executor.
71+
Default is `LocalCommandLineCodeExecutor.DEFAULT_SYSTEM_MESSAGE_UPDATE`.
72+
73+
"""
74+
75+
if timeout < 1:
76+
raise ValueError("Timeout must be greater than or equal to 1.")
77+
78+
if isinstance(work_dir, str):
79+
work_dir = Path(work_dir)
80+
81+
if not work_dir.exists():
82+
raise ValueError(f"Working directory {work_dir} does not exist.")
83+
84+
self._timeout = timeout
85+
self._work_dir: Path = work_dir
86+
self._system_message_update = system_message_update
7387

7488
class UserCapability:
7589
"""An AgentCapability class that gives agent ability use a command line
@@ -84,18 +98,21 @@ def add_to_agent(self, agent: LLMAgent) -> None:
8498
message."""
8599
agent.update_system_message(agent.system_message + self.system_message_update)
86100

87-
@field_validator("work_dir")
88-
@classmethod
89-
def _check_work_dir(cls, v: str) -> str:
90-
if os.path.exists(v):
91-
return v
92-
raise ValueError(f"Working directory {v} does not exist.")
93-
94101
@property
95102
def user_capability(self) -> "LocalCommandLineCodeExecutor.UserCapability":
96103
"""Export a user capability for this executor that can be added to
97104
an agent that produces code to be executed by this executor."""
98-
return LocalCommandLineCodeExecutor.UserCapability(self.system_message_update)
105+
return LocalCommandLineCodeExecutor.UserCapability(self._system_message_update)
106+
107+
@property
108+
def timeout(self) -> int:
109+
"""(Experimental) The timeout for code execution."""
110+
return self._timeout
111+
112+
@property
113+
def work_dir(self) -> Path:
114+
"""(Experimental) The working directory for the code execution."""
115+
return self._work_dir
99116

100117
@property
101118
def code_extractor(self) -> CodeExtractor:
@@ -133,7 +150,7 @@ def execute_code_blocks(self, code_blocks: List[CodeBlock]) -> CommandLineCodeRe
133150
Returns:
134151
CommandLineCodeResult: The result of the code execution."""
135152
logs_all = ""
136-
for i, code_block in enumerate(code_blocks):
153+
for code_block in code_blocks:
137154
lang, code = code_block.language, code_block.code
138155

139156
LocalCommandLineCodeExecutor.sanitize_command(lang, code)
@@ -144,8 +161,8 @@ def execute_code_blocks(self, code_blocks: List[CodeBlock]) -> CommandLineCodeRe
144161
exitcode, logs, _ = execute_code(
145162
code=code,
146163
lang=lang,
147-
timeout=self.timeout,
148-
work_dir=self.work_dir,
164+
timeout=self._timeout,
165+
work_dir=str(self._work_dir),
149166
filename=filename,
150167
use_docker=False,
151168
)
@@ -154,8 +171,8 @@ def execute_code_blocks(self, code_blocks: List[CodeBlock]) -> CommandLineCodeRe
154171
exitcode, logs, _ = execute_code(
155172
code=code,
156173
lang="python",
157-
timeout=self.timeout,
158-
work_dir=self.work_dir,
174+
timeout=self._timeout,
175+
work_dir=str(self._work_dir),
159176
filename=filename,
160177
use_docker=False,
161178
)
@@ -165,7 +182,8 @@ def execute_code_blocks(self, code_blocks: List[CodeBlock]) -> CommandLineCodeRe
165182
logs_all += "\n" + logs
166183
if exitcode != 0:
167184
break
168-
code_filename = os.path.join(self.work_dir, filename) if filename is not None else None
185+
186+
code_filename = str(self._work_dir / filename) if filename is not None else None
169187
return CommandLineCodeResult(exit_code=exitcode, output=logs_all, code_file=code_filename)
170188

171189
def restart(self) -> None:

website/docs/topics/code-execution/cli-code-executor.ipynb

+2-2
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@
9595
"work_dir = Path(\"coding\")\n",
9696
"work_dir.mkdir(exist_ok=True)\n",
9797
"\n",
98-
"executor = LocalCommandLineCodeExecutor(work_dir=str(work_dir))\n",
98+
"executor = LocalCommandLineCodeExecutor(work_dir=work_dir)\n",
9999
"print(\n",
100100
" executor.execute_code_blocks(\n",
101101
" code_blocks=[\n",
@@ -354,7 +354,7 @@
354354
"name": "python",
355355
"nbconvert_exporter": "python",
356356
"pygments_lexer": "ipython3",
357-
"version": "3.11.5"
357+
"version": "3.11.7"
358358
}
359359
},
360360
"nbformat": 4,

0 commit comments

Comments
 (0)