| | import asyncio |
| | import sys |
| | from tinyagent import tool |
| | from textwrap import dedent |
| | from typing import Optional, List, Dict, Any,Union |
| | from tinyagent.hooks.logging_manager import LoggingManager |
| | import modal |
| | import cloudpickle |
| |
|
| |
|
| |
|
| | def clean_response(resp): |
| | return {k:v for k,v in resp.items() if k in ['printed_output','return_value','stderr','error_traceback']} |
| |
|
| | def make_session_blob(ns: dict) -> bytes: |
| | clean = {} |
| | for name, val in ns.items(): |
| | try: |
| | |
| | cloudpickle.dumps(val) |
| | except Exception: |
| | |
| | continue |
| | else: |
| | clean[name] = val |
| |
|
| | return cloudpickle.dumps(clean) |
| |
|
| | def _run_python(code: str,globals_dict:Dict[str,Any]={},locals_dict:Dict[str,Any]={}): |
| |
|
| | import contextlib |
| | import traceback |
| | import io |
| | import ast |
| | |
| | |
| | updated_globals = globals_dict.copy() |
| | updated_locals = locals_dict.copy() |
| | |
| | |
| | |
| | essential_modules = ['requests', 'json', 'os', 'sys', 'time', 'datetime', 're', 'random', 'math'] |
| | |
| | for module_name in essential_modules: |
| | try: |
| | module = __import__(module_name) |
| | updated_globals[module_name] = module |
| | print(f"✓ {module_name} module loaded successfully") |
| | except ImportError: |
| | print(f"⚠️ Warning: {module_name} module not available") |
| | |
| | tree = ast.parse(code, mode="exec") |
| | compiled = compile(tree, filename="<ast>", mode="exec") |
| | stdout_buf = io.StringIO() |
| | stderr_buf = io.StringIO() |
| |
|
| | |
| | error_traceback = None |
| | output = None |
| |
|
| | with contextlib.redirect_stdout(stdout_buf), contextlib.redirect_stderr(stderr_buf): |
| | try: |
| | output = exec(code, updated_globals, updated_locals) |
| | except Exception: |
| | |
| | error_traceback = traceback.format_exc() |
| |
|
| |
|
| | printed_output = stdout_buf.getvalue() |
| | stderr_output = stderr_buf.getvalue() |
| | error_traceback_output = error_traceback |
| |
|
| | return { |
| | "printed_output": printed_output, |
| | "return_value": output, |
| | "stderr": stderr_output, |
| | "error_traceback": error_traceback_output, |
| | "updated_globals": updated_globals, |
| | "updated_locals": updated_locals |
| | } |
| |
|
| |
|
| | class PythonCodeInterpreter: |
| | executed_default_codes = False |
| | PYTHON_VERSION = f"{sys.version_info.major}.{sys.version_info.minor}" |
| | sandbox_name = "tinycodeagent-sandbox" |
| | app = None |
| | _app_run_python = None |
| | _globals_dict = {} |
| | _locals_dict = {} |
| | def __init__(self,log_manager: LoggingManager, |
| | default_python_codes:Optional[List[str]]=[], |
| | code_tools:List[Dict[str,Any]]=[], |
| | pip_packages:List[str]=[], |
| | modal_secrets:Dict[str,Union[str,None]]={}, |
| | lazy_init:bool=True, |
| | **kwargs): |
| | self.log_manager = log_manager |
| | self.code_tools = code_tools |
| |
|
| | self._globals_dict.update(**kwargs.get("globals_dict",{})) |
| | self._locals_dict.update(**kwargs.get("locals_dict",{})) |
| |
|
| | self.default_python_codes = default_python_codes |
| |
|
| | self.modal_secrets = modal.Secret.from_dict(modal_secrets) |
| | self.pip_packages = list(set(["cloudpickle","requests","tinyagent-py[all]==0.0.8", |
| | "gradio", |
| | "arize-phoenix-otel",]+pip_packages)) |
| | self.lazy_init = lazy_init |
| | self.create_app(self.modal_secrets,self.pip_packages,self.code_tools) |
| | |
| |
|
| | def create_app(self,modal_secrets:Dict[str,Union[str,None]],pip_packages:List[str]=[],code_tools:List[Dict[str,Any]]=[]): |
| | |
| | agent_image = modal.Image.debian_slim(python_version=self.PYTHON_VERSION).pip_install( |
| | |
| | *pip_packages |
| | ) |
| | self.app = modal.App( |
| | name=self.sandbox_name, |
| | image=agent_image, |
| | secrets=[modal_secrets] |
| | ) |
| | self._app_run_python = self.app.function()(_run_python) |
| | self.add_tools(code_tools) |
| | return self.app |
| | |
| |
|
| | def add_tools(self,tools): |
| |
|
| | tools_str_list = ["import cloudpickle"] |
| | tools_str_list.append("###########<tools>###########\n") |
| | for tool in tools: |
| | tools_str_list.append(f"globals()['{tool._tool_metadata['name']}'] = cloudpickle.loads( {cloudpickle.dumps(tool)})") |
| | tools_str_list.append("\n\n") |
| | tools_str_list.append("###########</tools>###########\n") |
| | tools_str_list.append("\n\n") |
| | self.default_python_codes.extend(tools_str_list) |
| | |
| | |
| | def _python_executor(self,code: str,globals_dict:Dict[str,Any]={},locals_dict:Dict[str,Any]={}): |
| | with self.app.run(): |
| | if self.executed_default_codes: |
| | print("✔️ default codes already executed") |
| | full_code = code |
| | else: |
| | full_code = "\n".join(self.default_python_codes)+ "\n\n"+(code) |
| | self.executed_default_codes = True |
| | return self._app_run_python.remote(full_code,globals_dict,locals_dict) |
| |
|
| |
|
| | @tool(name="run_python",description=dedent(""" |
| | This tool receive python code, and execute it, |
| | During each intermediate step, you can use 'print()' to save whatever important information you will then need. |
| | These print outputs will then appear in the 'Observation:' field, which will be available as input for the next step. |
| | |
| | |
| | Args: |
| | code_lines: list[str]: The python code to execute, it should be a valid python code, and it should be able to run without any errors. |
| | Your code should be include all the steps neccesary for successful run, cover edge cases, error handling |
| | In case of an error, you will see the error, so get the most out of print function to debug your code. |
| | Each line of code should be an independent line of code, and it should be able to run without any errors. |
| | |
| | Returns: |
| | Status of code execution or error message. |
| | |
| | """)) |
| |
|
| | async def run_python(self,code_lines:list[str],timeout:int=120) -> str: |
| | |
| | |
| |
|
| | if type(code_lines) == str: |
| | code_lines = [code_lines] |
| | code = code_lines |
| | |
| | full_code = "\n".join(code) |
| | print("##"*50) |
| | print("#########################code#########################") |
| | print(full_code) |
| | print("##"*50) |
| |
|
| | response = self._python_executor(full_code,self._globals_dict,self._locals_dict) |
| | print("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!<response>!!!!!!!!!!!!!!!!!!!!!!!!!") |
| | |
| | self._globals_dict = cloudpickle.loads(make_session_blob(response["updated_globals"])) |
| | self._locals_dict = cloudpickle.loads(make_session_blob(response["updated_locals"])) |
| | |
| | print("#########################<printed_output>#########################") |
| | print(response["printed_output"]) |
| | print("#########################</printed_output>#########################") |
| | print("#########################<return_value>#########################") |
| | print(response["return_value"]) |
| | print("#########################</return_value>#########################") |
| | print("#########################<stderr>#########################") |
| | print(response["stderr"]) |
| | print("#########################</stderr>#########################") |
| | print("#########################<traceback>#########################") |
| | print(response["error_traceback"]) |
| | print("#########################</traceback>#########################") |
| |
|
| | return clean_response(response) |
| | |
| |
|
| |
|
| | weather_global = '-' |
| | traffic_global = '-' |
| |
|
| | @tool(name="get_weather",description="Get the weather for a given city.") |
| | def get_weather(city: str)->str: |
| | """Get the weather for a given city. |
| | Args: |
| | city: The city to get the weather for |
| | |
| | Returns: |
| | The weather for the given city |
| | """ |
| | import random |
| | global weather_global |
| | output = f"Last time weather was checked was {weather_global}" |
| | weather_global = random.choice(['sunny','cloudy','rainy','snowy']) |
| | output += f"\n\nThe weather in {city} is now {weather_global}" |
| |
|
| | return output |
| |
|
| |
|
| | @tool(name="get_traffic",description="Get the traffic for a given city.") |
| | def get_traffic(city: str)->str: |
| | """Get the traffic for a given city. |
| | Args: |
| | city: The city to get the traffic for |
| | |
| | Returns: |
| | The traffic for the given city |
| | """ |
| | import random |
| | global traffic_global |
| | output = f"Last time traffic was checked was {traffic_global}" |
| | traffic_global = random.choice(['light','moderate','heavy','blocked']) |
| | output += f"\n\nThe traffic in {city} is now {traffic_global}" |
| |
|
| | return output |
| |
|
| |
|
| |
|
| | async def run_example(): |
| | """Example usage of GradioCallback with TinyAgent.""" |
| | import os |
| | import sys |
| | import tempfile |
| | import shutil |
| | import asyncio |
| | from tinyagent import TinyAgent |
| | from tinyagent.hooks.logging_manager import LoggingManager |
| | from tinyagent.hooks.gradio_callback import GradioCallback |
| | import logging |
| |
|
| |
|
| | |
| | log_manager = LoggingManager(default_level=logging.INFO) |
| | log_manager.set_levels({ |
| | 'tinyagent.hooks.gradio_callback': logging.DEBUG, |
| | 'tinyagent.tiny_agent': logging.DEBUG, |
| | 'tinyagent.mcp_client': logging.DEBUG, |
| | }) |
| | console_handler = logging.StreamHandler(sys.stdout) |
| | log_manager.configure_handler( |
| | console_handler, |
| | format_string='%(asctime)s - %(name)s - %(levelname)s - %(message)s', |
| | level=logging.DEBUG |
| | ) |
| | ui_logger = log_manager.get_logger('tinyagent.hooks.gradio_callback') |
| | agent_logger = log_manager.get_logger('tinyagent.tiny_agent') |
| | ui_logger.info("--- Starting GradioCallback Example ---") |
| | |
| |
|
| | |
| | |
| | |
| | model = "gpt-4.1-mini" |
| | api_key = os.environ.get("OPENAI_API_KEY") |
| | if not api_key: |
| | ui_logger.error("NEBIUS_API_KEY environment variable not set.") |
| | return |
| |
|
| | |
| | upload_folder = tempfile.mkdtemp(prefix="gradio_uploads_") |
| | ui_logger.info(f"Created temporary upload folder: {upload_folder}") |
| |
|
| | |
| | loop = asyncio.get_event_loop() |
| | ui_logger.debug(f"Using event loop: {loop}") |
| |
|
| | |
| | |
| | from helper import translate_tool_for_code_agent,load_template,render_system_prompt,prompt_code_example,prompt_qwen_helper |
| | tools = [get_weather,get_traffic] |
| |
|
| | tools_meta_data = {} |
| | for tool in tools: |
| | metadata = translate_tool_for_code_agent(tool) |
| | tools_meta_data[metadata["name"]] = metadata |
| | template_str = load_template("./prompts/code_agent.yaml") |
| | system_prompt = render_system_prompt(template_str, tools_meta_data, {}, ["tinyagent","gradio","requests","asyncio"]) + prompt_code_example + prompt_qwen_helper |
| | agent = TinyAgent(model=model, api_key=api_key, |
| | |
| | logger=agent_logger, |
| | system_prompt=system_prompt, |
| | |
| | ) |
| | python_interpreter = PythonCodeInterpreter(log_manager=log_manager,code_tools=tools,pip_packages=["tinyagent-py[all]","requests","cloudpickle"], |
| | default_python_codes=["import random","import requests","import cloudpickle","import tempfile","import shutil","import asyncio","import logging","import time"]) |
| | agent.add_tool(python_interpreter.run_python) |
| |
|
| |
|
| | |
| | gradio_ui = GradioCallback( |
| | file_upload_folder=upload_folder, |
| | show_thinking=True, |
| | show_tool_calls=True, |
| | logger=ui_logger |
| | ) |
| | agent.add_callback(gradio_ui) |
| |
|
| | |
| | try: |
| | ui_logger.info("Connecting to MCP servers...") |
| | |
| | |
| | await agent.connect_to_server("npx", ["-y", "@modelcontextprotocol/server-sequential-thinking"]) |
| | ui_logger.info("Connected to MCP servers.") |
| | except Exception as e: |
| | ui_logger.error(f"Failed to connect to MCP servers: {e}", exc_info=True) |
| | |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | ui_logger.info("Launching Gradio interface...") |
| | try: |
| | |
| | |
| | |
| | |
| | |
| | |
| | gradio_ui.launch( |
| | agent, |
| | title="TinyCodeAgent Chat Interface", |
| | description="Chat with TinyAgent. Try asking: 'I need to know the weather and traffic in Toronto, Montreal, New York, Paris and San Francisco.'", |
| | share=False, |
| | prevent_thread_lock=True, |
| | show_error=True, |
| | mcp_server=True, |
| | ) |
| | ui_logger.info("Gradio interface launched (non-blocking).") |
| | |
| | |
| | |
| | |
| | while True: |
| | await asyncio.sleep(1) |
| | |
| | except KeyboardInterrupt: |
| | ui_logger.info("Received keyboard interrupt, shutting down...") |
| | except Exception as e: |
| | ui_logger.error(f"Failed to launch or run Gradio app: {e}", exc_info=True) |
| | finally: |
| | |
| | ui_logger.info("Cleaning up resources...") |
| | if os.path.exists(upload_folder): |
| | ui_logger.info(f"Removing temporary upload folder: {upload_folder}") |
| | shutil.rmtree(upload_folder) |
| | await agent.close() |
| | ui_logger.info("--- GradioCallback Example Finished ---") |
| |
|
| | if __name__ == "__main__": |
| | asyncio.run(run_example()) |