tinyagent-py 0.0.7__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.
@@ -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 = {}