tinyagent-py 0.0.8__py3-none-any.whl → 0.0.9__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- tinyagent/__init__.py +2 -1
- tinyagent/code_agent/__init__.py +12 -0
- tinyagent/code_agent/example.py +176 -0
- tinyagent/code_agent/helper.py +173 -0
- tinyagent/code_agent/modal_sandbox.py +478 -0
- tinyagent/code_agent/providers/__init__.py +4 -0
- tinyagent/code_agent/providers/base.py +152 -0
- tinyagent/code_agent/providers/modal_provider.py +202 -0
- tinyagent/code_agent/tiny_code_agent.py +573 -0
- tinyagent/code_agent/tools/__init__.py +3 -0
- tinyagent/code_agent/tools/example_tools.py +41 -0
- tinyagent/code_agent/utils.py +120 -0
- tinyagent/hooks/__init__.py +2 -1
- {tinyagent_py-0.0.8.dist-info → tinyagent_py-0.0.9.dist-info}/METADATA +138 -5
- tinyagent_py-0.0.9.dist-info/RECORD +31 -0
- tinyagent_py-0.0.8.dist-info/RECORD +0 -20
- {tinyagent_py-0.0.8.dist-info → tinyagent_py-0.0.9.dist-info}/WHEEL +0 -0
- {tinyagent_py-0.0.8.dist-info → tinyagent_py-0.0.9.dist-info}/licenses/LICENSE +0 -0
- {tinyagent_py-0.0.8.dist-info → tinyagent_py-0.0.9.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,202 @@
|
|
1
|
+
import sys
|
2
|
+
import modal
|
3
|
+
import cloudpickle
|
4
|
+
from typing import Dict, List, Any, Optional, Union
|
5
|
+
from .base import CodeExecutionProvider
|
6
|
+
from ..utils import clean_response, make_session_blob, _run_python
|
7
|
+
|
8
|
+
|
9
|
+
class ModalProvider(CodeExecutionProvider):
|
10
|
+
"""
|
11
|
+
Modal-based code execution provider.
|
12
|
+
|
13
|
+
This provider uses Modal.com to execute Python code in a remote, sandboxed environment.
|
14
|
+
It provides scalable, secure code execution with automatic dependency management.
|
15
|
+
Can also run locally for development/testing purposes using Modal's native .local() method.
|
16
|
+
"""
|
17
|
+
|
18
|
+
PYTHON_VERSION = f"{sys.version_info.major}.{sys.version_info.minor}"
|
19
|
+
|
20
|
+
def __init__(
|
21
|
+
self,
|
22
|
+
log_manager,
|
23
|
+
default_python_codes: Optional[List[str]] = None,
|
24
|
+
code_tools: List[Dict[str, Any]] = None,
|
25
|
+
pip_packages: List[str] | None = None,
|
26
|
+
default_packages: Optional[List[str]] = None,
|
27
|
+
apt_packages: Optional[List[str]] = None,
|
28
|
+
python_version: Optional[str] = None,
|
29
|
+
modal_secrets: Dict[str, Union[str, None]] | None = None,
|
30
|
+
lazy_init: bool = True,
|
31
|
+
sandbox_name: str = "tinycodeagent-sandbox",
|
32
|
+
local_execution: bool = False,
|
33
|
+
**kwargs
|
34
|
+
):
|
35
|
+
"""Create a ModalProvider instance.
|
36
|
+
|
37
|
+
Additional keyword arguments (passed via **kwargs) are ignored by the
|
38
|
+
base class but accepted here for forward-compatibility.
|
39
|
+
|
40
|
+
Args:
|
41
|
+
default_packages: Base set of Python packages installed into the
|
42
|
+
sandbox image. If ``None`` a sane default list is used. The
|
43
|
+
final set of installed packages is the union of
|
44
|
+
``default_packages`` and ``pip_packages``.
|
45
|
+
apt_packages: Debian/Ubuntu APT packages to install into the image
|
46
|
+
prior to ``pip install``. Defaults to an empty list. Always
|
47
|
+
installed *in addition to* the basics required by TinyAgent
|
48
|
+
(git, curl, …) so you only need to specify the extras.
|
49
|
+
python_version: Python version used for the sandbox image. If
|
50
|
+
``None`` the current interpreter version is used.
|
51
|
+
"""
|
52
|
+
|
53
|
+
# Resolve default values ------------------------------------------------
|
54
|
+
if default_packages is None:
|
55
|
+
default_packages = [
|
56
|
+
"cloudpickle",
|
57
|
+
"requests",
|
58
|
+
"tinyagent-py[all]",
|
59
|
+
"gradio",
|
60
|
+
"arize-phoenix-otel",
|
61
|
+
]
|
62
|
+
|
63
|
+
if apt_packages is None:
|
64
|
+
apt_packages = ["git", "curl", "nodejs", "npm"]
|
65
|
+
|
66
|
+
if python_version is None:
|
67
|
+
python_version = self.PYTHON_VERSION
|
68
|
+
|
69
|
+
# Keep references so callers can introspect / mutate later -------------
|
70
|
+
self.default_packages: List[str] = default_packages
|
71
|
+
self.apt_packages: List[str] = apt_packages
|
72
|
+
self.python_version: str = python_version
|
73
|
+
|
74
|
+
# ----------------------------------------------------------------------
|
75
|
+
final_packages = list(set(self.default_packages + (pip_packages or [])))
|
76
|
+
|
77
|
+
super().__init__(
|
78
|
+
log_manager=log_manager,
|
79
|
+
default_python_codes=default_python_codes or [],
|
80
|
+
code_tools=code_tools or [],
|
81
|
+
pip_packages=final_packages,
|
82
|
+
secrets=modal_secrets or {},
|
83
|
+
lazy_init=lazy_init,
|
84
|
+
**kwargs
|
85
|
+
)
|
86
|
+
|
87
|
+
self.sandbox_name = sandbox_name
|
88
|
+
self.local_execution = local_execution
|
89
|
+
self.modal_secrets = modal.Secret.from_dict(self.secrets)
|
90
|
+
self.app = None
|
91
|
+
self._app_run_python = None
|
92
|
+
|
93
|
+
self._setup_modal_app()
|
94
|
+
|
95
|
+
def _setup_modal_app(self):
|
96
|
+
"""Set up the Modal application and functions."""
|
97
|
+
execution_mode = "🏠 LOCAL" if self.local_execution else "☁️ REMOTE"
|
98
|
+
print(f"{execution_mode} ModalProvider setting up Modal app")
|
99
|
+
|
100
|
+
agent_image = modal.Image.debian_slim(python_version=self.python_version)
|
101
|
+
|
102
|
+
# Install APT packages first (if any were requested)
|
103
|
+
if self.apt_packages:
|
104
|
+
agent_image = agent_image.apt_install(*self.apt_packages)
|
105
|
+
|
106
|
+
# Then install pip packages (including the union of default + user)
|
107
|
+
agent_image = agent_image.pip_install(*self.pip_packages)
|
108
|
+
|
109
|
+
self.app = modal.App(
|
110
|
+
name=self.sandbox_name,
|
111
|
+
image=agent_image,
|
112
|
+
secrets=[self.modal_secrets]
|
113
|
+
)
|
114
|
+
|
115
|
+
self._app_run_python = self.app.function()(_run_python)
|
116
|
+
|
117
|
+
# Add tools if provided
|
118
|
+
if self.code_tools:
|
119
|
+
self.add_tools(self.code_tools)
|
120
|
+
|
121
|
+
async def execute_python(self, code_lines: List[str], timeout: int = 120) -> Dict[str, Any]:
|
122
|
+
"""
|
123
|
+
Execute Python code using Modal's native .local() or .remote() methods.
|
124
|
+
|
125
|
+
Args:
|
126
|
+
code_lines: List of Python code lines to execute
|
127
|
+
timeout: Maximum execution time in seconds
|
128
|
+
|
129
|
+
Returns:
|
130
|
+
Dictionary containing execution results
|
131
|
+
"""
|
132
|
+
if isinstance(code_lines, str):
|
133
|
+
code_lines = [code_lines]
|
134
|
+
|
135
|
+
full_code = "\n".join(code_lines)
|
136
|
+
|
137
|
+
print("#" * 100)
|
138
|
+
print("#########################code#########################")
|
139
|
+
print(full_code)
|
140
|
+
print("#" * 100)
|
141
|
+
|
142
|
+
|
143
|
+
|
144
|
+
# Use Modal's native execution methods
|
145
|
+
response = self._python_executor(full_code, self._globals_dict, self._locals_dict)
|
146
|
+
|
147
|
+
print("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!<response>!!!!!!!!!!!!!!!!!!!!!!!!!")
|
148
|
+
|
149
|
+
# Update the instance globals and locals with the execution results
|
150
|
+
self._globals_dict = cloudpickle.loads(make_session_blob(response["updated_globals"]))
|
151
|
+
self._locals_dict = cloudpickle.loads(make_session_blob(response["updated_locals"]))
|
152
|
+
|
153
|
+
|
154
|
+
self._log_response(response)
|
155
|
+
|
156
|
+
return clean_response(response)
|
157
|
+
|
158
|
+
def _python_executor(self, code: str, globals_dict: Dict[str, Any] = None, locals_dict: Dict[str, Any] = None):
|
159
|
+
"""Execute Python code using Modal's native .local() or .remote() methods."""
|
160
|
+
execution_mode = "🏠 LOCALLY" if self.local_execution else "☁️ REMOTELY"
|
161
|
+
print(f"Executing code {execution_mode} via Modal")
|
162
|
+
|
163
|
+
# Prepare the full code with default codes if needed
|
164
|
+
if self.executed_default_codes:
|
165
|
+
print("✔️ default codes already executed")
|
166
|
+
full_code = "\n".join(self.code_tools_definitions) +"\n\n"+code
|
167
|
+
else:
|
168
|
+
full_code = "\n".join(self.code_tools_definitions) +"\n\n"+ "\n".join(self.default_python_codes) + "\n\n" + code
|
169
|
+
self.executed_default_codes = True
|
170
|
+
|
171
|
+
# Use Modal's native execution methods
|
172
|
+
if self.local_execution:
|
173
|
+
# Use Modal's .local() method for local execution
|
174
|
+
return self._app_run_python.local(full_code, globals_dict or {}, locals_dict or {})
|
175
|
+
else:
|
176
|
+
# Use Modal's .remote() method for remote execution
|
177
|
+
with self.app.run():
|
178
|
+
return self._app_run_python.remote(full_code, globals_dict or {}, locals_dict or {})
|
179
|
+
|
180
|
+
def _log_response(self, response: Dict[str, Any]):
|
181
|
+
"""Log the response from code execution."""
|
182
|
+
execution_mode = "🏠 LOCAL" if self.local_execution else "☁️ REMOTE"
|
183
|
+
print(f"#########################{execution_mode} EXECUTION#########################")
|
184
|
+
print("#########################<printed_output>#########################")
|
185
|
+
print(response["printed_output"])
|
186
|
+
print("#########################</printed_output>#########################")
|
187
|
+
print("#########################<return_value>#########################")
|
188
|
+
print(response["return_value"])
|
189
|
+
print("#########################</return_value>#########################")
|
190
|
+
print("#########################<stderr>#########################")
|
191
|
+
print(response["stderr"])
|
192
|
+
print("#########################</stderr>#########################")
|
193
|
+
print("#########################<traceback>#########################")
|
194
|
+
print(response["error_traceback"])
|
195
|
+
print("#########################</traceback>#########################")
|
196
|
+
|
197
|
+
async def cleanup(self):
|
198
|
+
"""Clean up Modal resources."""
|
199
|
+
# Modal handles cleanup automatically, but we can reset state
|
200
|
+
self.executed_default_codes = False
|
201
|
+
self._globals_dict = {}
|
202
|
+
self._locals_dict = {}
|