|
|
|
from langchain_core.tools import BaseTool |
|
from typing import Type, Optional |
|
from pydantic import BaseModel, Field |
|
|
|
from clinical_nlp.umls_bioportal import search_bioportal_term |
|
from services.logger import app_logger |
|
from services.metrics import log_tool_usage |
|
|
|
class BioPortalInput(BaseModel): |
|
term: str = Field(description="The medical term to search for.") |
|
ontology: Optional[str] = Field( |
|
default="SNOMEDCT_US", |
|
description=( |
|
"The specific ontology acronym to search within BioPortal (e.g., SNOMEDCT_US, ICD10CM, RXNORM, MESH, LOINC, NCIT). " |
|
"Defaults to SNOMEDCT_US if not specified." |
|
) |
|
) |
|
|
|
class BioPortalLookupTool(BaseTool): |
|
name: str = "bioportal_lookup" |
|
description: str = ( |
|
"Use this tool to search for medical terms, codes (like ICD-10, SNOMED CT, RxNorm codes), definitions, and concept details " |
|
"across a wide range of biomedical ontologies via BioPortal. " |
|
"Useful when you need information from a specific ontology or want to explore different coding systems. " |
|
"Input is a dictionary with 'term' and optional 'ontology'. If ontology is not specified by the user, you should default to 'SNOMEDCT_US'." |
|
) |
|
args_schema: Type[BaseModel] = BioPortalInput |
|
|
|
def _run(self, term: str, ontology: Optional[str] = "SNOMEDCT_US") -> str: |
|
|
|
target_ontology = ontology or "SNOMEDCT_US" |
|
app_logger.info(f"BioPortal Tool called with term: '{term}', ontology: '{target_ontology}'") |
|
log_tool_usage(self.name, {"query_term": term, "ontology": target_ontology}) |
|
|
|
if not term or not term.strip(): |
|
return "Error from BioPortal lookup: No search term provided." |
|
|
|
try: |
|
results = search_bioportal_term(term, ontology=target_ontology) |
|
except Exception as e: |
|
app_logger.error(f"Exception during BioPortal search for '{term}' in '{target_ontology}': {e}", exc_info=True) |
|
return f"Error performing BioPortal lookup for '{term}': An unexpected error occurred during the search." |
|
|
|
if isinstance(results, dict) and "error" in results: |
|
app_logger.warning(f"BioPortal lookup for '{term}' in '{target_ontology}' returned an error: {results['error']}") |
|
return f"Error from BioPortal lookup for '{term}' (Ontology: {target_ontology}): {results['error']}" |
|
|
|
collection = results.get("collection", []) if isinstance(results, dict) else [] |
|
if collection: |
|
formatted_results = [] |
|
for item in collection[:3]: |
|
pref_label = item.get('prefLabel', 'N/A') |
|
defs = item.get('definition', []) |
|
definition_str = ("; ".join(d for d in defs if d) if defs else "No definition available.")[:200] + "..." |
|
cui_list = item.get('cui', []) |
|
cui_str = ", ".join(cui_list) if cui_list else "N/A" |
|
|
|
formatted_results.append( |
|
f"- Term: {pref_label} (CUIs: {cui_str}). Definition: {definition_str}" |
|
) |
|
if formatted_results: |
|
return f"BioPortal Search Results for '{term}' (Ontology: {target_ontology}):\n" + "\n".join(formatted_results) |
|
else: |
|
return f"No specific items found in BioPortal results for '{term}' (Ontology: {target_ontology}), though the query was successful." |
|
|
|
app_logger.warning(f"No results or unexpected format from BioPortal for '{term}' in '{target_ontology}'. Raw: {str(results)[:200]}") |
|
return f"No results found in BioPortal for term '{term}' in ontology '{target_ontology}'." |
|
|
|
async def _arun(self, term: str, ontology: Optional[str] = "SNOMEDCT_US") -> str: |
|
|
|
return self._run(term, ontology) |