tooluniverse 1.0.10__py3-none-any.whl → 1.0.11__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 (150) hide show
  1. tooluniverse/__init__.py +57 -1
  2. tooluniverse/blast_tool.py +132 -0
  3. tooluniverse/boltz_tool.py +2 -2
  4. tooluniverse/cbioportal_tool.py +42 -0
  5. tooluniverse/clinvar_tool.py +268 -74
  6. tooluniverse/compose_scripts/tool_discover.py +1941 -443
  7. tooluniverse/data/agentic_tools.json +0 -370
  8. tooluniverse/data/alphafold_tools.json +6 -6
  9. tooluniverse/data/blast_tools.json +112 -0
  10. tooluniverse/data/cbioportal_tools.json +87 -0
  11. tooluniverse/data/clinvar_tools.json +235 -0
  12. tooluniverse/data/compose_tools.json +0 -89
  13. tooluniverse/data/dbsnp_tools.json +275 -0
  14. tooluniverse/data/emdb_tools.json +61 -0
  15. tooluniverse/data/ensembl_tools.json +259 -0
  16. tooluniverse/data/file_download_tools.json +275 -0
  17. tooluniverse/data/geo_tools.json +200 -48
  18. tooluniverse/data/gnomad_tools.json +109 -0
  19. tooluniverse/data/gtopdb_tools.json +68 -0
  20. tooluniverse/data/gwas_tools.json +32 -0
  21. tooluniverse/data/interpro_tools.json +199 -0
  22. tooluniverse/data/jaspar_tools.json +70 -0
  23. tooluniverse/data/kegg_tools.json +356 -0
  24. tooluniverse/data/mpd_tools.json +87 -0
  25. tooluniverse/data/ols_tools.json +314 -0
  26. tooluniverse/data/package_discovery_tools.json +64 -0
  27. tooluniverse/data/packages/categorized_tools.txt +0 -1
  28. tooluniverse/data/packages/machine_learning_tools.json +0 -47
  29. tooluniverse/data/paleobiology_tools.json +91 -0
  30. tooluniverse/data/pride_tools.json +62 -0
  31. tooluniverse/data/pypi_package_inspector_tools.json +158 -0
  32. tooluniverse/data/python_executor_tools.json +341 -0
  33. tooluniverse/data/regulomedb_tools.json +50 -0
  34. tooluniverse/data/remap_tools.json +89 -0
  35. tooluniverse/data/screen_tools.json +89 -0
  36. tooluniverse/data/tool_discovery_agents.json +428 -0
  37. tooluniverse/data/tool_discovery_agents.json.backup +1343 -0
  38. tooluniverse/data/uniprot_tools.json +77 -0
  39. tooluniverse/data/web_search_tools.json +250 -0
  40. tooluniverse/data/worms_tools.json +55 -0
  41. tooluniverse/dbsnp_tool.py +196 -58
  42. tooluniverse/default_config.py +35 -2
  43. tooluniverse/emdb_tool.py +30 -0
  44. tooluniverse/ensembl_tool.py +140 -47
  45. tooluniverse/execute_function.py +74 -14
  46. tooluniverse/file_download_tool.py +269 -0
  47. tooluniverse/geo_tool.py +81 -28
  48. tooluniverse/gnomad_tool.py +100 -52
  49. tooluniverse/gtopdb_tool.py +41 -0
  50. tooluniverse/interpro_tool.py +72 -0
  51. tooluniverse/jaspar_tool.py +30 -0
  52. tooluniverse/kegg_tool.py +230 -0
  53. tooluniverse/mpd_tool.py +42 -0
  54. tooluniverse/ncbi_eutils_tool.py +96 -0
  55. tooluniverse/ols_tool.py +435 -0
  56. tooluniverse/package_discovery_tool.py +217 -0
  57. tooluniverse/paleobiology_tool.py +30 -0
  58. tooluniverse/pride_tool.py +30 -0
  59. tooluniverse/pypi_package_inspector_tool.py +593 -0
  60. tooluniverse/python_executor_tool.py +711 -0
  61. tooluniverse/regulomedb_tool.py +30 -0
  62. tooluniverse/remap_tool.py +44 -0
  63. tooluniverse/remote/depmap_24q2/depmap_24q2_mcp_tool.py +1 -1
  64. tooluniverse/screen_tool.py +44 -0
  65. tooluniverse/smcp_server.py +3 -3
  66. tooluniverse/tool_finder_embedding.py +3 -1
  67. tooluniverse/tool_finder_keyword.py +3 -1
  68. tooluniverse/tool_finder_llm.py +6 -2
  69. tooluniverse/tools/{UCSC_get_genes_by_region.py → BLAST_nucleotide_search.py} +22 -26
  70. tooluniverse/tools/BLAST_protein_search.py +63 -0
  71. tooluniverse/tools/ClinVar_search_variants.py +26 -15
  72. tooluniverse/tools/CodeQualityAnalyzer.py +3 -3
  73. tooluniverse/tools/EMDB_get_structure.py +46 -0
  74. tooluniverse/tools/GtoPdb_get_targets.py +52 -0
  75. tooluniverse/tools/InterPro_get_domain_details.py +46 -0
  76. tooluniverse/tools/InterPro_get_protein_domains.py +49 -0
  77. tooluniverse/tools/InterPro_search_domains.py +52 -0
  78. tooluniverse/tools/JASPAR_get_transcription_factors.py +52 -0
  79. tooluniverse/tools/MPD_get_phenotype_data.py +59 -0
  80. tooluniverse/tools/PRIDE_search_proteomics.py +52 -0
  81. tooluniverse/tools/PackageAnalyzer.py +55 -0
  82. tooluniverse/tools/Paleobiology_get_fossils.py +52 -0
  83. tooluniverse/tools/PyPIPackageInspector.py +59 -0
  84. tooluniverse/tools/ReMap_get_transcription_factor_binding.py +59 -0
  85. tooluniverse/tools/ReferenceInfoAnalyzer.py +55 -0
  86. tooluniverse/tools/RegulomeDB_query_variant.py +46 -0
  87. tooluniverse/tools/SCREEN_get_regulatory_elements.py +59 -0
  88. tooluniverse/tools/{ArgumentDescriptionOptimizer.py → TestResultsAnalyzer.py} +13 -13
  89. tooluniverse/tools/ToolDiscover.py +11 -11
  90. tooluniverse/tools/UniProt_id_mapping.py +63 -0
  91. tooluniverse/tools/UniProt_search.py +63 -0
  92. tooluniverse/tools/UnifiedToolGenerator.py +59 -0
  93. tooluniverse/tools/WoRMS_search_species.py +49 -0
  94. tooluniverse/tools/XMLToolOptimizer.py +55 -0
  95. tooluniverse/tools/__init__.py +119 -29
  96. tooluniverse/tools/alphafold_get_annotations.py +3 -3
  97. tooluniverse/tools/alphafold_get_prediction.py +3 -3
  98. tooluniverse/tools/alphafold_get_summary.py +3 -3
  99. tooluniverse/tools/cBioPortal_get_cancer_studies.py +46 -0
  100. tooluniverse/tools/cBioPortal_get_mutations.py +52 -0
  101. tooluniverse/tools/{gnomAD_query_variant.py → clinvar_get_clinical_significance.py} +8 -11
  102. tooluniverse/tools/clinvar_get_variant_details.py +49 -0
  103. tooluniverse/tools/dbSNP_get_variant_by_rsid.py +7 -7
  104. tooluniverse/tools/dbsnp_get_frequencies.py +46 -0
  105. tooluniverse/tools/dbsnp_search_by_gene.py +52 -0
  106. tooluniverse/tools/download_binary_file.py +66 -0
  107. tooluniverse/tools/download_file.py +71 -0
  108. tooluniverse/tools/download_text_content.py +55 -0
  109. tooluniverse/tools/dynamic_package_discovery.py +59 -0
  110. tooluniverse/tools/ensembl_get_sequence.py +52 -0
  111. tooluniverse/tools/{Ensembl_lookup_gene_by_symbol.py → ensembl_get_variants.py} +11 -11
  112. tooluniverse/tools/ensembl_lookup_gene.py +46 -0
  113. tooluniverse/tools/geo_get_dataset_info.py +46 -0
  114. tooluniverse/tools/geo_get_sample_info.py +46 -0
  115. tooluniverse/tools/geo_search_datasets.py +67 -0
  116. tooluniverse/tools/gnomad_get_gene_constraints.py +49 -0
  117. tooluniverse/tools/kegg_find_genes.py +52 -0
  118. tooluniverse/tools/kegg_get_gene_info.py +46 -0
  119. tooluniverse/tools/kegg_get_pathway_info.py +46 -0
  120. tooluniverse/tools/kegg_list_organisms.py +44 -0
  121. tooluniverse/tools/kegg_search_pathway.py +46 -0
  122. tooluniverse/tools/ols_find_similar_terms.py +63 -0
  123. tooluniverse/tools/{get_hyperopt_info.py → ols_get_ontology_info.py} +13 -10
  124. tooluniverse/tools/ols_get_term_ancestors.py +67 -0
  125. tooluniverse/tools/ols_get_term_children.py +67 -0
  126. tooluniverse/tools/{TestCaseGenerator.py → ols_get_term_info.py} +12 -9
  127. tooluniverse/tools/{CodeOptimizer.py → ols_search_ontologies.py} +22 -14
  128. tooluniverse/tools/ols_search_terms.py +71 -0
  129. tooluniverse/tools/python_code_executor.py +79 -0
  130. tooluniverse/tools/python_script_runner.py +79 -0
  131. tooluniverse/tools/web_api_documentation_search.py +63 -0
  132. tooluniverse/tools/web_search.py +71 -0
  133. tooluniverse/uniprot_tool.py +219 -16
  134. tooluniverse/url_tool.py +18 -0
  135. tooluniverse/utils.py +2 -2
  136. tooluniverse/web_search_tool.py +229 -0
  137. tooluniverse/worms_tool.py +64 -0
  138. {tooluniverse-1.0.10.dist-info → tooluniverse-1.0.11.dist-info}/METADATA +3 -2
  139. {tooluniverse-1.0.10.dist-info → tooluniverse-1.0.11.dist-info}/RECORD +143 -54
  140. tooluniverse/data/genomics_tools.json +0 -174
  141. tooluniverse/tools/ToolDescriptionOptimizer.py +0 -67
  142. tooluniverse/tools/ToolImplementationGenerator.py +0 -67
  143. tooluniverse/tools/ToolOptimizer.py +0 -59
  144. tooluniverse/tools/ToolSpecificationGenerator.py +0 -67
  145. tooluniverse/tools/ToolSpecificationOptimizer.py +0 -63
  146. tooluniverse/ucsc_tool.py +0 -60
  147. {tooluniverse-1.0.10.dist-info → tooluniverse-1.0.11.dist-info}/WHEEL +0 -0
  148. {tooluniverse-1.0.10.dist-info → tooluniverse-1.0.11.dist-info}/entry_points.txt +0 -0
  149. {tooluniverse-1.0.10.dist-info → tooluniverse-1.0.11.dist-info}/licenses/LICENSE +0 -0
  150. {tooluniverse-1.0.10.dist-info → tooluniverse-1.0.11.dist-info}/top_level.txt +0 -0
