parthib07's picture
Upload 9 files
975c388 verified
"""
Healthcare Agents - Multi-Agent System
Contains all agent definitions and LangGraph workflow
"""
from typing import TypedDict
from langchain_core.prompts import ChatPromptTemplate
from langgraph.graph import StateGraph, END
# State definition for LangGraph
class AgentState(TypedDict):
user_input: str
symptom_analysis: str
medication_advice: str
home_remedies: str
diet_lifestyle: str
doctor_recommendations: str
error: str
# Helper function to search Tavily
def search_tavily(tavily_client, query: str, max_results: int = 3) -> str:
"""Search Tavily API for verified health information"""
if not tavily_client:
return "Tavily client not initialized."
try:
response = tavily_client.search(
query=query,
search_depth="advanced",
max_results=max_results
)
if response.get("results"):
sources = []
for result in response["results"]:
title = result.get("title", "")
content = result.get("content", "")
url = result.get("url", "")
sources.append(f"**{title}**\n{content}\nSource: {url}")
return "\n\n".join(sources)
return "No verified information found."
except Exception as e:
return f"Error fetching data: {str(e)}"
# Agent 1: Symptom Analyzer
def symptom_analyzer_agent(state: AgentState, llm, tavily_client) -> AgentState:
"""Analyze symptoms and identify possible conditions"""
if not llm:
state["symptom_analysis"] = "LLM not initialized."
return state
user_input = state["user_input"]
# Search for verified information
tavily_info = search_tavily(tavily_client, f"medical symptoms analysis {user_input}")
prompt = ChatPromptTemplate.from_messages([
("system", """You are a clinical triage assistant. Use verified Tavily evidence to deliver a concise, non-repetitive assessment.
Output STRICTLY in this structure (markdown):
**Structured Symptom Analysis**
- **Summary:** • sentence 1 • sentence 2 (max 25 words total)
- **Possible Conditions:** markdown table with columns `Condition | Key Indicators | Likelihood` (3–4 rows, concise phrases)
- **Assessment:** markdown table with rows for `Severity`, `Red-Flag Risks`, `Who Needs Urgent Care`
- **Next Steps:** numbered list (max 4 items) combining home monitoring + escalation criteria (include temperature threshold + specific danger signs)
Rules:
- Keep total word count ≤ 220.
- Do not repeat medication, home-remedy, or doctor lists (other agents provide them).
- Reference Tavily evidence implicitly; no raw URLs.
- Never add extra sections or disclaimers.
"""),
("human", f"User symptoms: {user_input}\n\nVerified medical information:\n{tavily_info}\n\nProvide the Structured Symptom Analysis as specified.")
])
try:
chain = prompt | llm
response = chain.invoke({})
state["symptom_analysis"] = response.content
except Exception as e:
state["symptom_analysis"] = f"Error in symptom analysis: {str(e)}"
state["error"] = str(e)
return state
# Agent 2: Medication Agent
def medication_agent(state: AgentState, llm, tavily_client) -> AgentState:
"""Suggest medications with dosage, side effects, and precautions"""
if not llm:
state["medication_advice"] = "LLM not initialized."
return state
user_input = state["user_input"]
symptom_analysis = state.get("symptom_analysis", "")
# Enhanced search for medication information with multiple queries
tavily_info1 = search_tavily(tavily_client, f"medication names brand names treatment {user_input} prescription drugs", max_results=5)
tavily_info2 = search_tavily(tavily_client, f"medication dosage timing frequency {user_input} how to take", max_results=5)
tavily_info = f"{tavily_info1}\n\n{tavily_info2}"
prompt = ChatPromptTemplate.from_messages([
("system", """You are a clinical pharmacology assistant. Produce a concise, data-backed plan without repetition.
Requirements:
- Begin and end with the exact disclaimer: "⚠️ CONSULT A DOCTOR BEFORE TAKING ANY MEDICATION. This information is for educational purposes only."
- Limit to the top 3 evidence-backed medications (prioritize those appearing in Tavily results). If fewer than 3 are appropriate, list fewer.
- Present medications in a single markdown table with columns: `Medication (Generic / Brand) | When & How to Take | Adult Dose | Key Precautions | Common Side Effects | Rx Status`.
- Timing must include meal relation and time-of-day guidance when available. Keep each cell ≤ 25 words; no sub-bullets.
- After the table, add one short paragraph titled **Missed Dose & Duration** (≤ 40 words) summarizing course length and what to do if a dose is missed.
- Do NOT restate home remedies, lifestyle tips, or doctor information.
- No additional sections or repeated text.
"""),
("human", f"Symptoms: {user_input}\n\nSymptom Analysis:\n{symptom_analysis}\n\nVERIFIED MEDICATION INFORMATION FROM WEB SEARCH:\n{tavily_info}\n\nDeliver the concise table and summary as specified, using only medications supported by the verified information.")
])
try:
chain = prompt | llm
response = chain.invoke({})
state["medication_advice"] = response.content
except Exception as e:
state["medication_advice"] = f"Error in medication advice: {str(e)}"
state["error"] = str(e)
return state
# Agent 3: Home Remedies Agent
def home_remedies_agent(state: AgentState, llm, tavily_client) -> AgentState:
"""Suggest safe natural remedies and self-care practices"""
if not llm:
state["home_remedies"] = "LLM not initialized."
return state
user_input = state["user_input"]
symptom_analysis = state.get("symptom_analysis", "")
# Search for home remedies
tavily_info = search_tavily(tavily_client, f"home remedies natural treatment {user_input} self-care")
prompt = ChatPromptTemplate.from_messages([
("system", """You are a clinical self-care advisor. Produce a concise, evidence-backed plan without duplication or fluff.
Output format (markdown, ≤ 180 words):
**Home Remedies & Self-Care**
- Markdown table with columns `Remedy | How to Use | Why It Helps | Caution` (max 4 rows, ≤ 20 words per cell)
- **Stop Home Care If:** bullet list of 3 concrete escalation triggers (temperatures/symptoms)
- **Evidence Notes:** bullet list (max 2) citing source type (e.g., "WHO fever guidance", "Mayo Clinic patient sheet") without URLs
Rules:
- Mention only remedies supported by Tavily evidence.
- Do NOT repeat medication dosages, diet plans, or doctor advice.
- No additional sections, disclaimers, or repeated text.
"""),
("human", f"Symptoms: {user_input}\n\nSymptom Analysis:\n{symptom_analysis}\n\nVerified home remedy information:\n{tavily_info}\n\nProvide the structured response exactly as specified.")
])
try:
chain = prompt | llm
response = chain.invoke({})
state["home_remedies"] = response.content
except Exception as e:
state["home_remedies"] = f"Error in home remedies: {str(e)}"
state["error"] = str(e)
return state
# Agent 4: Diet & Lifestyle Advisor
def diet_lifestyle_agent(state: AgentState, llm, tavily_client) -> AgentState:
"""Provide diet, meal plans, and lifestyle recommendations"""
if not llm:
state["diet_lifestyle"] = "LLM not initialized."
return state
user_input = state["user_input"]
symptom_analysis = state.get("symptom_analysis", "")
# Search for diet and lifestyle information
tavily_info = search_tavily(tavily_client, f"diet nutrition lifestyle recommendations {user_input} meal plan")
prompt = ChatPromptTemplate.from_messages([
("system", """You are a clinical nutrition & lifestyle coach. Deliver a succinct plan tailored to the reported symptoms.
Output format (markdown, ≤ 220 words):
- **Hydration & Monitoring:** bullet list (max 3) with fluid targets and temperature-check cadence.
- **Day Plan Table:** table with columns `Meal Time | What to Eat | Why It Helps | Portion Tips` covering Breakfast, Lunch, Dinner, Snacks (≤ 18 words per cell).
- **Foods to Limit:** bullet list (max 4) focusing on items that hinder recovery.
- **Rest & Activity:** bullet list (max 3) for sleep, movement, environment (no overlap with other bullets).
- **Prevention After Recovery:** single sentence (≤ 20 words) highlighting one sustainable habit.
Rules:
- Do NOT repeat medication names, home remedies, or doctor contacts.
- Keep language direct; no duplicated advice.
- Reference Tavily evidence implicitly; no URLs.
"""),
("human", f"Symptoms: {user_input}\n\nSymptom Analysis:\n{symptom_analysis}\n\nVerified diet and lifestyle information:\n{tavily_info}\n\nProvide the structured response exactly as specified.")
])
try:
chain = prompt | llm
response = chain.invoke({})
state["diet_lifestyle"] = response.content
except Exception as e:
state["diet_lifestyle"] = f"Error in diet/lifestyle advice: {str(e)}"
state["error"] = str(e)
return state
# Agent 5: Doctor Recommendation Agent
def doctor_recommendation_agent(state: AgentState, llm, tavily_client) -> AgentState:
"""Suggest relevant specialists and telemedicine platforms"""
if not llm:
state["doctor_recommendations"] = "LLM not initialized."
return state
user_input = state["user_input"]
symptom_analysis = state.get("symptom_analysis", "")
# Enhanced search for doctor information with multiple queries
tavily_info1 = search_tavily(tavily_client, f"doctor names specialists {user_input} healthcare providers hospitals", max_results=5)
tavily_info2 = search_tavily(tavily_client, f"doctor appointment booking consultation hours availability {user_input}", max_results=5)
tavily_info3 = search_tavily(tavily_client, f"telemedicine platforms online doctors {user_input} virtual consultation", max_results=5)
tavily_info = f"{tavily_info1}\n\n{tavily_info2}\n\n{tavily_info3}"
prompt = ChatPromptTemplate.from_messages([
("system", """You are a medical referral coordinator. Provide concise, non-redundant guidance using verified Tavily data.
Output format (markdown, ≤ 220 words):
- **Best Specialist Type & Why:** one sentence.
- **In-Person Options:** table with columns `Doctor / Facility | Specialty | Location | Key Hours | How to Book`. Include up to 4 rows drawn from Tavily results (≤ 18 words per cell, no duplicate facilities).
- **Telemedicine Options:** table with columns `Platform | Available Providers | Service Hours | Booking Method | Typical Wait`. Up to 3 rows.
- **Escalate Immediately If:** bullet list (max 3) noting concrete red-flag scenarios.
- **Emergency Access:** one sentence naming the 24/7 facility from search results.
Rules:
- Do NOT repeat medication, home-remedy, or diet advice.
- Mention each contact number or booking URL only once (omit full URLs; note “call” or “app”).
- Keep wording tight; no repeated phrases across rows.
"""),
("human", f"Symptoms: {user_input}\n\nSymptom Analysis:\n{symptom_analysis}\n\nVERIFIED DOCTOR AND HEALTHCARE INFORMATION FROM WEB SEARCH:\n{tavily_info}\n\nProvide the structured response exactly as specified, extracting real doctor/platform names and hours from the verified information.")
])
try:
chain = prompt | llm
response = chain.invoke({})
state["doctor_recommendations"] = response.content
except Exception as e:
state["doctor_recommendations"] = f"Error in doctor recommendations: {str(e)}"
state["error"] = str(e)
return state
# Build LangGraph workflow
def create_workflow(llm, tavily_client):
"""Create the LangGraph workflow with all agents"""
# Create wrapper functions that include llm and tavily_client
def symptom_wrapper(state):
return symptom_analyzer_agent(state, llm, tavily_client)
def medication_wrapper(state):
return medication_agent(state, llm, tavily_client)
def home_remedies_wrapper(state):
return home_remedies_agent(state, llm, tavily_client)
def diet_lifestyle_wrapper(state):
return diet_lifestyle_agent(state, llm, tavily_client)
def doctor_recommendation_wrapper(state):
return doctor_recommendation_agent(state, llm, tavily_client)
workflow = StateGraph(AgentState)
# Add nodes
workflow.add_node("symptom_analyzer", symptom_wrapper)
workflow.add_node("medication", medication_wrapper)
workflow.add_node("home_remedies", home_remedies_wrapper)
workflow.add_node("diet_lifestyle", diet_lifestyle_wrapper)
workflow.add_node("doctor_recommendation", doctor_recommendation_wrapper)
# Define the flow
workflow.set_entry_point("symptom_analyzer")
workflow.add_edge("symptom_analyzer", "medication")
workflow.add_edge("medication", "home_remedies")
workflow.add_edge("home_remedies", "diet_lifestyle")
workflow.add_edge("diet_lifestyle", "doctor_recommendation")
workflow.add_edge("doctor_recommendation", END)
return workflow.compile()