tooluniverse 1.0.11.2__py3-none-any.whl → 1.0.12__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.

Potentially problematic release.


This version of tooluniverse might be problematic. Click here for more details.

Files changed (58) hide show
  1. tooluniverse/build_optimizer.py +115 -22
  2. tooluniverse/data/encode_tools.json +139 -0
  3. tooluniverse/data/gbif_tools.json +152 -0
  4. tooluniverse/data/gdc_tools.json +116 -0
  5. tooluniverse/data/gtex_tools.json +116 -0
  6. tooluniverse/data/icgc_tools.json +0 -0
  7. tooluniverse/data/mgnify_tools.json +121 -0
  8. tooluniverse/data/obis_tools.json +122 -0
  9. tooluniverse/data/optimizer_tools.json +275 -0
  10. tooluniverse/data/rnacentral_tools.json +99 -0
  11. tooluniverse/data/smolagent_tools.json +206 -0
  12. tooluniverse/data/wikipathways_tools.json +106 -0
  13. tooluniverse/default_config.py +12 -0
  14. tooluniverse/encode_tool.py +245 -0
  15. tooluniverse/execute_function.py +46 -8
  16. tooluniverse/gbif_tool.py +166 -0
  17. tooluniverse/gdc_tool.py +175 -0
  18. tooluniverse/generate_tools.py +121 -9
  19. tooluniverse/gtex_tool.py +168 -0
  20. tooluniverse/mgnify_tool.py +181 -0
  21. tooluniverse/obis_tool.py +185 -0
  22. tooluniverse/pypi_package_inspector_tool.py +3 -2
  23. tooluniverse/rnacentral_tool.py +124 -0
  24. tooluniverse/smcp_server.py +1 -1
  25. tooluniverse/smolagent_tool.py +555 -0
  26. tooluniverse/tools/ArgumentDescriptionOptimizer.py +55 -0
  27. tooluniverse/tools/ENCODE_list_files.py +59 -0
  28. tooluniverse/tools/ENCODE_search_experiments.py +67 -0
  29. tooluniverse/tools/GBIF_search_occurrences.py +67 -0
  30. tooluniverse/tools/GBIF_search_species.py +55 -0
  31. tooluniverse/tools/GDC_list_files.py +55 -0
  32. tooluniverse/tools/GDC_search_cases.py +55 -0
  33. tooluniverse/tools/GTEx_get_expression_summary.py +49 -0
  34. tooluniverse/tools/GTEx_query_eqtl.py +59 -0
  35. tooluniverse/tools/MGnify_list_analyses.py +52 -0
  36. tooluniverse/tools/MGnify_search_studies.py +55 -0
  37. tooluniverse/tools/OBIS_search_occurrences.py +59 -0
  38. tooluniverse/tools/OBIS_search_taxa.py +52 -0
  39. tooluniverse/tools/RNAcentral_get_by_accession.py +46 -0
  40. tooluniverse/tools/RNAcentral_search.py +52 -0
  41. tooluniverse/tools/TestCaseGenerator.py +46 -0
  42. tooluniverse/tools/ToolDescriptionOptimizer.py +67 -0
  43. tooluniverse/tools/ToolDiscover.py +4 -0
  44. tooluniverse/tools/UniProt_search.py +17 -44
  45. tooluniverse/tools/WikiPathways_get_pathway.py +52 -0
  46. tooluniverse/tools/WikiPathways_search.py +52 -0
  47. tooluniverse/tools/__init__.py +43 -1
  48. tooluniverse/tools/advanced_literature_search_agent.py +46 -0
  49. tooluniverse/tools/alphafold_get_annotations.py +4 -10
  50. tooluniverse/tools/download_binary_file.py +3 -6
  51. tooluniverse/tools/open_deep_research_agent.py +46 -0
  52. tooluniverse/wikipathways_tool.py +122 -0
  53. {tooluniverse-1.0.11.2.dist-info → tooluniverse-1.0.12.dist-info}/METADATA +3 -1
  54. {tooluniverse-1.0.11.2.dist-info → tooluniverse-1.0.12.dist-info}/RECORD +58 -17
  55. {tooluniverse-1.0.11.2.dist-info → tooluniverse-1.0.12.dist-info}/WHEEL +0 -0
  56. {tooluniverse-1.0.11.2.dist-info → tooluniverse-1.0.12.dist-info}/entry_points.txt +0 -0
  57. {tooluniverse-1.0.11.2.dist-info → tooluniverse-1.0.12.dist-info}/licenses/LICENSE +0 -0
  58. {tooluniverse-1.0.11.2.dist-info → tooluniverse-1.0.12.dist-info}/top_level.txt +0 -0
