Nymbo commited on
Commit
6fcf4d0
·
verified ·
1 Parent(s): ad14ea1

Update Modules/Agent_Terminal.py

Browse files
Files changed (1) hide show
  1. Modules/Agent_Terminal.py +172 -56
Modules/Agent_Terminal.py CHANGED
@@ -4,8 +4,10 @@ import os
4
  import sys
5
  import types
6
  import ast
 
 
7
  from io import StringIO
8
- from typing import Annotated
9
  import importlib.metadata
10
 
11
  import gradio as gr
@@ -24,6 +26,145 @@ from .Code_Interpreter import Code_Interpreter
24
 
25
  from app import _log_call_end, _log_call_start, _truncate_for_log
26
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
27
  def search_packages(query: str = "") -> str:
28
  """Search for installed Python packages by name. If query is empty, lists all."""
29
  packages = []
@@ -38,7 +179,8 @@ def search_packages(query: str = "") -> str:
38
  return "\n".join(packages)
39
 
40
  def _get_tools_map():
41
- return {
 
42
  "Web_Fetch": Web_Fetch,
43
  "Web_Search": Web_Search,
44
  "Memory_Manager": Memory_Manager,
@@ -53,10 +195,7 @@ def _get_tools_map():
53
  "Shell_Command": Shell_Command,
54
  "Code_Interpreter": Code_Interpreter,
55
  }
56
-
57
- def list_tools() -> list[str]:
58
- """List all available tools in the Code Interpreter environment."""
59
- return list(_get_tools_map().keys())
60
 
61
  def search_tools(query: str) -> str:
62
  """Search for tools by name or description. Returns usage info for matches."""
@@ -64,7 +203,9 @@ def search_tools(query: str) -> str:
64
  matches = []
65
  tools = _get_tools_map()
66
  for name, func in tools.items():
67
- doc = (func.__doc__ or "").lower()
 
 
68
  if query in name.lower() or query in doc:
69
  matches.append((name, func))
70
 
@@ -73,35 +214,25 @@ def search_tools(query: str) -> str:
73
 
74
  output = []
75
  for name, func in matches:
76
- output.append(f"--- {name} ---")
77
- output.append(func.__doc__ or "No documentation available.")
78
  output.append("")
79
  return "\n".join(output)
80
 
81
- def usage(tool_name: str) -> str:
82
- """Get detailed usage information for a specific tool."""
83
- tools = _get_tools_map()
84
- if tool_name not in tools:
85
- return f"Tool '{tool_name}' not found. Available tools: {', '.join(tools.keys())}"
86
- func = tools[tool_name]
87
- return f"--- {tool_name} ---\n{func.__doc__ or 'No documentation available.'}"
88
-
89
  def _initialize_mock_modules():
90
  """
91
  Registers a mock 'functions' module in sys.modules so that LLMs
92
  can do 'from functions import ...' without error.
 
93
  """
94
  mock_module = types.ModuleType("functions")
95
 
96
- # Add tools
97
  for name, tool in _get_tools_map().items():
98
  setattr(mock_module, name, tool)
99
 
100
  # Add helpers
101
  helpers = {
102
- "list_tools": list_tools,
103
  "search_tools": search_tools,
104
- "usage": usage,
105
  "search_packages": search_packages,
106
  }
107
  for name, func in helpers.items():
@@ -114,8 +245,7 @@ _initialize_mock_modules()
114
  # Single source of truth for the LLM-facing tool description
115
  TOOL_SUMMARY = (
116
  "Executes Python code as the unified interface for the entire tools ecosystem. "
117
- "All tool interactions must happen through this code-execution gateway. "
118
- "Use Agent Terminal repeatedly whenever you need to chain or combine tool operations. "
119
  "Available tools: `Web_Fetch`, `Web_Search`, `Code_Interpreter`, `Shell_Command`, `File_System`, `Obsidian_Vault`, `Memory_Manager`, `Generate_Speech`, `Generate_Image`, `Generate_Video`, `Deep_Research`."
120
  )
121
 
@@ -126,10 +256,9 @@ TOOL_SUMMARY = (
126
  )
127
  def Agent_Terminal(input: Annotated[str, (
128
  "Python source code to run; stdout is captured and returned. "
129
- "Execute these commands: "
130
- "`search_tools('query')` to search for tools by name or capability; "
131
- "`list_tools()` to list all available tools; "
132
- "`usage('ToolName')` to inspect a tool’s expected input parameters; "
133
  "`search_packages('query')` to search for installed Python libraries."
134
  )]) -> str:
135
  _log_call_start("Agent_Terminal", input=_truncate_for_log(input or "", 300))
@@ -141,24 +270,13 @@ def Agent_Terminal(input: Annotated[str, (
141
  old_cwd = os.getcwd()
142
  redirected_output = sys.stdout = StringIO()
143
 
 
 
 
144
  # Prepare the execution environment with all tools
145
  tools_env = {
146
- "Web_Fetch": Web_Fetch,
147
- "Web_Search": Web_Search,
148
- "Memory_Manager": Memory_Manager,
149
- "Generate_Speech": Generate_Speech,
150
- "List_Kokoro_Voices": List_Kokoro_Voices,
151
- "List_Supertonic_Voices": List_Supertonic_Voices,
152
- "Generate_Image": Generate_Image,
153
- "Generate_Video": Generate_Video,
154
- "Deep_Research": Deep_Research,
155
- "File_System": File_System,
156
- "Obsidian_Vault": Obsidian_Vault,
157
- "Shell_Command": Shell_Command,
158
- "Code_Interpreter": Code_Interpreter,
159
- "list_tools": list_tools,
160
  "search_tools": search_tools,
161
- "usage": usage,
162
  "search_packages": search_packages,
163
  "print": print, # Ensure print is available
164
  "__builtins__": __builtins__,
@@ -167,22 +285,20 @@ def Agent_Terminal(input: Annotated[str, (
167
  try:
168
  os.chdir(ROOT_DIR)
169
 
170
- # Parse code to check if the last statement is an expression
171
  tree = ast.parse(input)
172
- if tree.body and isinstance(tree.body[-1], ast.Expr):
173
- last_node = tree.body.pop()
174
-
175
- # Execute preceding statements
176
- if tree.body:
177
- exec(compile(tree, filename="<string>", mode="exec"), tools_env)
178
-
179
- # Evaluate and print the last expression
180
- expr = compile(ast.Expression(last_node.value), filename="<string>", mode="eval")
181
- result_val = eval(expr, tools_env)
182
- if result_val is not None:
183
- print(result_val)
184
- else:
185
- exec(input, tools_env)
186
 
187
  result = redirected_output.getvalue()
188
  except Exception as exc: # pylint: disable=broad-except
 
4
  import sys
5
  import types
6
  import ast
7
+ import inspect
8
+ import functools
9
  from io import StringIO
10
+ from typing import Annotated, get_type_hints, get_origin, get_args
11
  import importlib.metadata
12
 
13
  import gradio as gr
 
26
 
27
  from app import _log_call_end, _log_call_start, _truncate_for_log
28
 
29
+
30
+ # Example usages for each tool - simple and advanced
31
+ _TOOL_EXAMPLES = {
32
+ "Web_Fetch": (
33
+ 'Web_Fetch(url="https://example.com")',
34
+ 'Web_Fetch(url="https://example.com", max_chars=5000, mode="url_scraper")',
35
+ ),
36
+ "Web_Search": (
37
+ 'Web_Search(query="Python tutorials")',
38
+ 'Web_Search(query="AI news", max_results=10, search_type="news", date_filter="week")',
39
+ ),
40
+ "Code_Interpreter": (
41
+ 'Code_Interpreter(code="print(2 + 2)")',
42
+ 'Code_Interpreter(code="import math; print(math.pi)", timeout=60)',
43
+ ),
44
+ "Shell_Command": (
45
+ 'Shell_Command(command="echo Hello")',
46
+ 'Shell_Command(command="ls -la", timeout=30)',
47
+ ),
48
+ "File_System": (
49
+ 'File_System(action="list", path="/")',
50
+ 'File_System(action="read", path="/notes.txt", max_chars=5000)',
51
+ ),
52
+ "Obsidian_Vault": (
53
+ 'Obsidian_Vault(action="list", path="/")',
54
+ 'Obsidian_Vault(action="search", query="meeting notes", recursive=True)',
55
+ ),
56
+ "Memory_Manager": (
57
+ 'Memory_Manager(action="list")',
58
+ 'Memory_Manager(action="save", text="Remember this fact", tags="important, facts")',
59
+ ),
60
+ "Generate_Speech": (
61
+ 'Generate_Speech(text="Hello, world!")',
62
+ 'Generate_Speech(text="Welcome to the demo", model="Kokoro", voice="af_heart", speed=1.2)',
63
+ ),
64
+ "Generate_Image": (
65
+ 'Generate_Image(prompt="A sunset over mountains")',
66
+ 'Generate_Image(prompt="A cyberpunk city", steps=50, cfg_scale=9.0, width=1024, height=768)',
67
+ ),
68
+ "Generate_Video": (
69
+ 'Generate_Video(prompt="A cat playing piano")',
70
+ 'Generate_Video(prompt="Ocean waves", duration=5, aspect_ratio="16:9")',
71
+ ),
72
+ "Deep_Research": (
73
+ 'Deep_Research(query="Climate change effects")',
74
+ 'Deep_Research(query="Quantum computing advances", max_sources=10, search_type="news")',
75
+ ),
76
+ }
77
+
78
+
79
+ def _format_tool_usage(func) -> str:
80
+ """Generate detailed usage information for a tool function."""
81
+ name = func.__name__
82
+ doc = func.__doc__ or "No description available."
83
+
84
+ # Extract just the summary (first paragraph) - skip Args/Returns sections
85
+ # since we generate our own detailed parameter list
86
+ doc_lines = doc.strip().split('\n')
87
+ summary_lines = []
88
+ for line in doc_lines:
89
+ stripped = line.strip().lower()
90
+ # Stop at Args:, Returns:, Parameters:, etc.
91
+ if stripped.startswith(('args:', 'returns:', 'parameters:', 'raises:', 'example:', 'note:', 'notes:')):
92
+ break
93
+ summary_lines.append(line)
94
+ summary = '\n'.join(summary_lines).strip()
95
+
96
+ # Get the signature
97
+ sig = inspect.signature(func)
98
+
99
+ # Try to get type hints
100
+ try:
101
+ hints = get_type_hints(func, include_extras=True)
102
+ except Exception:
103
+ hints = {}
104
+
105
+ lines = [f"=== {name} ===", "", summary, "", "Parameters:"]
106
+
107
+ for param_name, param in sig.parameters.items():
108
+ if param_name in ("self", "cls"):
109
+ continue
110
+
111
+ # Get type and description from Annotated if available
112
+ hint = hints.get(param_name)
113
+ type_str = "any"
114
+ desc = ""
115
+
116
+ if hint is not None:
117
+ if get_origin(hint) is Annotated:
118
+ args = get_args(hint)
119
+ if args:
120
+ type_str = getattr(args[0], "__name__", str(args[0]))
121
+ if len(args) > 1 and isinstance(args[1], str):
122
+ desc = args[1]
123
+ else:
124
+ type_str = getattr(hint, "__name__", str(hint))
125
+
126
+ # Check for default
127
+ if param.default is not inspect.Parameter.empty:
128
+ default_repr = repr(param.default)
129
+ if len(default_repr) > 50:
130
+ default_repr = default_repr[:47] + "..."
131
+ default_str = f" = {default_repr}"
132
+ else:
133
+ default_str = " (required)"
134
+
135
+ lines.append(f" - {param_name}: {type_str}{default_str}")
136
+ if desc:
137
+ lines.append(f" {desc}")
138
+
139
+ # Add examples
140
+ lines.append("")
141
+ lines.append("Examples:")
142
+ if name in _TOOL_EXAMPLES:
143
+ simple, advanced = _TOOL_EXAMPLES[name]
144
+ lines.append(f" {simple}")
145
+ lines.append(f" {advanced}")
146
+ else:
147
+ lines.append(f" {name}(...)")
148
+
149
+ return "\n".join(lines)
150
+
151
+
152
+ def _wrap_tool_for_no_arg_usage(func):
153
+ """
154
+ Wrap a tool function so that calling it with no arguments
155
+ returns usage information instead of raising an error.
156
+ """
157
+ @functools.wraps(func)
158
+ def wrapper(*args, **kwargs):
159
+ # If called with no arguments, return usage info
160
+ if not args and not kwargs:
161
+ return _format_tool_usage(func)
162
+ return func(*args, **kwargs)
163
+
164
+ # Preserve the original function for introspection
165
+ wrapper._original_func = func
166
+ return wrapper
167
+
168
  def search_packages(query: str = "") -> str:
169
  """Search for installed Python packages by name. If query is empty, lists all."""
170
  packages = []
 
179
  return "\n".join(packages)
180
 
181
  def _get_tools_map():
182
+ """Get all tools wrapped to return usage info when called with no arguments."""
183
+ raw_tools = {
184
  "Web_Fetch": Web_Fetch,
185
  "Web_Search": Web_Search,
186
  "Memory_Manager": Memory_Manager,
 
195
  "Shell_Command": Shell_Command,
196
  "Code_Interpreter": Code_Interpreter,
197
  }
198
+ return {name: _wrap_tool_for_no_arg_usage(func) for name, func in raw_tools.items()}
 
 
 
199
 
200
  def search_tools(query: str) -> str:
201
  """Search for tools by name or description. Returns usage info for matches."""
 
203
  matches = []
204
  tools = _get_tools_map()
205
  for name, func in tools.items():
206
+ # Get original function for docstring if wrapped
207
+ original = getattr(func, '_original_func', func)
208
+ doc = (original.__doc__ or "").lower()
209
  if query in name.lower() or query in doc:
210
  matches.append((name, func))
211
 
 
214
 
215
  output = []
216
  for name, func in matches:
217
+ output.append(_format_tool_usage(getattr(func, '_original_func', func)))
 
218
  output.append("")
219
  return "\n".join(output)
220
 
 
 
 
 
 
 
 
 
221
  def _initialize_mock_modules():
222
  """
223
  Registers a mock 'functions' module in sys.modules so that LLMs
224
  can do 'from functions import ...' without error.
225
+ Uses wrapped tools that return usage info when called with no args.
226
  """
227
  mock_module = types.ModuleType("functions")
228
 
229
+ # Add wrapped tools (return usage when called with no args)
230
  for name, tool in _get_tools_map().items():
231
  setattr(mock_module, name, tool)
232
 
233
  # Add helpers
234
  helpers = {
 
235
  "search_tools": search_tools,
 
236
  "search_packages": search_packages,
237
  }
238
  for name, func in helpers.items():
 
245
  # Single source of truth for the LLM-facing tool description
246
  TOOL_SUMMARY = (
247
  "Executes Python code as the unified interface for the entire tools ecosystem. "
248
+ "Use Agent Terminal repeatedly whenever you need to chain or combine tool operations. Always read a tool's definiton to see usage info before invoking it. "
 
249
  "Available tools: `Web_Fetch`, `Web_Search`, `Code_Interpreter`, `Shell_Command`, `File_System`, `Obsidian_Vault`, `Memory_Manager`, `Generate_Speech`, `Generate_Image`, `Generate_Video`, `Deep_Research`."
250
  )
251
 
 
256
  )
257
  def Agent_Terminal(input: Annotated[str, (
258
  "Python source code to run; stdout is captured and returned. "
259
+ "Call any tool with no arguments to get its full usage info (e.g., `Generate_Image()`). "
260
+ "Helper commands: "
261
+ "`search_tools('query')` to search tools by name or capability; "
 
262
  "`search_packages('query')` to search for installed Python libraries."
263
  )]) -> str:
264
  _log_call_start("Agent_Terminal", input=_truncate_for_log(input or "", 300))
 
270
  old_cwd = os.getcwd()
271
  redirected_output = sys.stdout = StringIO()
272
 
273
+ # Get wrapped tools that return usage info when called with no args
274
+ wrapped_tools = _get_tools_map()
275
+
276
  # Prepare the execution environment with all tools
277
  tools_env = {
278
+ **wrapped_tools,
 
 
 
 
 
 
 
 
 
 
 
 
 
279
  "search_tools": search_tools,
 
280
  "search_packages": search_packages,
281
  "print": print, # Ensure print is available
282
  "__builtins__": __builtins__,
 
285
  try:
286
  os.chdir(ROOT_DIR)
287
 
288
+ # Parse code and print results of ALL expression statements (not just the last)
289
  tree = ast.parse(input)
290
+
291
+ for node in tree.body:
292
+ if isinstance(node, ast.Expr):
293
+ # This is a standalone expression - evaluate and print its result
294
+ expr = compile(ast.Expression(node.value), filename="<string>", mode="eval")
295
+ result_val = eval(expr, tools_env)
296
+ if result_val is not None:
297
+ print(result_val)
298
+ else:
299
+ # This is a statement (assignment, if, for, etc.) - just execute it
300
+ mod = ast.Module(body=[node], type_ignores=[])
301
+ exec(compile(mod, filename="<string>", mode="exec"), tools_env)
 
 
302
 
303
  result = redirected_output.getvalue()
304
  except Exception as exc: # pylint: disable=broad-except