NeuroBio_Agent / tools.py
arnavmishra4's picture
Update tools.py
2663f7a verified
Raw
History Blame Contribute Delete
11 kB
import requests
from langchain_core.tools import tool
# ─────────────────────────────────────────────────────────────────────
# 1. PubMed β€” primary literature search
# ─────────────────────────────────────────────────────────────────────
@tool
def search_pubmed(query: str) -> str:
"""Search PubMed for peer-reviewed papers.
IMPORTANT: Use short 2-5 keyword queries only (e.g. 'MGMT GBM TMZ resistance',
'pseudoprogression GBM IDH-wildtype', 'cfDNA glioma liquid biopsy').
Do NOT use long sentences or phrases β€” PubMed requires every word to match.
You can call this multiple times with different short queries."""
search_url = "https://eutils.ncbi.nlm.nih.gov/entrez/eutils/esearch.fcgi"
params = {
"db": "pubmed",
"term": query, # short keywords only
"retmax": 5,
"retmode": "json",
"sort": "relevance",
# removed "field": "tiab" β€” not a valid esearch param
}
r = requests.get(search_url, params=params, timeout=15)
ids = r.json()["esearchresult"]["idlist"]
if not ids:
return f"No PubMed results found for '{query}'. Try a shorter query with 2-4 keywords."
fetch_url = "https://eutils.ncbi.nlm.nih.gov/entrez/eutils/efetch.fcgi"
params = {
"db": "pubmed",
"id": ",".join(ids),
"rettype": "abstract",
"retmode": "text",
}
r = requests.get(fetch_url, params=params, timeout=15)
return r.text[:3000]
# ─────────────────────────────────────────────────────────────────────
# 2. ClinicalTrials.gov β€” recruiting/relevant trials
# ─────────────────────────────────────────────────────────────────────
@tool
def search_clinical_trials(query: str) -> str:
"""Search ClinicalTrials.gov for active or recruiting trials related to
a condition, drug, gene status, or treatment combination. Use this to
find trials a patient might be eligible for, or to see what treatment
approaches are currently under investigation."""
url = "https://clinicaltrials.gov/api/v2/studies"
params = {
"query.term": query,
"pageSize": 5,
"fields": "NCTId,BriefTitle,OverallStatus,Condition,InterventionName,BriefSummary",
}
r = requests.get(url, params=params, timeout=15)
data = r.json()
studies = data.get("studies", [])
if not studies:
return f"No clinical trials found for '{query}'."
out = []
for s in studies:
proto = s.get("protocolSection", {})
ident = proto.get("identificationModule", {})
status = proto.get("statusModule", {})
out.append(
f"NCT ID: {ident.get('nctId')}\n"
f"Title: {ident.get('briefTitle')}\n"
f"Status: {status.get('overallStatus')}\n"
)
return "\n".join(out)[:3000]
# ─────────────────────────────────────────────────────────────────────
# 3. bioRxiv β€” latest preprints
# ─────────────────────────────────────────────────────────────────────
@tool
def search_biorxiv(query: str) -> str:
"""Search bioRxiv for recent preprints on a biological mechanism or
topic. Use this when you want the most recent, not-yet-peer-reviewed
research β€” useful for cutting-edge mechanisms that may not yet be in
PubMed. Note: bioRxiv's API is date-range based rather than full-text
search, so this returns recent preprints that you can scan for
relevance to the query."""
url = "https://api.biorxiv.org/details/biorxiv/2024-01-01/2025-12-31/0"
r = requests.get(url, timeout=15)
data = r.json()
collection = data.get("collection", [])
if not collection:
return f"No bioRxiv results retrieved (query context: '{query}')."
out = []
for paper in collection[:5]:
out.append(
f"Title: {paper.get('title')}\n"
f"Date: {paper.get('date')}\n"
f"Abstract: {paper.get('abstract', '')[:300]}\n"
)
return "\n".join(out)[:3000]
# ─────────────────────────────────────────────────────────────────────
# 4. OMIM β€” gene <-> disease associations
# ─────────────────────────────────────────────────────────────────────
@tool
def search_omim(gene_or_disease: str) -> str:
"""Search OMIM (Online Mendelian Inheritance in Man) for gene-disease
associations. Use this when you need to understand the genetic basis
or known disease associations of a specific gene (e.g. MGMT, EGFR,
IDH1) or condition. Requires an OMIM API key (set OMIM_API_KEY)."""
import os
api_key = os.environ.get("OMIM_API_KEY", "")
if not api_key:
return (
"OMIM API key not configured. Set OMIM_API_KEY environment "
"variable to enable this tool. Skipping OMIM search for "
f"'{gene_or_disease}'."
)
url = "https://api.omim.org/api/entry/search"
params = {
"search": gene_or_disease,
"limit": 5,
"format": "json",
"apiKey": api_key,
}
r = requests.get(url, params=params, timeout=15)
data = r.json()
entries = data.get("omim", {}).get("searchResponse", {}).get("entryList", [])
if not entries:
return f"No OMIM results found for '{gene_or_disease}'."
out = []
for e in entries:
entry = e.get("entry", {})
out.append(
f"MIM Number: {entry.get('mimNumber')}\n"
f"Title: {entry.get('titles', {}).get('preferredTitle')}\n"
)
return "\n".join(out)[:3000]
# ─────────────────────────────────────────────────────────────────────
# 5. DrugBank β€” drug mechanisms / interactions
# ─────────────────────────────────────────────────────────────────────
@tool
def search_drugbank(drug_name: str) -> str:
"""Look up a drug's mechanism of action, target, and known interactions
via DrugBank. Use this when the hypothesis involves a specific drug
(e.g. Temozolomide) and you need its pharmacological mechanism.
Requires a DrugBank API key (set DRUGBANK_API_KEY)."""
import os
api_key = os.environ.get("DRUGBANK_API_KEY", "")
if not api_key:
return (
"DrugBank API key not configured. Set DRUGBANK_API_KEY "
"environment variable to enable this tool. Skipping DrugBank "
f"search for '{drug_name}'."
)
url = "https://api.drugbank.com/v1/drug_interactions"
headers = {"Authorization": api_key}
params = {"q": drug_name}
r = requests.get(url, headers=headers, params=params, timeout=15)
if r.status_code != 200:
return f"DrugBank lookup failed for '{drug_name}' (status {r.status_code})."
return r.text[:3000]
# ─────────────────────────────────────────────────────────────────────
# 6. Internal FAISS RAG β€” RANO, Zetterberg, curated GBM papers
# (Agent RAG index β€” built separately, path provided below)
# ─────────────────────────────────────────────────────────────────────
# NOTE: Set this to the path of your pre-built Agent RAG FAISS index.
AGENT_RAG_INDEX_PATH = r"NeuroAgent\NeuroBio_faiss_index"
_agent_rag_retriever = None
def _get_agent_rag_retriever():
"""Lazy-load the FAISS retriever so it's only loaded once, on first use."""
global _agent_rag_retriever
if _agent_rag_retriever is None:
from langchain_community.vectorstores import FAISS
from langchain_huggingface import HuggingFaceEmbeddings
# Swap embedding model below for whatever was used to BUILD the index.
embeddings = HuggingFaceEmbeddings(model_name="sentence-transformers/all-MiniLM-L6-v2")
vectorstore = FAISS.load_local(
AGENT_RAG_INDEX_PATH,
embeddings,
allow_dangerous_deserialization=True,
)
_agent_rag_retriever = vectorstore.as_retriever(search_kwargs={"k": 4})
return _agent_rag_retriever
@tool
def query_agent_rag(query: str) -> str:
"""Search the curated internal research library (RANO criteria,
Zetterberg cfDNA papers, and other pre-vetted GBM literature) via
FAISS retrieval. Use this for grounded, pre-vetted reference material
in addition to live PubMed/bioRxiv search."""
retriever = _get_agent_rag_retriever()
docs = retriever.invoke(query)
if not docs:
return f"No results found in Agent RAG index for '{query}'."
out = []
for d in docs:
source = d.metadata.get("source", "unknown")
out.append(f"[Source: {source}]\n{d.page_content[:600]}")
return "\n\n".join(out)[:3000]
# ─────────────────────────────────────────────────────────────────────
# Tool registry β€” imported by nodes.py for bind_tools()
# ─────────────────────────────────────────────────────────────────────
all_tools = [
search_pubmed,
search_clinical_trials,
search_biorxiv,
search_omim,
search_drugbank,
query_agent_rag,
]