@@ -455,7 +455,7 @@ Examples:
455
455
 
456
456
  try:
457
457
  print(f"🚀 Starting {args.name}...", file=sys.stderr)
458
- print("📡 Transport: stdio (for Claude Desktop)", file=sys.stderr)
458
+ print("📡 Transport: stdio", file=sys.stderr)
459
459
  print(f"🔍 Search enabled: {not args.no_search}", file=sys.stderr)
460
460
 
461
461
  if args.categories is not None:
@@ -0,0 +1,555 @@
1
+ from __future__ import annotations
2
+
3
+ import threading
4
+ from typing import Any, Callable, Dict, List, Optional
5
+
6
+ from .base_tool import BaseTool
7
+ from .tool_registry import register_tool
8
+
9
+ # Global lock for stdout/stderr redirection (for thread safety)
10
+ _STREAM_LOCK = threading.Lock()
11
+
12
+
13
+ def _safe_import(module_path: str, symbol: str):
14
+ """Safely import a symbol from a module, raising a helpful error if missing."""
15
+ try:
16
+ module = __import__(module_path, fromlist=[symbol])
17
+ return getattr(module, symbol)
18
+ except Exception as e: # noqa: BLE001
19
+ raise ImportError(
20
+ f"Failed to import '{symbol}' from '{module_path}'. Please install and configure 'smolagents'. Original error: {e}"
21
+ )
22
+
23
+
24
+ class ToolUniverseTool: # Lazy base; will subclass smolagents.Tool at runtime
25
+ """
26
+ Adapter that wraps a ToolUniverse tool and exposes it as a smolagents Tool.
27
+
28
+ We create the real subclass dynamically to avoid hard dependency when the
29
+ module is imported without smolagents installed.
30
+ """
31
+
32
+ def __new__(cls, *args, **kwargs): # pragma: no cover - construct dynamic subclass
33
+ # Import here to avoid import-time dependency when not used
34
+ Tool = _safe_import("smolagents", "Tool")
35
+
36
+ # Arguments: tool_name, tooluniverse_instance, tool_config
37
+ tool_name: str = args[0]
38
+ tooluniverse_instance = args[1]
39
+ tool_config = args[2] if len(args) > 2 else None
40
+
41
+ tu_config = getattr(tooluniverse_instance, "all_tool_dict", {}).get(
42
+ tool_name, {}
43
+ )
44
+
45
+ # Helpers to build class attributes
46
+ def _convert_parameter_schema(parameter_schema: Dict) -> Dict:
47
+ properties = parameter_schema.get("properties", {})
48
+ required = set(parameter_schema.get("required", []) or [])
49
+ inputs: Dict[str, Dict[str, Any]] = {}
50
+ for param_name, info in properties.items():
51
+ entry: Dict[str, Any] = {
52
+ "type": info.get("type", "string"),
53
+ "description": info.get("description", ""),
54
+ }
55
+ if param_name not in required and "default" in info:
56
+ entry["nullable"] = True
57
+ inputs[param_name] = entry
58
+ return inputs
59
+
60
+ def _infer_output_type(return_schema: Dict) -> str:
61
+ schema_type = return_schema.get("type", "string")
62
+ mapping = {
63
+ "object": "string",
64
+ "array": "string",
65
+ "string": "string",
66
+ "integer": "integer",
67
+ "number": "number",
68
+ "boolean": "boolean",
69
+ }
70
+ return mapping.get(schema_type, "string")
71
+
72
+ inputs_schema = _convert_parameter_schema(tu_config.get("parameter", {}))
73
+ output_type = _infer_output_type(tu_config.get("return_schema", {}))
74
+
75
+ # Build a forward function with explicit parameters to satisfy
76
+ # smolagents' validation (parameters must match keys in `inputs`).
77
+ def __call_tool(
78
+ self, __kwargs, _tool_name=tool_name, _tu=tooluniverse_instance
79
+ ):
80
+ try:
81
+ result = _tu.run_one_function(
82
+ {"name": _tool_name, "arguments": __kwargs}
83
+ )
84
+ if isinstance(result, dict):
85
+ import json
86
+
87
+ return json.dumps(result, ensure_ascii=False)
88
+ return result
89
+ except Exception as e: # noqa: BLE001
90
+ return f"Error executing tool {_tool_name}: {e}"
91
+
92
+ param_names = list(inputs_schema.keys())
93
+ if param_names:
94
+ # Dynamically create a function with signature: (self, p1, p2, ...)
95
+ params_sig = ", ".join(param_names)
96
+ body_lines = [" _kwargs = {"]
97
+ for p in param_names:
98
+ body_lines.append(f" '{p}': {p},")
99
+ body_lines.append(" }")
100
+ body_lines.append(" return __call_tool(self, _kwargs)")
101
+ func_src = [f"def _forward(self, {params_sig}):"] + body_lines
102
+ func_src = "\n".join(func_src)
103
+ ns: Dict[str, Any] = {"__call_tool": __call_tool}
104
+ exec(func_src, ns)
105
+ _forward = ns["_forward"] # type: ignore[assignment]
106
+ else:
107
+ # No inputs -> 0-arg forward
108
+ def _forward(self): # type: ignore[override]
109
+ return __call_tool(self, {})
110
+
111
+ attrs = {
112
+ "name": tool_name,
113
+ "description": tu_config.get("description", ""),
114
+ "inputs": inputs_schema,
115
+ "output_type": output_type,
116
+ "forward": _forward,
117
+ "tool_config": tool_config or {},
118
+ }
119
+
120
+ DynamicToolCls = type(f"ToolUniverseTool_{tool_name}", (Tool,), attrs) # type: ignore[misc]
121
+ return DynamicToolCls()
122
+
123
+ @classmethod
124
+ def from_tooluniverse(
125
+ cls,
126
+ tool_name: str,
127
+ tooluniverse_instance,
128
+ tool_config: Optional[Dict[str, Any]] = None,
129
+ ):
130
+ """Factory to create a smolagents-compatible Tool from a ToolUniverse tool.
131
+
132
+ This mirrors common factory patterns (e.g., from_langchain) and returns
133
+ an instance of the dynamically constructed Tool subclass.
134
+ """
135
+ return cls(tool_name, tooluniverse_instance, tool_config or {})
136
+
137
+
138
+ @register_tool("SmolAgentTool")
139
+ class SmolAgentTool(BaseTool):
140
+ """Wrap smolagents agents so they can be used as ToolUniverse tools.
141
+
142
+ Supports:
143
+ - CodeAgent, ToolCallingAgent, Agent, ManagedAgent
144
+ - Mixed tools: ToolUniverse tools and smolagents-native tools
145
+ - Streaming integration with ToolUniverse stream callbacks
146
+ """
147
+
148
+ def __init__(self, tool_config: Dict[str, Any]):
149
+ super().__init__(tool_config)
150
+ settings = tool_config.get("settings", {})
151
+
152
+ self.agent_type: str = settings.get("agent_type", "CodeAgent")
153
+ self.available_tools: List[Any] = settings.get("available_tools", [])
154
+ self.model_config: Dict[str, Any] = settings.get("model", {})
155
+ self.agent_init_params: Dict[str, Any] = settings.get("agent_init_params", {})
156
+ self.sub_agents_config: List[Dict[str, Any]] = settings.get("sub_agents", [])
157
+
158
+ # Will be set by ToolUniverse runtime
159
+ self.tooluniverse = None
160
+ self.agent = None
161
+
162
+ # -------------------------
163
+ # Initialization helpers
164
+ # -------------------------
165
+ def _get_api_key(self) -> Optional[str]:
166
+ api_key = self.model_config.get("api_key")
167
+ if isinstance(api_key, str) and api_key.startswith("env:"):
168
+ import os
169
+
170
+ return os.environ.get(api_key[4:])
171
+ return api_key
172
+
173
+ def _init_model(self):
174
+ provider = self.model_config.get("provider", "HfApiModel")
175
+ model_id = self.model_config.get("model_id")
176
+ api_key = self._get_api_key()
177
+
178
+ if provider == "HfApiModel":
179
+ HfApiModel = _safe_import("smolagents", "HfApiModel")
180
+ return HfApiModel(model_id, token=api_key)
181
+ if provider == "OpenAIModel":
182
+ OpenAIModel = _safe_import("smolagents", "OpenAIModel")
183
+ return OpenAIModel(
184
+ model_id=model_id,
185
+ api_key=api_key,
186
+ api_base=self.model_config.get("api_base"),
187
+ )
188
+ if provider == "LiteLLMModel":
189
+ LiteLLMModel = _safe_import("smolagents", "LiteLLMModel")
190
+ return LiteLLMModel(model_id=model_id, api_key=api_key)
191
+ if provider == "InferenceClientModel":
192
+ InferenceClientModel = _safe_import("smolagents", "InferenceClientModel")
193
+ return InferenceClientModel(
194
+ model_id=model_id,
195
+ provider=self.model_config.get("provider_name"),
196
+ token=api_key,
197
+ )
198
+ if provider == "TransformersModel":
199
+ TransformersModel = _safe_import("smolagents", "TransformersModel")
200
+ return TransformersModel(
201
+ model_id=model_id,
202
+ )
203
+ if provider == "AzureOpenAIModel":
204
+ AzureOpenAIModel = _safe_import("smolagents", "AzureOpenAIModel")
205
+ return AzureOpenAIModel(
206
+ model_id=model_id,
207
+ azure_endpoint=self.model_config.get("azure_endpoint"),
208
+ api_key=api_key,
209
+ api_version=self.model_config.get("api_version"),
210
+ )
211
+ if provider == "AmazonBedrockModel":
212
+ AmazonBedrockModel = _safe_import("smolagents", "AmazonBedrockModel")
213
+ return AmazonBedrockModel(model_id=model_id)
214
+
215
+ raise ValueError(f"Unsupported model provider: {provider}")
216
+
217
+ def _import_smolagents_tool(self, class_name: str, import_path: str):
218
+ """Dynamically import smolagents tool class with helpful error messages."""
219
+ import importlib
220
+
221
+ try:
222
+ module = importlib.import_module(import_path)
223
+ except ImportError as e:
224
+ raise ImportError(
225
+ f"Failed to import module '{import_path}' for smolagents tool '{class_name}'. "
226
+ f"Please ensure the module path is correct. "
227
+ f"Common paths include 'smolagents.tools' or 'smolagents.default_tools'. "
228
+ f"Original error: {e}"
229
+ ) from e
230
+
231
+ try:
232
+ tool_class = getattr(module, class_name)
233
+ except AttributeError as e:
234
+ available_attrs = [attr for attr in dir(module) if not attr.startswith("_")]
235
+ raise AttributeError(
236
+ f"Class '{class_name}' not found in module '{import_path}'. "
237
+ f"Available classes in the module: {', '.join(available_attrs[:10])}"
238
+ f"{'...' if len(available_attrs) > 10 else ''}. "
239
+ f"Please check the class name spelling and ensure it exists in the module. "
240
+ f"Original error: {e}"
241
+ ) from e
242
+
243
+ return tool_class
244
+
245
+ def _convert_tools(self) -> List[Any]:
246
+ """Convert mixed tool definitions to smolagents Tool instances."""
247
+ converted: List[Any] = []
248
+ for spec in self.available_tools:
249
+ if isinstance(spec, str):
250
+ converted.append(
251
+ ToolUniverseTool.from_tooluniverse(spec, self.tooluniverse)
252
+ )
253
+ continue
254
+
255
+ if not isinstance(spec, dict):
256
+ continue
257
+
258
+ spec_type = spec.get("type", "tooluniverse")
259
+ if spec_type == "smolagents":
260
+ cls_name = spec.get("class")
261
+ if not cls_name:
262
+ continue
263
+ import_path = spec.get("import_path", "smolagents.tools")
264
+ kwargs = spec.get("kwargs", {})
265
+ tool_cls = self._import_smolagents_tool(cls_name, import_path)
266
+ converted.append(tool_cls(**kwargs))
267
+ else:
268
+ name = spec.get("name")
269
+ if name:
270
+ converted.append(
271
+ ToolUniverseTool.from_tooluniverse(name, self.tooluniverse)
272
+ )
273
+ return converted
274
+
275
+ def _create_sub_agents(self, sub_configs: List[Dict[str, Any]]) -> List[Any]:
276
+ """Recursively create sub-agent instances (for ManagedAgent)."""
277
+ sub_agents: List[Any] = []
278
+ for cfg in sub_configs:
279
+ sub_tool_config: Dict[str, Any] = {
280
+ "name": cfg.get("name", "sub_agent"),
281
+ "type": "SmolAgentTool",
282
+ "description": cfg.get("description", ""),
283
+ "settings": cfg,
284
+ }
285
+ sub_tool = SmolAgentTool(sub_tool_config)
286
+ sub_tool.tooluniverse = self.tooluniverse
287
+ sub_tool._init_agent()
288
+ if sub_tool.agent is not None:
289
+ sub_agents.append(sub_tool.agent)
290
+ return sub_agents
291
+
292
+ def _init_agent(self) -> None:
293
+ if self.agent is not None:
294
+ return
295
+
296
+ model = self._init_model()
297
+ tools = self._convert_tools()
298
+
299
+ init_kwargs: Dict[str, Any] = {"tools": tools, "model": model}
300
+ # Give the agent an explicit name if supported
301
+ if isinstance(self.tool_config.get("name", None), str):
302
+ init_kwargs["name"] = self.tool_config["name"]
303
+ init_kwargs.update(self.agent_init_params or {})
304
+
305
+ # Sanitize unsupported kwargs based on agent type and common params
306
+ def _sanitize(agent_type: str, params: Dict[str, Any]) -> Dict[str, Any]:
307
+ common_allowed = {
308
+ "tools",
309
+ "model",
310
+ "name",
311
+ "prompt_templates",
312
+ "planning_interval",
313
+ "stream_outputs",
314
+ "max_steps",
315
+ }
316
+ codeagent_allowed = common_allowed.union(
317
+ {
318
+ "add_base_tools",
319
+ "additional_authorized_imports",
320
+ "verbosity_level",
321
+ "executor_type",
322
+ "executor_kwargs",
323
+ }
324
+ )
325
+ toolcalling_allowed = common_allowed
326
+ agent_allowed = common_allowed
327
+
328
+ if agent_type == "CodeAgent":
329
+ allowed = codeagent_allowed
330
+ elif agent_type == "ToolCallingAgent":
331
+ allowed = toolcalling_allowed
332
+ elif agent_type == "Agent" or agent_type == "ManagedAgent":
333
+ allowed = agent_allowed
334
+ else:
335
+ allowed = common_allowed
336
+
337
+ # Drop unsupported keys (e.g., max_tool_threads)
338
+ return {k: v for k, v in params.items() if k in allowed}
339
+
340
+ init_kwargs = _sanitize(self.agent_type, init_kwargs)
341
+
342
+ # Construct agent by type
343
+ if self.agent_type == "ManagedAgent":
344
+ # Emulate a managed multi-agent system by wrapping sub-agents
345
+ # as smolagents Tools and composing a top-level CodeAgent.
346
+ CodeAgent = _safe_import("smolagents", "CodeAgent")
347
+
348
+ # Convert top-level available tools
349
+ top_tools = tools[:]
350
+
351
+ # Build sub-agents and wrap as tools
352
+ sub_agents = self._create_sub_agents(self.sub_agents_config)
353
+
354
+ # Dynamically create a Tool wrapper around a smolagents agent
355
+ Tool = _safe_import("smolagents", "Tool")
356
+
357
+ def _wrap_agent_as_tool(agent_obj, tool_name: str):
358
+ # smolagents expects class attributes on Tool subclasses
359
+ def _forward(self, task: str): # type: ignore[override]
360
+ return agent_obj.run(task)
361
+
362
+ attrs = {
363
+ "name": tool_name,
364
+ "description": f"Agent tool wrapper for {tool_name}",
365
+ "inputs": {
366
+ "task": {
367
+ "type": "string",
368
+ "description": "Task for sub-agent",
369
+ }
370
+ },
371
+ "output_type": "string",
372
+ "forward": _forward,
373
+ }
374
+ AgentToolCls = type(f"AgentTool_{tool_name}", (Tool,), attrs) # type: ignore[misc]
375
+ return AgentToolCls()
376
+
377
+ for idx, sub in enumerate(sub_agents):
378
+ name = getattr(sub, "name", f"sub_agent_{idx+1}")
379
+ top_tools.append(_wrap_agent_as_tool(sub, name))
380
+
381
+ # Construct the orchestrator agent (CodeAgent) with both native tools and agent-tools
382
+ orchestrator_kwargs = {"tools": top_tools, "model": model}
383
+ if isinstance(self.tool_config.get("name", None), str):
384
+ orchestrator_kwargs["name"] = self.tool_config["name"]
385
+ orchestrator_kwargs.update(self.agent_init_params or {})
386
+ orchestrator_kwargs = _sanitize("CodeAgent", orchestrator_kwargs)
387
+ self.agent = CodeAgent(**orchestrator_kwargs)
388
+ return
389
+
390
+ if self.agent_type == "CodeAgent":
391
+ CodeAgent = _safe_import("smolagents", "CodeAgent")
392
+ self.agent = CodeAgent(**init_kwargs)
393
+ return
394
+
395
+ if self.agent_type == "ToolCallingAgent":
396
+ ToolCallingAgent = _safe_import("smolagents", "ToolCallingAgent")
397
+ self.agent = ToolCallingAgent(**init_kwargs)
398
+ return
399
+
400
+ if self.agent_type == "Agent":
401
+ Agent = _safe_import("smolagents", "Agent")
402
+ self.agent = Agent(**init_kwargs)
403
+ return
404
+
405
+ raise ValueError(f"Unsupported agent type: {self.agent_type}")
406
+
407
+ # -------------------------
408
+ # Execution
409
+ # -------------------------
410
+ def run(
411
+ self,
412
+ arguments: Dict[str, Any],
413
+ stream_callback: Optional[Callable[[str], None]] = None,
414
+ **_: Any,
415
+ ) -> Dict[str, Any]:
416
+ """Execute the agent with optional streaming back into ToolUniverse.
417
+
418
+ Supports:
419
+ - Streaming output (when stream_callback is provided and agent.stream_outputs=True)
420
+ - Execution timeout (via agent_init_params.max_execution_time)
421
+ - Thread-safe stdout/stderr redirection
422
+ """
423
+ import sys
424
+ import time
425
+
426
+ self._init_agent()
427
+ task = arguments.get("task", "")
428
+ if not task:
429
+ # Fallback to 'query' for agents whose parameter is named 'query'
430
+ task = arguments.get("query", "")
431
+
432
+ # Get max_execution_time from config (default: None = unlimited)
433
+ max_execution_time = self.agent_init_params.get("max_execution_time")
434
+ timeout_error: Optional[Exception] = None
435
+ execution_completed = threading.Event()
436
+
437
+ def _execute_with_timeout():
438
+ """Inner function to execute agent.run with timeout protection."""
439
+ try:
440
+ # If streaming desired and agent supports streaming via stdout, capture and forward
441
+ wants_stream = bool(stream_callback) and bool(
442
+ getattr(self.agent, "stream_outputs", False)
443
+ )
444
+ if wants_stream:
445
+
446
+ class _StreamProxy:
447
+ def __init__(self, cb):
448
+ self._cb = cb
449
+ self._buf = ""
450
+ self._last_line = None
451
+
452
+ def write(self, s: str):
453
+ if not s:
454
+ return
455
+ self._buf += s
456
+ while "\n" in self._buf:
457
+ line, self._buf = self._buf.split("\n", 1)
458
+ if not line.strip():
459
+ continue
460
+ # Deduplicate consecutive identical lines
461
+ if line == self._last_line:
462
+ continue
463
+ self._last_line = line
464
+ self._cb(line + "\n")
465
+
466
+ def flush(self):
467
+ if self._buf.strip():
468
+ if self._buf != self._last_line:
469
+ self._cb(self._buf)
470
+ self._last_line = self._buf
471
+ self._buf = ""
472
+
473
+ # Use lock to protect stdout redirection (thread-safe)
474
+ with _STREAM_LOCK:
475
+ old_stdout, old_stderr = sys.stdout, sys.stderr
476
+ proxy = _StreamProxy(stream_callback)
477
+ sys.stdout = proxy # forward stdout only to avoid dupes
478
+ try:
479
+ result = self.agent.run(task)
480
+ finally:
481
+ sys.stdout = old_stdout
482
+ sys.stderr = old_stderr
483
+
484
+ execution_completed.set()
485
+ return result
486
+
487
+ # Non-streaming path (also protect with lock for consistency)
488
+ with _STREAM_LOCK:
489
+ result = self.agent.run(task)
490
+ execution_completed.set()
491
+ return result
492
+
493
+ except Exception as e: # noqa: BLE001
494
+ execution_completed.set()
495
+ raise e
496
+
497
+ try:
498
+ start_time = time.time()
499
+
500
+ # Execute with timeout if specified
501
+ if max_execution_time is not None and max_execution_time > 0:
502
+ import threading as th
503
+
504
+ result_container: List[Any] = []
505
+ exception_container: List[Exception] = []
506
+
507
+ def _worker():
508
+ try:
509
+ result_container.append(_execute_with_timeout())
510
+ except Exception as e: # noqa: BLE001
511
+ exception_container.append(e)
512
+
513
+ worker_thread = th.Thread(target=_worker, daemon=True)
514
+ worker_thread.start()
515
+ worker_thread.join(timeout=max_execution_time)
516
+
517
+ if worker_thread.is_alive():
518
+ # Timeout occurred
519
+ timeout_error = TimeoutError(
520
+ f"Agent execution exceeded maximum time limit of {max_execution_time} seconds. "
521
+ f"Task: {task[:100]}..."
522
+ )
523
+ if stream_callback:
524
+ stream_callback(f"\n[TIMEOUT] {timeout_error}\n")
525
+ return {
526
+ "output": None,
527
+ "success": False,
528
+ "error": str(timeout_error),
529
+ "error_type": "timeout",
530
+ }
531
+
532
+ if exception_container:
533
+ raise exception_container[0]
534
+
535
+ result = result_container[0] if result_container else None
536
+ else:
537
+ # No timeout - direct execution
538
+ result = _execute_with_timeout()
539
+
540
+ elapsed_time = time.time() - start_time
541
+ return {
542
+ "output": result,
543
+ "success": True,
544
+ "execution_time": elapsed_time,
545
+ }
546
+
547
+ except Exception as e: # noqa: BLE001
548
+ if stream_callback:
549
+ stream_callback(f"\n[ERROR] {e}\n")
550
+ return {
551
+ "output": None,
552
+ "success": False,
553
+ "error": str(e),
554
+ "error_type": type(e).__name__,
555
+ }
@@ -0,0 +1,55 @@
1
+ """
2
+ ArgumentDescriptionOptimizer
3
+
4
+ Optimizes the descriptions of tool arguments/parameters based on test case results and actual usa...
5
+ """
6
+
7
+ from typing import Any, Optional, Callable
8
+ from ._shared_client import get_shared_client
9
+
10
+
11
+ def ArgumentDescriptionOptimizer(
12
+ parameter_schema: str,
13
+ test_results: str,
14
+ *,
15
+ stream_callback: Optional[Callable[[str], None]] = None,
16
+ use_cache: bool = False,
17
+ validate: bool = True,
18
+ ) -> dict[str, Any]:
19
+ """
20
+ Optimizes the descriptions of tool arguments/parameters based on test case results and actual usa...
21
+
22
+ Parameters
23
+ ----------
24
+ parameter_schema : str
25
+ JSON string of the original parameter schema with properties and descriptions.
26
+ test_results : str
27
+ A JSON string containing test case input/output pairs showing parameter usage.
28
+ stream_callback : Callable, optional
29
+ Callback for streaming output
30
+ use_cache : bool, default False
31
+ Enable caching
32
+ validate : bool, default True
33
+ Validate parameters
34
+
35
+ Returns
36
+ -------
37
+ dict[str, Any]
38
+ """
39
+ # Handle mutable defaults to avoid B006 linting error
40
+
41
+ return get_shared_client().run_one_function(
42
+ {
43
+ "name": "ArgumentDescriptionOptimizer",
44
+ "arguments": {
45
+ "parameter_schema": parameter_schema,
46
+ "test_results": test_results,
47
+ },
48
+ },
49
+ stream_callback=stream_callback,
50
+ use_cache=use_cache,
51
+ validate=validate,
52
+ )
53
+
54
+
55
+ __all__ = ["ArgumentDescriptionOptimizer"]
@@ -0,0 +1,59 @@
1
+ """
2
+ ENCODE_list_files
3
+
4
+ List ENCODE files with filters (file_format, output_type, assay). Use to programmatically retriev...
5
+ """
6
+
7
+ from typing import Any, Optional, Callable
8
+ from ._shared_client import get_shared_client
9
+
10
+
11
+ def ENCODE_list_files(
12
+ file_type: Optional[str] = None,
13
+ assay_title: Optional[str] = None,
14
+ limit: Optional[int] = 10,
15
+ *,
16
+ stream_callback: Optional[Callable[[str], None]] = None,
17
+ use_cache: bool = False,
18
+ validate: bool = True,
19
+ ) -> dict[str, Any]:
20
+ """
21
+ List ENCODE files with filters (file_format, output_type, assay). Use to programmatically retriev...
22
+
23
+ Parameters
24
+ ----------
25
+ file_type : str
26
+ File type filter (e.g., 'fastq', 'bam', 'bigWig').
27
+ assay_title : str
28
+ Assay filter (e.g., 'ChIP-seq').
29
+ limit : int
30
+ Max number of results (1–100).
31
+ stream_callback : Callable, optional
32
+ Callback for streaming output
33
+ use_cache : bool, default False
34
+ Enable caching
35
+ validate : bool, default True
36
+ Validate parameters
37
+
38
+ Returns
39
+ -------
40
+ dict[str, Any]
41
+ """
42
+ # Handle mutable defaults to avoid B006 linting error
43
+
44
+ return get_shared_client().run_one_function(
45
+ {
46
+ "name": "ENCODE_list_files",
47
+ "arguments": {
48
+ "file_type": file_type,
49
+ "assay_title": assay_title,
50
+ "limit": limit,
51
+ },
52
+ },
53
+ stream_callback=stream_callback,
54
+ use_cache=use_cache,
55
+ validate=validate,
56
+ )
57
+
58
+
59
+ __all__ = ["ENCODE_list_files"]