Source code for tooluniverse.opsin_tool

"""
OPSIN tool for ToolUniverse — deterministic IUPAC chemical name -> structure.

OPSIN (Open Parser for Systematic IUPAC Nomenclature) is a grammar-based parser
that converts systematic chemical names into structures (SMILES / InChI / InChIKey).
Unlike a name-lookup service (e.g. NCI/CADD Cactus, which matches names against a
database), OPSIN parses the name itself, so it resolves novel systematic names that
are not present in any database.

API: https://www.ebi.ac.uk/opsin/ws/{name}.json  (EBI-hosted, public, no auth, MIT-licensed)
On a parseable name it returns {"status": "SUCCESS", "smiles", "inchi",
"stdinchi", "stdinchikey", "cml"}; on an unparseable name {"status": "FAILURE", "message"}.
"""

from typing import Any, Dict
from urllib.parse import quote

import requests

from .base_tool import BaseTool
from .tool_registry import register_tool

OPSIN_BASE = "https://www.ebi.ac.uk/opsin/ws"


[docs] @register_tool("OPSINNameToStructureTool") class OPSINNameToStructureTool(BaseTool): """Convert a systematic (IUPAC) chemical name to a chemical structure."""
[docs] def __init__(self, tool_config: Dict[str, Any]): super().__init__(tool_config) self.timeout = tool_config.get("fields", {}).get("timeout", 30)
[docs] def run(self, arguments: Dict[str, Any]) -> Dict[str, Any]: name = (arguments.get("name") or "").strip() if not name: return {"status": "error", "error": "'name' parameter is required"} url = f"{OPSIN_BASE}/{quote(name, safe='')}.json" try: resp = requests.get( url, headers={"Accept": "application/json"}, timeout=self.timeout ) # OPSIN returns HTTP 404 (with a JSON body) for names it cannot parse; # treat that as a structured "not parseable" result, not a hard error. if resp.status_code not in (200, 404): resp.raise_for_status() payload = resp.json() except requests.exceptions.Timeout: return { "status": "error", "error": f"OPSIN request timed out after {self.timeout}s", } except requests.exceptions.RequestException as e: return {"status": "error", "error": f"OPSIN request failed: {e}"} except ValueError: return {"status": "error", "error": "OPSIN returned a non-JSON response"} if (payload.get("status") or "").upper() != "SUCCESS": return { "status": "success", "data": { "name": name, "parsed": False, "smiles": None, "inchi": None, "inchikey": None, }, "metadata": { "parsed": False, "note": payload.get("message") or f"OPSIN could not parse the name '{name}'. " "It must be a systematic/IUPAC name, not a trade or trivial name.", "source": "OPSIN (EBI)", }, } return { "status": "success", "data": { "name": name, "parsed": True, "smiles": payload.get("smiles"), "inchi": payload.get("stdinchi") or payload.get("inchi"), "inchikey": payload.get("stdinchikey"), }, "metadata": {"parsed": True, "source": "OPSIN (EBI)"}, }