Source code for tooluniverse.allen_cell_types_tool

"""
Allen Cell Types tool for ToolUniverse — single-neuron ephys/morphology specimens.

The Allen Cell Types Database catalogs individual neurons characterized by
electrophysiology, morphology, and transcriptomics (human + mouse). This wraps the
Brain-Map RMA query for ApiCellTypesSpecimenDetail. It is distinct from TU's existing
AllenBrain tools, which cover gene-expression datasets and brain structures.

API: http://api.brain-map.org/api/v2/data/query.json  (public, no authentication, RMA)
"""

from typing import Any, Dict, List, Optional

import requests

from .base_tool import BaseTool
from .tool_registry import register_tool

BRAINMAP_QUERY = "https://api.brain-map.org/api/v2/data/query.json"
_MODEL = "model::ApiCellTypesSpecimenDetail"


[docs] @register_tool("AllenCellTypesSpecimensTool") class AllenCellTypesSpecimensTool(BaseTool): """Search Allen Cell Types single-neuron specimens (ephys/morphology)."""
[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]: criteria = _MODEL filters: List[str] = [] species_value = arguments.get("species") if species_value is not None and not isinstance(species_value, str): return { "status": "error", "error": "species must be a string when provided", "parameter": "species", } species = (species_value or "").strip() if species: filters.append(f"donor__species$eq'{species}'") structure_value = arguments.get("structure") if structure_value is not None and not isinstance(structure_value, str): return { "status": "error", "error": "structure must be a string when provided", "parameter": "structure", } structure = (structure_value or "").strip() if structure: filters.append(f"structure__name$il'%{structure}%'") if filters: criteria += ",rma::criteria," + ",".join(f"[{f}]" for f in filters) num_rows = arguments.get("limit", 20) if ( isinstance(num_rows, bool) or not isinstance(num_rows, int) or not 1 <= num_rows <= 2000 ): return { "status": "error", "error": "limit must be an integer between 1 and 2000", "parameter": "limit", } start_row = arguments.get("start_row", 0) if ( isinstance(start_row, bool) or not isinstance(start_row, int) or start_row < 0 ): return { "status": "error", "error": "start_row must be a non-negative integer", "parameter": "start_row", } params = { "criteria": criteria, "num_rows": num_rows, "start_row": start_row, } try: resp = requests.get( BRAINMAP_QUERY, params=params, headers={"Accept": "application/json"}, timeout=self.timeout, ) resp.raise_for_status() payload = resp.json() except requests.exceptions.Timeout: return { "status": "error", "error": f"Allen Brain-Map request timed out after {self.timeout}s", } except requests.exceptions.RequestException as e: return {"status": "error", "error": f"Allen Brain-Map request failed: {e}"} except ValueError: return { "status": "error", "error": "Allen Brain-Map returned a non-JSON response", } if not payload.get("success", False): return { "status": "error", "error": f"Allen Brain-Map query error: {payload.get('msg')}", } rows = payload.get("msg", []) or [] if not isinstance(rows, list): return { "status": "error", "error": "Allen Brain-Map returned an unexpected result payload", } total = payload.get("total_rows") if isinstance(total, bool) or not isinstance(total, int) or total < 0: total = len(rows) page_advance = len(rows) has_more = page_advance > 0 and start_row + page_advance < total truncated = start_row > 0 or has_more specimens = [self._summarize(r) for r in rows if isinstance(r, dict)] return { "status": "success", "data": specimens, "metadata": { "total_available": total, "returned": len(specimens), "limit": num_rows, "start_row": start_row, "has_more": has_more, "next_start_row": (start_row + page_advance if has_more else None), "truncated": truncated, "result_set_complete": not truncated, "query": {"species": species or None, "structure": structure or None}, "source": "Allen Cell Types Database", }, }
[docs] @staticmethod def _summarize(r: Dict[str, Any]) -> Dict[str, Any]: def clean(v: Any) -> Optional[str]: return v.strip('"') if isinstance(v, str) else v return { "specimen_id": r.get("specimen__id"), "specimen_name": clean(r.get("specimen__name") or r.get("name")), "species": clean(r.get("donor__species")), "sex": clean(r.get("donor__sex")), "age": clean(r.get("donor__age")), "disease_state": clean(r.get("donor__disease_state")), "brain_structure": clean(r.get("structure__name")), "transgenic_line": clean(r.get("line_name")) or None, "avg_firing_rate": r.get("ef__avg_firing_rate"), "input_resistance": r.get("ef__ri"), "tau": r.get("ef__tau"), "upstroke_downstroke_ratio": r.get( "ef__upstroke_downstroke_ratio_long_square" ), "has_reconstruction": r.get("nr__reconstruction_type") not in (None, ""), }