Update tools/bioportal_tool.py
Browse files- tools/bioportal_tool.py +50 -19
tools/bioportal_tool.py
CHANGED
@@ -1,41 +1,72 @@
|
|
1 |
-
|
|
|
2 |
from typing import Type, Optional
|
3 |
from pydantic import BaseModel, Field
|
|
|
4 |
from clinical_nlp.umls_bioportal import search_bioportal_term
|
5 |
from services.logger import app_logger
|
6 |
from services.metrics import log_tool_usage
|
7 |
|
8 |
class BioPortalInput(BaseModel):
|
9 |
term: str = Field(description="The medical term to search for.")
|
10 |
-
ontology: Optional[str] = Field(
|
|
|
|
|
|
|
|
|
|
|
|
|
11 |
|
12 |
class BioPortalLookupTool(BaseTool):
|
13 |
name: str = "bioportal_lookup"
|
14 |
description: str = (
|
15 |
-
"
|
16 |
-
"
|
|
|
|
|
17 |
)
|
18 |
args_schema: Type[BaseModel] = BioPortalInput
|
19 |
|
20 |
-
def _run(self, term: str, ontology: Optional[str] = "
|
21 |
-
|
22 |
-
|
23 |
-
|
24 |
-
|
25 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
26 |
|
27 |
-
|
28 |
-
collection = results.get("collection", [])
|
29 |
if collection:
|
30 |
formatted_results = []
|
31 |
-
for item in collection[:3]: # Limit to 3 results
|
32 |
-
|
33 |
-
|
|
|
|
|
|
|
|
|
34 |
formatted_results.append(
|
35 |
-
f"- Term: {
|
36 |
)
|
37 |
-
|
38 |
-
|
|
|
|
|
|
|
|
|
|
|
39 |
|
40 |
-
async def _arun(self, term: str, ontology: Optional[str] = "
|
|
|
41 |
return self._run(term, ontology)
|
|
|
1 |
+
# /home/user/app/tools/bioportal_tool.py
|
2 |
+
from langchain_core.tools import BaseTool
|
3 |
from typing import Type, Optional
|
4 |
from pydantic import BaseModel, Field
|
5 |
+
# Assuming clinical_nlp.umls_bioportal.search_bioportal_term exists and works
|
6 |
from clinical_nlp.umls_bioportal import search_bioportal_term
|
7 |
from services.logger import app_logger
|
8 |
from services.metrics import log_tool_usage
|
9 |
|
10 |
class BioPortalInput(BaseModel):
|
11 |
term: str = Field(description="The medical term to search for.")
|
12 |
+
ontology: Optional[str] = Field(
|
13 |
+
default="SNOMEDCT_US", # Default to SNOMEDCT US Edition
|
14 |
+
description=(
|
15 |
+
"The specific ontology acronym to search within BioPortal (e.g., SNOMEDCT_US, ICD10CM, RXNORM, MESH, LOINC, NCIT). "
|
16 |
+
"Defaults to SNOMEDCT_US if not specified."
|
17 |
+
)
|
18 |
+
)
|
19 |
|
20 |
class BioPortalLookupTool(BaseTool):
|
21 |
name: str = "bioportal_lookup"
|
22 |
description: str = (
|
23 |
+
"Use this tool to search for medical terms, codes (like ICD-10, SNOMED CT, RxNorm codes), definitions, and concept details "
|
24 |
+
"across a wide range of biomedical ontologies via BioPortal. "
|
25 |
+
"Useful when you need information from a specific ontology or want to explore different coding systems. "
|
26 |
+
"Input is a dictionary with 'term' and optional 'ontology'. If ontology is not specified by the user, you should default to 'SNOMEDCT_US'."
|
27 |
)
|
28 |
args_schema: Type[BaseModel] = BioPortalInput
|
29 |
|
30 |
+
def _run(self, term: str, ontology: Optional[str] = "SNOMEDCT_US") -> str:
|
31 |
+
# Pydantic ensures 'term' is provided. 'ontology' will have its default if not given.
|
32 |
+
target_ontology = ontology or "SNOMEDCT_US" # Ensure a default if None is passed
|
33 |
+
app_logger.info(f"BioPortal Tool called with term: '{term}', ontology: '{target_ontology}'")
|
34 |
+
log_tool_usage(self.name, {"query_term": term, "ontology": target_ontology})
|
35 |
+
|
36 |
+
if not term or not term.strip():
|
37 |
+
return "Error from BioPortal lookup: No search term provided."
|
38 |
+
|
39 |
+
try:
|
40 |
+
results = search_bioportal_term(term, ontology=target_ontology)
|
41 |
+
except Exception as e:
|
42 |
+
app_logger.error(f"Exception during BioPortal search for '{term}' in '{target_ontology}': {e}", exc_info=True)
|
43 |
+
return f"Error performing BioPortal lookup for '{term}': An unexpected error occurred during the search."
|
44 |
+
|
45 |
+
if isinstance(results, dict) and "error" in results:
|
46 |
+
app_logger.warning(f"BioPortal lookup for '{term}' in '{target_ontology}' returned an error: {results['error']}")
|
47 |
+
return f"Error from BioPortal lookup for '{term}' (Ontology: {target_ontology}): {results['error']}"
|
48 |
|
49 |
+
collection = results.get("collection", []) if isinstance(results, dict) else []
|
|
|
50 |
if collection:
|
51 |
formatted_results = []
|
52 |
+
for item in collection[:3]: # Limit to 3 results for brevity
|
53 |
+
pref_label = item.get('prefLabel', 'N/A')
|
54 |
+
defs = item.get('definition', [])
|
55 |
+
definition_str = ("; ".join(d for d in defs if d) if defs else "No definition available.")[:200] + "..." # Truncate long defs
|
56 |
+
cui_list = item.get('cui', [])
|
57 |
+
cui_str = ", ".join(cui_list) if cui_list else "N/A"
|
58 |
+
|
59 |
formatted_results.append(
|
60 |
+
f"- Term: {pref_label} (CUIs: {cui_str}). Definition: {definition_str}"
|
61 |
)
|
62 |
+
if formatted_results:
|
63 |
+
return f"BioPortal Search Results for '{term}' (Ontology: {target_ontology}):\n" + "\n".join(formatted_results)
|
64 |
+
else: # Collection was present but no usable items after formatting
|
65 |
+
return f"No specific items found in BioPortal results for '{term}' (Ontology: {target_ontology}), though the query was successful."
|
66 |
+
|
67 |
+
app_logger.warning(f"No results or unexpected format from BioPortal for '{term}' in '{target_ontology}'. Raw: {str(results)[:200]}")
|
68 |
+
return f"No results found in BioPortal for term '{term}' in ontology '{target_ontology}'."
|
69 |
|
70 |
+
async def _arun(self, term: str, ontology: Optional[str] = "SNOMEDCT_US") -> str:
|
71 |
+
# For simplicity, using sync version. Implement true async if search_bioportal_term is async.
|
72 |
return self._run(term, ontology)
|