@@ -51,6 +51,16 @@ default_tool_files = {
51
51
  "fatcat": os.path.join(current_dir, "data", "fatcat_tools.json"),
52
52
  "wikidata_sparql": os.path.join(current_dir, "data", "wikidata_sparql_tools.json"),
53
53
  "agents": os.path.join(current_dir, "data", "agentic_tools.json"),
54
+ "tool_discovery_agents": os.path.join(
55
+ current_dir, "data", "tool_discovery_agents.json"
56
+ ),
57
+ "web_search_tools": os.path.join(current_dir, "data", "web_search_tools.json"),
58
+ "package_discovery_tools": os.path.join(
59
+ current_dir, "data", "package_discovery_tools.json"
60
+ ),
61
+ "pypi_package_inspector_tools": os.path.join(
62
+ current_dir, "data", "pypi_package_inspector_tools.json"
63
+ ),
54
64
  "drug_discovery_agents": os.path.join(
55
65
  current_dir, "data", "drug_discovery_agents.json"
56
66
  ),
@@ -100,6 +110,20 @@ default_tool_files = {
100
110
  "visualization_molecule_2d": os.path.join(
101
111
  current_dir, "data", "molecule_2d_tools.json"
102
112
  ),
113
+ # New database tools
114
+ "interpro": os.path.join(current_dir, "data", "interpro_tools.json"),
115
+ "blast": os.path.join(current_dir, "data", "blast_tools.json"),
116
+ "cbioportal": os.path.join(current_dir, "data", "cbioportal_tools.json"),
117
+ "regulomedb": os.path.join(current_dir, "data", "regulomedb_tools.json"),
118
+ "jaspar": os.path.join(current_dir, "data", "jaspar_tools.json"),
119
+ "remap": os.path.join(current_dir, "data", "remap_tools.json"),
120
+ "screen": os.path.join(current_dir, "data", "screen_tools.json"),
121
+ "pride": os.path.join(current_dir, "data", "pride_tools.json"),
122
+ "emdb": os.path.join(current_dir, "data", "emdb_tools.json"),
123
+ "gtopdb": os.path.join(current_dir, "data", "gtopdb_tools.json"),
124
+ "mpd": os.path.join(current_dir, "data", "mpd_tools.json"),
125
+ "worms": os.path.join(current_dir, "data", "worms_tools.json"),
126
+ "paleobiology": os.path.join(current_dir, "data", "paleobiology_tools.json"),
103
127
  "visualization_molecule_3d": os.path.join(
104
128
  current_dir, "data", "molecule_3d_tools.json"
105
129
  ),
@@ -120,6 +144,7 @@ default_tool_files = {
120
144
  ),
121
145
  "go": os.path.join(current_dir, "data", "gene_ontology_tools.json"),
122
146
  "compose": os.path.join(current_dir, "data", "compose_tools.json"),
147
+ "python_executor": os.path.join(current_dir, "data", "python_executor_tools.json"),
123
148
  "idmap": os.path.join(current_dir, "data", "idmap_tools.json"),
124
149
  "disease_target_score": os.path.join(
125
150
  current_dir, "data", "disease_target_score_tools.json"
@@ -131,6 +156,7 @@ default_tool_files = {
131
156
  "xml": os.path.join(current_dir, "data", "xml_tools.json"),
132
157
  "mcp_auto_loader_boltz": os.path.join(current_dir, "data", "boltz_tools.json"),
133
158
  "url": os.path.join(current_dir, "data", "url_fetch_tools.json"),
159
+ "file_download": os.path.join(current_dir, "data", "file_download_tools.json"),
134
160
  # 'langchain': os.path.join(current_dir, 'data', 'langchain_tools.json'),
135
161
  "rcsb_pdb": os.path.join(current_dir, "data", "rcsb_pdb_tools.json"),
136
162
  "tool_composition": os.path.join(
@@ -146,10 +172,17 @@ default_tool_files = {
146
172
  ),
147
173
  "odphp": os.path.join(current_dir, "data", "odphp_tools.json"),
148
174
  "markitdown": os.path.join(current_dir, "data", "markitdown_tools.json"),
149
- # Genomics tools
150
- "genomics": os.path.join(current_dir, "data", "genomics_tools.json"),
151
175
  # Guideline and health policy tools
152
176
  "guidelines": os.path.join(current_dir, "data", "unified_guideline_tools.json"),
177
+ # Database tools
178
+ "kegg": os.path.join(current_dir, "data", "kegg_tools.json"),
179
+ "ensembl": os.path.join(current_dir, "data", "ensembl_tools.json"),
180
+ "clinvar": os.path.join(current_dir, "data", "clinvar_tools.json"),
181
+ "geo": os.path.join(current_dir, "data", "geo_tools.json"),
182
+ "dbsnp": os.path.join(current_dir, "data", "dbsnp_tools.json"),
183
+ "gnomad": os.path.join(current_dir, "data", "gnomad_tools.json"),
184
+ # Ontology tools
185
+ "ols": os.path.join(current_dir, "data", "ols_tools.json"),
153
186
  }
154
187
 
155
188
 
@@ -0,0 +1,30 @@
1
+ import requests
2
+ from typing import Any, Dict
3
+ from .base_tool import BaseTool
4
+ from .tool_registry import register_tool
5
+
6
+
7
+ @register_tool("EMDBRESTTool")
8
+ class EMDBRESTTool(BaseTool):
9
+ def __init__(self, tool_config: Dict):
10
+ super().__init__(tool_config)
11
+ self.base_url = "https://www.ebi.ac.uk/emdb/api"
12
+ self.session = requests.Session()
13
+ self.session.headers.update({"Accept": "application/json"})
14
+ self.timeout = 30
15
+
16
+ def _build_url(self, args: Dict[str, Any]) -> str:
17
+ url = self.tool_config["fields"]["endpoint"]
18
+ for k, v in args.items():
19
+ url = url.replace(f"{{{k}}}", str(v))
20
+ return url
21
+
22
+ def run(self, arguments: Dict[str, Any]) -> Dict[str, Any]:
23
+ try:
24
+ url = self._build_url(arguments)
25
+ response = self.session.get(url, timeout=self.timeout)
26
+ response.raise_for_status()
27
+ data = response.json()
28
+ return {"status": "success", "data": data, "url": url}
29
+ except Exception as e:
30
+ return {"status": "error", "error": f"EMDB API error: {str(e)}"}
@@ -1,61 +1,154 @@
1
+ """
2
+ Ensembl REST API Tool
3
+
4
+ This tool provides access to the Ensembl genome browser database for gene
5
+ lookup, sequence retrieval, variant information, and homology data.
6
+ """
7
+
1
8
  import requests
9
+ from typing import Dict, Any, Optional
2
10
  from .base_tool import BaseTool
3
11
  from .tool_registry import register_tool
4
12
 
5
13
 
6
- @register_tool("EnsemblTool")
7
- class EnsemblTool(BaseTool):
8
- """
9
- Local tool wrapper for Ensembl REST API lookups.
10
- Supports symbol→gene lookup (xrefs/symbol) then lookup/id to
11
- fetch metadata.
12
- """
14
+ class EnsemblRESTTool(BaseTool):
15
+ """Base class for Ensembl REST API tools."""
13
16
 
14
17
  def __init__(self, tool_config):
15
18
  super().__init__(tool_config)
16
- self.base = "https://rest.ensembl.org"
19
+ self.base_url = "https://rest.ensembl.org"
17
20
  self.session = requests.Session()
18
21
  self.session.headers.update(
19
- {"Accept": "application/json", "Content-Type": "application/json"}
22
+ {
23
+ "Accept": "application/json",
24
+ "Content-Type": "application/json",
25
+ "User-Agent": "ToolUniverse/1.0",
26
+ }
20
27
  )
28
+ self.timeout = 30
29
+
30
+ def _make_request(
31
+ self, endpoint: str, params: Optional[Dict] = None
32
+ ) -> Dict[str, Any]:
33
+ """Make a request to the Ensembl API."""
34
+ url = f"{self.base_url}{endpoint}"
35
+ try:
36
+ response = self.session.get(url, params=params, timeout=self.timeout)
37
+ response.raise_for_status()
38
+
39
+ data = response.json()
40
+ return {
41
+ "status": "success",
42
+ "data": data,
43
+ "url": url,
44
+ "content_type": response.headers.get(
45
+ "content-type", "application/json"
46
+ ),
47
+ }
48
+
49
+ except requests.exceptions.RequestException as e:
50
+ return {
51
+ "status": "error",
52
+ "error": f"Ensembl API request failed: {str(e)}",
53
+ "url": url,
54
+ }
21
55
 
22
- def run(self, arguments):
23
- species = arguments.get("species", "homo_sapiens")
24
- symbol = arguments.get("symbol")
25
- if not symbol:
26
- return {"error": "Missing required parameter: symbol"}
27
-
28
- # 1) symbol -> xref(s) to get Ensembl gene ID
29
- xref_url = f"{self.base}/xrefs/symbol/{species}/{symbol}"
30
- xref_resp = self.session.get(xref_url, timeout=20)
31
- xref_resp.raise_for_status()
32
- xrefs = xref_resp.json() or []
33
- gene_id = None
34
- for item in xrefs:
35
- if item.get("type") == "gene" and item.get("id"):
36
- gene_id = item["id"]
37
- break
38
- if not gene_id and xrefs:
39
- gene_id = xrefs[0].get("id")
56
+ def run(self, arguments: Dict[str, Any]) -> Dict[str, Any]:
57
+ """Execute the tool with given arguments."""
58
+ return self._make_request(self.endpoint, arguments)
59
+
60
+
61
+ @register_tool("EnsemblLookupGene")
62
+ class EnsemblLookupGene(EnsemblRESTTool):
63
+ """Lookup gene information by ID or symbol."""
64
+
65
+ def __init__(self, tool_config):
66
+ super().__init__(tool_config)
67
+ self.endpoint = "/lookup/id"
68
+
69
+ def run(self, arguments: Dict[str, Any]) -> Dict[str, Any]:
70
+ """Lookup gene by ID or symbol."""
71
+ gene_id = arguments.get("gene_id", "")
40
72
  if not gene_id:
41
- return {"error": f"No Ensembl gene found for {symbol}"}
42
-
43
- # 2) lookup by Ensembl ID (expand=1 to include transcripts)
44
- lookup_url = f"{self.base}/lookup/id/{gene_id}?expand=1"
45
- look_resp = self.session.get(lookup_url, timeout=30)
46
- look_resp.raise_for_status()
47
- data = look_resp.json() or {}
48
-
49
- transcripts = data.get("Transcript") or []
50
- return {
51
- "id": data.get("id"),
52
- "symbol": symbol,
53
- "display_name": data.get("display_name"),
54
- "species": data.get("species"),
55
- "seq_region_name": data.get("seq_region_name"),
56
- "start": data.get("start"),
57
- "end": data.get("end"),
58
- "strand": data.get("strand"),
59
- "biotype": data.get("biotype"),
60
- "transcript_count": len(transcripts),
73
+ return {"status": "error", "error": "gene_id is required"}
74
+
75
+ # Ensembl API requires the ID in the URL path
76
+ endpoint = f"{self.endpoint}/{gene_id}"
77
+ params = {"expand": 1}
78
+
79
+ # Add species if specified
80
+ if "species" in arguments:
81
+ params["species"] = arguments["species"]
82
+
83
+ result = self._make_request(endpoint, params)
84
+
85
+ # Add gene_id to result for reference
86
+ if result.get("status") == "success":
87
+ result["gene_id"] = gene_id
88
+
89
+ return result
90
+
91
+
92
+ @register_tool("EnsemblGetSequence")
93
+ class EnsemblGetSequence(EnsemblRESTTool):
94
+ """Get DNA or protein sequences by region or gene ID."""
95
+
96
+ def __init__(self, tool_config):
97
+ super().__init__(tool_config)
98
+ self.endpoint = "/sequence/id"
99
+
100
+ def run(self, arguments: Dict[str, Any]) -> Dict[str, Any]:
101
+ """Get sequence by gene ID or region."""
102
+ sequence_id = arguments.get("sequence_id", "")
103
+ if not sequence_id:
104
+ return {"status": "error", "error": "sequence_id is required"}
105
+
106
+ # Ensembl API requires the ID in the URL path
107
+ endpoint = f"{self.endpoint}/{sequence_id}"
108
+ params = {
109
+ "type": arguments.get("type", "genomic"), # genomic, cds, protein
110
+ "multiple_sequences": "true",
61
111
  }
112
+
113
+ # Add species if specified
114
+ if "species" in arguments:
115
+ params["species"] = arguments["species"]
116
+
117
+ result = self._make_request(endpoint, params)
118
+
119
+ # Add sequence_id to result for reference
120
+ if result.get("status") == "success":
121
+ result["sequence_id"] = sequence_id
122
+
123
+ return result
124
+
125
+
126
+ @register_tool("EnsemblGetVariants")
127
+ class EnsemblGetVariants(EnsemblRESTTool):
128
+ """Get variant information for a genomic region."""
129
+
130
+ def __init__(self, tool_config):
131
+ super().__init__(tool_config)
132
+ self.endpoint = "/overlap/id"
133
+
134
+ def run(self, arguments: Dict[str, Any]) -> Dict[str, Any]:
135
+ """Get variants for a region."""
136
+ region = arguments.get("region", "")
137
+ if not region:
138
+ return {
139
+ "status": "error",
140
+ "error": "region is required (e.g., '1:1000000..2000000')",
141
+ }
142
+
143
+ # Ensembl API requires the region in the URL path with species
144
+ species = arguments.get("species", "human")
145
+ endpoint = f"/overlap/region/{species}/{region}"
146
+ params = {"feature": "variation", "content-type": "application/json"}
147
+
148
+ result = self._make_request(endpoint, params)
149
+
150
+ # Add region to result for reference
151
+ if result.get("status") == "success":
152
+ result["region"] = region
153
+
154
+ return result
@@ -563,8 +563,9 @@ class ToolUniverse:
563
563
  for key in sorted(all_missing_keys):
564
564
  f.write(f"{key}=your_api_key_here\n\n")
565
565
 
566
- self.logger.info(f"Generated API key template: {output_file}")
567
- self.logger.info("Copy this file to .env and fill in your API keys")
566
+ self.logger.info(
567
+ f"Generated API key template: {output_file}. Copy this file to .env and fill in your API keys"
568
+ )
568
569
 
569
570
  def _create_hook_config_from_type(self, hook_type):
570
571
  """
@@ -966,8 +967,10 @@ class ToolUniverse:
966
967
 
967
968
  # Generate template for missing API keys
968
969
  if len(all_missing_keys) > 0:
969
- warning(f"\nMissing API keys: {', '.join(all_missing_keys)}")
970
- info("Generating .env.template file with missing API keys...")
970
+ warning(
971
+ f"Some tools will not be loaded due to missing API keys: {', '.join(all_missing_keys)}"
972
+ )
973
+ # info("Generating .env.template file with missing API keys...")
971
974
  self.generate_env_template(all_missing_keys)
972
975
 
973
976
  def _load_auto_discovered_configs(self):
@@ -2137,16 +2140,42 @@ class ToolUniverse:
2137
2140
  tool_instance, arguments, stream_callback, use_cache, validate
2138
2141
  )
2139
2142
  else:
2140
- error_msg = f"Tool '{function_name}' not found"
2141
- return self._create_dual_format_error(
2142
- ToolUnavailableError(
2143
- error_msg,
2144
- next_steps=[
2145
- "Check tool name spelling",
2146
- "Run tu.tools.refresh()",
2147
- ],
2143
+ # Try to auto-load tools if dictionary is empty
2144
+ if not self._auto_load_tools_if_empty(function_name):
2145
+ error_msg = "Failed to auto-load tools"
2146
+ return self._create_dual_format_error(
2147
+ ToolUnavailableError(
2148
+ error_msg,
2149
+ next_steps=[
2150
+ "Manually run tu.load_tools()",
2151
+ "Check tool configuration",
2152
+ ],
2153
+ )
2154
+ )
2155
+
2156
+ # Try to get the tool instance again after loading
2157
+ tool_instance = self._get_tool_instance(function_name, cache=True)
2158
+ if tool_instance:
2159
+ result, tool_arguments = self._execute_tool_with_stream(
2160
+ tool_instance,
2161
+ arguments,
2162
+ stream_callback,
2163
+ use_cache,
2164
+ validate,
2165
+ )
2166
+ else:
2167
+ error_msg = (
2168
+ f"Tool '{function_name}' not found even after loading tools"
2169
+ )
2170
+ return self._create_dual_format_error(
2171
+ ToolUnavailableError(
2172
+ error_msg,
2173
+ next_steps=[
2174
+ "Check tool name spelling",
2175
+ "Verify tool is available in loaded categories",
2176
+ ],
2177
+ )
2148
2178
  )
2149
- )
2150
2179
  except Exception as e:
2151
2180
  # Classify and return structured error
2152
2181
  classified_error = self._classify_exception(e, function_name, arguments)
@@ -2356,6 +2385,29 @@ class ToolUniverse:
2356
2385
 
2357
2386
  return None
2358
2387
 
2388
+ def _auto_load_tools_if_empty(self, function_name: str = None) -> bool:
2389
+ """
2390
+ Automatically load tools if the tools dictionary is empty.
2391
+
2392
+ Args:
2393
+ function_name: Optional tool name to check after loading
2394
+
2395
+ Returns:
2396
+ bool: True if tools were loaded successfully, False otherwise
2397
+ """
2398
+ if not self.all_tool_dict:
2399
+ print(
2400
+ "⚠️ Warning: No tools loaded. Automatically running tu.load_tools()..."
2401
+ )
2402
+ try:
2403
+ self.load_tools()
2404
+ print("✅ Tools loaded successfully.")
2405
+ return True
2406
+ except Exception as load_error:
2407
+ print(f"❌ Failed to auto-load tools: {load_error}")
2408
+ return False
2409
+ return True
2410
+
2359
2411
  def _make_cache_key(self, function_name: str, arguments: dict) -> str:
2360
2412
  """Generate cache key by delegating to BaseTool."""
2361
2413
  tool_instance = self._get_tool_instance(function_name, cache=False)
@@ -2374,7 +2426,15 @@ class ToolUniverse:
2374
2426
  ) -> Optional[ToolError]:
2375
2427
  """Validate parameters by delegating to BaseTool."""
2376
2428
  if function_name not in self.all_tool_dict:
2377
- return ToolUnavailableError(f"Tool '{function_name}' not found")
2429
+ # Try to auto-load tools if dictionary is empty
2430
+ if not self._auto_load_tools_if_empty(function_name):
2431
+ return ToolUnavailableError("Failed to auto-load tools")
2432
+
2433
+ # Check again after loading
2434
+ if function_name not in self.all_tool_dict:
2435
+ return ToolUnavailableError(
2436
+ f"Tool '{function_name}' not found even after loading tools"
2437
+ )
2378
2438
 
2379
2439
  tool_instance = self._get_tool_instance(function_name, cache=False)
2380
2440
  if not tool_instance: