|
"""
|
|
HealthBot: An AI-Powered Patient Education System
|
|
|
|
This module implements a LangGraph workflow for a healthcare chatbot that can:
|
|
1. Ask patients about health topics they want to learn about
|
|
2. Search for information using Tavily
|
|
3. Summarize the information in patient-friendly language
|
|
4. Create and grade comprehension quizzes
|
|
5. Provide feedback and suggestions for related topics
|
|
|
|
The implementation includes a Gradio UI for easy interaction.
|
|
"""
|
|
|
|
import os
|
|
from typing import TypedDict, List, Dict, Any, Optional, Literal
|
|
from dotenv import load_dotenv
|
|
|
|
|
|
from langgraph.graph import StateGraph, START, END
|
|
from langchain_core.messages import HumanMessage, SystemMessage, AIMessage
|
|
from langchain_openai import ChatOpenAI
|
|
from langchain_community.tools.tavily_search import TavilySearchResults
|
|
|
|
|
|
import gradio as gr
|
|
|
|
|
|
load_dotenv()
|
|
|
|
|
|
llm = ChatOpenAI(
|
|
model="gpt-4.1",
|
|
temperature=0.0,
|
|
)
|
|
|
|
|
|
search_tool = TavilySearchResults(max_results=5)
|
|
|
|
|
|
class HealthBotState(TypedDict):
|
|
|
|
health_topic: Optional[str]
|
|
quiz_ready: Optional[bool]
|
|
quiz_answer: Optional[str]
|
|
next_action: Optional[Literal["new_topic", "exit", "more_questions"]]
|
|
difficulty: Optional[Literal["easy", "medium", "hard"]]
|
|
level_of_details: Optional[Literal["easy", "medium", "hard"]]
|
|
num_questions: Optional[int]
|
|
current_question_index: Optional[int]
|
|
|
|
|
|
search_results: Optional[List[Dict[str, Any]]]
|
|
summary: Optional[str]
|
|
quiz_questions: Optional[List[str]]
|
|
current_quiz_question: Optional[str]
|
|
quiz_grade: Optional[str]
|
|
quiz_feedback: Optional[str]
|
|
quiz_grades: Optional[List[str]]
|
|
related_topics: Optional[List[str]]
|
|
|
|
|
|
messages: List[Dict[str, Any]]
|
|
|
|
|
|
|
|
|
|
def ask_health_topic(state: HealthBotState) -> HealthBotState:
|
|
"""Ask the patient what health topic they'd like to learn about."""
|
|
|
|
if state.get("health_topic") is None or state.get("next_action") == "new_topic":
|
|
|
|
if state.get("next_action") == "new_topic":
|
|
state = HealthBotState(messages=state.get("messages", []))
|
|
|
|
|
|
if not state.get("messages"):
|
|
state["messages"] = [
|
|
{
|
|
"role": "system",
|
|
"content": "You are HealthBot, an AI assistant that helps patients learn about health topics."
|
|
}
|
|
]
|
|
|
|
|
|
state["messages"].append({
|
|
"role": "assistant",
|
|
"content": "What health topic or medical condition would you like to learn about today?"
|
|
})
|
|
|
|
|
|
|
|
|
|
health_topic = input("HealthBot: What health topic or medical condition would you like to learn about today? ")
|
|
|
|
|
|
state["messages"].append({
|
|
"role": "user",
|
|
"content": health_topic
|
|
})
|
|
|
|
|
|
state["health_topic"] = health_topic
|
|
|
|
|
|
state["messages"].append({
|
|
"role": "assistant",
|
|
"content": "What level of detail would you like? (easy, medium, hard)"
|
|
})
|
|
|
|
difficulty = input("HealthBot: What level of detail would you like? (easy, medium, hard) ")
|
|
|
|
|
|
state["messages"].append({
|
|
"role": "user",
|
|
"content": difficulty
|
|
})
|
|
|
|
|
|
state["difficulty"] = difficulty
|
|
state["level_of_details"] = difficulty
|
|
|
|
return state
|
|
|
|
def search_health_info(state: HealthBotState) -> HealthBotState:
|
|
"""Search for health information using Tavily."""
|
|
health_topic = state["health_topic"]
|
|
level_of_details = state.get("level_of_details", state.get("difficulty", "medium"))
|
|
|
|
|
|
if level_of_details == "easy":
|
|
query = f"{health_topic} simple explanation for patients"
|
|
elif level_of_details == "hard":
|
|
query = f"{health_topic} detailed medical information"
|
|
else:
|
|
query = f"{health_topic} patient information"
|
|
|
|
|
|
search_results = search_tool.invoke(query)
|
|
|
|
|
|
state["search_results"] = search_results
|
|
|
|
return state
|
|
|
|
def summarize_health_info(state: HealthBotState) -> HealthBotState:
|
|
"""Summarize the health information in patient-friendly language."""
|
|
search_results = state["search_results"]
|
|
health_topic = state["health_topic"]
|
|
level_of_details = state.get("level_of_details", state.get("difficulty", "medium"))
|
|
|
|
|
|
prompt = f"""
|
|
You are a healthcare educator explaining {health_topic} to a patient.
|
|
|
|
Based on the following search results, create a {level_of_details} level summary about {health_topic}.
|
|
|
|
If the level is 'easy', use simple language, avoid medical jargon, and keep it brief (2-3 paragraphs).
|
|
If the level is 'medium', use moderately complex language and provide more details (3-4 paragraphs).
|
|
If the level is 'hard', use more technical language and provide comprehensive information (4-5 paragraphs).
|
|
|
|
Search Results:
|
|
{search_results}
|
|
|
|
Your summary should be informative, accurate, and helpful for a patient trying to understand this health topic.
|
|
Include important facts, symptoms, treatments, and preventive measures when applicable.
|
|
"""
|
|
|
|
|
|
messages = [
|
|
SystemMessage(content="You are a healthcare educator explaining medical topics to patients."),
|
|
HumanMessage(content=prompt)
|
|
]
|
|
response = llm.invoke(messages)
|
|
|
|
|
|
state["summary"] = response.content
|
|
|
|
|
|
state["messages"].append({
|
|
"role": "assistant",
|
|
"content": response.content
|
|
})
|
|
|
|
return state
|
|
|
|
def present_summary(state: HealthBotState) -> HealthBotState:
|
|
"""Present the summary to the patient."""
|
|
summary = state["summary"]
|
|
|
|
|
|
print(f"HealthBot: Here's what I found about {state['health_topic']}:\n")
|
|
print(summary)
|
|
print("\n")
|
|
|
|
return state
|
|
|
|
def prompt_for_quiz(state: HealthBotState) -> HealthBotState:
|
|
"""Ask if the patient is ready for a comprehension check."""
|
|
|
|
state["messages"].append({
|
|
"role": "assistant",
|
|
"content": "Would you like to take a quick quiz to test your understanding? (yes/no)"
|
|
})
|
|
|
|
|
|
response = input("HealthBot: Would you like to take a quick quiz to test your understanding? (yes/no) ")
|
|
|
|
|
|
state["messages"].append({
|
|
"role": "user",
|
|
"content": response
|
|
})
|
|
|
|
|
|
state["quiz_ready"] = response.lower() in ["yes", "y", "sure", "ok", "okay"]
|
|
|
|
if state["quiz_ready"]:
|
|
|
|
state["messages"].append({
|
|
"role": "assistant",
|
|
"content": "How many questions would you like? (1-5)"
|
|
})
|
|
|
|
num_questions = input("HealthBot: How many questions would you like? (1-5) ")
|
|
|
|
|
|
state["messages"].append({
|
|
"role": "user",
|
|
"content": num_questions
|
|
})
|
|
|
|
|
|
try:
|
|
state["num_questions"] = min(5, max(1, int(num_questions)))
|
|
except ValueError:
|
|
state["num_questions"] = 1
|
|
print("HealthBot: I'll ask you 1 question.")
|
|
|
|
state["current_question_index"] = 0
|
|
|
|
return state
|
|
|
|
def create_quiz_questions(state: HealthBotState) -> HealthBotState:
|
|
"""Create quiz questions based on the health information summary."""
|
|
if not state["quiz_ready"]:
|
|
return state
|
|
|
|
summary = state["summary"]
|
|
health_topic = state["health_topic"]
|
|
difficulty = state.get("difficulty", "medium")
|
|
num_questions = state.get("num_questions", 1)
|
|
|
|
|
|
prompt = f"""
|
|
Based on the following summary about {health_topic}, create {num_questions} quiz question(s) to test the patient's understanding.
|
|
|
|
Summary:
|
|
{summary}
|
|
|
|
If the difficulty is 'easy', create straightforward questions with clear answers from the summary.
|
|
If the difficulty is 'medium', create questions that require some synthesis of information.
|
|
If the difficulty is 'hard', create questions that require deeper understanding and application of concepts.
|
|
|
|
Format your response as a JSON array of strings, with each string being a question.
|
|
"""
|
|
|
|
|
|
messages = [
|
|
SystemMessage(content="You are creating quiz questions to test patient understanding of medical topics."),
|
|
HumanMessage(content=prompt)
|
|
]
|
|
response = llm.invoke(messages)
|
|
|
|
|
|
|
|
import json
|
|
try:
|
|
questions_text = response.content
|
|
|
|
start_idx = questions_text.find('[')
|
|
end_idx = questions_text.rfind(']') + 1
|
|
if start_idx >= 0 and end_idx > start_idx:
|
|
questions_json = questions_text[start_idx:end_idx]
|
|
questions = json.loads(questions_json)
|
|
else:
|
|
|
|
questions = [questions_text]
|
|
except:
|
|
|
|
questions = [response.content]
|
|
|
|
|
|
state["quiz_questions"] = questions
|
|
state["current_quiz_question"] = questions[0]
|
|
|
|
return state
|
|
|
|
def present_quiz_question(state: HealthBotState) -> HealthBotState:
|
|
"""Present a quiz question to the patient."""
|
|
if not state["quiz_ready"]:
|
|
return state
|
|
|
|
current_index = state.get("current_question_index", 0)
|
|
questions = state["quiz_questions"]
|
|
|
|
if current_index < len(questions):
|
|
current_question = questions[current_index]
|
|
|
|
|
|
state["messages"].append({
|
|
"role": "assistant",
|
|
"content": f"Question {current_index + 1}: {current_question}"
|
|
})
|
|
|
|
|
|
print(f"HealthBot: Question {current_index + 1}: {current_question}")
|
|
|
|
|
|
state["current_quiz_question"] = current_question
|
|
|
|
return state
|
|
|
|
def collect_quiz_answer(state: HealthBotState) -> HealthBotState:
|
|
"""Collect the patient's answer to the quiz question."""
|
|
if not state["quiz_ready"]:
|
|
return state
|
|
|
|
|
|
answer = input("Your answer: ")
|
|
|
|
|
|
state["messages"].append({
|
|
"role": "user",
|
|
"content": answer
|
|
})
|
|
|
|
|
|
state["quiz_answer"] = answer
|
|
|
|
return state
|
|
|
|
def grade_quiz_answer(state: HealthBotState) -> HealthBotState:
|
|
"""Grade the patient's answer to the quiz question."""
|
|
if not state["quiz_ready"]:
|
|
return state
|
|
|
|
question = state["current_quiz_question"]
|
|
answer = state["quiz_answer"]
|
|
summary = state["summary"]
|
|
|
|
|
|
prompt = f"""
|
|
Grade the patient's answer to the following question about the health topic.
|
|
|
|
Question: {question}
|
|
|
|
Patient's Answer: {answer}
|
|
|
|
Information from the summary:
|
|
{summary}
|
|
|
|
Provide a grade 'Pass' or 'Fail' and detailed feedback explaining why the answer received that grade.
|
|
Include specific information from the summary that supports or contradicts the patient's answer.
|
|
Be encouraging and educational in your feedback.
|
|
|
|
Format your response as markdown text:
|
|
- Grade: [text values of 'Pass' or 'Fail' only]
|
|
- Feedback: [detailed feedback with citations from the summary. The text should be in markdown bullets and always nested under the bullet Feedback.]
|
|
"""
|
|
|
|
|
|
messages = [
|
|
SystemMessage(content="You are grading a patient's understanding of a health topic."),
|
|
HumanMessage(content=prompt)
|
|
]
|
|
response = llm.invoke(messages)
|
|
|
|
|
|
state["quiz_grade"] = response.content
|
|
|
|
|
|
if "quiz_grades" not in state or state["quiz_grades"] is None:
|
|
state["quiz_grades"] = []
|
|
|
|
|
|
state["quiz_grades"].append({
|
|
"question": question,
|
|
"grade": response.content
|
|
})
|
|
|
|
|
|
|
|
|
|
return state
|
|
|
|
def present_feedback(state: HealthBotState) -> HealthBotState:
|
|
"""Present the grade and feedback to the patient."""
|
|
if not state["quiz_ready"]:
|
|
return state
|
|
|
|
|
|
current_index = state.get("current_question_index", 0)
|
|
num_questions = state.get("num_questions", 1)
|
|
|
|
|
|
if current_index >= num_questions - 1:
|
|
|
|
quiz_grades = state.get("quiz_grades", [])
|
|
|
|
|
|
summary = "Quiz Results:\n\n"
|
|
|
|
|
|
for i, grade_item in enumerate(quiz_grades):
|
|
summary += f"Question {i+1}: {grade_item['question']}\n\n"
|
|
|
|
|
|
grade_str = grade_item['grade']
|
|
|
|
|
|
if isinstance(grade_item, dict) and 'grade' in grade_item:
|
|
|
|
summary += f"{grade_item['grade']}\n\n"
|
|
elif grade_str.startswith('{') and "grade" in grade_str:
|
|
try:
|
|
|
|
import ast
|
|
grade_dict = ast.literal_eval(grade_str)
|
|
if isinstance(grade_dict, dict) and 'grade' in grade_dict:
|
|
|
|
summary += f"{grade_dict['grade']}\n\n"
|
|
else:
|
|
|
|
summary += f"{grade_str}\n\n"
|
|
except:
|
|
|
|
summary += f"{grade_str}\n\n"
|
|
else:
|
|
|
|
summary += f"{grade_str}\n\n"
|
|
|
|
|
|
summary += f"You've completed all {num_questions} questions! Thank you for testing your knowledge."
|
|
|
|
|
|
state["messages"].append({
|
|
"role": "assistant",
|
|
"content": summary
|
|
})
|
|
|
|
|
|
print(f"HealthBot: {summary}")
|
|
|
|
else:
|
|
|
|
state["current_question_index"] = current_index + 1
|
|
|
|
|
|
state["messages"].append({
|
|
"role": "assistant",
|
|
"content": "Ready for the next question? (yes/no)"
|
|
})
|
|
|
|
response = input("HealthBot: Ready for the next question? (yes/no) ")
|
|
|
|
|
|
state["messages"].append({
|
|
"role": "user",
|
|
"content": response
|
|
})
|
|
|
|
if response.lower() not in ["yes", "y", "sure", "ok", "okay"]:
|
|
|
|
state["current_question_index"] = num_questions
|
|
|
|
|
|
state = present_feedback(state)
|
|
|
|
return state
|
|
|
|
def suggest_related_topics(state: HealthBotState) -> HealthBotState:
|
|
"""Suggest related health topics based on the current topic."""
|
|
health_topic = state["health_topic"]
|
|
summary = state["summary"]
|
|
|
|
|
|
prompt = f"""
|
|
Based on the patient's interest in {health_topic} and the summary provided, suggest 3 related health topics that the patient might want to learn about next.
|
|
|
|
Summary:
|
|
{summary}
|
|
|
|
Format your response as a JSON array of strings, with each string being a related topic.
|
|
"""
|
|
|
|
|
|
messages = [
|
|
SystemMessage(content="You are suggesting related health topics to a patient."),
|
|
HumanMessage(content=prompt)
|
|
]
|
|
response = llm.invoke(messages)
|
|
|
|
|
|
import json
|
|
try:
|
|
topics_text = response.content
|
|
|
|
start_idx = topics_text.find('[')
|
|
end_idx = topics_text.rfind(']') + 1
|
|
if start_idx >= 0 and end_idx > start_idx:
|
|
topics_json = topics_text[start_idx:end_idx]
|
|
topics = json.loads(topics_json)
|
|
else:
|
|
|
|
topics = ["Related topic 1", "Related topic 2", "Related topic 3"]
|
|
except:
|
|
|
|
topics = ["Related topic 1", "Related topic 2", "Related topic 3"]
|
|
|
|
|
|
state["related_topics"] = topics
|
|
|
|
|
|
suggestion_text = "You might also be interested in these related topics:\n"
|
|
for i, topic in enumerate(topics):
|
|
suggestion_text += f"{i+1}. {topic}\n"
|
|
|
|
state["messages"].append({
|
|
"role": "assistant",
|
|
"content": suggestion_text
|
|
})
|
|
|
|
|
|
print(f"HealthBot: {suggestion_text}")
|
|
|
|
return state
|
|
|
|
def ask_next_action(state: HealthBotState) -> HealthBotState:
|
|
"""Ask the patient if they'd like to learn about a new topic or exit."""
|
|
|
|
related_topics = state.get("related_topics", [])
|
|
|
|
if related_topics:
|
|
prompt = "Would you like to:\n1. Learn about one of these related topics (enter the number)\n2. Learn about a new health topic (enter 'new')\n3. Exit (enter 'exit')"
|
|
else:
|
|
prompt = "Would you like to learn about a new health topic (enter 'new') or exit (enter 'exit')?"
|
|
|
|
state["messages"].append({
|
|
"role": "assistant",
|
|
"content": prompt
|
|
})
|
|
|
|
|
|
response = input(f"HealthBot: {prompt} ")
|
|
|
|
|
|
state["messages"].append({
|
|
"role": "user",
|
|
"content": response
|
|
})
|
|
|
|
|
|
if response.lower() in ["exit", "quit", "bye", "goodbye"]:
|
|
state["next_action"] = "exit"
|
|
elif response.lower() in ["new", "new topic"]:
|
|
state["next_action"] = "new_topic"
|
|
elif related_topics and response.isdigit() and 1 <= int(response) <= len(related_topics):
|
|
|
|
selected_topic = related_topics[int(response) - 1]
|
|
state["health_topic"] = selected_topic
|
|
state["next_action"] = "new_topic"
|
|
else:
|
|
|
|
state["next_action"] = "new_topic"
|
|
|
|
return state
|
|
|
|
def router(state: HealthBotState) -> str:
|
|
"""Route to the next node based on the state."""
|
|
|
|
if state.get("next_action") == "exit":
|
|
return "end_conversation"
|
|
|
|
|
|
if state.get("next_action") == "new_topic":
|
|
return "ask_health_topic"
|
|
|
|
|
|
if state.get("quiz_ready") is False:
|
|
return "suggest_related_topics"
|
|
|
|
|
|
current_index = state.get("current_question_index", 0)
|
|
num_questions = state.get("num_questions", 1)
|
|
if state.get("quiz_ready") and current_index < num_questions:
|
|
return "present_quiz_question"
|
|
|
|
|
|
return "suggest_related_topics"
|
|
|
|
def end_conversation(state: HealthBotState) -> HealthBotState:
|
|
"""End the conversation with a farewell message."""
|
|
|
|
state["messages"].append({
|
|
"role": "assistant",
|
|
"content": "Thank you for using HealthBot! Take care and stay healthy!"
|
|
})
|
|
|
|
|
|
print("HealthBot: Thank you for using HealthBot! Take care and stay healthy!")
|
|
|
|
return state
|
|
|
|
|
|
workflow = StateGraph(state_schema=HealthBotState)
|
|
|
|
|
|
workflow.add_node("ask_health_topic", ask_health_topic)
|
|
workflow.add_node("search_health_info", search_health_info)
|
|
workflow.add_node("summarize_health_info", summarize_health_info)
|
|
workflow.add_node("present_summary", present_summary)
|
|
workflow.add_node("prompt_for_quiz", prompt_for_quiz)
|
|
workflow.add_node("create_quiz_questions", create_quiz_questions)
|
|
workflow.add_node("present_quiz_question", present_quiz_question)
|
|
workflow.add_node("collect_quiz_answer", collect_quiz_answer)
|
|
workflow.add_node("grade_quiz_answer", grade_quiz_answer)
|
|
workflow.add_node("present_feedback", present_feedback)
|
|
workflow.add_node("suggest_related_topics", suggest_related_topics)
|
|
workflow.add_node("ask_next_action", ask_next_action)
|
|
workflow.add_node("end_conversation", end_conversation)
|
|
workflow.add_node("router", router)
|
|
|
|
|
|
workflow.add_edge(START, "ask_health_topic")
|
|
workflow.add_edge("ask_health_topic", "search_health_info")
|
|
workflow.add_edge("search_health_info", "summarize_health_info")
|
|
workflow.add_edge("summarize_health_info", "present_summary")
|
|
workflow.add_edge("present_summary", "prompt_for_quiz")
|
|
workflow.add_edge("prompt_for_quiz", "create_quiz_questions")
|
|
workflow.add_edge("create_quiz_questions", "present_quiz_question")
|
|
workflow.add_edge("present_quiz_question", "collect_quiz_answer")
|
|
workflow.add_edge("collect_quiz_answer", "grade_quiz_answer")
|
|
workflow.add_edge("grade_quiz_answer", "present_feedback")
|
|
workflow.add_edge("present_feedback", "router")
|
|
workflow.add_edge("suggest_related_topics", "ask_next_action")
|
|
workflow.add_edge("ask_next_action", "router")
|
|
|
|
|
|
workflow.add_conditional_edges(
|
|
"router",
|
|
{
|
|
"ask_health_topic": lambda state: state.get("next_action") == "new_topic",
|
|
"suggest_related_topics": lambda state: state.get("quiz_ready") is False,
|
|
"present_quiz_question": lambda state: state.get("quiz_ready") and state.get("current_question_index", 0) < state.get("num_questions", 1),
|
|
"end_conversation": lambda state: state.get("next_action") == "exit",
|
|
}
|
|
)
|
|
|
|
workflow.add_edge("end_conversation", END)
|
|
|
|
|
|
graph = workflow.compile()
|
|
|
|
|
|
def healthbot_chat(message, history, difficulty="medium", level_of_detail="medium", num_questions=1):
|
|
"""Function to handle the Gradio chat interface."""
|
|
|
|
if not hasattr(healthbot_chat, "state"):
|
|
healthbot_chat.state = HealthBotState(
|
|
messages=[],
|
|
health_topic=None,
|
|
quiz_ready=None,
|
|
quiz_answer=None,
|
|
next_action=None,
|
|
difficulty=difficulty,
|
|
level_of_details=level_of_detail,
|
|
num_questions=num_questions,
|
|
current_question_index=0,
|
|
search_results=None,
|
|
summary=None,
|
|
quiz_questions=None,
|
|
current_quiz_question=None,
|
|
quiz_grade=None,
|
|
quiz_feedback=None,
|
|
quiz_grades=[],
|
|
related_topics=None
|
|
)
|
|
|
|
|
|
if message.lower() in ["restart", "new", "new topic"]:
|
|
healthbot_chat.state = HealthBotState(
|
|
messages=[],
|
|
health_topic=None,
|
|
quiz_ready=None,
|
|
quiz_answer=None,
|
|
next_action=None,
|
|
difficulty=difficulty,
|
|
level_of_details=level_of_detail,
|
|
num_questions=num_questions,
|
|
current_question_index=0,
|
|
search_results=None,
|
|
summary=None,
|
|
quiz_questions=None,
|
|
current_quiz_question=None,
|
|
quiz_grade=None,
|
|
quiz_feedback=None,
|
|
quiz_grades=[],
|
|
related_topics=None
|
|
)
|
|
return history + [{"role": "user", "content": message}, {"role": "assistant", "content": "What health topic or medical condition would you like to learn about today?"}], ""
|
|
|
|
|
|
if healthbot_chat.state.get("health_topic") is None:
|
|
healthbot_chat.state["health_topic"] = message
|
|
healthbot_chat.state["difficulty"] = difficulty
|
|
healthbot_chat.state["level_of_details"] = level_of_detail
|
|
healthbot_chat.state["num_questions"] = num_questions
|
|
|
|
|
|
healthbot_chat.state = search_health_info(healthbot_chat.state)
|
|
healthbot_chat.state = summarize_health_info(healthbot_chat.state)
|
|
|
|
|
|
return history + [{"role": "user", "content": message}, {"role": "assistant", "content": healthbot_chat.state["summary"] + "\n\nWould you like to take a quick quiz to test your understanding? (yes/no)"}], ""
|
|
|
|
|
|
if healthbot_chat.state.get("quiz_ready") is None:
|
|
healthbot_chat.state["quiz_ready"] = message.lower() in ["yes", "y", "sure", "ok", "okay"]
|
|
|
|
if healthbot_chat.state["quiz_ready"]:
|
|
|
|
healthbot_chat.state = create_quiz_questions(healthbot_chat.state)
|
|
|
|
|
|
return history + [{"role": "user", "content": message}, {"role": "assistant", "content": f"Question 1: {healthbot_chat.state['current_quiz_question']}"}], ""
|
|
else:
|
|
|
|
healthbot_chat.state = suggest_related_topics(healthbot_chat.state)
|
|
|
|
|
|
related_topics = healthbot_chat.state.get("related_topics", [])
|
|
if related_topics:
|
|
prompt = "Would you like to:\n1. Learn about one of these related topics (enter the number)\n2. Learn about a new health topic (enter 'new')\n3. Exit (enter 'exit')"
|
|
else:
|
|
prompt = "Would you like to learn about a new health topic (enter 'new') or exit (enter 'exit')?"
|
|
|
|
suggestion_text = "You might also be interested in these related topics:\n"
|
|
for i, topic in enumerate(related_topics):
|
|
suggestion_text += f"{i+1}. {topic}\n"
|
|
|
|
return history + [{"role": "user", "content": message}, {"role": "assistant", "content": suggestion_text + "\n" + prompt}], ""
|
|
|
|
|
|
if healthbot_chat.state.get("quiz_ready") and healthbot_chat.state.get("quiz_answer") is None:
|
|
healthbot_chat.state["quiz_answer"] = message
|
|
|
|
|
|
healthbot_chat.state = grade_quiz_answer(healthbot_chat.state)
|
|
|
|
|
|
current_index = healthbot_chat.state.get("current_question_index", 0)
|
|
num_questions = healthbot_chat.state.get("num_questions", 1)
|
|
|
|
if current_index < num_questions - 1:
|
|
|
|
healthbot_chat.state["current_question_index"] = current_index + 1
|
|
healthbot_chat.state["quiz_answer"] = None
|
|
|
|
|
|
next_question = healthbot_chat.state["quiz_questions"][current_index + 1]
|
|
return history + [{"role": "user", "content": message}, {"role": "assistant", "content": f"Question {current_index + 2}: {next_question}"}], ""
|
|
else:
|
|
|
|
quiz_grades = healthbot_chat.state.get("quiz_grades", [])
|
|
quiz_questions = healthbot_chat.state.get("quiz_questions", [])
|
|
|
|
|
|
summary = "Quiz Results:\n\n"
|
|
|
|
|
|
for i, (question, grade_item) in enumerate(zip(quiz_questions, quiz_grades)):
|
|
summary += f"Question {i+1}: {question}\n"
|
|
|
|
|
|
if isinstance(grade_item, dict) and 'grade' in grade_item:
|
|
|
|
summary += f"{grade_item['grade']}\n\n"
|
|
elif isinstance(grade_item, str):
|
|
|
|
if grade_item.startswith('{') and "grade" in grade_item:
|
|
try:
|
|
|
|
import ast
|
|
grade_dict = ast.literal_eval(grade_item)
|
|
if isinstance(grade_dict, dict) and 'grade' in grade_dict:
|
|
|
|
summary += f"{grade_dict['grade']}\n\n"
|
|
else:
|
|
|
|
summary += f"{grade_item}\n\n"
|
|
except:
|
|
|
|
summary += f"{grade_item}\n\n"
|
|
else:
|
|
|
|
summary += f"{grade_item}\n\n"
|
|
else:
|
|
|
|
summary += f"{grade_item}\n\n"
|
|
|
|
|
|
summary += f"You've completed all {num_questions} questions! Thank you for testing your knowledge.\n\n"
|
|
|
|
|
|
healthbot_chat.state = suggest_related_topics(healthbot_chat.state)
|
|
|
|
|
|
related_topics = healthbot_chat.state.get("related_topics", [])
|
|
if related_topics:
|
|
prompt = "Would you like to:\n1. Learn about one of these related topics (enter the number)\n2. Learn about a new health topic (enter 'new')\n3. Exit (enter 'exit')"
|
|
else:
|
|
prompt = "Would you like to learn about a new health topic (enter 'new') or exit (enter 'exit')?"
|
|
|
|
suggestion_text = "You might also be interested in these related topics:\n"
|
|
for i, topic in enumerate(related_topics):
|
|
suggestion_text += f"{i+1}. {topic}\n"
|
|
|
|
return history + [{"role": "user", "content": message}, {"role": "assistant", "content": summary + suggestion_text + "\n" + prompt}], ""
|
|
|
|
|
|
if healthbot_chat.state.get("related_topics") is not None and healthbot_chat.state.get("next_action") is None:
|
|
related_topics = healthbot_chat.state.get("related_topics", [])
|
|
|
|
if message.lower() in ["exit", "quit", "bye", "goodbye"]:
|
|
healthbot_chat.state["next_action"] = "exit"
|
|
return history + [{"role": "user", "content": message}, {"role": "assistant", "content": "Thank you for using HealthBot! Take care and stay healthy!"}], ""
|
|
elif message.lower() in ["new", "new topic"]:
|
|
|
|
healthbot_chat.state = HealthBotState(
|
|
messages=[],
|
|
health_topic=None,
|
|
quiz_ready=None,
|
|
quiz_answer=None,
|
|
next_action=None,
|
|
difficulty=difficulty,
|
|
level_of_details=level_of_detail,
|
|
num_questions=num_questions,
|
|
current_question_index=0,
|
|
search_results=None,
|
|
summary=None,
|
|
quiz_questions=None,
|
|
current_quiz_question=None,
|
|
quiz_grade=None,
|
|
quiz_feedback=None,
|
|
quiz_grades=[],
|
|
related_topics=None
|
|
)
|
|
return history + [{"role": "user", "content": message}, {"role": "assistant", "content": "What health topic or medical condition would you like to learn about today?"}], ""
|
|
elif related_topics and message.isdigit() and 1 <= int(message) <= len(related_topics):
|
|
|
|
selected_topic = related_topics[int(message) - 1]
|
|
|
|
|
|
healthbot_chat.state = HealthBotState(
|
|
messages=[],
|
|
health_topic=selected_topic,
|
|
quiz_ready=None,
|
|
quiz_answer=None,
|
|
next_action=None,
|
|
difficulty=difficulty,
|
|
level_of_details=level_of_detail,
|
|
num_questions=num_questions,
|
|
current_question_index=0,
|
|
search_results=None,
|
|
summary=None,
|
|
quiz_questions=None,
|
|
current_quiz_question=None,
|
|
quiz_grade=None,
|
|
quiz_feedback=None,
|
|
quiz_grades=[],
|
|
related_topics=None
|
|
)
|
|
|
|
|
|
healthbot_chat.state = search_health_info(healthbot_chat.state)
|
|
healthbot_chat.state = summarize_health_info(healthbot_chat.state)
|
|
|
|
|
|
return history + [{"role": "user", "content": message}, {"role": "assistant", "content": f"Here's information about {selected_topic}:\n\n" + healthbot_chat.state["summary"] + "\n\nWould you like to take a quick quiz to test your understanding? (yes/no)"}], ""
|
|
else:
|
|
|
|
healthbot_chat.state = HealthBotState(
|
|
messages=[],
|
|
health_topic=None,
|
|
quiz_ready=None,
|
|
quiz_answer=None,
|
|
next_action=None,
|
|
difficulty=difficulty,
|
|
level_of_details=level_of_detail,
|
|
num_questions=num_questions,
|
|
current_question_index=0,
|
|
search_results=None,
|
|
summary=None,
|
|
quiz_questions=None,
|
|
current_quiz_question=None,
|
|
quiz_grade=None,
|
|
quiz_feedback=None,
|
|
quiz_grades=[],
|
|
related_topics=None
|
|
)
|
|
return history + [{"role": "user", "content": message}, {"role": "assistant", "content": "What health topic or medical condition would you like to learn about today?"}], ""
|
|
|
|
|
|
return history + [{"role": "user", "content": message}, {"role": "assistant", "content": "I'm not sure how to respond to that. Would you like to learn about a health topic?"}], ""
|
|
|
|
|
|
|
|
with gr.Blocks() as demo:
|
|
gr.Markdown("# HealthBot: AI-Powered Patient Education System")
|
|
gr.Markdown("Ask about any health topic, get a summary, take a quiz, and explore related topics.")
|
|
|
|
with gr.Row():
|
|
with gr.Column(scale = 3):
|
|
|
|
initial_message = [{"role": "assistant", "content": "What health topic or medical condition would you like to learn about today?"}]
|
|
chatbot = gr.Chatbot(height = 600, type = "messages", value=initial_message)
|
|
|
|
|
|
msg = gr.Textbox(
|
|
label = "Type your message here...",
|
|
placeholder = "e.g., diabetes, asthma, heart disease",
|
|
visible = True
|
|
)
|
|
|
|
|
|
with gr.Row():
|
|
yes_btn = gr.Button("✅ Yes", variant = "primary", visible = False)
|
|
no_btn = gr.Button("❌ No", variant = "secondary", visible = False)
|
|
|
|
clear = gr.Button("Clear Conversation")
|
|
|
|
with gr.Column(scale = 1):
|
|
difficulty = gr.Radio(
|
|
["easy", "medium", "hard"],
|
|
label = "Difficulty Level",
|
|
info = "Select the difficulty level for quizzes",
|
|
value = "medium"
|
|
)
|
|
|
|
level_of_detail = gr.Radio(
|
|
["easy", "medium", "hard"],
|
|
label = "Level of Detail",
|
|
info = "Select the level of detail for information",
|
|
value = "medium"
|
|
)
|
|
|
|
num_questions = gr.Slider(
|
|
minimum = 1,
|
|
maximum = 5,
|
|
step = 1,
|
|
label = "Number of Quiz Questions",
|
|
info = "How many questions would you like in your quiz?",
|
|
value = 1
|
|
)
|
|
|
|
|
|
|
|
def enhanced_healthbot_chat(message, history, difficulty = "medium", level_of_detail = "medium", num_questions = 1):
|
|
response_history, _ = healthbot_chat(message, history, difficulty, level_of_detail, num_questions)
|
|
|
|
|
|
last_message = response_history[-1]["content"].lower() if response_history else ""
|
|
is_yes_no_question = any(phrase in last_message for phrase in [
|
|
"would you like to take a quiz",
|
|
"ready for the next question",
|
|
"yes/no"
|
|
]
|
|
)
|
|
|
|
if is_yes_no_question:
|
|
|
|
return (
|
|
response_history,
|
|
"",
|
|
gr.update(visible = False),
|
|
gr.update(visible = True),
|
|
gr.update(visible = True)
|
|
)
|
|
else:
|
|
|
|
return (
|
|
response_history,
|
|
"",
|
|
gr.update(visible = True),
|
|
gr.update(visible = False),
|
|
gr.update(visible = False)
|
|
)
|
|
|
|
|
|
|
|
def handle_yes_click(history, difficulty, level_of_detail, num_questions):
|
|
|
|
response_history, _ = healthbot_chat("yes", history, difficulty, level_of_detail, num_questions)
|
|
|
|
|
|
last_message = response_history[-1]["content"].lower() if response_history else ""
|
|
is_yes_no_question = any(phrase in last_message for phrase in [
|
|
"would you like to take a quiz",
|
|
"ready for the next question",
|
|
"yes/no"
|
|
]
|
|
)
|
|
|
|
if is_yes_no_question:
|
|
|
|
return (
|
|
response_history,
|
|
"",
|
|
gr.update(visible = False),
|
|
gr.update(visible = True),
|
|
gr.update(visible = True)
|
|
)
|
|
else:
|
|
|
|
return (
|
|
response_history,
|
|
"",
|
|
gr.update(visible = True),
|
|
gr.update(visible = False),
|
|
gr.update(visible = False)
|
|
)
|
|
|
|
|
|
def handle_no_click(history, difficulty, level_of_detail, num_questions):
|
|
|
|
response_history, _ = healthbot_chat("no", history, difficulty, level_of_detail, num_questions)
|
|
|
|
|
|
last_message = response_history[-1]["content"].lower() if response_history else ""
|
|
is_yes_no_question = any(phrase in last_message for phrase in [
|
|
"would you like to take a quiz",
|
|
"ready for the next question",
|
|
"yes/no",
|
|
"(yes / no)",
|
|
"Would you like to take a quick quiz to test your understanding? (yes / no)"
|
|
]
|
|
)
|
|
|
|
if is_yes_no_question:
|
|
|
|
return (
|
|
response_history,
|
|
"",
|
|
gr.update(visible = False),
|
|
gr.update(visible = True),
|
|
gr.update(visible = True)
|
|
)
|
|
else:
|
|
|
|
return (
|
|
response_history,
|
|
"",
|
|
gr.update(visible = True),
|
|
gr.update(visible = False),
|
|
gr.update(visible = False)
|
|
)
|
|
|
|
|
|
|
|
msg.submit(
|
|
enhanced_healthbot_chat,
|
|
[msg, chatbot, difficulty, level_of_detail, num_questions],
|
|
[chatbot, msg, msg, yes_btn, no_btn]
|
|
)
|
|
|
|
yes_btn.click(
|
|
handle_yes_click,
|
|
[chatbot, difficulty, level_of_detail, num_questions],
|
|
[chatbot, msg, msg, yes_btn, no_btn]
|
|
)
|
|
|
|
no_btn.click(
|
|
handle_no_click,
|
|
[chatbot, difficulty, level_of_detail, num_questions],
|
|
[chatbot, msg, msg, yes_btn, no_btn]
|
|
)
|
|
|
|
clear.click(
|
|
lambda: (
|
|
None,
|
|
"",
|
|
gr.update(visible = True),
|
|
gr.update(visible = False),
|
|
gr.update(visible = False)
|
|
),
|
|
None,
|
|
[chatbot, msg, msg, yes_btn, no_btn],
|
|
queue = False
|
|
)
|
|
|
|
|
|
def run_healthbot():
|
|
"""Run the HealthBot workflow in the terminal."""
|
|
|
|
state = HealthBotState(
|
|
messages=[],
|
|
health_topic=None,
|
|
quiz_ready=None,
|
|
quiz_answer=None,
|
|
next_action=None,
|
|
difficulty=None,
|
|
level_of_details=None,
|
|
num_questions=None,
|
|
current_question_index=0,
|
|
search_results=None,
|
|
summary=None,
|
|
quiz_questions=None,
|
|
current_quiz_question=None,
|
|
quiz_grade=None,
|
|
quiz_feedback=None,
|
|
related_topics=None
|
|
)
|
|
|
|
|
|
graph.invoke(state)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
demo.launch()
|
|
|
|
|
|
|
|
|