Revamp booking flow
Build Agents Runtime / build (push) Successful in 2m58s Details

This commit is contained in:
Deep Koluguri 2026-05-26 21:51:50 -04:00
parent 871d341161
commit f9e8786921
3 changed files with 47 additions and 26 deletions

View File

@ -163,31 +163,53 @@ async def save_clinical_record_activity(payload: dict) -> str:
class AppointmentIntentSchema(BaseModel):
is_booking_request: bool = Field(description="True if the user is asking to book, schedule, or modify an appointment.")
department: str = Field(description="The department they want to visit (e.g. Cardiology, General, Dental). Leave empty if not specified.")
appointment_date: str = Field(description="The date they want to visit (e.g. 'Monday', 'Tomorrow', '10/12/2026'). Leave empty if not specified.")
appointment_time: str = Field(description="The time they want to visit (e.g. 'Morning', '3 PM'). Leave empty if not specified.")
missing_info: list[str] = Field(description="List of fields that are still missing and need to be asked. E.g. ['department', 'appointment_date']")
reply_message: str = Field(description="The message to send to the user. IMPORTANT: If asking for department, you MUST list the 4 available departments: Cardiology, General Medicine, Pediatrics, Orthopedics.")
doctor_name: str = Field(description="The specific doctor they want to visit. Leave empty if not specified.")
appointment_date: str = Field(description="The date they want to visit. Leave empty if not specified.")
appointment_time: str = Field(description="The time they want to visit. Leave empty if not specified.")
missing_info: list[str] = Field(description="List of fields that are still missing and need to be asked.")
reply_message: str = Field(description="The exact text to send to the user. Ask for missing details using numbered lists.")
@activity.defn
async def parse_booking_intent_activity(chat_history: list[dict]) -> dict:
"""
Parses the chat history using the LLM to extract booking intent and details.
chat_history is a list of {"role": "user"|"assistant", "content": "..."}
"""
async def parse_booking_intent_activity(payload: dict) -> dict:
chat_history = payload.get("chat_history", [])
to_number = payload.get("to_number", "")
# 1. Fetch available doctors for the given tenant
import psycopg2
doctors = []
try:
conn = psycopg2.connect("postgres://postgres:curio_secret@curio-db:5432/curio")
cur = conn.cursor()
# Find tenant by whatsapp_number
cur.execute("SELECT id FROM tenants WHERE whatsapp_number = %s OR whatsapp_number = %s", (to_number, to_number.replace('whatsapp:', '')))
row = cur.fetchone()
if row:
tenant_id = row[0]
cur.execute("SELECT name FROM users WHERE tenant_id = %s AND role = 'DOCTOR' AND status = 'active'", (tenant_id,))
doctors = [doc[0] for doc in cur.fetchall()]
cur.close()
conn.close()
except Exception as e:
print(f"DB Error fetching doctors: {e}")
doctors_str = ", ".join(doctors) if doctors else "Any Available Doctor"
from langchain_core.prompts import PromptTemplate
prompt = PromptTemplate.from_template(
"You are a helpful hospital receptionist bot talking to a patient on WhatsApp.\n"
"Here is the conversation history:\n"
"{history}\n\n"
"Analyze the conversation. Extract any appointment details (department, date, time).\n"
"If they want to book an appointment but haven't specified department, date, and time, "
"add those to missing_info and formulate a reply_message asking for them.\n"
"CRITICAL INSTRUCTIONS FOR ASKING:\n"
"- If the user hasn't specified a department, you MUST list the 4 available departments (Cardiology, General Medicine, Pediatrics, Orthopedics) so they know what to choose. Do not make them guess!\n"
"- If they haven't specified a time, suggest common intervals (e.g. 10:00 AM, 11:00 AM, 6:00 PM).\n"
"If all details are present, set missing_info to empty and formulate a confirmation reply_message.\n"
f"Available Doctors: {doctors_str}\n\n"
"Analyze the conversation. Extract any appointment details (doctor_name, appointment_date, appointment_time).\n"
"If they want to book an appointment, gather missing details sequentially:\n"
"1. If doctor_name is missing, check the Available Doctors. If there is more than 1 doctor, ask the user to choose using a numbered list (1. Dr. X, 2. Dr. Y). If there is only 1 doctor, auto-select them and move to the next step.\n"
"2. If appointment_date is missing, ask for a date and suggest 2 or 3 specific dates for the current week as a numbered list (1. Monday, 2. Tuesday).\n"
"3. If appointment_time is missing, ask for a time and suggest two 30-minute time slots as a numbered list (1. 10:00 AM, 2. 10:30 AM).\n"
"CRITICAL INSTRUCTIONS:\n"
"- Only ask for ONE missing piece of information at a time.\n"
"- Format your choices as strict numbered lists so the user can just type the number.\n"
"- If all details are present, set missing_info to empty and formulate a confirmation reply_message.\n"
"Format output strictly as JSON matching the schema."
)
@ -204,14 +226,13 @@ async def parse_booking_intent_activity(chat_history: list[dict]) -> dict:
return result.dict()
except Exception as e:
print(f"Failed to parse booking intent: {e}")
# Fallback due to Pydantic strictness failures
return {
"is_booking_request": True,
"department": "",
"doctor_name": "",
"appointment_date": "",
"appointment_time": "",
"missing_info": ["department", "appointment_date", "appointment_time"],
"reply_message": "I'm sorry, I'm having trouble understanding. Could you please specify the department, date, and time you'd like to book?"
"missing_info": ["doctor_name", "appointment_date", "appointment_time"],
"reply_message": "I'm sorry, I encountered an error. Could you please let me know which doctor you want to book?"
}
@activity.defn
@ -252,7 +273,7 @@ async def save_appointment_activity(payload: dict) -> str:
try:
record = Appointment(
phone_number=payload.get("phone_number", ""),
department=payload.get("department", ""),
doctor_name=payload.get("doctor_name", ""),
appointment_date=payload.get("appointment_date", ""),
appointment_time=payload.get("appointment_time", ""),
status="CONFIRMED"

View File

@ -33,7 +33,7 @@ class Appointment(Base):
id = Column(Integer, primary_key=True, index=True)
phone_number = Column(String(50), index=True, nullable=False)
department = Column(String(100), nullable=True)
doctor_name = Column(String(100), nullable=True)
appointment_date = Column(String(50), nullable=True)
appointment_time = Column(String(50), nullable=True)
status = Column(String(20), default="PENDING")

View File

@ -130,14 +130,14 @@ class AppointmentBookingWorkflow:
self._new_message_event.set()
@workflow.run
async def run(self, initial_message: str, phone_number: str) -> dict:
async def run(self, initial_message: str, phone_number: str, to_number: str = "") -> dict:
self._chat_history.append({"role": "user", "content": initial_message})
while True:
# 1. Parse intent and get next action
intent_data = await workflow.execute_activity(
"parse_booking_intent_activity",
self._chat_history,
{"chat_history": self._chat_history, "to_number": to_number},
retry_policy=RetryPolicy(maximum_attempts=3), schedule_to_close_timeout=timedelta(minutes=5)
)
@ -159,7 +159,7 @@ class AppointmentBookingWorkflow:
"save_appointment_activity",
{
"phone_number": phone_number,
"department": intent_data.get("department"),
"doctor_name": intent_data.get("doctor_name"),
"appointment_date": intent_data.get("appointment_date"),
"appointment_time": intent_data.get("appointment_time")
},