tooluniverse.oncotree_tool 源代码
"""OncoTree API tool for ToolUniverse.
OncoTree is a cancer type ontology developed at Memorial Sloan Kettering
Cancer Center (MSK). It provides a hierarchical classification of cancers
with standardized codes, cross-references to UMLS and NCI thesaurus, and
tissue-based organization.
API: https://oncotree.mskcc.org/api/
No authentication required. Free public access.
"""
import requests
from typing import Dict, Any, Optional
from .base_tool import BaseTool
from .tool_registry import register_tool
ONCOTREE_BASE_URL = "https://oncotree.mskcc.org/api"
[文档]
class OncoTreeBaseTool(BaseTool):
"""Base class for OncoTree API tools."""
[文档]
def __init__(self, tool_config: Dict[str, Any]):
super().__init__(tool_config)
self.timeout = tool_config.get("timeout", 30)
[文档]
def _get(self, endpoint: str, params: Optional[Dict] = None) -> Any:
url = f"{ONCOTREE_BASE_URL}{endpoint}"
resp = requests.get(url, params=params, timeout=self.timeout)
resp.raise_for_status()
return resp.json()
[文档]
def _handle_request(self, fn, *args, **kwargs) -> Dict[str, Any]:
try:
return fn(*args, **kwargs)
except requests.exceptions.Timeout:
return {
"status": "error",
"error": "OncoTree API request timed out",
"retryable": True,
}
except requests.exceptions.ConnectionError:
return {
"status": "error",
"error": "Failed to connect to OncoTree API",
"retryable": True,
}
except requests.exceptions.HTTPError as e:
code = e.response.status_code if e.response is not None else "unknown"
return {
"status": "error",
"error": f"OncoTree API HTTP {code}",
"retryable": code in (429, 502, 503),
}
except Exception as e:
return {
"status": "error",
"error": f"OncoTree error: {e}",
"retryable": False,
}
[文档]
@register_tool("OncoTreeSearchTool")
class OncoTreeSearchTool(OncoTreeBaseTool):
"""Search OncoTree cancer types by name, code, main type, or tissue."""
[文档]
def run(self, arguments: Dict[str, Any]) -> Dict[str, Any]:
return self._handle_request(self._run, arguments)
[文档]
def _run(self, arguments: Dict[str, Any]) -> Dict[str, Any]:
query = arguments.get("query", "").strip()
field = arguments.get("field", "name").strip().lower()
exact_match = bool(arguments.get("exact_match", False))
version = arguments.get("version", "oncotree_latest_stable")
if not query:
return {"status": "error", "error": "query is required", "retryable": False}
valid_fields = ("name", "code", "main_type", "tissue")
if field not in valid_fields:
return {
"status": "error",
"error": f"field must be one of: {', '.join(valid_fields)}",
"retryable": False,
}
if field in ("tissue", "main_type"):
all_types = self._get("/tumorTypes", {"version": version})
if not isinstance(all_types, list):
return {
"status": "error",
"error": "Unexpected response from OncoTree API",
"retryable": False,
}
attr = "tissue" if field == "tissue" else "mainType"
q_lower = query.lower()
if exact_match:
raw = [t for t in all_types if (t.get(attr) or "").lower() == q_lower]
else:
raw = [t for t in all_types if q_lower in (t.get(attr) or "").lower()]
else:
params = {"exactMatch": str(exact_match).lower(), "version": version}
raw = self._get(
f"/tumorTypes/search/{field}/{requests.utils.quote(query)}", params
)
if not isinstance(raw, list):
return {
"status": "error",
"error": "Unexpected response from OncoTree API",
"retryable": False,
}
items = [
{
"code": t.get("code"),
"name": t.get("name"),
"main_type": t.get("mainType"),
"tissue": t.get("tissue"),
"parent": t.get("parent"),
"level": t.get("level"),
"external_references": t.get("externalReferences", {}),
}
for t in raw
]
return {
"status": "success",
"data": items,
"metadata": {
"query": query,
"field": field,
"exact_match": exact_match,
"count": len(items),
"version": version,
},
}
[文档]
@register_tool("OncoTreeGetTypeTool")
class OncoTreeGetTypeTool(OncoTreeBaseTool):
"""Get a specific OncoTree cancer type by its unique code."""
[文档]
def run(self, arguments: Dict[str, Any]) -> Dict[str, Any]:
return self._handle_request(self._run, arguments)
[文档]
def _run(self, arguments: Dict[str, Any]) -> Dict[str, Any]:
code = arguments.get("code", "").strip().upper()
version = arguments.get("version", "oncotree_latest_stable")
if not code:
return {
"status": "error",
"error": "code is required (e.g. 'BRCA', 'LUAD')",
"retryable": False,
}
results = self._get(
f"/tumorTypes/search/code/{requests.utils.quote(code)}",
{"exactMatch": "true", "version": version},
)
if not isinstance(results, list) or not results:
return {
"status": "error",
"error": f"OncoTree code '{code}' not found",
"retryable": False,
}
t = results[0]
return {
"status": "success",
"data": {
"code": t.get("code"),
"name": t.get("name"),
"main_type": t.get("mainType"),
"tissue": t.get("tissue"),
"color": t.get("color"),
"parent": t.get("parent"),
"level": t.get("level"),
"history": t.get("history", []),
"external_references": t.get("externalReferences", {}),
},
"metadata": {"source": "OncoTree MSK", "version": version},
}
[文档]
@register_tool("OncoTreeListTissuesTool")
class OncoTreeListTissuesTool(OncoTreeBaseTool):
"""List all top-level tissue categories in the OncoTree hierarchy."""
[文档]
def run(self, arguments: Dict[str, Any]) -> Dict[str, Any]:
return self._handle_request(self._run, arguments)
[文档]
def _run(self, arguments: Dict[str, Any]) -> Dict[str, Any]:
version = arguments.get("version", "oncotree_latest_stable")
all_types = self._get("/tumorTypes", {"version": version})
if not isinstance(all_types, list):
return {
"status": "error",
"error": "Unexpected response from OncoTree API",
"retryable": False,
}
tissues = sorted(
{t.get("tissue") for t in all_types if t.get("tissue")},
)
return {
"status": "success",
"data": tissues,
"metadata": {"count": len(tissues), "version": version},
}