Project context and problem domain
Healthcare inquiries often span two distinct types of data: structured entity networks (such as which doctors belong to which hospital networks, insurance payer acceptances, and visit schedules) and unstructured text (such as patient reviews and hospital policy documents).
Traditional vector-only RAG pipelines frequently fail when handling multi-hop relational questions, such as counting visits across specific departments or verifying whether a given physician accepts an insurance provider. Sanitas addresses this by implementing an agentic architecture combining graph database queries, semantic vector search, document retrieval, and specialized Python tools.
Architecture and microservices
The application is structured into three containerized services managed by Docker Compose:
- ETL pipeline (
hospital_neo4j_etl): A Python script that ingests hospital, physician, patient, visit, and review CSV files and constructs an interconnected graph in Neo4j AuraDB. - Chatbot API (
chatbot_api): A FastAPI application hosting a LangChain agent powered by Google Gemini 2.0 Flash models (HOSPITAL_AGENT_MODEL,HOSPITAL_CYPHER_MODEL,HOSPITAL_QA_MODEL). - User interface (
chatbot_frontend): A Streamlit web application providing a conversational UI for querying the agent.
Graph schema and Cypher query protection
The Neo4j database models entities as nodes (Hospital, Physician, Patient, Visit, Payer, Review) linked by explicit relationships like WORKS_AT, TREATS, and COVERED_BY.
Natural language questions requiring counts or network traversals pass through LangChain's GraphCypherQAChain, which generates Cypher queries dynamically from user prompts.
Because LLMs can produce unintended write operations when translating user input, a custom query validator (ReadOnlyCorrector) intercepts every generated Cypher statement before execution. It scans for forbidden modification keywords:
class ReadOnlyCorrector(CypherQueryCorrector):
"""Ensures generated queries contain only read-only statements."""
def __call__(self, query: str) -> str:
forbid = ["CREATE", "DELETE", "SET ", "REMOVE", "DROP", "CALL"]
upper = query.upper()
for word in forbid:
if word in upper:
raise ValueError(f"Unsafe keyword '{word}' in Cypher: {query}")
return query
Agent tool integration
The central LangChain agent dynamically selects among several retrieval tools based on the user request:
- Cypher graph query tool: Selected for objective, quantitative questions involving counts, relationship traversals, insurance networks, and physician specialties.
- Patient reviews vector chain: Uses semantic vector embeddings to answer qualitative questions about patient satisfaction, bedside manner, and overall hospital experiences.
- Knowledge base document retrieval: Reads local text files containing institutional policies, emergency procedures, and service overviews.
- Dynamic operational tools: Custom Python functions that compute current hospital wait times (
get_current_wait_times) and route patients to the facility with highest current availability (get_most_available_hospital).
You can watch a demo here:
