tech-envision commited on
Commit
ecc9e42
·
1 Parent(s): c7de39d

Replace python execution tool with terminal

Browse files
Files changed (5) hide show
  1. README.md +3 -4
  2. run.py +1 -1
  3. src/__init__.py +2 -2
  4. src/chat.py +4 -4
  5. src/tools.py +25 -54
README.md CHANGED
@@ -5,9 +5,8 @@ and demonstrates basic tool usage. Chat histories are stored in a local SQLite
5
  database using Peewee. Histories are persisted per user and session so
6
  conversations can be resumed with context. One example tool is included:
7
 
8
- * **execute_python** – Executes Python code in a sandbox with selected built-ins
9
- and allows importing safe modules like ``math``. The result is returned from a
10
- ``result`` variable or captured output.
11
 
12
  The application now injects a system prompt that instructs the model to chain
13
  multiple tools when required. This prompt ensures the assistant can orchestrate
@@ -19,4 +18,4 @@ tool calls in sequence to satisfy the user's request.
19
  python run.py
20
  ```
21
 
22
- The script will ask the model to compute an arithmetic expression and print the answer. Conversations are automatically persisted to `chat.db` and are now associated with a user and session.
 
5
  database using Peewee. Histories are persisted per user and session so
6
  conversations can be resumed with context. One example tool is included:
7
 
8
+ * **execute_terminal** – Executes a shell command in a Linux VM with network
9
+ access. Output from ``stdout`` and ``stderr`` is captured and returned.
 
10
 
11
  The application now injects a system prompt that instructs the model to chain
12
  multiple tools when required. This prompt ensures the assistant can orchestrate
 
18
  python run.py
19
  ```
20
 
21
+ The script will instruct the model to run a simple shell command and print the result. Conversations are automatically persisted to `chat.db` and are now associated with a user and session.
run.py CHANGED
@@ -8,7 +8,7 @@ from src.chat import ChatSession
8
  async def _main() -> None:
9
  async with ChatSession(user="demo_user", session="demo_session") as chat:
10
  answer = await chat.chat(
11
- "Run this Python code: import math\nresult = math.factorial(5)"
12
  )
13
  print("\n>>>", answer)
14
 
 
8
  async def _main() -> None:
9
  async with ChatSession(user="demo_user", session="demo_session") as chat:
10
  answer = await chat.chat(
11
+ "Execute this command: echo Hello from the VM"
12
  )
13
  print("\n>>>", answer)
14
 
src/__init__.py CHANGED
@@ -1,4 +1,4 @@
1
  from .chat import ChatSession
2
- from .tools import execute_python
3
 
4
- __all__ = ["ChatSession", "execute_python"]
 
1
  from .chat import ChatSession
2
+ from .tools import execute_terminal
3
 
4
+ __all__ = ["ChatSession", "execute_terminal"]
src/chat.py CHANGED
@@ -15,7 +15,7 @@ from .config import (
15
  from .db import Conversation, Message as DBMessage, User, _db, init_db
16
  from .log import get_logger
17
  from .schema import Msg
18
- from .tools import execute_python
19
 
20
  _LOG = get_logger(__name__)
21
 
@@ -93,7 +93,7 @@ class ChatSession:
93
  self._model,
94
  messages=messages,
95
  think=think,
96
- tools=[execute_python],
97
  options={"num_ctx": NUM_CTX},
98
  )
99
 
@@ -108,8 +108,8 @@ class ChatSession:
108
  return response
109
 
110
  for call in response.message.tool_calls:
111
- if call.function.name == "execute_python":
112
- result = execute_python(**call.function.arguments)
113
  else:
114
  continue
115
 
 
15
  from .db import Conversation, Message as DBMessage, User, _db, init_db
16
  from .log import get_logger
17
  from .schema import Msg
18
+ from .tools import execute_terminal
19
 
20
  _LOG = get_logger(__name__)
21
 
 
93
  self._model,
94
  messages=messages,
95
  think=think,
96
+ tools=[execute_terminal],
97
  options={"num_ctx": NUM_CTX},
98
  )
99
 
 
108
  return response
109
 
110
  for call in response.message.tool_calls:
111
+ if call.function.name == "execute_terminal":
112
+ result = execute_terminal(**call.function.arguments)
113
  else:
114
  continue
115
 
src/tools.py CHANGED
@@ -1,61 +1,32 @@
1
  from __future__ import annotations
2
 
3
- __all__ = ["execute_python"]
4
 
 
 
5
 
6
- def execute_python(code: str) -> str:
7
- """Execute Python code in a sandbox with a broader set of built-ins.
8
 
9
- The code is executed with restricted but useful built-ins and can import a
10
- small whitelist of standard library modules. Results should be stored in a
11
- variable named ``result`` or printed. The value of ``result`` is returned if
12
- present; otherwise any standard output captured during execution is
13
- returned.
14
- """
15
- import sys
16
- from io import StringIO
17
-
18
- allowed_modules = {"math", "random", "statistics"}
19
-
20
- def _safe_import(name: str, globals=None, locals=None, fromlist=(), level=0):
21
- if name in allowed_modules:
22
- return __import__(name, globals, locals, fromlist, level)
23
- raise ImportError(f"Import of '{name}' is not allowed")
24
-
25
- allowed_builtins = {
26
- "abs": abs,
27
- "min": min,
28
- "max": max,
29
- "sum": sum,
30
- "len": len,
31
- "range": range,
32
- "sorted": sorted,
33
- "enumerate": enumerate,
34
- "map": map,
35
- "filter": filter,
36
- "list": list,
37
- "dict": dict,
38
- "set": set,
39
- "tuple": tuple,
40
- "float": float,
41
- "int": int,
42
- "str": str,
43
- "bool": bool,
44
- "print": print,
45
- "__import__": _safe_import,
46
- }
47
 
48
- safe_globals: dict[str, object] = {"__builtins__": allowed_builtins}
49
- safe_locals: dict[str, object] = {}
50
-
51
- stdout = StringIO()
52
- original_stdout = sys.stdout
53
  try:
54
- sys.stdout = stdout
55
- exec(code, safe_globals, safe_locals)
56
- finally:
57
- sys.stdout = original_stdout
58
-
59
- if "result" in safe_locals:
60
- return str(safe_locals["result"])
61
- return stdout.getvalue().strip()
 
 
 
 
 
 
 
 
 
1
  from __future__ import annotations
2
 
3
+ __all__ = ["execute_terminal"]
4
 
5
+ import subprocess
6
+ from typing import Final
7
 
 
 
8
 
9
+ def execute_terminal(command: str, *, timeout: int = 30) -> str:
10
+ """Execute a shell command inside an isolated Linux VM.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
11
 
12
+ The command is executed with network access enabled. Output from both
13
+ ``stdout`` and ``stderr`` is captured and returned. Commands are killed if
14
+ they exceed ``timeout`` seconds.
15
+ """
 
16
  try:
17
+ completed = subprocess.run(
18
+ command,
19
+ shell=True,
20
+ capture_output=True,
21
+ text=True,
22
+ timeout=timeout,
23
+ )
24
+ except subprocess.TimeoutExpired as exc:
25
+ return f"Command timed out after {timeout}s: {exc.cmd}"
26
+ except Exception as exc: # pragma: no cover - unforeseen errors
27
+ return f"Failed to execute command: {exc}"
28
+
29
+ output = completed.stdout
30
+ if completed.stderr:
31
+ output = f"{output}\n{completed.stderr}" if output else completed.stderr
32
+ return output.strip()