Revamp booking flow
Build Agents Runtime / build (push) Successful in 2m58s
Details
Build Agents Runtime / build (push) Successful in 2m58s
Details
This commit is contained in:
parent
871d341161
commit
f9e8786921
|
|
@ -163,31 +163,53 @@ async def save_clinical_record_activity(payload: dict) -> str:
|
||||||
|
|
||||||
class AppointmentIntentSchema(BaseModel):
|
class AppointmentIntentSchema(BaseModel):
|
||||||
is_booking_request: bool = Field(description="True if the user is asking to book, schedule, or modify an appointment.")
|
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.")
|
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 (e.g. 'Monday', 'Tomorrow', '10/12/2026'). 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 (e.g. 'Morning', '3 PM'). 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. E.g. ['department', 'appointment_date']")
|
missing_info: list[str] = Field(description="List of fields that are still missing and need to be asked.")
|
||||||
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.")
|
reply_message: str = Field(description="The exact text to send to the user. Ask for missing details using numbered lists.")
|
||||||
|
|
||||||
@activity.defn
|
@activity.defn
|
||||||
async def parse_booking_intent_activity(chat_history: list[dict]) -> dict:
|
async def parse_booking_intent_activity(payload: dict) -> dict:
|
||||||
"""
|
chat_history = payload.get("chat_history", [])
|
||||||
Parses the chat history using the LLM to extract booking intent and details.
|
to_number = payload.get("to_number", "")
|
||||||
chat_history is a list of {"role": "user"|"assistant", "content": "..."}
|
|
||||||
"""
|
# 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
|
from langchain_core.prompts import PromptTemplate
|
||||||
|
|
||||||
prompt = PromptTemplate.from_template(
|
prompt = PromptTemplate.from_template(
|
||||||
"You are a helpful hospital receptionist bot talking to a patient on WhatsApp.\n"
|
"You are a helpful hospital receptionist bot talking to a patient on WhatsApp.\n"
|
||||||
"Here is the conversation history:\n"
|
"Here is the conversation history:\n"
|
||||||
"{history}\n\n"
|
"{history}\n\n"
|
||||||
"Analyze the conversation. Extract any appointment details (department, date, time).\n"
|
f"Available Doctors: {doctors_str}\n\n"
|
||||||
"If they want to book an appointment but haven't specified department, date, and time, "
|
"Analyze the conversation. Extract any appointment details (doctor_name, appointment_date, appointment_time).\n"
|
||||||
"add those to missing_info and formulate a reply_message asking for them.\n"
|
"If they want to book an appointment, gather missing details sequentially:\n"
|
||||||
"CRITICAL INSTRUCTIONS FOR ASKING:\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"
|
||||||
"- 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"
|
"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"
|
||||||
"- If they haven't specified a time, suggest common intervals (e.g. 10:00 AM, 11:00 AM, 6:00 PM).\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"
|
||||||
"If all details are present, set missing_info to empty and formulate a confirmation reply_message.\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."
|
"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()
|
return result.dict()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Failed to parse booking intent: {e}")
|
print(f"Failed to parse booking intent: {e}")
|
||||||
# Fallback due to Pydantic strictness failures
|
|
||||||
return {
|
return {
|
||||||
"is_booking_request": True,
|
"is_booking_request": True,
|
||||||
"department": "",
|
"doctor_name": "",
|
||||||
"appointment_date": "",
|
"appointment_date": "",
|
||||||
"appointment_time": "",
|
"appointment_time": "",
|
||||||
"missing_info": ["department", "appointment_date", "appointment_time"],
|
"missing_info": ["doctor_name", "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?"
|
"reply_message": "I'm sorry, I encountered an error. Could you please let me know which doctor you want to book?"
|
||||||
}
|
}
|
||||||
|
|
||||||
@activity.defn
|
@activity.defn
|
||||||
|
|
@ -252,7 +273,7 @@ async def save_appointment_activity(payload: dict) -> str:
|
||||||
try:
|
try:
|
||||||
record = Appointment(
|
record = Appointment(
|
||||||
phone_number=payload.get("phone_number", ""),
|
phone_number=payload.get("phone_number", ""),
|
||||||
department=payload.get("department", ""),
|
doctor_name=payload.get("doctor_name", ""),
|
||||||
appointment_date=payload.get("appointment_date", ""),
|
appointment_date=payload.get("appointment_date", ""),
|
||||||
appointment_time=payload.get("appointment_time", ""),
|
appointment_time=payload.get("appointment_time", ""),
|
||||||
status="CONFIRMED"
|
status="CONFIRMED"
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,7 @@ class Appointment(Base):
|
||||||
|
|
||||||
id = Column(Integer, primary_key=True, index=True)
|
id = Column(Integer, primary_key=True, index=True)
|
||||||
phone_number = Column(String(50), index=True, nullable=False)
|
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_date = Column(String(50), nullable=True)
|
||||||
appointment_time = Column(String(50), nullable=True)
|
appointment_time = Column(String(50), nullable=True)
|
||||||
status = Column(String(20), default="PENDING")
|
status = Column(String(20), default="PENDING")
|
||||||
|
|
|
||||||
|
|
@ -130,14 +130,14 @@ class AppointmentBookingWorkflow:
|
||||||
self._new_message_event.set()
|
self._new_message_event.set()
|
||||||
|
|
||||||
@workflow.run
|
@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})
|
self._chat_history.append({"role": "user", "content": initial_message})
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
# 1. Parse intent and get next action
|
# 1. Parse intent and get next action
|
||||||
intent_data = await workflow.execute_activity(
|
intent_data = await workflow.execute_activity(
|
||||||
"parse_booking_intent_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)
|
retry_policy=RetryPolicy(maximum_attempts=3), schedule_to_close_timeout=timedelta(minutes=5)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -159,7 +159,7 @@ class AppointmentBookingWorkflow:
|
||||||
"save_appointment_activity",
|
"save_appointment_activity",
|
||||||
{
|
{
|
||||||
"phone_number": phone_number,
|
"phone_number": phone_number,
|
||||||
"department": intent_data.get("department"),
|
"doctor_name": intent_data.get("doctor_name"),
|
||||||
"appointment_date": intent_data.get("appointment_date"),
|
"appointment_date": intent_data.get("appointment_date"),
|
||||||
"appointment_time": intent_data.get("appointment_time")
|
"appointment_time": intent_data.get("appointment_time")
|
||||||
},
|
},
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue