tinyagent-py 0.0.11__tar.gz → 0.0.13__tar.gz

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.
Files changed (39) hide show
  1. {tinyagent_py-0.0.11/tinyagent_py.egg-info → tinyagent_py-0.0.13}/PKG-INFO +11 -1
  2. {tinyagent_py-0.0.11 → tinyagent_py-0.0.13}/README.md +10 -0
  3. {tinyagent_py-0.0.11 → tinyagent_py-0.0.13}/pyproject.toml +1 -1
  4. {tinyagent_py-0.0.11 → tinyagent_py-0.0.13}/tinyagent/code_agent/modal_sandbox.py +3 -1
  5. {tinyagent_py-0.0.11 → tinyagent_py-0.0.13}/tinyagent/code_agent/providers/base.py +60 -5
  6. {tinyagent_py-0.0.11 → tinyagent_py-0.0.13}/tinyagent/code_agent/providers/modal_provider.py +61 -18
  7. tinyagent_py-0.0.13/tinyagent/code_agent/safety.py +546 -0
  8. {tinyagent_py-0.0.11 → tinyagent_py-0.0.13}/tinyagent/code_agent/tiny_code_agent.py +105 -0
  9. tinyagent_py-0.0.13/tinyagent/code_agent/utils.py +193 -0
  10. {tinyagent_py-0.0.11 → tinyagent_py-0.0.13}/tinyagent/hooks/gradio_callback.py +100 -35
  11. {tinyagent_py-0.0.11 → tinyagent_py-0.0.13}/tinyagent/tiny_agent.py +4 -7
  12. {tinyagent_py-0.0.11 → tinyagent_py-0.0.13/tinyagent_py.egg-info}/PKG-INFO +11 -1
  13. {tinyagent_py-0.0.11 → tinyagent_py-0.0.13}/tinyagent_py.egg-info/SOURCES.txt +1 -0
  14. tinyagent_py-0.0.11/tinyagent/code_agent/utils.py +0 -120
  15. {tinyagent_py-0.0.11 → tinyagent_py-0.0.13}/LICENSE +0 -0
  16. {tinyagent_py-0.0.11 → tinyagent_py-0.0.13}/setup.cfg +0 -0
  17. {tinyagent_py-0.0.11 → tinyagent_py-0.0.13}/tinyagent/__init__.py +0 -0
  18. {tinyagent_py-0.0.11 → tinyagent_py-0.0.13}/tinyagent/code_agent/__init__.py +0 -0
  19. {tinyagent_py-0.0.11 → tinyagent_py-0.0.13}/tinyagent/code_agent/example.py +0 -0
  20. {tinyagent_py-0.0.11 → tinyagent_py-0.0.13}/tinyagent/code_agent/helper.py +0 -0
  21. {tinyagent_py-0.0.11 → tinyagent_py-0.0.13}/tinyagent/code_agent/providers/__init__.py +0 -0
  22. {tinyagent_py-0.0.11 → tinyagent_py-0.0.13}/tinyagent/code_agent/tools/__init__.py +0 -0
  23. {tinyagent_py-0.0.11 → tinyagent_py-0.0.13}/tinyagent/code_agent/tools/example_tools.py +0 -0
  24. {tinyagent_py-0.0.11 → tinyagent_py-0.0.13}/tinyagent/hooks/__init__.py +0 -0
  25. {tinyagent_py-0.0.11 → tinyagent_py-0.0.13}/tinyagent/hooks/logging_manager.py +0 -0
  26. {tinyagent_py-0.0.11 → tinyagent_py-0.0.13}/tinyagent/hooks/rich_code_ui_callback.py +0 -0
  27. {tinyagent_py-0.0.11 → tinyagent_py-0.0.13}/tinyagent/hooks/rich_ui_callback.py +0 -0
  28. {tinyagent_py-0.0.11 → tinyagent_py-0.0.13}/tinyagent/mcp_client.py +0 -0
  29. {tinyagent_py-0.0.11 → tinyagent_py-0.0.13}/tinyagent/memory_manager.py +0 -0
  30. {tinyagent_py-0.0.11 → tinyagent_py-0.0.13}/tinyagent/prompts/code_agent.yaml +0 -0
  31. {tinyagent_py-0.0.11 → tinyagent_py-0.0.13}/tinyagent/storage/__init__.py +0 -0
  32. {tinyagent_py-0.0.11 → tinyagent_py-0.0.13}/tinyagent/storage/base.py +0 -0
  33. {tinyagent_py-0.0.11 → tinyagent_py-0.0.13}/tinyagent/storage/json_file_storage.py +0 -0
  34. {tinyagent_py-0.0.11 → tinyagent_py-0.0.13}/tinyagent/storage/postgres_storage.py +0 -0
  35. {tinyagent_py-0.0.11 → tinyagent_py-0.0.13}/tinyagent/storage/redis_storage.py +0 -0
  36. {tinyagent_py-0.0.11 → tinyagent_py-0.0.13}/tinyagent/storage/sqlite_storage.py +0 -0
  37. {tinyagent_py-0.0.11 → tinyagent_py-0.0.13}/tinyagent_py.egg-info/dependency_links.txt +0 -0
  38. {tinyagent_py-0.0.11 → tinyagent_py-0.0.13}/tinyagent_py.egg-info/requires.txt +0 -0
  39. {tinyagent_py-0.0.11 → tinyagent_py-0.0.13}/tinyagent_py.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tinyagent-py
