Source code for tooluniverse.rhea_tool
# rhea_tool.py
"""
Rhea Biochemical Reactions database tool for ToolUniverse.
Rhea is an expert-curated knowledgebase of chemical and transport
reactions of biological interest from SIB Swiss Institute of Bioinformatics.
All reactions are linked to ChEBI (Chemical Entities of Biological Interest)
and EC numbers.
API: https://www.rhea-db.org/help/rest-api
Returns TSV format which is parsed to JSON by this tool.
No authentication required. Free public access.
"""
import requests
from typing import Dict, Any, List
from .base_tool import BaseTool
from .tool_registry import register_tool
RHEA_BASE_URL = "https://www.rhea-db.org/rhea"
[docs]
@register_tool("RheaTool")
class RheaTool(BaseTool):
"""
Tool for querying the Rhea biochemical reaction database.
Rhea contains over 15,000 manually curated biochemical reactions,
each linked to ChEBI compounds and EC enzyme numbers. The search
API returns TSV which is parsed to structured JSON.
No authentication required.
"""
[docs]
def __init__(self, tool_config: Dict[str, Any]):
super().__init__(tool_config)
self.timeout = tool_config.get("timeout", 30)
fields = tool_config.get("fields", {})
self.endpoint = fields.get("endpoint", "search_reactions")
[docs]
def run(self, arguments: Dict[str, Any]) -> Dict[str, Any]:
"""Execute the Rhea API call."""
try:
return self._query(arguments)
except requests.exceptions.Timeout:
return {"error": f"Rhea API request timed out after {self.timeout} seconds"}
except requests.exceptions.ConnectionError:
return {"error": "Failed to connect to Rhea API."}
except requests.exceptions.HTTPError as e:
return {"error": f"Rhea API HTTP error: {e.response.status_code}"}
except Exception as e:
return {"error": f"Unexpected error querying Rhea: {str(e)}"}
[docs]
def _query(self, arguments: Dict[str, Any]) -> Dict[str, Any]:
"""Route to appropriate Rhea endpoint."""
if self.endpoint == "search_reactions":
return self._search_reactions(arguments)
elif self.endpoint == "search_by_ec":
return self._search_by_ec(arguments)
elif self.endpoint == "search_by_chebi":
return self._search_by_chebi(arguments)
else:
return {"error": f"Unknown endpoint: {self.endpoint}"}
[docs]
def _parse_tsv(self, text: str) -> List[Dict[str, str]]:
"""Parse TSV response into list of dicts."""
lines = text.strip().split("\n")
if len(lines) < 2:
return []
headers = lines[0].split("\t")
# Normalize header names
header_map = {
"Reaction identifier": "rhea_id",
"Equation": "equation",
"EC number": "ec_numbers",
"ChEBI identifier": "chebi_ids",
}
normalized_headers = [
header_map.get(h.strip(), h.strip().lower().replace(" ", "_"))
for h in headers
]
results = []
for line in lines[1:]:
if not line.strip():
continue
values = line.split("\t")
row = {}
for i, header in enumerate(normalized_headers):
val = values[i].strip() if i < len(values) else ""
row[header] = val
results.append(row)
return results
[docs]
def _search_reactions(self, arguments: Dict[str, Any]) -> Dict[str, Any]:
"""Search for biochemical reactions by name, compound, or keyword."""
query = arguments.get("query", "")
if not query:
return {
"error": "query parameter is required (e.g., 'glucose', 'ATP', 'kinase')"
}
limit = arguments.get("limit", 20)
params = {
"query": query,
"columns": "rhea-id,equation,ec",
"format": "tsv",
"limit": min(limit, 50),
}
response = requests.get(RHEA_BASE_URL, params=params, timeout=self.timeout)
response.raise_for_status()
results = self._parse_tsv(response.text)
return {
"data": results,
"metadata": {
"source": "Rhea (SIB)",
"query": query,
"total_results": len(results),
},
}
[docs]
def _search_by_ec(self, arguments: Dict[str, Any]) -> Dict[str, Any]:
"""Search for reactions by EC (Enzyme Commission) number."""
ec_number = arguments.get("ec_number", "")
if not ec_number:
return {
"error": "ec_number parameter is required (e.g., 'EC:1.1.1.1', '3.5.1.50')"
}
# Normalize EC number format
if not ec_number.startswith("EC:"):
ec_number = f"EC:{ec_number}"
limit = arguments.get("limit", 20)
params = {
"query": ec_number,
"columns": "rhea-id,equation,ec",
"format": "tsv",
"limit": min(limit, 50),
}
response = requests.get(RHEA_BASE_URL, params=params, timeout=self.timeout)
response.raise_for_status()
results = self._parse_tsv(response.text)
return {
"data": results,
"metadata": {
"source": "Rhea (SIB)",
"ec_number": ec_number,
"total_results": len(results),
},
}
[docs]
def _search_by_chebi(self, arguments: Dict[str, Any]) -> Dict[str, Any]:
"""Search for reactions involving a specific ChEBI compound."""
chebi_id = arguments.get("chebi_id", "")
if not chebi_id:
return {
"error": "chebi_id parameter is required (e.g., 'CHEBI:17234' for glucose)"
}
# Normalize ChEBI ID format
if not chebi_id.startswith("CHEBI:"):
chebi_id = f"CHEBI:{chebi_id}"
limit = arguments.get("limit", 20)
params = {
"query": chebi_id,
"columns": "rhea-id,equation,chebi-id,ec",
"format": "tsv",
"limit": min(limit, 50),
}
response = requests.get(RHEA_BASE_URL, params=params, timeout=self.timeout)
response.raise_for_status()
results = self._parse_tsv(response.text)
return {
"data": results,
"metadata": {
"source": "Rhea (SIB)",
"chebi_id": chebi_id,
"total_results": len(results),
},
}