Spaces:
Runtime error
Runtime error
tech-envision
commited on
Commit
·
7bd7366
1
Parent(s):
4f519ea
Allow limited imports in execute_python
Browse files- README.md +9 -1
- run.py +3 -1
- src/__init__.py +2 -2
- src/chat.py +24 -19
- src/tools.py +59 -1
README.md
CHANGED
@@ -1,6 +1,14 @@
|
|
1 |
# llm-backend
|
2 |
|
3 |
-
This project provides a simple async interface to interact with an Ollama model
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
4 |
|
5 |
## Usage
|
6 |
|
|
|
1 |
# llm-backend
|
2 |
|
3 |
+
This project provides a simple async interface to interact with an Ollama model
|
4 |
+
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. Two example tools are included:
|
7 |
+
|
8 |
+
* **add_two_numbers** – Adds two integers.
|
9 |
+
* **execute_python** – Executes Python code in a sandbox with selected built-ins
|
10 |
+
and allows importing safe modules like ``math``. The result is returned from a
|
11 |
+
``result`` variable or captured output.
|
12 |
|
13 |
## Usage
|
14 |
|
run.py
CHANGED
@@ -7,7 +7,9 @@ from src.chat import ChatSession
|
|
7 |
|
8 |
async def _main() -> None:
|
9 |
async with ChatSession(user="demo_user", session="demo_session") as chat:
|
10 |
-
answer = await chat.chat(
|
|
|
|
|
11 |
print("\n>>>", answer)
|
12 |
|
13 |
|
|
|
7 |
|
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 |
|
15 |
|
src/__init__.py
CHANGED
@@ -1,4 +1,4 @@
|
|
1 |
from .chat import ChatSession
|
2 |
-
from .tools import add_two_numbers
|
3 |
|
4 |
-
__all__ = ["ChatSession", "add_two_numbers"]
|
|
|
1 |
from .chat import ChatSession
|
2 |
+
from .tools import add_two_numbers, execute_python
|
3 |
|
4 |
+
__all__ = ["ChatSession", "add_two_numbers", "execute_python"]
|
src/chat.py
CHANGED
@@ -9,7 +9,7 @@ from .config import MAX_TOOL_CALL_DEPTH, MODEL_NAME, OLLAMA_HOST
|
|
9 |
from .db import Conversation, Message as DBMessage, User, _db, init_db
|
10 |
from .log import get_logger
|
11 |
from .schema import Msg
|
12 |
-
from .tools import add_two_numbers
|
13 |
|
14 |
_LOG = get_logger(__name__)
|
15 |
|
@@ -77,7 +77,7 @@ class ChatSession:
|
|
77 |
self._model,
|
78 |
messages=messages,
|
79 |
think=think,
|
80 |
-
tools=[add_two_numbers],
|
81 |
)
|
82 |
|
83 |
async def _handle_tool_calls(
|
@@ -93,23 +93,28 @@ class ChatSession:
|
|
93 |
for call in response.message.tool_calls:
|
94 |
if call.function.name == "add_two_numbers":
|
95 |
result = add_two_numbers(**call.function.arguments)
|
96 |
-
|
97 |
-
|
98 |
-
|
99 |
-
|
100 |
-
|
101 |
-
|
102 |
-
|
103 |
-
|
104 |
-
|
105 |
-
|
106 |
-
|
107 |
-
|
108 |
-
|
109 |
-
|
110 |
-
|
111 |
-
|
112 |
-
|
|
|
|
|
|
|
|
|
|
|
113 |
|
114 |
return response
|
115 |
|
|
|
9 |
from .db import Conversation, Message as DBMessage, User, _db, init_db
|
10 |
from .log import get_logger
|
11 |
from .schema import Msg
|
12 |
+
from .tools import add_two_numbers, execute_python
|
13 |
|
14 |
_LOG = get_logger(__name__)
|
15 |
|
|
|
77 |
self._model,
|
78 |
messages=messages,
|
79 |
think=think,
|
80 |
+
tools=[add_two_numbers, execute_python],
|
81 |
)
|
82 |
|
83 |
async def _handle_tool_calls(
|
|
|
93 |
for call in response.message.tool_calls:
|
94 |
if call.function.name == "add_two_numbers":
|
95 |
result = add_two_numbers(**call.function.arguments)
|
96 |
+
elif call.function.name == "execute_python":
|
97 |
+
result = execute_python(**call.function.arguments)
|
98 |
+
else:
|
99 |
+
continue
|
100 |
+
|
101 |
+
messages.append(
|
102 |
+
{
|
103 |
+
"role": "tool",
|
104 |
+
"name": call.function.name,
|
105 |
+
"content": str(result),
|
106 |
+
}
|
107 |
+
)
|
108 |
+
DBMessage.create(
|
109 |
+
conversation=conversation,
|
110 |
+
role="tool",
|
111 |
+
content=str(result),
|
112 |
+
)
|
113 |
+
nxt = await self.ask(messages, think=True)
|
114 |
+
self._store_assistant_message(conversation, nxt.message)
|
115 |
+
return await self._handle_tool_calls(
|
116 |
+
messages, nxt, conversation, depth + 1
|
117 |
+
)
|
118 |
|
119 |
return response
|
120 |
|
src/tools.py
CHANGED
@@ -1,6 +1,6 @@
|
|
1 |
from __future__ import annotations
|
2 |
|
3 |
-
__all__ = ["add_two_numbers"]
|
4 |
|
5 |
|
6 |
def add_two_numbers(a: int, b: int) -> int: # noqa: D401
|
@@ -14,3 +14,61 @@ def add_two_numbers(a: int, b: int) -> int: # noqa: D401
|
|
14 |
int: The sum of the two numbers.
|
15 |
"""
|
16 |
return a + b
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
from __future__ import annotations
|
2 |
|
3 |
+
__all__ = ["add_two_numbers", "execute_python"]
|
4 |
|
5 |
|
6 |
def add_two_numbers(a: int, b: int) -> int: # noqa: D401
|
|
|
14 |
int: The sum of the two numbers.
|
15 |
"""
|
16 |
return a + b
|
17 |
+
|
18 |
+
|
19 |
+
def execute_python(code: str) -> str:
|
20 |
+
"""Execute Python code in a sandbox with a broader set of built-ins.
|
21 |
+
|
22 |
+
The code is executed with restricted but useful built-ins and can import a
|
23 |
+
small whitelist of standard library modules. Results should be stored in a
|
24 |
+
variable named ``result`` or printed. The value of ``result`` is returned if
|
25 |
+
present; otherwise any standard output captured during execution is
|
26 |
+
returned.
|
27 |
+
"""
|
28 |
+
import sys
|
29 |
+
from io import StringIO
|
30 |
+
|
31 |
+
allowed_modules = {"math", "random", "statistics"}
|
32 |
+
|
33 |
+
def _safe_import(name: str, globals=None, locals=None, fromlist=(), level=0):
|
34 |
+
if name in allowed_modules:
|
35 |
+
return __import__(name, globals, locals, fromlist, level)
|
36 |
+
raise ImportError(f"Import of '{name}' is not allowed")
|
37 |
+
|
38 |
+
allowed_builtins = {
|
39 |
+
"abs": abs,
|
40 |
+
"min": min,
|
41 |
+
"max": max,
|
42 |
+
"sum": sum,
|
43 |
+
"len": len,
|
44 |
+
"range": range,
|
45 |
+
"sorted": sorted,
|
46 |
+
"enumerate": enumerate,
|
47 |
+
"map": map,
|
48 |
+
"filter": filter,
|
49 |
+
"list": list,
|
50 |
+
"dict": dict,
|
51 |
+
"set": set,
|
52 |
+
"tuple": tuple,
|
53 |
+
"float": float,
|
54 |
+
"int": int,
|
55 |
+
"str": str,
|
56 |
+
"bool": bool,
|
57 |
+
"print": print,
|
58 |
+
"__import__": _safe_import,
|
59 |
+
}
|
60 |
+
|
61 |
+
safe_globals: dict[str, object] = {"__builtins__": allowed_builtins}
|
62 |
+
safe_locals: dict[str, object] = {}
|
63 |
+
|
64 |
+
stdout = StringIO()
|
65 |
+
original_stdout = sys.stdout
|
66 |
+
try:
|
67 |
+
sys.stdout = stdout
|
68 |
+
exec(code, safe_globals, safe_locals)
|
69 |
+
finally:
|
70 |
+
sys.stdout = original_stdout
|
71 |
+
|
72 |
+
if "result" in safe_locals:
|
73 |
+
return str(safe_locals["result"])
|
74 |
+
return stdout.getvalue().strip()
|