3
- Version: 0.0.11
3
+ Version: 0.0.13
4
4
  Summary: TinyAgent with MCP Client, Code Agent (Thinking, Planning, and Executing in Python), and Extendable Hooks, Tiny but powerful
5
5
  Author-email: Mahdi Golchin <golchin@askdev.ai>
6
6
  Project-URL: Homepage, https://github.com/askbudi/tinyagent
@@ -60,6 +60,16 @@ Inspired by:
60
60
  ## Quick Links
61
61
  - [Build your own Tiny Agent](https://askdev.ai/github/askbudi/tinyagent)
62
62
 
63
+
64
+ ## Live Projects using TinyAgent (🔥)
65
+ - [AskDev.AI](https://askdev.ai) - Understand, chat, and summarize codebase of any project on GitHub.
66
+ - [HackBuddy AI](https://huggingface.co/spaces/ask-dev/HackBuddyAI) - A Hackathon Assistant Agent, built with TinyCodeAgent and Gradio. Match invdividuals to teams based on their skills, interests and organizer preferences.
67
+
68
+ - [TinyCodeAgent Demo](https://huggingface.co/spaces/ask-dev/TinyCodeAgent) - A playground for TinyCodeAgent, built with tinyagent, Gradio and Modal.com
69
+
70
+ ** Building something with TinyAgent? Let us know and I'll add it here!**
71
+
72
+
63
73
  ## Overview
64
74
  This is a tiny agent framework that uses MCP and LiteLLM to interact with language models. You have full control over the agent, you can add any tools you like from MCP and extend the agent using its event system.
65
75
 
@@ -18,6 +18,16 @@ Inspired by:
18
18
  ## Quick Links
19
19
  - [Build your own Tiny Agent](https://askdev.ai/github/askbudi/tinyagent)
20
20
 
21
+
22
+ ## Live Projects using TinyAgent (🔥)
23
+ - [AskDev.AI](https://askdev.ai) - Understand, chat, and summarize codebase of any project on GitHub.
24
+ - [HackBuddy AI](https://huggingface.co/spaces/ask-dev/HackBuddyAI) - A Hackathon Assistant Agent, built with TinyCodeAgent and Gradio. Match invdividuals to teams based on their skills, interests and organizer preferences.
25
+
26
+ - [TinyCodeAgent Demo](https://huggingface.co/spaces/ask-dev/TinyCodeAgent) - A playground for TinyCodeAgent, built with tinyagent, Gradio and Modal.com
27
+
28
+ ** Building something with TinyAgent? Let us know and I'll add it here!**
29
+
30
+
21
31
  ## Overview
22
32
  This is a tiny agent framework that uses MCP and LiteLLM to interact with language models. You have full control over the agent, you can add any tools you like from MCP and extend the agent using its event system.
23
33
 
@@ -12,7 +12,7 @@ tinyagent = ["prompts/*.yaml"]
12
12
 
13
13
  [project]
14
14
  name = "tinyagent-py"
15
- version = "0.0.11"
15
+ version = "0.0.13"
16
16
  description = "TinyAgent with MCP Client, Code Agent (Thinking, Planning, and Executing in Python), and Extendable Hooks, Tiny but powerful"
17
17
  readme = "README.md"
18
18
  authors = [
@@ -63,6 +63,7 @@ def create_sandbox(
63
63
  pip_install: Sequence[str] | None = None,
64
64
  image_name: str = "tinyagent-sandbox-image",
65
65
  app_name: str = "persistent-code-session",
66
+ force_build: bool = False,
66
67
  **sandbox_kwargs,
67
68
  ) -> Tuple[modal.Sandbox, modal.App]:
68
69
  """Create (or lookup) a `modal.Sandbox` pre-configured for code execution.
@@ -99,7 +100,7 @@ def create_sandbox(
99
100
 
100
101
  # Build image -----------------------------------------------------------
101
102
  agent_image = (
102
- modal.Image.debian_slim(python_version=python_version)
103
+ modal.Image.debian_slim(python_version=python_version,force_build=force_build)
103
104
  .apt_install(*apt_packages)
104
105
  .pip_install(*full_pip_list)
105
106
  )
@@ -196,6 +197,7 @@ class SandboxSession:
196
197
  modal_secrets: modal.Secret,
197
198
  *,
198
199
  timeout: int = 5 * 60,
200
+
199
201
  **create_kwargs,
200
202
  ) -> None:
201
203
  self.modal_secrets = modal_secrets
@@ -1,6 +1,7 @@
1
1
  from abc import ABC, abstractmethod
2
2
  from typing import Dict, List, Any, Optional
3
3
  from tinyagent.hooks.logging_manager import LoggingManager
4
+ import cloudpickle
4
5
 
5
6
 
6
7
  class CodeExecutionProvider(ABC):
@@ -69,8 +70,6 @@ class CodeExecutionProvider(ABC):
69
70
  Args:
70
71
  tools: List of tool objects to add
71
72
  """
72
- import cloudpickle
73
-
74
73
  tools_str_list = ["import cloudpickle"]
75
74
  tools_str_list.append("###########<tools>###########\n")
76
75
  for tool in tools:
@@ -82,6 +81,22 @@ class CodeExecutionProvider(ABC):
82
81
  tools_str_list.append("\n\n")
83
82
  self.code_tools_definitions.extend(tools_str_list)
84
83
 
84
+ def set_code_tools(self, tools: List[Any]) -> None:
85
+ """
86
+ Set the code tools available in the execution environment.
87
+ Replaces any existing tools with the new list.
88
+
89
+ Args:
90
+ tools: List of tool objects to set
91
+ """
92
+ # Clear existing tools
93
+ self.code_tools = tools.copy()
94
+ self.code_tools_definitions = []
95
+
96
+ # Add the new tools
97
+ if tools:
98
+ self.add_tools(tools)
99
+
85
100
  def set_user_variables(self, variables: Dict[str, Any]) -> None:
86
101
  """
87
102
  Set user variables that will be available in the Python environment.
@@ -89,8 +104,6 @@ class CodeExecutionProvider(ABC):
89
104
  Args:
90
105
  variables: Dictionary of variable name -> value pairs
91
106
  """
92
- import cloudpickle
93
-
94
107
  self._user_variables = variables.copy()
95
108
 
96
109
  # Add variables to the execution environment by serializing them
@@ -149,4 +162,46 @@ class CodeExecutionProvider(ABC):
149
162
  Returns:
150
163
  Dictionary of current user variables
151
164
  """
152
- return self._user_variables.copy()
165
+ return self._user_variables.copy()
166
+
167
+ def update_user_variables_from_globals(self, globals_dict: Dict[str, Any]) -> None:
168
+ """
169
+ Extract and update user variables from the globals dictionary after code execution.
170
+ This ensures that any modifications to user variables during code execution are preserved.
171
+
172
+ Args:
173
+ globals_dict: The globals dictionary after code execution
174
+ """
175
+ if not globals_dict or not self._user_variables:
176
+ return
177
+
178
+ # Update user variables with values from globals
179
+ for var_name in list(self._user_variables.keys()):
180
+ if var_name in globals_dict:
181
+ try:
182
+ # Try to serialize the value to ensure it's valid
183
+ cloudpickle.dumps(globals_dict[var_name])
184
+ # Update the user variable with the new value
185
+ self._user_variables[var_name] = globals_dict[var_name]
186
+ except Exception:
187
+ # If serialization fails, keep the old value
188
+ pass
189
+
190
+ # Check for new variables that might have been created
191
+ # This handles cases where LLM creates new variables that should be preserved
192
+ for var_name, var_value in globals_dict.items():
193
+ # Skip special variables, modules, and functions
194
+ if (var_name.startswith('__') or
195
+ var_name in ['builtins', 'cloudpickle'] or
196
+ callable(var_value) or
197
+ var_name in self._user_variables):
198
+ continue
199
+
200
+ try:
201
+ # Try to serialize the value to ensure it's valid
202
+ cloudpickle.dumps(var_value)
203
+ # Add the new variable to user variables
204
+ self._user_variables[var_name] = var_value
205
+ except Exception:
206
+ # If serialization fails, skip this variable
207
+ pass
@@ -26,6 +26,7 @@ class ModalProvider(CodeExecutionProvider):
26
26
  default_packages: Optional[List[str]] = None,
27
27
  apt_packages: Optional[List[str]] = None,
28
28
  python_version: Optional[str] = None,
29
+ authorized_imports: list[str] | None = None,
29
30
  modal_secrets: Dict[str, Union[str, None]] | None = None,
30
31
  lazy_init: bool = True,
31
32
  sandbox_name: str = "tinycodeagent-sandbox",
@@ -48,6 +49,7 @@ class ModalProvider(CodeExecutionProvider):
48
49
  (git, curl, …) so you only need to specify the extras.
49
50
  python_version: Python version used for the sandbox image. If
50
51
  ``None`` the current interpreter version is used.
52
+ authorized_imports: Optional allow-list of modules the user code is permitted to import. Supports wildcard patterns (e.g. "pandas.*"). If ``None`` the safety layer blocks only the predefined dangerous modules.
51
53
  """
52
54
 
53
55
  # Resolve default values ------------------------------------------------
@@ -70,6 +72,7 @@ class ModalProvider(CodeExecutionProvider):
70
72
  self.default_packages: List[str] = default_packages
71
73
  self.apt_packages: List[str] = apt_packages
72
74
  self.python_version: str = python_version
75
+ self.authorized_imports = authorized_imports
73
76
 
74
77
  # ----------------------------------------------------------------------
75
78
  final_packages = list(set(self.default_packages + (pip_packages or [])))
@@ -89,6 +92,7 @@ class ModalProvider(CodeExecutionProvider):
89
92
  self.modal_secrets = modal.Secret.from_dict(self.secrets)
90
93
  self.app = None
91
94
  self._app_run_python = None
95
+ self.is_trusted_code = kwargs.get("trust_code", False)
92
96
 
93
97
  self._setup_modal_app()
94
98
 
@@ -139,17 +143,28 @@ class ModalProvider(CodeExecutionProvider):
139
143
  print(full_code)
140
144
  print("#" * 100)
141
145
 
142
-
143
146
 
144
147
  # Use Modal's native execution methods
145
148
  response = self._python_executor(full_code, self._globals_dict, self._locals_dict)
146
149
 
147
150
  print("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!<response>!!!!!!!!!!!!!!!!!!!!!!!!!")
148
151
 
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
-
152
+ # Always update globals and locals dictionaries, regardless of whether there was an error
153
+ # This ensures variables are preserved even when code execution fails
154
+ try:
155
+ # Update globals and locals from the response
156
+ if "updated_globals" in response:
157
+ self._globals_dict = cloudpickle.loads(make_session_blob(response["updated_globals"]))
158
+
159
+ if "updated_locals" in response:
160
+ self._locals_dict = cloudpickle.loads(make_session_blob(response["updated_locals"]))
161
+
162
+ # Update user variables from the updated globals and locals
163
+ # This preserves any changes made to variables by the LLM
164
+ self.update_user_variables_from_globals(self._globals_dict)
165
+ self.update_user_variables_from_globals(self._locals_dict)
166
+ except Exception as e:
167
+ print(f"Warning: Failed to update globals/locals after execution: {str(e)}")
153
168
 
154
169
  self._log_response(response)
155
170
 
@@ -164,18 +179,30 @@ class ModalProvider(CodeExecutionProvider):
164
179
  if self.executed_default_codes:
165
180
  print("✔️ default codes already executed")
166
181
  full_code = "\n".join(self.code_tools_definitions) +"\n\n"+code
182
+ # Code tools and default code are trusted, user code is not
167
183
  else:
168
184
  full_code = "\n".join(self.code_tools_definitions) +"\n\n"+ "\n".join(self.default_python_codes) + "\n\n" + code
169
185
  self.executed_default_codes = True
186
+ # First execution includes framework code which is trusted
170
187
 
171
188
  # Use Modal's native execution methods
172
189
  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 {})
190
+ return self._app_run_python.local(
191
+ full_code,
192
+ globals_dict or {},
193
+ locals_dict or {},
194
+ self.authorized_imports,
195
+ self.is_trusted_code,
196
+ )
175
197
  else:
176
- # Use Modal's .remote() method for remote execution
177
198
  with self.app.run():
178
- return self._app_run_python.remote(full_code, globals_dict or {}, locals_dict or {})
199
+ return self._app_run_python.remote(
200
+ full_code,
201
+ globals_dict or {},
202
+ locals_dict or {},
203
+ self.authorized_imports,
204
+ self.is_trusted_code,
205
+ )
179
206
 
180
207
  def _log_response(self, response: Dict[str, Any]):
181
208
  """Log the response from code execution."""
@@ -184,15 +211,31 @@ class ModalProvider(CodeExecutionProvider):
184
211
  print("#########################<printed_output>#########################")
185
212
  print(response["printed_output"])
186
213
  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>#########################")
214
+ if response.get("return_value",None) not in [None,""]:
215
+ print("#########################<return_value>#########################")
216
+ print(response["return_value"])
217
+ print("#########################</return_value>#########################")
218
+ if response.get("stderr",None) not in [None,""]:
219
+ print("#########################<stderr>#########################")
220
+ print(response["stderr"])
221
+ print("#########################</stderr>#########################")
222
+ if response.get("error_traceback",None) not in [None,""]:
223
+ print("#########################<traceback>#########################")
224
+ # Check if this is a security exception and highlight it in red if so
225
+ error_text = response["error_traceback"]
226
+ if "SECURITY" in error_text:
227
+ try:
228
+ from ..modal_sandbox import COLOR
229
+ except ImportError:
230
+ # Fallback colors if modal_sandbox is not available
231
+ COLOR = {
232
+ "RED": "\033[91m",
233
+ "ENDC": "\033[0m",
234
+ }
235
+ print(f"{COLOR['RED']}{error_text}{COLOR['ENDC']}")
236
+ else:
237
+ print(error_text)
238
+ print("#########################</traceback>#########################")
196
239
 
197
240
  async def cleanup(self):
198
241
  """Clean up Modal resources."""