|
|
from __future__ import annotations |
|
|
|
|
|
import os |
|
|
import sys |
|
|
import types |
|
|
import ast |
|
|
from io import StringIO |
|
|
from typing import Annotated |
|
|
import importlib.metadata |
|
|
|
|
|
import gradio as gr |
|
|
from ._docstrings import autodoc |
|
|
from .File_System import ROOT_DIR, File_System |
|
|
from .Web_Fetch import Web_Fetch |
|
|
from .Web_Search import Web_Search |
|
|
from .Memory_Manager import Memory_Manager |
|
|
from .Generate_Speech import Generate_Speech, List_Kokoro_Voices, List_Supertonic_Voices |
|
|
from .Generate_Image import Generate_Image |
|
|
from .Generate_Video import Generate_Video |
|
|
from .Deep_Research import Deep_Research |
|
|
from .Obsidian_Vault import Obsidian_Vault |
|
|
from .Shell_Command import Shell_Command |
|
|
from .Code_Interpreter import Code_Interpreter |
|
|
|
|
|
from app import _log_call_end, _log_call_start, _truncate_for_log |
|
|
|
|
|
def search_packages(query: str = "") -> str: |
|
|
"""Search for installed Python packages by name. If query is empty, lists all.""" |
|
|
packages = [] |
|
|
query = query.lower() |
|
|
for dist in importlib.metadata.distributions(): |
|
|
name = dist.metadata['Name'] |
|
|
if query in name.lower(): |
|
|
packages.append(f"{name} ({dist.version})") |
|
|
packages.sort() |
|
|
if not packages: |
|
|
return f"No packages found matching '{query}'." |
|
|
return "\n".join(packages) |
|
|
|
|
|
def _get_tools_map(): |
|
|
return { |
|
|
"Web_Fetch": Web_Fetch, |
|
|
"Web_Search": Web_Search, |
|
|
"Memory_Manager": Memory_Manager, |
|
|
"Generate_Speech": Generate_Speech, |
|
|
"List_Kokoro_Voices": List_Kokoro_Voices, |
|
|
"List_Supertonic_Voices": List_Supertonic_Voices, |
|
|
"Generate_Image": Generate_Image, |
|
|
"Generate_Video": Generate_Video, |
|
|
"Deep_Research": Deep_Research, |
|
|
"File_System": File_System, |
|
|
"Obsidian_Vault": Obsidian_Vault, |
|
|
"Shell_Command": Shell_Command, |
|
|
"Code_Interpreter": Code_Interpreter, |
|
|
} |
|
|
|
|
|
def list_tools() -> list[str]: |
|
|
"""List all available tools in the Code Interpreter environment.""" |
|
|
return list(_get_tools_map().keys()) |
|
|
|
|
|
def search_tools(query: str) -> str: |
|
|
"""Search for tools by name or description. Returns usage info for matches.""" |
|
|
query = query.lower() |
|
|
matches = [] |
|
|
tools = _get_tools_map() |
|
|
for name, func in tools.items(): |
|
|
doc = (func.__doc__ or "").lower() |
|
|
if query in name.lower() or query in doc: |
|
|
matches.append((name, func)) |
|
|
|
|
|
if not matches: |
|
|
return f"No tools found matching '{query}'." |
|
|
|
|
|
output = [] |
|
|
for name, func in matches: |
|
|
output.append(f"--- {name} ---") |
|
|
output.append(func.__doc__ or "No documentation available.") |
|
|
output.append("") |
|
|
return "\n".join(output) |
|
|
|
|
|
def usage(tool_name: str) -> str: |
|
|
"""Get detailed usage information for a specific tool.""" |
|
|
tools = _get_tools_map() |
|
|
if tool_name not in tools: |
|
|
return f"Tool '{tool_name}' not found. Available tools: {', '.join(tools.keys())}" |
|
|
func = tools[tool_name] |
|
|
return f"--- {tool_name} ---\n{func.__doc__ or 'No documentation available.'}" |
|
|
|
|
|
def _initialize_mock_modules(): |
|
|
""" |
|
|
Registers a mock 'functions' module in sys.modules so that LLMs |
|
|
can do 'from functions import ...' without error. |
|
|
""" |
|
|
mock_module = types.ModuleType("functions") |
|
|
|
|
|
|
|
|
for name, tool in _get_tools_map().items(): |
|
|
setattr(mock_module, name, tool) |
|
|
|
|
|
|
|
|
helpers = { |
|
|
"list_tools": list_tools, |
|
|
"search_tools": search_tools, |
|
|
"usage": usage, |
|
|
"search_packages": search_packages, |
|
|
} |
|
|
for name, func in helpers.items(): |
|
|
setattr(mock_module, name, func) |
|
|
|
|
|
sys.modules["functions"] = mock_module |
|
|
|
|
|
_initialize_mock_modules() |
|
|
|
|
|
|
|
|
TOOL_SUMMARY = ( |
|
|
"Executes Python code as the unified interface for the entire tools ecosystem. " |
|
|
"All tool interactions must happen through this code-execution gateway. " |
|
|
"Use Agent Terminal repeatedly whenever you need to chain or combine tool operations. " |
|
|
"Available tools: `Web_Fetch`, `Web_Search`, `Code_Interpreter`, `Shell_Command`, `File_System`, `Obsidian_Vault`, `Memory_Manager`, `Generate_Speech`, `Generate_Image`, `Generate_Video`, `Deep_Research`." |
|
|
) |
|
|
|
|
|
|
|
|
|
|
|
@autodoc( |
|
|
summary=TOOL_SUMMARY, |
|
|
) |
|
|
def Agent_Terminal(code: Annotated[str, ( |
|
|
"Python source code to run; stdout is captured and returned. " |
|
|
"Execute these commands: " |
|
|
"`search_tools('query')` to search for tools by name or capability; " |
|
|
"`list_tools()` to list all available tools; " |
|
|
"`usage('ToolName')` to inspect a tool’s expected input parameters; " |
|
|
"`search_packages('query')` to search for installed Python libraries." |
|
|
)]) -> str: |
|
|
_log_call_start("Agent_Terminal", code=_truncate_for_log(code or "", 300)) |
|
|
if code is None: |
|
|
result = "No code provided." |
|
|
_log_call_end("Agent_Terminal", result) |
|
|
return result |
|
|
old_stdout = sys.stdout |
|
|
old_cwd = os.getcwd() |
|
|
redirected_output = sys.stdout = StringIO() |
|
|
|
|
|
|
|
|
tools_env = { |
|
|
"Web_Fetch": Web_Fetch, |
|
|
"Web_Search": Web_Search, |
|
|
"Memory_Manager": Memory_Manager, |
|
|
"Generate_Speech": Generate_Speech, |
|
|
"List_Kokoro_Voices": List_Kokoro_Voices, |
|
|
"List_Supertonic_Voices": List_Supertonic_Voices, |
|
|
"Generate_Image": Generate_Image, |
|
|
"Generate_Video": Generate_Video, |
|
|
"Deep_Research": Deep_Research, |
|
|
"File_System": File_System, |
|
|
"Obsidian_Vault": Obsidian_Vault, |
|
|
"Shell_Command": Shell_Command, |
|
|
"Code_Interpreter": Code_Interpreter, |
|
|
"list_tools": list_tools, |
|
|
"search_tools": search_tools, |
|
|
"usage": usage, |
|
|
"search_packages": search_packages, |
|
|
"print": print, |
|
|
"__builtins__": __builtins__, |
|
|
} |
|
|
|
|
|
try: |
|
|
os.chdir(ROOT_DIR) |
|
|
|
|
|
|
|
|
tree = ast.parse(code) |
|
|
if tree.body and isinstance(tree.body[-1], ast.Expr): |
|
|
last_node = tree.body.pop() |
|
|
|
|
|
|
|
|
if tree.body: |
|
|
exec(compile(tree, filename="<string>", mode="exec"), tools_env) |
|
|
|
|
|
|
|
|
expr = compile(ast.Expression(last_node.value), filename="<string>", mode="eval") |
|
|
result_val = eval(expr, tools_env) |
|
|
if result_val is not None: |
|
|
print(result_val) |
|
|
else: |
|
|
exec(code, tools_env) |
|
|
|
|
|
result = redirected_output.getvalue() |
|
|
except Exception as exc: |
|
|
result = str(exc) |
|
|
finally: |
|
|
sys.stdout = old_stdout |
|
|
try: |
|
|
os.chdir(old_cwd) |
|
|
except Exception: |
|
|
pass |
|
|
_log_call_end("Agent_Terminal", _truncate_for_log(result)) |
|
|
return result |
|
|
|
|
|
|
|
|
def build_interface() -> gr.Interface: |
|
|
return gr.Interface( |
|
|
fn=Agent_Terminal, |
|
|
inputs=gr.Code(label="Python Code", language="python"), |
|
|
outputs=gr.Textbox(label="Output", lines=5, max_lines=20), |
|
|
title="Agent Terminal", |
|
|
description="<div style=\"text-align:center\">Interact with all other tools via a Python API. Reduces token usage by 90%.</div>", |
|
|
api_description=TOOL_SUMMARY, |
|
|
flagging_mode="never", |
|
|
) |
|
|
|
|
|
|
|
|
__all__ = ["Agent_Terminal", "build_interface"] |
|
|
|