Spaces:
Runtime error
Runtime error
tech-envision
commited on
Commit
·
ecc9e42
1
Parent(s):
c7de39d
Replace python execution tool with terminal
Browse files- README.md +3 -4
- run.py +1 -1
- src/__init__.py +2 -2
- src/chat.py +4 -4
- 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 |
-
* **
|
9 |
-
|
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
|
|
|
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 |
-
"
|
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
|
3 |
|
4 |
-
__all__ = ["ChatSession", "
|
|
|
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
|
19 |
|
20 |
_LOG = get_logger(__name__)
|
21 |
|
@@ -93,7 +93,7 @@ class ChatSession:
|
|
93 |
self._model,
|
94 |
messages=messages,
|
95 |
think=think,
|
96 |
-
tools=[
|
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 == "
|
112 |
-
result =
|
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__ = ["
|
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 |
-
|
10 |
-
|
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 |
-
|
49 |
-
|
50 |
-
|
51 |
-
|
52 |
-
original_stdout = sys.stdout
|
53 |
try:
|
54 |
-
|
55 |
-
|
56 |
-
|
57 |
-
|
58 |
-
|
59 |
-
|
60 |
-
|
61 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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()
|