Source code for tooluniverse.hemolytik2_tool
"""Hemolytik 2.0 hemolytic/toxic peptide tool (live REST, keyless).
Hemolytik 2.0 (Raghava lab, IIITD) is an updated database of experimentally
validated hemolytic and non-hemolytic peptides/proteins (~8,700 unique
peptides, ~13,215 entries; bioRxiv 2025). This tool fills the toxicity/safety
side of the therapeutic-peptide gap: given a record category it returns the
sequence, hemolytic activity (LC50/HC50), source organism, terminal and
chemical modifications, linear/cyclic form, and nature (Antimicrobial /
Anticancer / CPP, etc.).
The public API is keyless. It is queried with a ``dataType`` (the field to
filter on) and a ``dataValue`` (the value to match), e.g.::
api.php?dataType=nature&dataValue=Anticancer
api.php?dataType=source&dataValue=Human
api.php?dataType=seq&dataValue=<sequence>
A successful response is a JSON object ``{"status": 200, "count": int,
"data": [ ... ]}``. A no-match query returns ``{"status": 404,
"message": "Record Not Found"}``.
"""
from typing import Any, Dict
import requests
from .base_tool import BaseTool
from .tool_registry import register_tool
_BASE_URL = "https://webs.iiitd.edu.in/raghava/hemolytik2/api/api.php"
_TIMEOUT = 30
_HEADERS = {"Accept": "application/json"}
# Supported filter fields for the dataType parameter.
_VALID_DATA_TYPES = ("nature", "source", "seq")
def _err(message: str, **extra: Any) -> Dict[str, Any]:
out: Dict[str, Any] = {"status": "error", "error": message}
out.update(extra)
return out
[docs]
@register_tool(
"Hemolytik2SearchPeptidesTool",
config={
"name": "Hemolytik2_search_peptides",
"type": "Hemolytik2SearchPeptidesTool",
"description": (
"Search Hemolytik 2.0 (Raghava lab, IIITD) for experimentally "
"validated HEMOLYTIC / TOXIC peptide records. Filter by 'nature' "
"(e.g. Antimicrobial, Anticancer, CPP), 'source' (source organism, "
"e.g. Human), or 'seq' (sequence). Each record returns the peptide "
"sequence, hemolytic activity (LC50/HC50 with units), source "
"organism, origin, N/C-terminus and chemical (non-natural) "
"modifications, linear/cyclic form, L/D chirality mix, length, "
"nature, PubMed ID, and year. Use to assess peptide safety / "
"hemolysis liability. Keyless public API."
),
"parameter": {
"type": "object",
"properties": {
"dataType": {
"type": "string",
"description": (
"Field to filter on. One of: 'nature' (peptide nature, "
"e.g. Anticancer, Antimicrobial, CPP), 'source' (source "
"organism, e.g. Human), or 'seq' (sequence). "
"Default: 'nature'."
),
"enum": ["nature", "source", "seq"],
},
"dataValue": {
"type": "string",
"description": (
"Value to match for the chosen dataType. Examples: "
"'Anticancer' (with dataType=nature) -> ~166 records; "
"'Human' (with dataType=source) -> ~9255 records."
),
},
},
"required": ["dataValue"],
},
"return_schema": {
"oneOf": [
{
"type": "object",
"properties": {
"status": {"const": "success"},
"data": {
"type": "array",
"items": {"type": "object"},
"description": (
"Matching hemolytic peptide records. Each has "
"id, pmid, year, seq, name, cter, nter, "
"lyn_cyc, ldmix, non_nat, length, nature, "
"activity (e.g. 'LC50 =1.4 µM'), source, "
"origin, exp_str, non_hem."
),
},
"metadata": {"type": "object"},
},
"required": ["status", "data", "metadata"],
},
{
"type": "object",
"properties": {
"status": {"const": "error"},
"error": {"type": "string"},
},
"required": ["status", "error"],
},
]
},
"test_examples": [
{"dataType": "nature", "dataValue": "Anticancer"},
{"dataType": "source", "dataValue": "Human"},
],
},
)
class Hemolytik2SearchPeptidesTool(BaseTool):
"""Search Hemolytik 2.0 hemolytic/toxic peptide records."""
[docs]
def run(self, arguments: Dict[str, Any]) -> Dict[str, Any]:
arguments = arguments or {}
data_value = arguments.get("dataValue")
if data_value is None or str(data_value).strip() == "":
return _err("dataValue is required (e.g. 'Anticancer' or 'Human')")
data_type = str(arguments.get("dataType") or "nature").strip()
if data_type not in _VALID_DATA_TYPES:
return _err(
f"Invalid dataType {data_type!r}. "
f"Choose one of: {', '.join(_VALID_DATA_TYPES)}."
)
params = {"dataType": data_type, "dataValue": str(data_value)}
try:
resp = requests.get(
_BASE_URL, params=params, headers=_HEADERS, timeout=_TIMEOUT
)
except requests.exceptions.RequestException as exc:
return _err(f"Request to Hemolytik 2.0 failed: {exc}")
if resp.status_code != 200:
return _err(
f"Hemolytik 2.0 returned HTTP {resp.status_code}",
url=resp.url,
response_snippet=(resp.text or "")[:200],
)
try:
payload = resp.json()
except ValueError:
return _err(
"Hemolytik 2.0 returned a non-JSON response",
url=resp.url,
response_snippet=(resp.text or "")[:200],
)
if not isinstance(payload, dict):
return _err("Unexpected Hemolytik 2.0 response shape", url=resp.url)
# No-match: API returns {"status": 404, "message": "Record Not Found"}.
if payload.get("status") == 404 or "data" not in payload:
return _err(
payload.get("message")
or f"No Hemolytik 2.0 records for {data_type}={data_value!r}",
url=resp.url,
)
records = payload.get("data") or []
if not isinstance(records, list):
return _err("Unexpected Hemolytik 2.0 data shape", url=resp.url)
return {
"status": "success",
"data": records,
"metadata": {
"source": "Hemolytik 2.0 (Raghava lab, IIITD)",
"url": resp.url,
"data_type": data_type,
"data_value": str(data_value),
"total_count": payload.get("count", len(records)),
"returned_count": len(records),
},
}