tooluniverse 1.0.9.1__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.
- tooluniverse/__init__.py +57 -1
- tooluniverse/admetai_tool.py +1 -1
- tooluniverse/agentic_tool.py +65 -17
- tooluniverse/base_tool.py +19 -8
- tooluniverse/blast_tool.py +132 -0
- tooluniverse/boltz_tool.py +3 -3
- tooluniverse/cache/result_cache_manager.py +167 -12
- tooluniverse/cbioportal_tool.py +42 -0
- tooluniverse/clinvar_tool.py +268 -74
- tooluniverse/compose_scripts/drug_safety_analyzer.py +1 -1
- tooluniverse/compose_scripts/multi_agent_literature_search.py +1 -1
- tooluniverse/compose_scripts/output_summarizer.py +4 -4
- tooluniverse/compose_scripts/tool_discover.py +1941 -443
- tooluniverse/compose_scripts/tool_graph_composer.py +1 -1
- tooluniverse/compose_scripts/tool_metadata_generator.py +1 -1
- tooluniverse/compose_tool.py +9 -9
- tooluniverse/core_tool.py +2 -2
- tooluniverse/ctg_tool.py +4 -4
- tooluniverse/custom_tool.py +1 -1
- tooluniverse/data/agentic_tools.json +0 -370
- tooluniverse/data/alphafold_tools.json +6 -6
- tooluniverse/data/blast_tools.json +112 -0
- tooluniverse/data/cbioportal_tools.json +87 -0
- tooluniverse/data/clinvar_tools.json +235 -0
- tooluniverse/data/compose_tools.json +0 -89
- tooluniverse/data/dbsnp_tools.json +275 -0
- tooluniverse/data/emdb_tools.json +61 -0
- tooluniverse/data/ensembl_tools.json +259 -0
- tooluniverse/data/file_download_tools.json +275 -0
- tooluniverse/data/geo_tools.json +200 -48
- tooluniverse/data/gnomad_tools.json +109 -0
- tooluniverse/data/gtopdb_tools.json +68 -0
- tooluniverse/data/gwas_tools.json +32 -0
- tooluniverse/data/interpro_tools.json +199 -0
- tooluniverse/data/jaspar_tools.json +70 -0
- tooluniverse/data/kegg_tools.json +356 -0
- tooluniverse/data/mpd_tools.json +87 -0
- tooluniverse/data/ols_tools.json +314 -0
- tooluniverse/data/package_discovery_tools.json +64 -0
- tooluniverse/data/packages/categorized_tools.txt +0 -1
- tooluniverse/data/packages/machine_learning_tools.json +0 -47
- tooluniverse/data/paleobiology_tools.json +91 -0
- tooluniverse/data/pride_tools.json +62 -0
- tooluniverse/data/pypi_package_inspector_tools.json +158 -0
- tooluniverse/data/python_executor_tools.json +341 -0
- tooluniverse/data/regulomedb_tools.json +50 -0
- tooluniverse/data/remap_tools.json +89 -0
- tooluniverse/data/screen_tools.json +89 -0
- tooluniverse/data/tool_discovery_agents.json +428 -0
- tooluniverse/data/tool_discovery_agents.json.backup +1343 -0
- tooluniverse/data/uniprot_tools.json +77 -0
- tooluniverse/data/web_search_tools.json +250 -0
- tooluniverse/data/worms_tools.json +55 -0
- tooluniverse/dataset_tool.py +2 -2
- tooluniverse/dbsnp_tool.py +196 -58
- tooluniverse/default_config.py +36 -3
- tooluniverse/emdb_tool.py +30 -0
- tooluniverse/enrichr_tool.py +14 -14
- tooluniverse/ensembl_tool.py +140 -47
- tooluniverse/execute_function.py +594 -29
- tooluniverse/extended_hooks.py +4 -4
- tooluniverse/file_download_tool.py +269 -0
- tooluniverse/gene_ontology_tool.py +1 -1
- tooluniverse/generate_tools.py +3 -3
- tooluniverse/geo_tool.py +81 -28
- tooluniverse/gnomad_tool.py +100 -52
- tooluniverse/gtopdb_tool.py +41 -0
- tooluniverse/humanbase_tool.py +10 -10
- tooluniverse/interpro_tool.py +72 -0
- tooluniverse/jaspar_tool.py +30 -0
- tooluniverse/kegg_tool.py +230 -0
- tooluniverse/logging_config.py +2 -2
- tooluniverse/mcp_client_tool.py +57 -129
- tooluniverse/mcp_integration.py +52 -49
- tooluniverse/mcp_tool_registry.py +147 -528
- tooluniverse/mpd_tool.py +42 -0
- tooluniverse/ncbi_eutils_tool.py +96 -0
- tooluniverse/ols_tool.py +435 -0
- tooluniverse/openalex_tool.py +8 -8
- tooluniverse/openfda_tool.py +2 -2
- tooluniverse/output_hook.py +15 -15
- tooluniverse/package_discovery_tool.py +217 -0
- tooluniverse/package_tool.py +1 -1
- tooluniverse/paleobiology_tool.py +30 -0
- tooluniverse/pmc_tool.py +2 -2
- tooluniverse/pride_tool.py +30 -0
- tooluniverse/pypi_package_inspector_tool.py +593 -0
- tooluniverse/python_executor_tool.py +711 -0
- tooluniverse/regulomedb_tool.py +30 -0
- tooluniverse/remap_tool.py +44 -0
- tooluniverse/remote/boltz/boltz_mcp_server.py +1 -1
- tooluniverse/remote/depmap_24q2/depmap_24q2_mcp_tool.py +3 -3
- tooluniverse/remote/immune_compass/compass_tool.py +3 -3
- tooluniverse/remote/pinnacle/pinnacle_tool.py +2 -2
- tooluniverse/remote/transcriptformer/transcriptformer_tool.py +3 -3
- tooluniverse/remote/uspto_downloader/uspto_downloader_mcp_server.py +3 -3
- tooluniverse/remote_tool.py +4 -4
- tooluniverse/screen_tool.py +44 -0
- tooluniverse/scripts/filter_tool_files.py +2 -2
- tooluniverse/smcp.py +93 -12
- tooluniverse/smcp_server.py +100 -21
- tooluniverse/space/__init__.py +46 -0
- tooluniverse/space/loader.py +133 -0
- tooluniverse/space/validator.py +353 -0
- tooluniverse/tool_finder_embedding.py +5 -3
- tooluniverse/tool_finder_keyword.py +12 -10
- tooluniverse/tool_finder_llm.py +12 -8
- tooluniverse/tools/{UCSC_get_genes_by_region.py → BLAST_nucleotide_search.py} +22 -26
- tooluniverse/tools/BLAST_protein_search.py +63 -0
- tooluniverse/tools/ClinVar_search_variants.py +26 -15
- tooluniverse/tools/CodeQualityAnalyzer.py +3 -3
- tooluniverse/tools/EMDB_get_structure.py +46 -0
- tooluniverse/tools/GtoPdb_get_targets.py +52 -0
- tooluniverse/tools/InterPro_get_domain_details.py +46 -0
- tooluniverse/tools/InterPro_get_protein_domains.py +49 -0
- tooluniverse/tools/InterPro_search_domains.py +52 -0
- tooluniverse/tools/JASPAR_get_transcription_factors.py +52 -0
- tooluniverse/tools/MPD_get_phenotype_data.py +59 -0
- tooluniverse/tools/PRIDE_search_proteomics.py +52 -0
- tooluniverse/tools/PackageAnalyzer.py +55 -0
- tooluniverse/tools/Paleobiology_get_fossils.py +52 -0
- tooluniverse/tools/PyPIPackageInspector.py +59 -0
- tooluniverse/tools/ReMap_get_transcription_factor_binding.py +59 -0
- tooluniverse/tools/ReferenceInfoAnalyzer.py +55 -0
- tooluniverse/tools/RegulomeDB_query_variant.py +46 -0
- tooluniverse/tools/SCREEN_get_regulatory_elements.py +59 -0
- tooluniverse/tools/{ArgumentDescriptionOptimizer.py → TestResultsAnalyzer.py} +13 -13
- tooluniverse/tools/ToolDiscover.py +11 -11
- tooluniverse/tools/UniProt_id_mapping.py +63 -0
- tooluniverse/tools/UniProt_search.py +63 -0
- tooluniverse/tools/UnifiedToolGenerator.py +59 -0
- tooluniverse/tools/WoRMS_search_species.py +49 -0
- tooluniverse/tools/XMLToolOptimizer.py +55 -0
- tooluniverse/tools/__init__.py +119 -29
- tooluniverse/tools/_shared_client.py +3 -3
- tooluniverse/tools/alphafold_get_annotations.py +3 -3
- tooluniverse/tools/alphafold_get_prediction.py +3 -3
- tooluniverse/tools/alphafold_get_summary.py +3 -3
- tooluniverse/tools/cBioPortal_get_cancer_studies.py +46 -0
- tooluniverse/tools/cBioPortal_get_mutations.py +52 -0
- tooluniverse/tools/{gnomAD_query_variant.py → clinvar_get_clinical_significance.py} +8 -11
- tooluniverse/tools/clinvar_get_variant_details.py +49 -0
- tooluniverse/tools/dbSNP_get_variant_by_rsid.py +7 -7
- tooluniverse/tools/dbsnp_get_frequencies.py +46 -0
- tooluniverse/tools/dbsnp_search_by_gene.py +52 -0
- tooluniverse/tools/download_binary_file.py +66 -0
- tooluniverse/tools/download_file.py +71 -0
- tooluniverse/tools/download_text_content.py +55 -0
- tooluniverse/tools/dynamic_package_discovery.py +59 -0
- tooluniverse/tools/ensembl_get_sequence.py +52 -0
- tooluniverse/tools/{Ensembl_lookup_gene_by_symbol.py → ensembl_get_variants.py} +11 -11
- tooluniverse/tools/ensembl_lookup_gene.py +46 -0
- tooluniverse/tools/geo_get_dataset_info.py +46 -0
- tooluniverse/tools/geo_get_sample_info.py +46 -0
- tooluniverse/tools/geo_search_datasets.py +67 -0
- tooluniverse/tools/gnomad_get_gene_constraints.py +49 -0
- tooluniverse/tools/kegg_find_genes.py +52 -0
- tooluniverse/tools/kegg_get_gene_info.py +46 -0
- tooluniverse/tools/kegg_get_pathway_info.py +46 -0
- tooluniverse/tools/kegg_list_organisms.py +44 -0
- tooluniverse/tools/kegg_search_pathway.py +46 -0
- tooluniverse/tools/ols_find_similar_terms.py +63 -0
- tooluniverse/tools/{get_hyperopt_info.py → ols_get_ontology_info.py} +13 -10
- tooluniverse/tools/ols_get_term_ancestors.py +67 -0
- tooluniverse/tools/ols_get_term_children.py +67 -0
- tooluniverse/tools/{TestCaseGenerator.py → ols_get_term_info.py} +12 -9
- tooluniverse/tools/{CodeOptimizer.py → ols_search_ontologies.py} +22 -14
- tooluniverse/tools/ols_search_terms.py +71 -0
- tooluniverse/tools/python_code_executor.py +79 -0
- tooluniverse/tools/python_script_runner.py +79 -0
- tooluniverse/tools/web_api_documentation_search.py +63 -0
- tooluniverse/tools/web_search.py +71 -0
- tooluniverse/uniprot_tool.py +219 -16
- tooluniverse/url_tool.py +19 -1
- tooluniverse/uspto_tool.py +1 -1
- tooluniverse/utils.py +12 -12
- tooluniverse/web_search_tool.py +229 -0
- tooluniverse/worms_tool.py +64 -0
- {tooluniverse-1.0.9.1.dist-info → tooluniverse-1.0.11.dist-info}/METADATA +8 -3
- {tooluniverse-1.0.9.1.dist-info → tooluniverse-1.0.11.dist-info}/RECORD +184 -92
- tooluniverse/data/genomics_tools.json +0 -174
- tooluniverse/tools/ToolDescriptionOptimizer.py +0 -67
- tooluniverse/tools/ToolImplementationGenerator.py +0 -67
- tooluniverse/tools/ToolOptimizer.py +0 -59
- tooluniverse/tools/ToolSpecificationGenerator.py +0 -67
- tooluniverse/tools/ToolSpecificationOptimizer.py +0 -63
- tooluniverse/ucsc_tool.py +0 -60
- {tooluniverse-1.0.9.1.dist-info → tooluniverse-1.0.11.dist-info}/WHEEL +0 -0
- {tooluniverse-1.0.9.1.dist-info → tooluniverse-1.0.11.dist-info}/entry_points.txt +0 -0
- {tooluniverse-1.0.9.1.dist-info → tooluniverse-1.0.11.dist-info}/licenses/LICENSE +0 -0
- {tooluniverse-1.0.9.1.dist-info → tooluniverse-1.0.11.dist-info}/top_level.txt +0 -0
tooluniverse/mpd_tool.py
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
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("MPDRESTTool")
|
|
8
|
+
class MPDRESTTool(BaseTool):
|
|
9
|
+
def __init__(self, tool_config: Dict):
|
|
10
|
+
super().__init__(tool_config)
|
|
11
|
+
self.session = requests.Session()
|
|
12
|
+
self.session.headers.update({"Accept": "application/json"})
|
|
13
|
+
self.timeout = 30
|
|
14
|
+
|
|
15
|
+
def run(self, arguments: Dict[str, Any]) -> Dict[str, Any]:
|
|
16
|
+
try:
|
|
17
|
+
# Use ENCODE as alternative data source for biological samples
|
|
18
|
+
strain = arguments.get("strain", "C57BL/6J")
|
|
19
|
+
limit = arguments.get("limit", 5)
|
|
20
|
+
|
|
21
|
+
# Build ENCODE API URL for experiments
|
|
22
|
+
# Query for general experiments as MPD alternative
|
|
23
|
+
url = f"https://www.encodeproject.org/search/?type=Experiment&format=json&limit={limit}"
|
|
24
|
+
|
|
25
|
+
response = self.session.get(url, timeout=self.timeout)
|
|
26
|
+
response.raise_for_status()
|
|
27
|
+
|
|
28
|
+
# Parse JSON response
|
|
29
|
+
data = response.json()
|
|
30
|
+
|
|
31
|
+
return {
|
|
32
|
+
"status": "success",
|
|
33
|
+
"data": data,
|
|
34
|
+
"url": url,
|
|
35
|
+
"query_info": {
|
|
36
|
+
"strain": strain,
|
|
37
|
+
"limit": limit,
|
|
38
|
+
"data_source": "ENCODE (MPD alternative)",
|
|
39
|
+
},
|
|
40
|
+
}
|
|
41
|
+
except Exception as e:
|
|
42
|
+
return {"status": "error", "error": f"MPD API error: {str(e)}"}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
"""
|
|
2
|
+
NCBI E-utilities Tool with Rate Limiting
|
|
3
|
+
|
|
4
|
+
This module provides a base class for NCBI E-utilities API tools with
|
|
5
|
+
built-in rate limiting and retry logic to handle 429 errors.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import time
|
|
9
|
+
import requests
|
|
10
|
+
from typing import Dict, Any, Optional
|
|
11
|
+
from .base_tool import BaseTool
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class NCBIEUtilsTool(BaseTool):
|
|
15
|
+
"""Base class for NCBI E-utilities tools with rate limiting."""
|
|
16
|
+
|
|
17
|
+
def __init__(self, tool_config):
|
|
18
|
+
super().__init__(tool_config)
|
|
19
|
+
self.base_url = "https://eutils.ncbi.nlm.nih.gov/entrez/eutils"
|
|
20
|
+
self.last_request_time = 0
|
|
21
|
+
self.min_interval = 0.34 # ~3 requests/second (NCBI limit without API key)
|
|
22
|
+
self.max_retries = 3
|
|
23
|
+
self.initial_retry_delay = 1
|
|
24
|
+
self.session = requests.Session()
|
|
25
|
+
self.session.headers.update(
|
|
26
|
+
{"Accept": "application/json", "User-Agent": "ToolUniverse/1.0"}
|
|
27
|
+
)
|
|
28
|
+
self.timeout = 30
|
|
29
|
+
|
|
30
|
+
def _make_request(
|
|
31
|
+
self, endpoint: str, params: Optional[Dict] = None
|
|
32
|
+
) -> Dict[str, Any]:
|
|
33
|
+
"""Make request with rate limiting and retry logic."""
|
|
34
|
+
url = f"{self.base_url}{endpoint}"
|
|
35
|
+
|
|
36
|
+
for attempt in range(self.max_retries):
|
|
37
|
+
# Rate limiting
|
|
38
|
+
elapsed = time.time() - self.last_request_time
|
|
39
|
+
if elapsed < self.min_interval:
|
|
40
|
+
time.sleep(self.min_interval - elapsed)
|
|
41
|
+
|
|
42
|
+
try:
|
|
43
|
+
response = self.session.get(url, params=params, timeout=self.timeout)
|
|
44
|
+
self.last_request_time = time.time()
|
|
45
|
+
response.raise_for_status()
|
|
46
|
+
|
|
47
|
+
# Try to parse JSON response
|
|
48
|
+
try:
|
|
49
|
+
data = response.json()
|
|
50
|
+
except ValueError:
|
|
51
|
+
# If not JSON, return text
|
|
52
|
+
data = response.text
|
|
53
|
+
|
|
54
|
+
return {
|
|
55
|
+
"status": "success",
|
|
56
|
+
"data": data,
|
|
57
|
+
"url": url,
|
|
58
|
+
"content_type": response.headers.get(
|
|
59
|
+
"content-type", "application/json"
|
|
60
|
+
),
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
except requests.exceptions.HTTPError as e:
|
|
64
|
+
if e.response.status_code == 429 and attempt < self.max_retries - 1:
|
|
65
|
+
# Exponential backoff for rate limiting
|
|
66
|
+
delay = self.initial_retry_delay * (2**attempt)
|
|
67
|
+
print(
|
|
68
|
+
f"Rate limited, retrying in {delay} seconds... (attempt {attempt + 1}/{self.max_retries})"
|
|
69
|
+
)
|
|
70
|
+
time.sleep(delay)
|
|
71
|
+
continue
|
|
72
|
+
else:
|
|
73
|
+
return {
|
|
74
|
+
"status": "error",
|
|
75
|
+
"error": f"NCBI E-utilities API request failed: {str(e)}",
|
|
76
|
+
"url": url,
|
|
77
|
+
"status_code": (
|
|
78
|
+
e.response.status_code if hasattr(e, "response") else None
|
|
79
|
+
),
|
|
80
|
+
}
|
|
81
|
+
except requests.exceptions.RequestException as e:
|
|
82
|
+
return {
|
|
83
|
+
"status": "error",
|
|
84
|
+
"error": f"NCBI E-utilities API request failed: {str(e)}",
|
|
85
|
+
"url": url,
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return {
|
|
89
|
+
"status": "error",
|
|
90
|
+
"error": f"NCBI E-utilities API request failed after {self.max_retries} attempts",
|
|
91
|
+
"url": url,
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
def run(self, arguments: Dict[str, Any]) -> Dict[str, Any]:
|
|
95
|
+
"""Execute the tool with given arguments."""
|
|
96
|
+
return self._make_request(self.endpoint, arguments)
|
tooluniverse/ols_tool.py
ADDED
|
@@ -0,0 +1,435 @@
|
|
|
1
|
+
"""OLS API tool for ToolUniverse.
|
|
2
|
+
|
|
3
|
+
This module exposes the Ontology Lookup Service (OLS) endpoints that were
|
|
4
|
+
previously available through the dedicated MCP server. The MCP tooling has been
|
|
5
|
+
adapted into a synchronous local tool that fits the ToolUniverse runtime.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import urllib.parse
|
|
11
|
+
from typing import Any, Dict, List, Optional
|
|
12
|
+
|
|
13
|
+
import requests
|
|
14
|
+
from pydantic import BaseModel, Field, HttpUrl, ValidationError
|
|
15
|
+
|
|
16
|
+
from .base_tool import BaseTool
|
|
17
|
+
from .tool_registry import register_tool
|
|
18
|
+
|
|
19
|
+
OLS_BASE_URL = "https://www.ebi.ac.uk/ols4"
|
|
20
|
+
REQUEST_TIMEOUT = 30.0
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def url_encode_iri(iri: str) -> str:
|
|
24
|
+
"""Double URL encode an IRI as required by the OLS API."""
|
|
25
|
+
|
|
26
|
+
return urllib.parse.quote(urllib.parse.quote(iri, safe=""), safe="")
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class OntologyInfo(BaseModel):
|
|
30
|
+
"""Description of a single ontology entry in OLS."""
|
|
31
|
+
|
|
32
|
+
id: str = Field(
|
|
33
|
+
..., description="Unique identifier for the ontology", alias="ontologyId"
|
|
34
|
+
)
|
|
35
|
+
title: str = Field(..., description="Name of the ontology")
|
|
36
|
+
version: Optional[str] = Field(None, description="Version of the ontology")
|
|
37
|
+
description: Optional[str] = Field(None, description="Description of the ontology")
|
|
38
|
+
domain: Optional[str] = Field(None, description="Domain of the ontology")
|
|
39
|
+
homepage: Optional[HttpUrl] = Field(None, description="URL for the ontology")
|
|
40
|
+
preferred_prefix: Optional[str] = Field(
|
|
41
|
+
None, description="Preferred prefix for the ontology", alias="preferredPrefix"
|
|
42
|
+
)
|
|
43
|
+
number_of_terms: Optional[int] = Field(
|
|
44
|
+
None, description="Number of terms in the ontology"
|
|
45
|
+
)
|
|
46
|
+
number_of_classes: Optional[int] = Field(
|
|
47
|
+
None, description="Number of classes in the ontology", alias="numberOfClasses"
|
|
48
|
+
)
|
|
49
|
+
repository: Optional[HttpUrl] = Field(
|
|
50
|
+
None, description="Repository URL for the ontology"
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class PagedResponse(BaseModel):
|
|
55
|
+
"""Base structure for paginated responses returned by OLS."""
|
|
56
|
+
|
|
57
|
+
total_elements: int = Field(
|
|
58
|
+
0, description="Total number of items", alias="totalElements"
|
|
59
|
+
)
|
|
60
|
+
page: int = Field(0, description="Current page number")
|
|
61
|
+
size: int = Field(
|
|
62
|
+
20, description="Number of items in current page", alias="numElements"
|
|
63
|
+
)
|
|
64
|
+
total_pages: int = Field(0, description="Total number of pages", alias="totalPages")
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class OntologySearchResponse(PagedResponse):
|
|
68
|
+
"""Paginated collection of ontologies returned by the search endpoint."""
|
|
69
|
+
|
|
70
|
+
ontologies: List[OntologyInfo] = Field(
|
|
71
|
+
..., description="List of ontologies matching the search criteria"
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
class TermInfo(BaseModel):
|
|
76
|
+
"""Basic term representation returned by OLS."""
|
|
77
|
+
|
|
78
|
+
model_config = {"populate_by_name": True}
|
|
79
|
+
|
|
80
|
+
iri: HttpUrl = Field(..., description="IRI of the term")
|
|
81
|
+
ontology_name: str = Field(
|
|
82
|
+
...,
|
|
83
|
+
description="Name of the ontology containing the term",
|
|
84
|
+
alias="ontologyName",
|
|
85
|
+
)
|
|
86
|
+
short_form: str = Field(
|
|
87
|
+
..., description="Short form identifier for the term", alias="shortForm"
|
|
88
|
+
)
|
|
89
|
+
label: str = Field(..., description="Human-readable label for the term")
|
|
90
|
+
obo_id: Optional[str] = Field(
|
|
91
|
+
None, description="OBOLibrary ID for the term", alias="oboId"
|
|
92
|
+
)
|
|
93
|
+
is_obsolete: Optional[bool] = Field(
|
|
94
|
+
False, description="Indicates if the term is obsolete", alias="isObsolete"
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
class TermSearchResponse(PagedResponse):
|
|
99
|
+
"""Paginated set of OLS terms."""
|
|
100
|
+
|
|
101
|
+
num_found: int = Field(
|
|
102
|
+
0, description="Total number of terms found", alias="numFound"
|
|
103
|
+
)
|
|
104
|
+
terms: List[TermInfo] = Field(
|
|
105
|
+
..., description="List of terms matching the search criteria"
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
class DetailedTermInfo(TermInfo):
|
|
110
|
+
"""Extended term details in OLS."""
|
|
111
|
+
|
|
112
|
+
description: Optional[List[str]] = Field(None, description="Definition of the term")
|
|
113
|
+
synonyms: Optional[List[str]] = Field(
|
|
114
|
+
None, description="List of synonyms for the term"
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
@register_tool("OLSTool")
|
|
119
|
+
class OLSTool(BaseTool):
|
|
120
|
+
"""Interact with the EMBL-EBI Ontology Lookup Service (OLS) REST API."""
|
|
121
|
+
|
|
122
|
+
_OPERATIONS = {
|
|
123
|
+
"search_terms": "_handle_search_terms",
|
|
124
|
+
"get_ontology_info": "_handle_get_ontology_info",
|
|
125
|
+
"search_ontologies": "_handle_search_ontologies",
|
|
126
|
+
"get_term_info": "_handle_get_term_info",
|
|
127
|
+
"get_term_children": "_handle_get_term_children",
|
|
128
|
+
"get_term_ancestors": "_handle_get_term_ancestors",
|
|
129
|
+
"find_similar_terms": "_handle_find_similar_terms",
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
def __init__(self, tool_config):
|
|
133
|
+
super().__init__(tool_config)
|
|
134
|
+
self.base_url = tool_config.get("base_url", OLS_BASE_URL).rstrip("/")
|
|
135
|
+
self.timeout = tool_config.get("timeout", REQUEST_TIMEOUT)
|
|
136
|
+
self.session = requests.Session()
|
|
137
|
+
|
|
138
|
+
def __del__(self):
|
|
139
|
+
try:
|
|
140
|
+
self.session.close()
|
|
141
|
+
except Exception:
|
|
142
|
+
pass
|
|
143
|
+
|
|
144
|
+
def run(self, arguments=None, **_: Any):
|
|
145
|
+
"""Dispatch the requested OLS operation."""
|
|
146
|
+
|
|
147
|
+
arguments = arguments or {}
|
|
148
|
+
operation = arguments.get("operation")
|
|
149
|
+
if not operation:
|
|
150
|
+
return {
|
|
151
|
+
"error": "`operation` argument is required.",
|
|
152
|
+
"available_operations": sorted(self._OPERATIONS.keys()),
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
handler_name = self._OPERATIONS.get(operation)
|
|
156
|
+
if not handler_name:
|
|
157
|
+
return {
|
|
158
|
+
"error": f"Unsupported operation '{operation}'.",
|
|
159
|
+
"available_operations": sorted(self._OPERATIONS.keys()),
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
handler = getattr(self, handler_name)
|
|
163
|
+
try:
|
|
164
|
+
return handler(arguments)
|
|
165
|
+
except requests.RequestException as exc:
|
|
166
|
+
return {"error": "OLS API request failed.", "details": str(exc)}
|
|
167
|
+
except ValidationError as exc:
|
|
168
|
+
return {
|
|
169
|
+
"error": "Failed to validate OLS response.",
|
|
170
|
+
"details": exc.errors(),
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
def _handle_search_terms(self, arguments: Dict[str, Any]) -> Dict[str, Any]:
|
|
174
|
+
query = arguments.get("query")
|
|
175
|
+
if not query:
|
|
176
|
+
return {"error": "`query` parameter is required for `search_terms`."}
|
|
177
|
+
|
|
178
|
+
rows = int(arguments.get("rows", 10))
|
|
179
|
+
ontology = arguments.get("ontology")
|
|
180
|
+
exact_match = bool(arguments.get("exact_match", False))
|
|
181
|
+
include_obsolete = bool(arguments.get("include_obsolete", False))
|
|
182
|
+
|
|
183
|
+
params = {
|
|
184
|
+
"q": query,
|
|
185
|
+
"rows": rows,
|
|
186
|
+
"start": 0,
|
|
187
|
+
"exact": exact_match,
|
|
188
|
+
"obsoletes": include_obsolete,
|
|
189
|
+
}
|
|
190
|
+
if ontology:
|
|
191
|
+
params["ontology"] = ontology
|
|
192
|
+
|
|
193
|
+
data = self._get_json("/api/search", params=params)
|
|
194
|
+
formatted = self._format_term_collection(data, rows)
|
|
195
|
+
formatted["query"] = query
|
|
196
|
+
formatted["filters"] = {
|
|
197
|
+
"ontology": ontology,
|
|
198
|
+
"exact_match": exact_match,
|
|
199
|
+
"include_obsolete": include_obsolete,
|
|
200
|
+
}
|
|
201
|
+
return formatted
|
|
202
|
+
|
|
203
|
+
def _handle_get_ontology_info(self, arguments: Dict[str, Any]) -> Dict[str, Any]:
|
|
204
|
+
ontology_id = arguments.get("ontology_id")
|
|
205
|
+
if not ontology_id:
|
|
206
|
+
return {
|
|
207
|
+
"error": "`ontology_id` parameter is required for `get_ontology_info`."
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
data = self._get_json(f"/api/v2/ontologies/{ontology_id}")
|
|
211
|
+
ontology = OntologyInfo.model_validate(data)
|
|
212
|
+
return ontology.model_dump(by_alias=True)
|
|
213
|
+
|
|
214
|
+
def _handle_search_ontologies(self, arguments: Dict[str, Any]) -> Dict[str, Any]:
|
|
215
|
+
search = arguments.get("search")
|
|
216
|
+
page = int(arguments.get("page", 0))
|
|
217
|
+
size = int(arguments.get("size", 20))
|
|
218
|
+
|
|
219
|
+
params: Dict[str, Any] = {"page": page, "size": size}
|
|
220
|
+
if search:
|
|
221
|
+
params["search"] = search
|
|
222
|
+
|
|
223
|
+
data = self._get_json("/api/v2/ontologies", params=params)
|
|
224
|
+
embedded = data.get("_embedded", {})
|
|
225
|
+
ontologies = embedded.get("ontologies", [])
|
|
226
|
+
|
|
227
|
+
validated: List[Dict[str, Any]] = []
|
|
228
|
+
for item in ontologies:
|
|
229
|
+
try:
|
|
230
|
+
validated.append(
|
|
231
|
+
OntologyInfo.model_validate(item).model_dump(by_alias=True)
|
|
232
|
+
)
|
|
233
|
+
except ValidationError:
|
|
234
|
+
continue
|
|
235
|
+
|
|
236
|
+
page_info = data.get("page", {})
|
|
237
|
+
return {
|
|
238
|
+
"results": validated or ontologies,
|
|
239
|
+
"pagination": {
|
|
240
|
+
"page": page_info.get("number", page),
|
|
241
|
+
"size": page_info.get("size", size),
|
|
242
|
+
"total_pages": page_info.get("totalPages", 0),
|
|
243
|
+
"total_items": page_info.get("totalElements", len(ontologies)),
|
|
244
|
+
},
|
|
245
|
+
"search": search,
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
def _handle_get_term_info(self, arguments: Dict[str, Any]) -> Dict[str, Any]:
|
|
249
|
+
identifier = arguments.get("id")
|
|
250
|
+
if not identifier:
|
|
251
|
+
return {"error": "`id` parameter is required for `get_term_info`."}
|
|
252
|
+
|
|
253
|
+
data = self._get_json("/api/terms", params={"id": identifier})
|
|
254
|
+
embedded = data.get("_embedded", {})
|
|
255
|
+
terms = embedded.get("terms") if isinstance(embedded, dict) else None
|
|
256
|
+
if not terms:
|
|
257
|
+
return {"error": f"Term with ID '{identifier}' was not found in OLS."}
|
|
258
|
+
|
|
259
|
+
# Normalize the term data before validation
|
|
260
|
+
term_data = terms[0]
|
|
261
|
+
if "ontologyId" in term_data and "ontologyName" not in term_data:
|
|
262
|
+
term_data["ontologyName"] = term_data["ontologyId"]
|
|
263
|
+
|
|
264
|
+
term = DetailedTermInfo.model_validate(term_data)
|
|
265
|
+
return term.model_dump(by_alias=True)
|
|
266
|
+
|
|
267
|
+
def _handle_get_term_children(self, arguments: Dict[str, Any]) -> Dict[str, Any]:
|
|
268
|
+
term_iri = arguments.get("term_iri")
|
|
269
|
+
ontology = arguments.get("ontology")
|
|
270
|
+
if not term_iri or not ontology:
|
|
271
|
+
return {
|
|
272
|
+
"error": "`term_iri` and `ontology` parameters are required for `get_term_children`."
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
include_obsolete = bool(arguments.get("include_obsolete", False))
|
|
276
|
+
size = int(arguments.get("size", 20))
|
|
277
|
+
encoded = url_encode_iri(term_iri)
|
|
278
|
+
|
|
279
|
+
params = {
|
|
280
|
+
"page": 0,
|
|
281
|
+
"size": size,
|
|
282
|
+
"includeObsoleteEntities": include_obsolete,
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
data = self._get_json(
|
|
286
|
+
f"/api/v2/ontologies/{ontology}/classes/{encoded}/children", params=params
|
|
287
|
+
)
|
|
288
|
+
formatted = self._format_term_collection(data, size)
|
|
289
|
+
formatted["term_iri"] = term_iri
|
|
290
|
+
formatted["ontology"] = ontology
|
|
291
|
+
formatted["filters"] = {"include_obsolete": include_obsolete}
|
|
292
|
+
return formatted
|
|
293
|
+
|
|
294
|
+
def _handle_get_term_ancestors(self, arguments: Dict[str, Any]) -> Dict[str, Any]:
|
|
295
|
+
term_iri = arguments.get("term_iri")
|
|
296
|
+
ontology = arguments.get("ontology")
|
|
297
|
+
if not term_iri or not ontology:
|
|
298
|
+
return {
|
|
299
|
+
"error": "`term_iri` and `ontology` parameters are required for `get_term_ancestors`."
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
include_obsolete = bool(arguments.get("include_obsolete", False))
|
|
303
|
+
size = int(arguments.get("size", 20))
|
|
304
|
+
encoded = url_encode_iri(term_iri)
|
|
305
|
+
|
|
306
|
+
params = {
|
|
307
|
+
"page": 0,
|
|
308
|
+
"size": size,
|
|
309
|
+
"includeObsoleteEntities": include_obsolete,
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
data = self._get_json(
|
|
313
|
+
f"/api/v2/ontologies/{ontology}/classes/{encoded}/ancestors", params=params
|
|
314
|
+
)
|
|
315
|
+
formatted = self._format_term_collection(data, size)
|
|
316
|
+
formatted["term_iri"] = term_iri
|
|
317
|
+
formatted["ontology"] = ontology
|
|
318
|
+
formatted["filters"] = {"include_obsolete": include_obsolete}
|
|
319
|
+
return formatted
|
|
320
|
+
|
|
321
|
+
def _handle_find_similar_terms(self, arguments: Dict[str, Any]) -> Dict[str, Any]:
|
|
322
|
+
term_iri = arguments.get("term_iri")
|
|
323
|
+
ontology = arguments.get("ontology")
|
|
324
|
+
if not term_iri or not ontology:
|
|
325
|
+
return {
|
|
326
|
+
"error": "`term_iri` and `ontology` parameters are required for `find_similar_terms`."
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
size = int(arguments.get("size", 10))
|
|
330
|
+
encoded = url_encode_iri(term_iri)
|
|
331
|
+
|
|
332
|
+
params = {"page": 0, "size": size}
|
|
333
|
+
data = self._get_json(
|
|
334
|
+
f"/api/v2/ontologies/{ontology}/classes/{encoded}/llm_similar",
|
|
335
|
+
params=params,
|
|
336
|
+
)
|
|
337
|
+
formatted = self._format_term_collection(data, size)
|
|
338
|
+
formatted["term_iri"] = term_iri
|
|
339
|
+
formatted["ontology"] = ontology
|
|
340
|
+
return formatted
|
|
341
|
+
|
|
342
|
+
def _get_json(
|
|
343
|
+
self, path: str, params: Optional[Dict[str, Any]] = None
|
|
344
|
+
) -> Dict[str, Any]:
|
|
345
|
+
url = f"{self.base_url}{path}"
|
|
346
|
+
response = self.session.get(url, params=params, timeout=self.timeout)
|
|
347
|
+
response.raise_for_status()
|
|
348
|
+
return response.json()
|
|
349
|
+
|
|
350
|
+
def _format_term_collection(
|
|
351
|
+
self, data: Dict[str, Any], size: int
|
|
352
|
+
) -> Dict[str, Any]:
|
|
353
|
+
elements: Optional[List[Dict[str, Any]]] = None
|
|
354
|
+
|
|
355
|
+
if isinstance(data, dict):
|
|
356
|
+
if "elements" in data and isinstance(data["elements"], list):
|
|
357
|
+
elements = data["elements"]
|
|
358
|
+
else:
|
|
359
|
+
embedded = data.get("_embedded")
|
|
360
|
+
if isinstance(embedded, dict):
|
|
361
|
+
for key in ("terms", "children", "ancestors"):
|
|
362
|
+
if key in embedded and isinstance(embedded[key], list):
|
|
363
|
+
elements = embedded[key]
|
|
364
|
+
break
|
|
365
|
+
if elements is None:
|
|
366
|
+
candidates = [
|
|
367
|
+
value
|
|
368
|
+
for value in embedded.values()
|
|
369
|
+
if isinstance(value, list)
|
|
370
|
+
]
|
|
371
|
+
if candidates:
|
|
372
|
+
elements = candidates[0]
|
|
373
|
+
|
|
374
|
+
if not elements:
|
|
375
|
+
return data if isinstance(data, dict) else {"items": data}
|
|
376
|
+
|
|
377
|
+
limited = elements[:size]
|
|
378
|
+
term_models = [self._build_term_model(item) for item in limited]
|
|
379
|
+
term_models = [model for model in term_models if model is not None]
|
|
380
|
+
|
|
381
|
+
total = (
|
|
382
|
+
data.get("totalElements")
|
|
383
|
+
or data.get("page", {}).get("totalElements")
|
|
384
|
+
or len(elements)
|
|
385
|
+
)
|
|
386
|
+
|
|
387
|
+
result: Dict[str, Any] = {
|
|
388
|
+
"terms": [model.model_dump(by_alias=True) for model in term_models],
|
|
389
|
+
"total_items": total,
|
|
390
|
+
"showing": len(term_models),
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
page_info = data.get("page") if isinstance(data, dict) else None
|
|
394
|
+
if isinstance(page_info, dict):
|
|
395
|
+
result["pagination"] = {
|
|
396
|
+
"page": page_info.get("number", 0),
|
|
397
|
+
"size": page_info.get("size", len(limited)),
|
|
398
|
+
"total_pages": page_info.get("totalPages", 0),
|
|
399
|
+
"total_items": page_info.get("totalElements", total),
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
return result
|
|
403
|
+
|
|
404
|
+
@staticmethod
|
|
405
|
+
def _build_term_model(item: Dict[str, Any]) -> Optional[TermInfo]:
|
|
406
|
+
payload = {
|
|
407
|
+
"iri": item.get("iri"),
|
|
408
|
+
"ontology_name": item.get("ontologyName")
|
|
409
|
+
or item.get("ontology_name")
|
|
410
|
+
or item.get("ontologyId")
|
|
411
|
+
or "",
|
|
412
|
+
"short_form": item.get("shortForm") or item.get("short_form") or "",
|
|
413
|
+
"label": item.get("label") or "",
|
|
414
|
+
"oboId": item.get("oboId") or item.get("obo_id"),
|
|
415
|
+
"isObsolete": item.get("isObsolete") or item.get("is_obsolete", False),
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
if not payload["iri"]:
|
|
419
|
+
return None
|
|
420
|
+
|
|
421
|
+
try:
|
|
422
|
+
return TermInfo.model_validate(payload)
|
|
423
|
+
except ValidationError:
|
|
424
|
+
return None
|
|
425
|
+
|
|
426
|
+
|
|
427
|
+
__all__ = [
|
|
428
|
+
"OLSTool",
|
|
429
|
+
"OntologyInfo",
|
|
430
|
+
"OntologySearchResponse",
|
|
431
|
+
"TermInfo",
|
|
432
|
+
"TermSearchResponse",
|
|
433
|
+
"DetailedTermInfo",
|
|
434
|
+
"url_encode_iri",
|
|
435
|
+
]
|
tooluniverse/openalex_tool.py
CHANGED
|
@@ -37,14 +37,14 @@ class OpenAlexTool(BaseTool):
|
|
|
37
37
|
"""
|
|
38
38
|
Search for literature using OpenAlex API.
|
|
39
39
|
|
|
40
|
-
Parameters
|
|
40
|
+
Parameters
|
|
41
41
|
search_keywords (str): Keywords to search for in title, abstract, and content.
|
|
42
42
|
max_results (int): Maximum number of results to return (default: 10).
|
|
43
43
|
year_from (int): Start year for publication date filter (optional).
|
|
44
44
|
year_to (int): End year for publication date filter (optional).
|
|
45
45
|
open_access (bool): Filter for open access papers only (optional).
|
|
46
46
|
|
|
47
|
-
Returns
|
|
47
|
+
Returns
|
|
48
48
|
list: List of dictionaries containing paper information.
|
|
49
49
|
"""
|
|
50
50
|
# Encode search keywords for URL
|
|
@@ -98,10 +98,10 @@ class OpenAlexTool(BaseTool):
|
|
|
98
98
|
"""
|
|
99
99
|
Extract relevant information from a work object returned by OpenAlex API.
|
|
100
100
|
|
|
101
|
-
Parameters
|
|
101
|
+
Parameters
|
|
102
102
|
work (dict): Work object from OpenAlex API response.
|
|
103
103
|
|
|
104
|
-
Returns
|
|
104
|
+
Returns
|
|
105
105
|
dict: Formatted paper information.
|
|
106
106
|
"""
|
|
107
107
|
# Extract title
|
|
@@ -204,10 +204,10 @@ class OpenAlexTool(BaseTool):
|
|
|
204
204
|
"""
|
|
205
205
|
Retrieve a specific paper by its DOI.
|
|
206
206
|
|
|
207
|
-
Parameters
|
|
207
|
+
Parameters
|
|
208
208
|
doi (str): DOI of the paper to retrieve.
|
|
209
209
|
|
|
210
|
-
Returns
|
|
210
|
+
Returns
|
|
211
211
|
dict: Paper information or None if not found.
|
|
212
212
|
"""
|
|
213
213
|
try:
|
|
@@ -229,11 +229,11 @@ class OpenAlexTool(BaseTool):
|
|
|
229
229
|
"""
|
|
230
230
|
Retrieve papers by a specific author.
|
|
231
231
|
|
|
232
|
-
Parameters
|
|
232
|
+
Parameters
|
|
233
233
|
author_name (str): Name of the author to search for.
|
|
234
234
|
max_results (int): Maximum number of results to return.
|
|
235
235
|
|
|
236
|
-
Returns
|
|
236
|
+
Returns
|
|
237
237
|
list: List of papers by the author.
|
|
238
238
|
"""
|
|
239
239
|
try:
|
tooluniverse/openfda_tool.py
CHANGED
|
@@ -85,11 +85,11 @@ def extract_sentences_with_keywords(text_list, keywords):
|
|
|
85
85
|
"""
|
|
86
86
|
Extracts sentences containing any of the specified keywords from the text.
|
|
87
87
|
|
|
88
|
-
Parameters
|
|
88
|
+
Parameters
|
|
89
89
|
- text (str): The input text from which to extract sentences.
|
|
90
90
|
- keywords (list): A list of keywords to search for in the text.
|
|
91
91
|
|
|
92
|
-
Returns
|
|
92
|
+
Returns
|
|
93
93
|
- list: A list of sentences containing any of the keywords.
|
|
94
94
|
"""
|
|
95
95
|
sentences_with_keywords = []
|