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,573 @@
|
|
1
|
+
import traceback
|
2
|
+
from textwrap import dedent
|
3
|
+
from typing import Optional, List, Dict, Any
|
4
|
+
from pathlib import Path
|
5
|
+
from tinyagent import TinyAgent, tool
|
6
|
+
from tinyagent.hooks.logging_manager import LoggingManager
|
7
|
+
from .providers.base import CodeExecutionProvider
|
8
|
+
from .providers.modal_provider import ModalProvider
|
9
|
+
from .helper import translate_tool_for_code_agent, load_template, render_system_prompt, prompt_code_example, prompt_qwen_helper
|
10
|
+
|
11
|
+
|
12
|
+
class TinyCodeAgent:
|
13
|
+
"""
|
14
|
+
A TinyAgent specialized for code execution tasks.
|
15
|
+
|
16
|
+
This class provides a high-level interface for creating agents that can execute
|
17
|
+
Python code using various providers (Modal, Docker, local execution, etc.).
|
18
|
+
"""
|
19
|
+
|
20
|
+
def __init__(
|
21
|
+
self,
|
22
|
+
model: str = "gpt-4.1-mini",
|
23
|
+
api_key: Optional[str] = None,
|
24
|
+
log_manager: Optional[LoggingManager] = None,
|
25
|
+
provider: str = "modal",
|
26
|
+
tools: Optional[List[Any]] = None,
|
27
|
+
code_tools: Optional[List[Any]] = None,
|
28
|
+
authorized_imports: Optional[List[str]] = None,
|
29
|
+
system_prompt_template: Optional[str] = None,
|
30
|
+
provider_config: Optional[Dict[str, Any]] = None,
|
31
|
+
user_variables: Optional[Dict[str, Any]] = None,
|
32
|
+
pip_packages: Optional[List[str]] = None,
|
33
|
+
local_execution: bool = False,
|
34
|
+
**agent_kwargs
|
35
|
+
):
|
36
|
+
"""
|
37
|
+
Initialize TinyCodeAgent.
|
38
|
+
|
39
|
+
Args:
|
40
|
+
model: The language model to use
|
41
|
+
api_key: API key for the model
|
42
|
+
log_manager: Optional logging manager
|
43
|
+
provider: Code execution provider ("modal", "local", etc.)
|
44
|
+
tools: List of tools available to the LLM (regular tools)
|
45
|
+
code_tools: List of tools available in the Python execution environment
|
46
|
+
authorized_imports: List of authorized Python imports
|
47
|
+
system_prompt_template: Path to custom system prompt template
|
48
|
+
provider_config: Configuration for the code execution provider
|
49
|
+
user_variables: Dictionary of variables to make available in Python environment
|
50
|
+
pip_packages: List of additional Python packages to install in Modal environment
|
51
|
+
local_execution: If True, uses Modal's .local() method for local execution.
|
52
|
+
If False, uses Modal's .remote() method for cloud execution (default: False)
|
53
|
+
**agent_kwargs: Additional arguments passed to TinyAgent
|
54
|
+
"""
|
55
|
+
self.model = model
|
56
|
+
self.api_key = api_key
|
57
|
+
self.log_manager = log_manager
|
58
|
+
self.tools = tools or [] # LLM tools
|
59
|
+
self.code_tools = code_tools or [] # Python environment tools
|
60
|
+
self.authorized_imports = authorized_imports or ["tinyagent", "gradio", "requests", "asyncio"]
|
61
|
+
self.provider_config = provider_config or {}
|
62
|
+
self.user_variables = user_variables or {}
|
63
|
+
self.pip_packages = pip_packages or []
|
64
|
+
self.local_execution = local_execution
|
65
|
+
|
66
|
+
# Create the code execution provider
|
67
|
+
self.code_provider = self._create_provider(provider, self.provider_config)
|
68
|
+
|
69
|
+
# Set user variables in the provider
|
70
|
+
if self.user_variables:
|
71
|
+
self.code_provider.set_user_variables(self.user_variables)
|
72
|
+
|
73
|
+
# Build system prompt
|
74
|
+
self.system_prompt = self._build_system_prompt(system_prompt_template)
|
75
|
+
|
76
|
+
# Create the underlying TinyAgent
|
77
|
+
self.agent = TinyAgent(
|
78
|
+
model=model,
|
79
|
+
api_key=api_key,
|
80
|
+
system_prompt=self.system_prompt,
|
81
|
+
logger=log_manager.get_logger('tinyagent.tiny_agent') if log_manager else None,
|
82
|
+
**agent_kwargs
|
83
|
+
)
|
84
|
+
|
85
|
+
# Add the code execution tool
|
86
|
+
self._setup_code_execution_tool()
|
87
|
+
|
88
|
+
# Add LLM tools (not code tools - those go to the provider)
|
89
|
+
if self.tools:
|
90
|
+
self.agent.add_tools(self.tools)
|
91
|
+
|
92
|
+
def _create_provider(self, provider_type: str, config: Dict[str, Any]) -> CodeExecutionProvider:
|
93
|
+
"""Create a code execution provider based on the specified type."""
|
94
|
+
if provider_type.lower() == "modal":
|
95
|
+
# Merge pip_packages from both sources (direct parameter and provider_config)
|
96
|
+
config_pip_packages = config.get("pip_packages", [])
|
97
|
+
final_pip_packages = list(set(self.pip_packages + config_pip_packages))
|
98
|
+
|
99
|
+
final_config = config.copy()
|
100
|
+
final_config["pip_packages"] = final_pip_packages
|
101
|
+
|
102
|
+
return ModalProvider(
|
103
|
+
log_manager=self.log_manager,
|
104
|
+
code_tools=self.code_tools,
|
105
|
+
local_execution=self.local_execution,
|
106
|
+
**final_config
|
107
|
+
)
|
108
|
+
else:
|
109
|
+
raise ValueError(f"Unsupported provider type: {provider_type}")
|
110
|
+
|
111
|
+
def _build_system_prompt(self, template_path: Optional[str] = None) -> str:
|
112
|
+
"""Build the system prompt for the code agent."""
|
113
|
+
# Use default template if none provided
|
114
|
+
if template_path is None:
|
115
|
+
template_path = str(Path(__file__).parent.parent / "prompts" / "code_agent.yaml")
|
116
|
+
|
117
|
+
# Translate code tools to code agent format
|
118
|
+
code_tools_metadata = {}
|
119
|
+
for tool in self.code_tools:
|
120
|
+
if hasattr(tool, '_tool_metadata'):
|
121
|
+
metadata = translate_tool_for_code_agent(tool)
|
122
|
+
code_tools_metadata[metadata["name"]] = metadata
|
123
|
+
|
124
|
+
# Load and render template
|
125
|
+
try:
|
126
|
+
template_str = load_template(template_path)
|
127
|
+
system_prompt = render_system_prompt(
|
128
|
+
template_str,
|
129
|
+
code_tools_metadata,
|
130
|
+
{},
|
131
|
+
self.authorized_imports
|
132
|
+
)
|
133
|
+
base_prompt = system_prompt + prompt_code_example + prompt_qwen_helper
|
134
|
+
except Exception as e:
|
135
|
+
# Fallback to a basic prompt if template loading fails
|
136
|
+
traceback.print_exc()
|
137
|
+
print(f"Failed to load template from {template_path}: {e}")
|
138
|
+
base_prompt = self._get_fallback_prompt()
|
139
|
+
|
140
|
+
# Add user variables information to the prompt
|
141
|
+
if self.user_variables:
|
142
|
+
variables_info = self._build_variables_prompt()
|
143
|
+
base_prompt += "\n\n" + variables_info
|
144
|
+
|
145
|
+
return base_prompt
|
146
|
+
|
147
|
+
def _get_fallback_prompt(self) -> str:
|
148
|
+
"""Get a fallback system prompt if template loading fails."""
|
149
|
+
return dedent("""
|
150
|
+
You are a helpful AI assistant that can execute Python code to solve problems.
|
151
|
+
|
152
|
+
You have access to a run_python tool that can execute Python code in a sandboxed environment.
|
153
|
+
Use this tool to solve computational problems, analyze data, or perform any task that requires code execution.
|
154
|
+
|
155
|
+
When writing code:
|
156
|
+
- Always think step by step about the task
|
157
|
+
- Use print() statements to show intermediate results
|
158
|
+
- Handle errors gracefully
|
159
|
+
- Provide clear explanations of your approach
|
160
|
+
|
161
|
+
The user cannot see the direct output of run_python, so use final_answer to show results.
|
162
|
+
""")
|
163
|
+
|
164
|
+
def _build_variables_prompt(self) -> str:
|
165
|
+
"""Build the variables section for the system prompt."""
|
166
|
+
if not self.user_variables:
|
167
|
+
return ""
|
168
|
+
|
169
|
+
variables_lines = ["## Available Variables", ""]
|
170
|
+
variables_lines.append("The following variables are pre-loaded and available in your Python environment:")
|
171
|
+
variables_lines.append("")
|
172
|
+
|
173
|
+
for var_name, var_value in self.user_variables.items():
|
174
|
+
var_type = type(var_value).__name__
|
175
|
+
|
176
|
+
# Try to get a brief description of the variable
|
177
|
+
if hasattr(var_value, 'shape') and hasattr(var_value, 'dtype'):
|
178
|
+
# Likely numpy array or pandas DataFrame
|
179
|
+
if hasattr(var_value, 'columns'):
|
180
|
+
# DataFrame
|
181
|
+
desc = f"DataFrame with shape {var_value.shape} and columns: {list(var_value.columns)}"
|
182
|
+
else:
|
183
|
+
# Array
|
184
|
+
desc = f"Array with shape {var_value.shape} and dtype {var_value.dtype}"
|
185
|
+
elif isinstance(var_value, (list, tuple)):
|
186
|
+
length = len(var_value)
|
187
|
+
if length > 0:
|
188
|
+
first_type = type(var_value[0]).__name__
|
189
|
+
desc = f"{var_type} with {length} items (first item type: {first_type})"
|
190
|
+
else:
|
191
|
+
desc = f"Empty {var_type}"
|
192
|
+
elif isinstance(var_value, dict):
|
193
|
+
keys_count = len(var_value)
|
194
|
+
if keys_count > 0:
|
195
|
+
sample_keys = list(var_value.keys())[:3]
|
196
|
+
desc = f"Dictionary with {keys_count} keys. Sample keys: {sample_keys}"
|
197
|
+
else:
|
198
|
+
desc = "Empty dictionary"
|
199
|
+
elif isinstance(var_value, str):
|
200
|
+
length = len(var_value)
|
201
|
+
preview = var_value[:50] + "..." if length > 50 else var_value
|
202
|
+
desc = f"String with {length} characters: '{preview}'"
|
203
|
+
else:
|
204
|
+
desc = f"{var_type}: {str(var_value)[:100]}"
|
205
|
+
|
206
|
+
variables_lines.append(f"- **{var_name}** ({var_type}): {desc}")
|
207
|
+
|
208
|
+
variables_lines.extend([
|
209
|
+
"",
|
210
|
+
"These variables are already loaded and ready to use in your code. You don't need to import or define them.",
|
211
|
+
"You can directly reference them by name in your Python code."
|
212
|
+
])
|
213
|
+
|
214
|
+
return "\n".join(variables_lines)
|
215
|
+
|
216
|
+
def _build_code_tools_prompt(self) -> str:
|
217
|
+
"""Build the code tools section for the system prompt."""
|
218
|
+
if not self.code_tools:
|
219
|
+
return ""
|
220
|
+
|
221
|
+
code_tools_lines = ["## Available Code Tools", ""]
|
222
|
+
code_tools_lines.append("The following code tools are available in your Python environment:")
|
223
|
+
code_tools_lines.append("")
|
224
|
+
|
225
|
+
for tool in self.code_tools:
|
226
|
+
if hasattr(tool, '_tool_metadata'):
|
227
|
+
metadata = translate_tool_for_code_agent(tool)
|
228
|
+
desc = f"- **{metadata['name']}** ({metadata['type']}): {metadata['description']}"
|
229
|
+
code_tools_lines.append(desc)
|
230
|
+
|
231
|
+
code_tools_lines.extend([
|
232
|
+
"",
|
233
|
+
"These tools are already loaded and ready to use in your code. You don't need to import or define them.",
|
234
|
+
"You can directly reference them by name in your Python code."
|
235
|
+
])
|
236
|
+
|
237
|
+
return "\n".join(code_tools_lines)
|
238
|
+
|
239
|
+
def _setup_code_execution_tool(self):
|
240
|
+
"""Set up the run_python tool using the code provider."""
|
241
|
+
@tool(name="run_python", description=dedent("""
|
242
|
+
This tool receives Python code and executes it in a sandboxed environment.
|
243
|
+
During each intermediate step, you can use 'print()' to save important information.
|
244
|
+
These print outputs will appear in the 'Observation:' field for the next step.
|
245
|
+
|
246
|
+
Args:
|
247
|
+
code_lines: list[str]: The Python code to execute as a list of strings.
|
248
|
+
Your code should include all necessary steps for successful execution,
|
249
|
+
cover edge cases, and include error handling.
|
250
|
+
Each line should be an independent line of code.
|
251
|
+
|
252
|
+
Returns:
|
253
|
+
Status of code execution or error message.
|
254
|
+
"""))
|
255
|
+
async def run_python(code_lines: List[str], timeout: int = 120) -> str:
|
256
|
+
"""Execute Python code using the configured provider."""
|
257
|
+
try:
|
258
|
+
result = await self.code_provider.execute_python(code_lines, timeout)
|
259
|
+
return str(result)
|
260
|
+
except Exception as e:
|
261
|
+
return f"Error executing code: {str(e)}"
|
262
|
+
|
263
|
+
self.agent.add_tool(run_python)
|
264
|
+
|
265
|
+
async def run(self, user_input: str, max_turns: int = 10) -> str:
|
266
|
+
"""
|
267
|
+
Run the code agent with the given input.
|
268
|
+
|
269
|
+
Args:
|
270
|
+
user_input: The user's request or question
|
271
|
+
max_turns: Maximum number of conversation turns
|
272
|
+
|
273
|
+
Returns:
|
274
|
+
The agent's response
|
275
|
+
"""
|
276
|
+
return await self.agent.run(user_input, max_turns)
|
277
|
+
|
278
|
+
async def connect_to_server(self, command: str, args: List[str], **kwargs):
|
279
|
+
"""Connect to an MCP server."""
|
280
|
+
return await self.agent.connect_to_server(command, args, **kwargs)
|
281
|
+
|
282
|
+
def add_callback(self, callback):
|
283
|
+
"""Add a callback to the agent."""
|
284
|
+
self.agent.add_callback(callback)
|
285
|
+
|
286
|
+
def add_tool(self, tool):
|
287
|
+
"""Add a tool to the agent (LLM tool)."""
|
288
|
+
self.agent.add_tool(tool)
|
289
|
+
|
290
|
+
def add_tools(self, tools: List[Any]):
|
291
|
+
"""Add multiple tools to the agent (LLM tools)."""
|
292
|
+
self.agent.add_tools(tools)
|
293
|
+
|
294
|
+
def add_code_tool(self, tool):
|
295
|
+
"""
|
296
|
+
Add a code tool that will be available in the Python execution environment.
|
297
|
+
|
298
|
+
Args:
|
299
|
+
tool: The tool to add to the code execution environment
|
300
|
+
"""
|
301
|
+
self.code_tools.append(tool)
|
302
|
+
# Update the provider with the new code tools
|
303
|
+
self.code_provider.set_code_tools(self.code_tools)
|
304
|
+
# Rebuild system prompt to include new code tools info
|
305
|
+
self.system_prompt = self._build_system_prompt()
|
306
|
+
# Update the agent's system prompt
|
307
|
+
self.agent.system_prompt = self.system_prompt
|
308
|
+
|
309
|
+
def add_code_tools(self, tools: List[Any]):
|
310
|
+
"""
|
311
|
+
Add multiple code tools that will be available in the Python execution environment.
|
312
|
+
|
313
|
+
Args:
|
314
|
+
tools: List of tools to add to the code execution environment
|
315
|
+
"""
|
316
|
+
self.code_tools.extend(tools)
|
317
|
+
# Update the provider with the new code tools
|
318
|
+
self.code_provider.set_code_tools(self.code_tools)
|
319
|
+
# Rebuild system prompt to include new code tools info
|
320
|
+
self.system_prompt = self._build_system_prompt()
|
321
|
+
# Update the agent's system prompt
|
322
|
+
self.agent.system_prompt = self.system_prompt
|
323
|
+
|
324
|
+
def remove_code_tool(self, tool_name: str):
|
325
|
+
"""
|
326
|
+
Remove a code tool by name.
|
327
|
+
|
328
|
+
Args:
|
329
|
+
tool_name: Name of the tool to remove
|
330
|
+
"""
|
331
|
+
self.code_tools = [tool for tool in self.code_tools
|
332
|
+
if not (hasattr(tool, '_tool_metadata') and
|
333
|
+
tool._tool_metadata.get('name') == tool_name)]
|
334
|
+
# Update the provider
|
335
|
+
self.code_provider.set_code_tools(self.code_tools)
|
336
|
+
# Rebuild system prompt
|
337
|
+
self.system_prompt = self._build_system_prompt()
|
338
|
+
# Update the agent's system prompt
|
339
|
+
self.agent.system_prompt = self.system_prompt
|
340
|
+
|
341
|
+
def get_code_tools(self) -> List[Any]:
|
342
|
+
"""
|
343
|
+
Get a copy of current code tools.
|
344
|
+
|
345
|
+
Returns:
|
346
|
+
List of current code tools
|
347
|
+
"""
|
348
|
+
return self.code_tools.copy()
|
349
|
+
|
350
|
+
def get_llm_tools(self) -> List[Any]:
|
351
|
+
"""
|
352
|
+
Get a copy of current LLM tools.
|
353
|
+
|
354
|
+
Returns:
|
355
|
+
List of current LLM tools
|
356
|
+
"""
|
357
|
+
return self.tools.copy()
|
358
|
+
|
359
|
+
def set_user_variables(self, variables: Dict[str, Any]):
|
360
|
+
"""
|
361
|
+
Set user variables that will be available in the Python environment.
|
362
|
+
|
363
|
+
Args:
|
364
|
+
variables: Dictionary of variable name -> value pairs
|
365
|
+
"""
|
366
|
+
self.user_variables = variables.copy()
|
367
|
+
self.code_provider.set_user_variables(self.user_variables)
|
368
|
+
# Rebuild system prompt to include new variables info
|
369
|
+
self.system_prompt = self._build_system_prompt()
|
370
|
+
# Update the agent's system prompt
|
371
|
+
self.agent.system_prompt = self.system_prompt
|
372
|
+
|
373
|
+
def add_user_variable(self, name: str, value: Any):
|
374
|
+
"""
|
375
|
+
Add a single user variable.
|
376
|
+
|
377
|
+
Args:
|
378
|
+
name: Variable name
|
379
|
+
value: Variable value
|
380
|
+
"""
|
381
|
+
self.user_variables[name] = value
|
382
|
+
self.code_provider.set_user_variables(self.user_variables)
|
383
|
+
# Rebuild system prompt to include new variables info
|
384
|
+
self.system_prompt = self._build_system_prompt()
|
385
|
+
# Update the agent's system prompt
|
386
|
+
self.agent.system_prompt = self.system_prompt
|
387
|
+
|
388
|
+
def remove_user_variable(self, name: str):
|
389
|
+
"""
|
390
|
+
Remove a user variable.
|
391
|
+
|
392
|
+
Args:
|
393
|
+
name: Variable name to remove
|
394
|
+
"""
|
395
|
+
if name in self.user_variables:
|
396
|
+
del self.user_variables[name]
|
397
|
+
self.code_provider.set_user_variables(self.user_variables)
|
398
|
+
# Rebuild system prompt
|
399
|
+
self.system_prompt = self._build_system_prompt()
|
400
|
+
# Update the agent's system prompt
|
401
|
+
self.agent.system_prompt = self.system_prompt
|
402
|
+
|
403
|
+
def get_user_variables(self) -> Dict[str, Any]:
|
404
|
+
"""
|
405
|
+
Get a copy of current user variables.
|
406
|
+
|
407
|
+
Returns:
|
408
|
+
Dictionary of current user variables
|
409
|
+
"""
|
410
|
+
return self.user_variables.copy()
|
411
|
+
|
412
|
+
def add_pip_packages(self, packages: List[str]):
|
413
|
+
"""
|
414
|
+
Add additional pip packages to the Modal environment.
|
415
|
+
Note: This requires recreating the provider, so it's best to set packages during initialization.
|
416
|
+
|
417
|
+
Args:
|
418
|
+
packages: List of package names to install
|
419
|
+
"""
|
420
|
+
self.pip_packages.extend(packages)
|
421
|
+
self.pip_packages = list(set(self.pip_packages)) # Remove duplicates
|
422
|
+
|
423
|
+
# Note: Adding packages after initialization requires recreating the provider
|
424
|
+
# This is expensive, so it's better to set packages during initialization
|
425
|
+
print("⚠️ Warning: Adding packages after initialization requires recreating the Modal environment.")
|
426
|
+
print(" For better performance, set pip_packages during TinyCodeAgent initialization.")
|
427
|
+
|
428
|
+
# Recreate the provider with new packages
|
429
|
+
self.code_provider = self._create_provider(self.provider, self.provider_config)
|
430
|
+
|
431
|
+
# Re-set user variables if they exist
|
432
|
+
if self.user_variables:
|
433
|
+
self.code_provider.set_user_variables(self.user_variables)
|
434
|
+
|
435
|
+
def get_pip_packages(self) -> List[str]:
|
436
|
+
"""
|
437
|
+
Get a copy of current pip packages.
|
438
|
+
|
439
|
+
Returns:
|
440
|
+
List of pip packages that will be installed in Modal
|
441
|
+
"""
|
442
|
+
return self.pip_packages.copy()
|
443
|
+
|
444
|
+
async def close(self):
|
445
|
+
"""Clean up resources."""
|
446
|
+
await self.code_provider.cleanup()
|
447
|
+
await self.agent.close()
|
448
|
+
|
449
|
+
def clear_conversation(self):
|
450
|
+
"""Clear the conversation history."""
|
451
|
+
self.agent.clear_conversation()
|
452
|
+
|
453
|
+
@property
|
454
|
+
def messages(self):
|
455
|
+
"""Get the conversation messages."""
|
456
|
+
return self.agent.messages
|
457
|
+
|
458
|
+
@property
|
459
|
+
def session_id(self):
|
460
|
+
"""Get the session ID."""
|
461
|
+
return self.agent.session_id
|
462
|
+
|
463
|
+
|
464
|
+
# Example usage demonstrating both LLM tools and code tools
|
465
|
+
async def run_example():
|
466
|
+
"""
|
467
|
+
Example demonstrating TinyCodeAgent with both LLM tools and code tools.
|
468
|
+
Also shows how to use local vs remote execution.
|
469
|
+
|
470
|
+
LLM tools: Available to the LLM for direct calling
|
471
|
+
Code tools: Available in the Python execution environment
|
472
|
+
"""
|
473
|
+
from tinyagent import tool
|
474
|
+
|
475
|
+
# Example LLM tool - available to the LLM for direct calling
|
476
|
+
@tool(name="search_web", description="Search the web for information")
|
477
|
+
async def search_web(query: str) -> str:
|
478
|
+
"""Search the web for information."""
|
479
|
+
return f"Search results for: {query}"
|
480
|
+
|
481
|
+
# Example code tool - available in Python environment
|
482
|
+
@tool(name="data_processor", description="Process data arrays")
|
483
|
+
def data_processor(data: List[float]) -> Dict[str, Any]:
|
484
|
+
"""Process a list of numbers and return statistics."""
|
485
|
+
return {
|
486
|
+
"mean": sum(data) / len(data),
|
487
|
+
"max": max(data),
|
488
|
+
"min": min(data),
|
489
|
+
"count": len(data)
|
490
|
+
}
|
491
|
+
|
492
|
+
print("🚀 Testing TinyCodeAgent with REMOTE execution (Modal)")
|
493
|
+
# Create TinyCodeAgent with remote execution (default)
|
494
|
+
agent_remote = TinyCodeAgent(
|
495
|
+
model="gpt-4.1-mini",
|
496
|
+
tools=[search_web], # LLM tools
|
497
|
+
code_tools=[data_processor], # Code tools
|
498
|
+
user_variables={
|
499
|
+
"sample_data": [1, 2, 3, 4, 5, 10, 15, 20]
|
500
|
+
},
|
501
|
+
local_execution=False # Remote execution via Modal (default)
|
502
|
+
)
|
503
|
+
|
504
|
+
# Connect to MCP servers
|
505
|
+
await agent_remote.connect_to_server("npx", ["-y", "@openbnb/mcp-server-airbnb", "--ignore-robots-txt"])
|
506
|
+
await agent_remote.connect_to_server("npx", ["-y", "@modelcontextprotocol/server-sequential-thinking"])
|
507
|
+
|
508
|
+
# Test the remote agent
|
509
|
+
response_remote = await agent_remote.run("""
|
510
|
+
I have some sample data. Please use the data_processor tool in Python to analyze my sample_data
|
511
|
+
and show me the results.
|
512
|
+
""")
|
513
|
+
|
514
|
+
print("Remote Agent Response:")
|
515
|
+
print(response_remote)
|
516
|
+
print("\n" + "="*80 + "\n")
|
517
|
+
|
518
|
+
# Now test with local execution
|
519
|
+
print("🏠 Testing TinyCodeAgent with LOCAL execution")
|
520
|
+
agent_local = TinyCodeAgent(
|
521
|
+
model="gpt-4.1-mini",
|
522
|
+
tools=[search_web], # LLM tools
|
523
|
+
code_tools=[data_processor], # Code tools
|
524
|
+
user_variables={
|
525
|
+
"sample_data": [1, 2, 3, 4, 5, 10, 15, 20]
|
526
|
+
},
|
527
|
+
local_execution=True # Local execution
|
528
|
+
)
|
529
|
+
|
530
|
+
# Connect to MCP servers
|
531
|
+
await agent_local.connect_to_server("npx", ["-y", "@openbnb/mcp-server-airbnb", "--ignore-robots-txt"])
|
532
|
+
await agent_local.connect_to_server("npx", ["-y", "@modelcontextprotocol/server-sequential-thinking"])
|
533
|
+
|
534
|
+
# Test the local agent
|
535
|
+
response_local = await agent_local.run("""
|
536
|
+
I have some sample data. Please use the data_processor tool in Python to analyze my sample_data
|
537
|
+
and show me the results.
|
538
|
+
""")
|
539
|
+
|
540
|
+
print("Local Agent Response:")
|
541
|
+
print(response_local)
|
542
|
+
|
543
|
+
# Demonstrate adding tools dynamically
|
544
|
+
@tool(name="validator", description="Validate processed results")
|
545
|
+
def validator(results: Dict[str, Any]) -> bool:
|
546
|
+
"""Validate that results make sense."""
|
547
|
+
return all(key in results for key in ["mean", "max", "min", "count"])
|
548
|
+
|
549
|
+
# Add a new code tool to both agents
|
550
|
+
agent_remote.add_code_tool(validator)
|
551
|
+
agent_local.add_code_tool(validator)
|
552
|
+
|
553
|
+
print("\n" + "="*80)
|
554
|
+
print("🔧 Testing with dynamically added tools")
|
555
|
+
|
556
|
+
# Test both agents with the new tool
|
557
|
+
validation_prompt = "Now validate the previous analysis results using the validator tool."
|
558
|
+
|
559
|
+
response2_remote = await agent_remote.run(validation_prompt)
|
560
|
+
print("Remote Agent Validation Response:")
|
561
|
+
print(response2_remote)
|
562
|
+
|
563
|
+
response2_local = await agent_local.run(validation_prompt)
|
564
|
+
print("Local Agent Validation Response:")
|
565
|
+
print(response2_local)
|
566
|
+
|
567
|
+
await agent_remote.close()
|
568
|
+
await agent_local.close()
|
569
|
+
|
570
|
+
|
571
|
+
if __name__ == "__main__":
|
572
|
+
import asyncio
|
573
|
+
asyncio.run(run_example())
|
@@ -0,0 +1,41 @@
|
|
1
|
+
from tinyagent import tool
|
2
|
+
|
3
|
+
# Global variables to track state across calls
|
4
|
+
weather_global = '-'
|
5
|
+
traffic_global = '-'
|
6
|
+
|
7
|
+
|
8
|
+
@tool(name="get_weather", description="Get the weather for a given city.")
|
9
|
+
def get_weather(city: str) -> str:
|
10
|
+
"""Get the weather for a given city.
|
11
|
+
Args:
|
12
|
+
city: The city to get the weather for
|
13
|
+
|
14
|
+
Returns:
|
15
|
+
The weather for the given city
|
16
|
+
"""
|
17
|
+
import random
|
18
|
+
global weather_global
|
19
|
+
output = f"Last time weather was checked was {weather_global}"
|
20
|
+
weather_global = random.choice(['sunny', 'cloudy', 'rainy', 'snowy'])
|
21
|
+
output += f"\n\nThe weather in {city} is now {weather_global}"
|
22
|
+
|
23
|
+
return output
|
24
|
+
|
25
|
+
|
26
|
+
@tool(name="get_traffic", description="Get the traffic for a given city.")
|
27
|
+
def get_traffic(city: str) -> str:
|
28
|
+
"""Get the traffic for a given city.
|
29
|
+
Args:
|
30
|
+
city: The city to get the traffic for
|
31
|
+
|
32
|
+
Returns:
|
33
|
+
The traffic for the given city
|
34
|
+
"""
|
35
|
+
import random
|
36
|
+
global traffic_global
|
37
|
+
output = f"Last time traffic was checked was {traffic_global}"
|
38
|
+
traffic_global = random.choice(['light', 'moderate', 'heavy', 'blocked'])
|
39
|
+
output += f"\n\nThe traffic in {city} is now {traffic_global}"
|
40
|
+
|
41
|
+
return output
|