tooluniverse 0.2.0__py3-none-any.whl → 1.0.1__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.
- tooluniverse/__init__.py +340 -4
- tooluniverse/admetai_tool.py +84 -0
- tooluniverse/agentic_tool.py +563 -0
- tooluniverse/alphafold_tool.py +96 -0
- tooluniverse/base_tool.py +129 -6
- tooluniverse/boltz_tool.py +207 -0
- tooluniverse/chem_tool.py +192 -0
- tooluniverse/compose_scripts/__init__.py +1 -0
- tooluniverse/compose_scripts/biomarker_discovery.py +293 -0
- tooluniverse/compose_scripts/comprehensive_drug_discovery.py +186 -0
- tooluniverse/compose_scripts/drug_safety_analyzer.py +89 -0
- tooluniverse/compose_scripts/literature_tool.py +34 -0
- tooluniverse/compose_scripts/output_summarizer.py +279 -0
- tooluniverse/compose_scripts/tool_description_optimizer.py +681 -0
- tooluniverse/compose_scripts/tool_discover.py +705 -0
- tooluniverse/compose_scripts/tool_graph_composer.py +448 -0
- tooluniverse/compose_tool.py +371 -0
- tooluniverse/ctg_tool.py +1002 -0
- tooluniverse/custom_tool.py +81 -0
- tooluniverse/dailymed_tool.py +108 -0
- tooluniverse/data/admetai_tools.json +155 -0
- tooluniverse/data/adverse_event_tools.json +108 -0
- tooluniverse/data/agentic_tools.json +1156 -0
- tooluniverse/data/alphafold_tools.json +87 -0
- tooluniverse/data/boltz_tools.json +9 -0
- tooluniverse/data/chembl_tools.json +16 -0
- tooluniverse/data/clinicaltrials_gov_tools.json +326 -0
- tooluniverse/data/compose_tools.json +202 -0
- tooluniverse/data/dailymed_tools.json +70 -0
- tooluniverse/data/dataset_tools.json +646 -0
- tooluniverse/data/disease_target_score_tools.json +712 -0
- tooluniverse/data/efo_tools.json +17 -0
- tooluniverse/data/embedding_tools.json +319 -0
- tooluniverse/data/enrichr_tools.json +31 -0
- tooluniverse/data/europe_pmc_tools.json +22 -0
- tooluniverse/data/expert_feedback_tools.json +10 -0
- tooluniverse/data/fda_drug_adverse_event_tools.json +491 -0
- tooluniverse/data/fda_drug_labeling_tools.json +1 -1
- tooluniverse/data/fda_drugs_with_brand_generic_names_for_tool.py +76929 -148860
- tooluniverse/data/finder_tools.json +209 -0
- tooluniverse/data/gene_ontology_tools.json +113 -0
- tooluniverse/data/gwas_tools.json +1082 -0
- tooluniverse/data/hpa_tools.json +333 -0
- tooluniverse/data/humanbase_tools.json +47 -0
- tooluniverse/data/idmap_tools.json +74 -0
- tooluniverse/data/mcp_client_tools_example.json +113 -0
- tooluniverse/data/mcpautoloadertool_defaults.json +28 -0
- tooluniverse/data/medlineplus_tools.json +141 -0
- tooluniverse/data/monarch_tools.json +1 -1
- tooluniverse/data/openalex_tools.json +36 -0
- tooluniverse/data/opentarget_tools.json +1 -1
- tooluniverse/data/output_summarization_tools.json +101 -0
- tooluniverse/data/packages/bioinformatics_core_tools.json +1756 -0
- tooluniverse/data/packages/categorized_tools.txt +206 -0
- tooluniverse/data/packages/cheminformatics_tools.json +347 -0
- tooluniverse/data/packages/earth_sciences_tools.json +74 -0
- tooluniverse/data/packages/genomics_tools.json +776 -0
- tooluniverse/data/packages/image_processing_tools.json +38 -0
- tooluniverse/data/packages/machine_learning_tools.json +789 -0
- tooluniverse/data/packages/neuroscience_tools.json +62 -0
- tooluniverse/data/packages/original_tools.txt +0 -0
- tooluniverse/data/packages/physics_astronomy_tools.json +62 -0
- tooluniverse/data/packages/scientific_computing_tools.json +560 -0
- tooluniverse/data/packages/single_cell_tools.json +453 -0
- tooluniverse/data/packages/structural_biology_tools.json +396 -0
- tooluniverse/data/packages/visualization_tools.json +399 -0
- tooluniverse/data/pubchem_tools.json +215 -0
- tooluniverse/data/pubtator_tools.json +68 -0
- tooluniverse/data/rcsb_pdb_tools.json +1332 -0
- tooluniverse/data/reactome_tools.json +19 -0
- tooluniverse/data/semantic_scholar_tools.json +26 -0
- tooluniverse/data/special_tools.json +2 -25
- tooluniverse/data/tool_composition_tools.json +88 -0
- tooluniverse/data/toolfinderkeyword_defaults.json +34 -0
- tooluniverse/data/txagent_client_tools.json +9 -0
- tooluniverse/data/uniprot_tools.json +211 -0
- tooluniverse/data/url_fetch_tools.json +94 -0
- tooluniverse/data/uspto_downloader_tools.json +9 -0
- tooluniverse/data/uspto_tools.json +811 -0
- tooluniverse/data/xml_tools.json +3275 -0
- tooluniverse/dataset_tool.py +296 -0
- tooluniverse/default_config.py +165 -0
- tooluniverse/efo_tool.py +42 -0
- tooluniverse/embedding_database.py +630 -0
- tooluniverse/embedding_sync.py +396 -0
- tooluniverse/enrichr_tool.py +266 -0
- tooluniverse/europe_pmc_tool.py +52 -0
- tooluniverse/execute_function.py +1775 -95
- tooluniverse/extended_hooks.py +444 -0
- tooluniverse/gene_ontology_tool.py +194 -0
- tooluniverse/graphql_tool.py +158 -36
- tooluniverse/gwas_tool.py +358 -0
- tooluniverse/hpa_tool.py +1645 -0
- tooluniverse/humanbase_tool.py +389 -0
- tooluniverse/logging_config.py +254 -0
- tooluniverse/mcp_client_tool.py +764 -0
- tooluniverse/mcp_integration.py +413 -0
- tooluniverse/mcp_tool_registry.py +925 -0
- tooluniverse/medlineplus_tool.py +337 -0
- tooluniverse/openalex_tool.py +228 -0
- tooluniverse/openfda_adv_tool.py +283 -0
- tooluniverse/openfda_tool.py +393 -160
- tooluniverse/output_hook.py +1122 -0
- tooluniverse/package_tool.py +195 -0
- tooluniverse/pubchem_tool.py +158 -0
- tooluniverse/pubtator_tool.py +168 -0
- tooluniverse/rcsb_pdb_tool.py +38 -0
- tooluniverse/reactome_tool.py +108 -0
- tooluniverse/remote/boltz/boltz_mcp_server.py +50 -0
- tooluniverse/remote/depmap_24q2/depmap_24q2_mcp_tool.py +442 -0
- tooluniverse/remote/expert_feedback/human_expert_mcp_tools.py +2013 -0
- tooluniverse/remote/expert_feedback/simple_test.py +23 -0
- tooluniverse/remote/expert_feedback/start_web_interface.py +188 -0
- tooluniverse/remote/expert_feedback/web_only_interface.py +0 -0
- tooluniverse/remote/immune_compass/compass_tool.py +327 -0
- tooluniverse/remote/pinnacle/pinnacle_tool.py +328 -0
- tooluniverse/remote/transcriptformer/transcriptformer_tool.py +586 -0
- tooluniverse/remote/uspto_downloader/uspto_downloader_mcp_server.py +61 -0
- tooluniverse/remote/uspto_downloader/uspto_downloader_tool.py +120 -0
- tooluniverse/remote_tool.py +99 -0
- tooluniverse/restful_tool.py +53 -30
- tooluniverse/scripts/generate_tool_graph.py +408 -0
- tooluniverse/scripts/visualize_tool_graph.py +829 -0
- tooluniverse/semantic_scholar_tool.py +62 -0
- tooluniverse/smcp.py +2452 -0
- tooluniverse/smcp_server.py +975 -0
- tooluniverse/test/mcp_server_test.py +0 -0
- tooluniverse/test/test_admetai_tool.py +370 -0
- tooluniverse/test/test_agentic_tool.py +129 -0
- tooluniverse/test/test_alphafold_tool.py +71 -0
- tooluniverse/test/test_chem_tool.py +37 -0
- tooluniverse/test/test_compose_lieraturereview.py +63 -0
- tooluniverse/test/test_compose_tool.py +448 -0
- tooluniverse/test/test_dailymed.py +69 -0
- tooluniverse/test/test_dataset_tool.py +200 -0
- tooluniverse/test/test_disease_target_score.py +56 -0
- tooluniverse/test/test_drugbank_filter_examples.py +179 -0
- tooluniverse/test/test_efo.py +31 -0
- tooluniverse/test/test_enrichr_tool.py +21 -0
- tooluniverse/test/test_europe_pmc_tool.py +20 -0
- tooluniverse/test/test_fda_adv.py +95 -0
- tooluniverse/test/test_fda_drug_labeling.py +91 -0
- tooluniverse/test/test_gene_ontology_tools.py +66 -0
- tooluniverse/test/test_gwas_tool.py +139 -0
- tooluniverse/test/test_hpa.py +625 -0
- tooluniverse/test/test_humanbase_tool.py +20 -0
- tooluniverse/test/test_idmap_tools.py +61 -0
- tooluniverse/test/test_mcp_server.py +211 -0
- tooluniverse/test/test_mcp_tool.py +247 -0
- tooluniverse/test/test_medlineplus.py +220 -0
- tooluniverse/test/test_openalex_tool.py +32 -0
- tooluniverse/test/test_opentargets.py +28 -0
- tooluniverse/test/test_pubchem_tool.py +116 -0
- tooluniverse/test/test_pubtator_tool.py +37 -0
- tooluniverse/test/test_rcsb_pdb_tool.py +86 -0
- tooluniverse/test/test_reactome.py +54 -0
- tooluniverse/test/test_semantic_scholar_tool.py +24 -0
- tooluniverse/test/test_software_tools.py +147 -0
- tooluniverse/test/test_tool_description_optimizer.py +49 -0
- tooluniverse/test/test_tool_finder.py +26 -0
- tooluniverse/test/test_tool_finder_llm.py +252 -0
- tooluniverse/test/test_tools_find.py +195 -0
- tooluniverse/test/test_uniprot_tools.py +74 -0
- tooluniverse/test/test_uspto_tool.py +72 -0
- tooluniverse/test/test_xml_tool.py +113 -0
- tooluniverse/tool_finder_embedding.py +267 -0
- tooluniverse/tool_finder_keyword.py +693 -0
- tooluniverse/tool_finder_llm.py +699 -0
- tooluniverse/tool_graph_web_ui.py +955 -0
- tooluniverse/tool_registry.py +416 -0
- tooluniverse/uniprot_tool.py +155 -0
- tooluniverse/url_tool.py +253 -0
- tooluniverse/uspto_tool.py +240 -0
- tooluniverse/utils.py +369 -41
- tooluniverse/xml_tool.py +369 -0
- tooluniverse-1.0.1.dist-info/METADATA +387 -0
- tooluniverse-1.0.1.dist-info/RECORD +182 -0
- tooluniverse-1.0.1.dist-info/entry_points.txt +9 -0
- tooluniverse/generate_mcp_tools.py +0 -113
- tooluniverse/mcp_server.py +0 -3340
- tooluniverse-0.2.0.dist-info/METADATA +0 -139
- tooluniverse-0.2.0.dist-info/RECORD +0 -21
- tooluniverse-0.2.0.dist-info/entry_points.txt +0 -4
- {tooluniverse-0.2.0.dist-info → tooluniverse-1.0.1.dist-info}/WHEEL +0 -0
- {tooluniverse-0.2.0.dist-info → tooluniverse-1.0.1.dist-info}/licenses/LICENSE +0 -0
- {tooluniverse-0.2.0.dist-info → tooluniverse-1.0.1.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,444 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Extended Hook Types for ToolUniverse
|
|
3
|
+
|
|
4
|
+
This module demonstrates how to extend the hook system with additional
|
|
5
|
+
hook types beyond summarization. It shows the pattern for creating
|
|
6
|
+
new hook types while maintaining compatibility with the existing system.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import re
|
|
10
|
+
import json
|
|
11
|
+
from typing import Dict, Any, List
|
|
12
|
+
from .output_hook import OutputHook
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class FilteringHook(OutputHook):
|
|
16
|
+
"""
|
|
17
|
+
Hook for filtering sensitive or unwanted content from tool outputs.
|
|
18
|
+
|
|
19
|
+
This hook can be used to:
|
|
20
|
+
- Remove sensitive information (emails, phones, SSNs)
|
|
21
|
+
- Filter inappropriate content
|
|
22
|
+
- Sanitize data before display
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
config (Dict[str, Any]): Hook configuration containing filter settings
|
|
26
|
+
tooluniverse: Optional ToolUniverse instance (not used for filtering)
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
def __init__(self, config: Dict[str, Any], tooluniverse=None):
|
|
30
|
+
"""
|
|
31
|
+
Initialize the filtering hook with configuration.
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
config (Dict[str, Any]): Hook configuration
|
|
35
|
+
tooluniverse: ToolUniverse instance (optional, not used)
|
|
36
|
+
"""
|
|
37
|
+
super().__init__(config)
|
|
38
|
+
hook_config = config.get("hook_config", {})
|
|
39
|
+
|
|
40
|
+
# Filter configuration
|
|
41
|
+
self.filter_patterns = hook_config.get("filter_patterns", [])
|
|
42
|
+
self.replacement_text = hook_config.get("replacement_text", "[REDACTED]")
|
|
43
|
+
self.preserve_structure = hook_config.get("preserve_structure", True)
|
|
44
|
+
self.log_filtered_items = hook_config.get("log_filtered_items", False)
|
|
45
|
+
|
|
46
|
+
# Compile regex patterns for efficiency
|
|
47
|
+
self.compiled_patterns = []
|
|
48
|
+
for pattern in self.filter_patterns:
|
|
49
|
+
try:
|
|
50
|
+
compiled = re.compile(pattern, re.IGNORECASE)
|
|
51
|
+
self.compiled_patterns.append(compiled)
|
|
52
|
+
except re.error as e:
|
|
53
|
+
print(f"Warning: Invalid regex pattern '{pattern}': {e}")
|
|
54
|
+
|
|
55
|
+
def process(
|
|
56
|
+
self,
|
|
57
|
+
result: Any,
|
|
58
|
+
tool_name: str,
|
|
59
|
+
arguments: Dict[str, Any],
|
|
60
|
+
context: Dict[str, Any],
|
|
61
|
+
) -> Any:
|
|
62
|
+
"""
|
|
63
|
+
Apply filtering to the tool output.
|
|
64
|
+
|
|
65
|
+
Args:
|
|
66
|
+
result (Any): The tool output to filter
|
|
67
|
+
tool_name (str): Name of the tool that produced the output
|
|
68
|
+
arguments (Dict[str, Any]): Arguments passed to the tool
|
|
69
|
+
context (Dict[str, Any]): Additional context information
|
|
70
|
+
|
|
71
|
+
Returns:
|
|
72
|
+
Any: The filtered output, or original output if filtering fails
|
|
73
|
+
"""
|
|
74
|
+
try:
|
|
75
|
+
if not self.compiled_patterns:
|
|
76
|
+
return result
|
|
77
|
+
|
|
78
|
+
output_str = result if isinstance(result, str) else str(result)
|
|
79
|
+
filtered_output = output_str
|
|
80
|
+
filtered_count = 0
|
|
81
|
+
|
|
82
|
+
# Apply each filter pattern
|
|
83
|
+
for pattern in self.compiled_patterns:
|
|
84
|
+
matches = pattern.findall(filtered_output)
|
|
85
|
+
if matches:
|
|
86
|
+
filtered_count += len(matches)
|
|
87
|
+
if self.log_filtered_items:
|
|
88
|
+
print(
|
|
89
|
+
f"🔒 Filtered {len(matches)} items matching pattern: {pattern.pattern}"
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
filtered_output = pattern.sub(
|
|
93
|
+
self.replacement_text, filtered_output
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
if filtered_count > 0:
|
|
97
|
+
print(
|
|
98
|
+
f"🔒 FilteringHook: Filtered {filtered_count} sensitive items from {tool_name} output"
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
return filtered_output
|
|
102
|
+
|
|
103
|
+
except Exception as e:
|
|
104
|
+
print(f"Error in filtering hook: {str(e)}")
|
|
105
|
+
return result
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
class FormattingHook(OutputHook):
|
|
109
|
+
"""
|
|
110
|
+
Hook for formatting and beautifying tool outputs.
|
|
111
|
+
|
|
112
|
+
This hook can be used to:
|
|
113
|
+
- Pretty-print JSON/XML outputs
|
|
114
|
+
- Format text with proper indentation
|
|
115
|
+
- Standardize output formats
|
|
116
|
+
|
|
117
|
+
Args:
|
|
118
|
+
config (Dict[str, Any]): Hook configuration containing formatting settings
|
|
119
|
+
tooluniverse: Optional ToolUniverse instance (not used for formatting)
|
|
120
|
+
"""
|
|
121
|
+
|
|
122
|
+
def __init__(self, config: Dict[str, Any], tooluniverse=None):
|
|
123
|
+
"""
|
|
124
|
+
Initialize the formatting hook with configuration.
|
|
125
|
+
|
|
126
|
+
Args:
|
|
127
|
+
config (Dict[str, Any]): Hook configuration
|
|
128
|
+
tooluniverse: ToolUniverse instance (optional, not used)
|
|
129
|
+
"""
|
|
130
|
+
super().__init__(config)
|
|
131
|
+
hook_config = config.get("hook_config", {})
|
|
132
|
+
|
|
133
|
+
# Formatting configuration
|
|
134
|
+
self.indent_size = hook_config.get("indent_size", 2)
|
|
135
|
+
self.sort_keys = hook_config.get("sort_keys", True)
|
|
136
|
+
self.ensure_ascii = hook_config.get("ensure_ascii", False)
|
|
137
|
+
self.max_line_length = hook_config.get("max_line_length", 100)
|
|
138
|
+
self.pretty_print = hook_config.get("pretty_print", True)
|
|
139
|
+
|
|
140
|
+
def process(
|
|
141
|
+
self,
|
|
142
|
+
result: Any,
|
|
143
|
+
tool_name: str,
|
|
144
|
+
arguments: Dict[str, Any],
|
|
145
|
+
context: Dict[str, Any],
|
|
146
|
+
) -> Any:
|
|
147
|
+
"""
|
|
148
|
+
Apply formatting to the tool output.
|
|
149
|
+
|
|
150
|
+
Args:
|
|
151
|
+
result (Any): The tool output to format
|
|
152
|
+
tool_name (str): Name of the tool that produced the output
|
|
153
|
+
arguments (Dict[str, Any]): Arguments passed to the tool
|
|
154
|
+
context (Dict[str, Any]): Additional context information
|
|
155
|
+
|
|
156
|
+
Returns:
|
|
157
|
+
Any: The formatted output, or original output if formatting fails
|
|
158
|
+
"""
|
|
159
|
+
try:
|
|
160
|
+
if isinstance(result, dict):
|
|
161
|
+
return self._format_json(result)
|
|
162
|
+
elif isinstance(result, str):
|
|
163
|
+
return self._format_text(result)
|
|
164
|
+
elif isinstance(result, list):
|
|
165
|
+
return self._format_list(result)
|
|
166
|
+
else:
|
|
167
|
+
return result
|
|
168
|
+
|
|
169
|
+
except Exception as e:
|
|
170
|
+
print(f"Error in formatting hook: {str(e)}")
|
|
171
|
+
return result
|
|
172
|
+
|
|
173
|
+
def _format_json(self, data: Dict[str, Any]) -> str:
|
|
174
|
+
"""Format JSON data with pretty printing."""
|
|
175
|
+
if self.pretty_print:
|
|
176
|
+
return json.dumps(
|
|
177
|
+
data,
|
|
178
|
+
indent=self.indent_size,
|
|
179
|
+
sort_keys=self.sort_keys,
|
|
180
|
+
ensure_ascii=self.ensure_ascii,
|
|
181
|
+
)
|
|
182
|
+
return json.dumps(data, ensure_ascii=self.ensure_ascii)
|
|
183
|
+
|
|
184
|
+
def _format_text(self, text: str) -> str:
|
|
185
|
+
"""Format plain text with line wrapping."""
|
|
186
|
+
if len(text) <= self.max_line_length:
|
|
187
|
+
return text
|
|
188
|
+
|
|
189
|
+
# Simple line wrapping
|
|
190
|
+
lines = []
|
|
191
|
+
current_line = ""
|
|
192
|
+
|
|
193
|
+
for word in text.split():
|
|
194
|
+
if len(current_line + " " + word) <= self.max_line_length:
|
|
195
|
+
current_line += (" " + word) if current_line else word
|
|
196
|
+
else:
|
|
197
|
+
if current_line:
|
|
198
|
+
lines.append(current_line)
|
|
199
|
+
current_line = word
|
|
200
|
+
|
|
201
|
+
if current_line:
|
|
202
|
+
lines.append(current_line)
|
|
203
|
+
|
|
204
|
+
return "\n".join(lines)
|
|
205
|
+
|
|
206
|
+
def _format_list(self, data: List[Any]) -> str:
|
|
207
|
+
"""Format list data."""
|
|
208
|
+
if self.pretty_print:
|
|
209
|
+
formatted_items = []
|
|
210
|
+
for i, item in enumerate(data):
|
|
211
|
+
if isinstance(item, dict):
|
|
212
|
+
formatted_items.append(f"{i+1}. {self._format_json(item)}")
|
|
213
|
+
else:
|
|
214
|
+
formatted_items.append(f"{i+1}. {str(item)}")
|
|
215
|
+
return "\n".join(formatted_items)
|
|
216
|
+
return str(data)
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
class ValidationHook(OutputHook):
|
|
220
|
+
"""
|
|
221
|
+
Hook for validating tool outputs against schemas or rules.
|
|
222
|
+
|
|
223
|
+
This hook can be used to:
|
|
224
|
+
- Validate JSON against schemas
|
|
225
|
+
- Check required fields
|
|
226
|
+
- Ensure data quality
|
|
227
|
+
|
|
228
|
+
Args:
|
|
229
|
+
config (Dict[str, Any]): Hook configuration containing validation settings
|
|
230
|
+
tooluniverse: Optional ToolUniverse instance (not used for validation)
|
|
231
|
+
"""
|
|
232
|
+
|
|
233
|
+
def __init__(self, config: Dict[str, Any], tooluniverse=None):
|
|
234
|
+
"""
|
|
235
|
+
Initialize the validation hook with configuration.
|
|
236
|
+
|
|
237
|
+
Args:
|
|
238
|
+
config (Dict[str, Any]): Hook configuration
|
|
239
|
+
tooluniverse: ToolUniverse instance (optional, not used)
|
|
240
|
+
"""
|
|
241
|
+
super().__init__(config)
|
|
242
|
+
hook_config = config.get("hook_config", {})
|
|
243
|
+
|
|
244
|
+
# Validation configuration
|
|
245
|
+
self.validation_schema = hook_config.get("validation_schema", None)
|
|
246
|
+
self.strict_mode = hook_config.get("strict_mode", True)
|
|
247
|
+
self.error_action = hook_config.get("error_action", "warn") # warn, fix, fail
|
|
248
|
+
self.required_fields = hook_config.get("required_fields", [])
|
|
249
|
+
|
|
250
|
+
def process(
|
|
251
|
+
self,
|
|
252
|
+
result: Any,
|
|
253
|
+
tool_name: str,
|
|
254
|
+
arguments: Dict[str, Any],
|
|
255
|
+
context: Dict[str, Any],
|
|
256
|
+
) -> Any:
|
|
257
|
+
"""
|
|
258
|
+
Apply validation to the tool output.
|
|
259
|
+
|
|
260
|
+
Args:
|
|
261
|
+
result (Any): The tool output to validate
|
|
262
|
+
tool_name (str): Name of the tool that produced the output
|
|
263
|
+
arguments (Dict[str, Any]): Arguments passed to the tool
|
|
264
|
+
context (Dict[str, Any]): Additional context information
|
|
265
|
+
|
|
266
|
+
Returns:
|
|
267
|
+
Any: The validated output, or original output if validation fails
|
|
268
|
+
"""
|
|
269
|
+
try:
|
|
270
|
+
validation_result = self._validate_output(result)
|
|
271
|
+
|
|
272
|
+
if validation_result["valid"]:
|
|
273
|
+
if validation_result["warnings"]:
|
|
274
|
+
print(
|
|
275
|
+
f"✅ ValidationHook: {tool_name} output validated with warnings"
|
|
276
|
+
)
|
|
277
|
+
else:
|
|
278
|
+
print(
|
|
279
|
+
f"✅ ValidationHook: {tool_name} output validated successfully"
|
|
280
|
+
)
|
|
281
|
+
return result
|
|
282
|
+
else:
|
|
283
|
+
if self.error_action == "fail":
|
|
284
|
+
print(f"❌ ValidationHook: {tool_name} output validation failed")
|
|
285
|
+
return result
|
|
286
|
+
elif self.error_action == "fix":
|
|
287
|
+
fixed_result = self._fix_output(result, validation_result["errors"])
|
|
288
|
+
print(
|
|
289
|
+
f"🔧 ValidationHook: Fixed {tool_name} output based on validation errors"
|
|
290
|
+
)
|
|
291
|
+
return fixed_result
|
|
292
|
+
else: # warn
|
|
293
|
+
print(f"⚠️ ValidationHook: {tool_name} output has validation issues")
|
|
294
|
+
return result
|
|
295
|
+
|
|
296
|
+
except Exception as e:
|
|
297
|
+
print(f"Error in validation hook: {str(e)}")
|
|
298
|
+
return result
|
|
299
|
+
|
|
300
|
+
def _validate_output(self, result: Any) -> Dict[str, Any]:
|
|
301
|
+
"""Validate the output against configured rules."""
|
|
302
|
+
validation_result = {"valid": True, "errors": [], "warnings": []}
|
|
303
|
+
|
|
304
|
+
# Check required fields for dict outputs
|
|
305
|
+
if isinstance(result, dict) and self.required_fields:
|
|
306
|
+
for field in self.required_fields:
|
|
307
|
+
if field not in result:
|
|
308
|
+
validation_result["errors"].append(
|
|
309
|
+
f"Missing required field: {field}"
|
|
310
|
+
)
|
|
311
|
+
validation_result["valid"] = False
|
|
312
|
+
|
|
313
|
+
# Add schema validation here if needed
|
|
314
|
+
# This would integrate with jsonschema or similar libraries
|
|
315
|
+
|
|
316
|
+
return validation_result
|
|
317
|
+
|
|
318
|
+
def _fix_output(self, result: Any, errors: List[str]) -> Any:
|
|
319
|
+
"""Attempt to fix validation errors."""
|
|
320
|
+
# Simple fixes for common issues
|
|
321
|
+
if isinstance(result, dict):
|
|
322
|
+
fixed_result = result.copy()
|
|
323
|
+
|
|
324
|
+
for error in errors:
|
|
325
|
+
if "Missing required field" in error:
|
|
326
|
+
field_name = error.split(": ")[1]
|
|
327
|
+
fixed_result[field_name] = None # Add missing field with None value
|
|
328
|
+
|
|
329
|
+
return fixed_result
|
|
330
|
+
|
|
331
|
+
return result
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
class LoggingHook(OutputHook):
|
|
335
|
+
"""
|
|
336
|
+
Hook for logging tool outputs and execution details.
|
|
337
|
+
|
|
338
|
+
This hook can be used to:
|
|
339
|
+
- Log all tool outputs
|
|
340
|
+
- Track execution metrics
|
|
341
|
+
- Audit tool usage
|
|
342
|
+
|
|
343
|
+
Args:
|
|
344
|
+
config (Dict[str, Any]): Hook configuration containing logging settings
|
|
345
|
+
tooluniverse: Optional ToolUniverse instance (not used for logging)
|
|
346
|
+
"""
|
|
347
|
+
|
|
348
|
+
def __init__(self, config: Dict[str, Any], tooluniverse=None):
|
|
349
|
+
"""
|
|
350
|
+
Initialize the logging hook with configuration.
|
|
351
|
+
|
|
352
|
+
Args:
|
|
353
|
+
config (Dict[str, Any]): Hook configuration
|
|
354
|
+
tooluniverse: ToolUniverse instance (optional, not used)
|
|
355
|
+
"""
|
|
356
|
+
super().__init__(config)
|
|
357
|
+
hook_config = config.get("hook_config", {})
|
|
358
|
+
|
|
359
|
+
# Logging configuration
|
|
360
|
+
self.log_level = hook_config.get("log_level", "INFO")
|
|
361
|
+
self.log_format = hook_config.get(
|
|
362
|
+
"log_format", "detailed"
|
|
363
|
+
) # simple, detailed, json
|
|
364
|
+
self.log_file = hook_config.get("log_file", None)
|
|
365
|
+
self.max_log_size = hook_config.get("max_log_size", 1000) # characters
|
|
366
|
+
|
|
367
|
+
def process(
|
|
368
|
+
self,
|
|
369
|
+
result: Any,
|
|
370
|
+
tool_name: str,
|
|
371
|
+
arguments: Dict[str, Any],
|
|
372
|
+
context: Dict[str, Any],
|
|
373
|
+
) -> Any:
|
|
374
|
+
"""
|
|
375
|
+
Log the tool output and execution details.
|
|
376
|
+
|
|
377
|
+
Args:
|
|
378
|
+
result (Any): The tool output to log
|
|
379
|
+
tool_name (str): Name of the tool that produced the output
|
|
380
|
+
arguments (Dict[str, Any]): Arguments passed to the tool
|
|
381
|
+
context (Dict[str, Any]): Additional context information
|
|
382
|
+
|
|
383
|
+
Returns:
|
|
384
|
+
Any: The original output (logging doesn't modify the output)
|
|
385
|
+
"""
|
|
386
|
+
try:
|
|
387
|
+
log_entry = self._create_log_entry(result, tool_name, arguments, context)
|
|
388
|
+
self._write_log(log_entry)
|
|
389
|
+
|
|
390
|
+
except Exception as e:
|
|
391
|
+
print(f"Error in logging hook: {str(e)}")
|
|
392
|
+
|
|
393
|
+
# Logging hook always returns the original result unchanged
|
|
394
|
+
return result
|
|
395
|
+
|
|
396
|
+
def _create_log_entry(
|
|
397
|
+
self,
|
|
398
|
+
result: Any,
|
|
399
|
+
tool_name: str,
|
|
400
|
+
arguments: Dict[str, Any],
|
|
401
|
+
context: Dict[str, Any],
|
|
402
|
+
) -> str:
|
|
403
|
+
"""Create a log entry for the tool execution."""
|
|
404
|
+
if self.log_format == "simple":
|
|
405
|
+
return f"Tool: {tool_name} | Args: {arguments} | Output length: {len(str(result))}"
|
|
406
|
+
elif self.log_format == "json":
|
|
407
|
+
return json.dumps(
|
|
408
|
+
{
|
|
409
|
+
"tool_name": tool_name,
|
|
410
|
+
"arguments": arguments,
|
|
411
|
+
"output_length": len(str(result)),
|
|
412
|
+
"timestamp": context.get("execution_time", "unknown"),
|
|
413
|
+
"output_preview": str(result)[: self.max_log_size],
|
|
414
|
+
}
|
|
415
|
+
)
|
|
416
|
+
else: # detailed
|
|
417
|
+
return f"""
|
|
418
|
+
Tool Execution Log:
|
|
419
|
+
==================
|
|
420
|
+
Tool: {tool_name}
|
|
421
|
+
Arguments: {arguments}
|
|
422
|
+
Execution Time: {context.get('execution_time', 'unknown')}
|
|
423
|
+
Output Length: {len(str(result))} characters
|
|
424
|
+
Output Preview: {str(result)[:self.max_log_size]}{'...' if len(str(result)) > self.max_log_size else ''}
|
|
425
|
+
==================
|
|
426
|
+
"""
|
|
427
|
+
|
|
428
|
+
def _write_log(self, log_entry: str):
|
|
429
|
+
"""Write the log entry to the configured destination."""
|
|
430
|
+
if self.log_file:
|
|
431
|
+
with open(self.log_file, "a", encoding="utf-8") as f:
|
|
432
|
+
f.write(log_entry + "\n")
|
|
433
|
+
else:
|
|
434
|
+
print(f"📝 Log: {log_entry}")
|
|
435
|
+
|
|
436
|
+
|
|
437
|
+
# Hook type registry for easy extension
|
|
438
|
+
HOOK_TYPE_REGISTRY = {
|
|
439
|
+
"SummarizationHook": "SummarizationHook", # Import from parent module
|
|
440
|
+
"FilteringHook": FilteringHook,
|
|
441
|
+
"FormattingHook": FormattingHook,
|
|
442
|
+
"ValidationHook": ValidationHook,
|
|
443
|
+
"LoggingHook": LoggingHook,
|
|
444
|
+
}
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import requests
|
|
2
|
+
from typing import Any, Dict, Optional
|
|
3
|
+
from urllib.parse import quote
|
|
4
|
+
from .base_tool import BaseTool
|
|
5
|
+
from .tool_registry import register_tool
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@register_tool("GeneOntologyTool")
|
|
9
|
+
class GeneOntologyTool(BaseTool):
|
|
10
|
+
"""
|
|
11
|
+
A general-purpose tool for calling the Gene Ontology (GO) API.
|
|
12
|
+
It is configured via a dictionary that defines the specific API endpoint.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
def __init__(self, tool_config: Dict):
|
|
16
|
+
"""
|
|
17
|
+
Initializes the tool with a configuration.
|
|
18
|
+
|
|
19
|
+
Args:
|
|
20
|
+
tool_config (Dict): A dictionary containing 'fields' with an 'endpoint'.
|
|
21
|
+
"""
|
|
22
|
+
super().__init__(tool_config)
|
|
23
|
+
self.endpoint = tool_config["fields"]["endpoint"]
|
|
24
|
+
self.extract_path = tool_config["fields"].get("extract_path")
|
|
25
|
+
self.timeout = 20
|
|
26
|
+
|
|
27
|
+
def _build_url(self, args: Dict[str, Any]) -> str:
|
|
28
|
+
"""Builds the request URL from arguments."""
|
|
29
|
+
url = self.endpoint
|
|
30
|
+
for key, value in args.items():
|
|
31
|
+
url = url.replace(f"{{{key}}}", quote(str(value)))
|
|
32
|
+
return url
|
|
33
|
+
|
|
34
|
+
def _extract_data(self, data: Dict, extract_path: str) -> Any:
|
|
35
|
+
"""Extract specific data from the GO API response using custom paths."""
|
|
36
|
+
|
|
37
|
+
if extract_path == "response.docs[0]":
|
|
38
|
+
# Extract single document from GOlr response
|
|
39
|
+
response = data.get("response", {})
|
|
40
|
+
docs = response.get("docs", [])
|
|
41
|
+
if docs:
|
|
42
|
+
return docs[0]
|
|
43
|
+
else:
|
|
44
|
+
return {"error": "No GO term found"}
|
|
45
|
+
|
|
46
|
+
elif extract_path == "response.docs":
|
|
47
|
+
# Extract all documents from GOlr response
|
|
48
|
+
response = data.get("response", {})
|
|
49
|
+
docs = response.get("docs", [])
|
|
50
|
+
return docs
|
|
51
|
+
|
|
52
|
+
elif extract_path == "associations[*].subject":
|
|
53
|
+
# Extract gene/protein information from Biolink associations
|
|
54
|
+
result = []
|
|
55
|
+
# Handle both dict with associations and direct list from Biolink API
|
|
56
|
+
if isinstance(data, list):
|
|
57
|
+
# Direct list of associations from Biolink API
|
|
58
|
+
associations = data
|
|
59
|
+
else:
|
|
60
|
+
# Dictionary response with associations key
|
|
61
|
+
associations = data.get("associations", [])
|
|
62
|
+
|
|
63
|
+
for assoc in associations:
|
|
64
|
+
subject = assoc.get("subject", {})
|
|
65
|
+
result.append(subject)
|
|
66
|
+
return result
|
|
67
|
+
|
|
68
|
+
# For simple paths, try direct access
|
|
69
|
+
try:
|
|
70
|
+
if "." in extract_path:
|
|
71
|
+
keys = extract_path.split(".")
|
|
72
|
+
result = data
|
|
73
|
+
for key in keys:
|
|
74
|
+
if "[" in key and "]" in key:
|
|
75
|
+
# Handle array indexing like "docs[0]"
|
|
76
|
+
array_key = key.split("[")[0]
|
|
77
|
+
index_str = key.split("[")[1].split("]")[0]
|
|
78
|
+
result = result.get(array_key, [])
|
|
79
|
+
if index_str.isdigit():
|
|
80
|
+
index = int(index_str)
|
|
81
|
+
if index < len(result):
|
|
82
|
+
result = result[index]
|
|
83
|
+
else:
|
|
84
|
+
return {"error": f"Index {index} out of range"}
|
|
85
|
+
else:
|
|
86
|
+
return {"error": f"Invalid array index: {index_str}"}
|
|
87
|
+
else:
|
|
88
|
+
result = result.get(key, {})
|
|
89
|
+
return result
|
|
90
|
+
else:
|
|
91
|
+
return data.get(extract_path)
|
|
92
|
+
except Exception as e:
|
|
93
|
+
return {"error": f"Failed to extract data using path '{extract_path}': {e}"}
|
|
94
|
+
|
|
95
|
+
def run(self, arguments: Any = None) -> Any:
|
|
96
|
+
"""
|
|
97
|
+
Executes the API call and returns the data.
|
|
98
|
+
|
|
99
|
+
Args:
|
|
100
|
+
arguments (Dict[str, Any]): Parameters for the API call.
|
|
101
|
+
|
|
102
|
+
Returns:
|
|
103
|
+
Any: The JSON data from the API or an error dictionary.
|
|
104
|
+
"""
|
|
105
|
+
# Normalize arguments
|
|
106
|
+
if arguments is None:
|
|
107
|
+
arguments = {}
|
|
108
|
+
if not isinstance(arguments, dict):
|
|
109
|
+
return {"error": "Invalid arguments type; expected a mapping/dict."}
|
|
110
|
+
|
|
111
|
+
# Handle different endpoint formats
|
|
112
|
+
if "?" in self.endpoint:
|
|
113
|
+
# This is a complete URL with query parameters (GOlr format)
|
|
114
|
+
url = self.endpoint
|
|
115
|
+
for key, value in arguments.items():
|
|
116
|
+
url = url.replace(f"{{{key}}}", quote(str(value)))
|
|
117
|
+
params = {}
|
|
118
|
+
else:
|
|
119
|
+
# This is a template URL (Biolink format)
|
|
120
|
+
url_args = arguments.copy()
|
|
121
|
+
params = {}
|
|
122
|
+
|
|
123
|
+
# Move query parameters to params dict for Biolink API
|
|
124
|
+
if "taxon" in arguments:
|
|
125
|
+
params["taxon"] = url_args.pop("taxon")
|
|
126
|
+
if "rows" in arguments:
|
|
127
|
+
params["rows"] = url_args.pop("rows")
|
|
128
|
+
if "start" in arguments:
|
|
129
|
+
params["start"] = url_args.pop("start")
|
|
130
|
+
|
|
131
|
+
# Build URL with remaining arguments
|
|
132
|
+
url = self._build_url(url_args)
|
|
133
|
+
|
|
134
|
+
try:
|
|
135
|
+
resp = requests.get(
|
|
136
|
+
url,
|
|
137
|
+
params=params,
|
|
138
|
+
timeout=self.timeout,
|
|
139
|
+
headers={"Accept": "application/json"},
|
|
140
|
+
)
|
|
141
|
+
resp.raise_for_status()
|
|
142
|
+
data = resp.json()
|
|
143
|
+
except requests.exceptions.HTTPError as e:
|
|
144
|
+
if e.response.status_code == 404:
|
|
145
|
+
return {
|
|
146
|
+
"error": "The requested resource was not found (404 Not Found)."
|
|
147
|
+
}
|
|
148
|
+
return {
|
|
149
|
+
"error": f"GO API request failed with HTTP status: {e.response.status_code}",
|
|
150
|
+
"detail": e.response.text,
|
|
151
|
+
}
|
|
152
|
+
except requests.exceptions.RequestException as e:
|
|
153
|
+
return {
|
|
154
|
+
"error": f"A network error occurred while requesting the GO API: {e}"
|
|
155
|
+
}
|
|
156
|
+
except ValueError:
|
|
157
|
+
return {
|
|
158
|
+
"error": "Failed to parse GO API response, which may not be valid JSON.",
|
|
159
|
+
"content": resp.text,
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
# If extract_path is configured, extract the corresponding subset
|
|
163
|
+
if self.extract_path:
|
|
164
|
+
result = self._extract_data(data, self.extract_path)
|
|
165
|
+
|
|
166
|
+
# Handle empty results
|
|
167
|
+
if isinstance(result, list) and len(result) == 0:
|
|
168
|
+
return {"error": f"No data found for path: {self.extract_path}"}
|
|
169
|
+
elif isinstance(result, dict) and "error" in result:
|
|
170
|
+
return result
|
|
171
|
+
|
|
172
|
+
return result
|
|
173
|
+
|
|
174
|
+
return data
|
|
175
|
+
|
|
176
|
+
# Method bindings for backward compatibility and convenience
|
|
177
|
+
def search_terms(self, query: str) -> Any:
|
|
178
|
+
return self.run({"query": query})
|
|
179
|
+
|
|
180
|
+
def get_term_details(self, id: str) -> Any:
|
|
181
|
+
return self.run({"id": id})
|
|
182
|
+
|
|
183
|
+
def get_genes_for_term(
|
|
184
|
+
self, id: str, taxon: Optional[str] = None, rows: Optional[int] = None
|
|
185
|
+
) -> Any:
|
|
186
|
+
args = {"id": id}
|
|
187
|
+
if taxon:
|
|
188
|
+
args["taxon"] = taxon
|
|
189
|
+
if rows:
|
|
190
|
+
args["rows"] = rows
|
|
191
|
+
return self.run(args)
|
|
192
|
+
|
|
193
|
+
def get_terms_for_gene(self, id: str) -> Any:
|
|
194
|
+
return self.run({"id": id})
|