AI-powered receptionist chatbot for Acme Dental clinic. Aria handles appointment bookings, cancellations, FAQs, and clinic queries via a chat interface backed by a LangGraph + FastAPI agent and the Calendly API.
For architectural decisions and design rationale, see PROJECT_BRIEF.md.
| Layer | Technology |
|---|---|
| LLM | Google Gemini 2.0 Flash (via langchain-google-genai) |
| Agent | LangGraph create_react_agent with MemorySaver |
| API | FastAPI + Uvicorn |
| Appointments | Calendly API (v2) |
| Database | SQLite (SQLAlchemy) — persisted via Docker volume |
SMTP (Gmail/Outlook) via aiosmtplib |
|
| Security | bcrypt PIN hashing, 3-attempt lockout |
| Containerisation | Docker + Docker Compose |
| Package manager | uv |
| Testing | pytest |
backend/
agent.py # LLM + LangGraph agent assembly
guardrails.py # Input/output validation (injection, length, topic)
main.py # FastAPI app + CORS + lifespan
KNOWLEDGE_BASE.md # Clinic FAQ loaded by search_faq tool
db/
database.py # SQLAlchemy engine + session + init_db()
models.py # AppointmentPin, ConversationMessage, SessionBooking, ConversationReview
routes/
chat.py # POST /chat — main chat endpoint
admin.py # Admin API — conversation list, transcript view, review status
services/
email_service.py # Async SMTP confirmation emails
pin_service.py # PIN generation, hashing, verification, lockout
tools/
__init__.py # Exports all_tools
calendly.py # Booking, lookup, cancel, reschedule, available slots (Calendly API)
clinic.py # Clinic info, services, FAQ search, datetime, opening hours tools
frontend/
index.html # Patient-facing chat UI (HTML/CSS/JS)
admin.html # Admin UI — browse conversations, view transcripts, mark sessions
nginx.conf # Nginx — serves frontend, proxies /chat/ and /admin-api/ to backend
tests/
tools/
test_clinic.py # Tests for clinic tools
test_calendly.py # Slot filtering, booking validation, opening hours tests
services/
test_pin_service.py # PIN generation, verification, lockout tests
Dockerfile # Backend image (uv + Python 3.13)
docker-compose.yml # backend + nginx frontend + SQLite volume
- Docker Desktop
- A Google Gemini API key (free tier: 1,500 req/day for gemini-2.0-flash)
- A Calendly account with a PAT token
- An email account with an App Password (for SMTP)
Copy .env.example to .env and fill in your keys:
GEMINI_API_KEY=your_gemini_api_key
Calendly_API_Key=your_calendly_pat_token
SMTP_HOST=smtp.gmail.com
SMTP_PORT=587
SMTP_USER=you@gmail.com
SMTP_PASSWORD=your_gmail_app_password
SMTP_FROM=you@gmail.comdocker compose up --buildOpen http://localhost:3000 in your browser.
uv run uvicorn backend.main:app --reloadOpen frontend/index.html directly in your browser.
- Patient asks for available slots → agent calls
get_available_slots(date)→ real Calendly API - Patient selects a slot → agent calls
book_appointment(name, email, slot)→ creates local record + bcrypt PIN - Confirmation email sent with appointment details, 6-digit security PIN, and appointment reference
- PIN is never shown in chat — only delivered via email
- Patient provides name + PIN → agent calls
lookup_appointmentorcancel_appointment - PIN verified against bcrypt hash in DB (3-attempt lockout on failure)
- Cancellation forwarded to Calendly API using the patient's email to find the active event
- Works from any session — no session memory required
- Input length limit (1,000 chars)
- Prompt injection detection
- Data fishing detection
- Off-topic and sensitive medical topic blocking
- Max 3 bookings per session, unique patient names per session (this is to allow bookings for family members if convinient to book for kids at the same time.)
Every message pair is stored in ConversationMessage with a session_id.
The admin dashboard (http://localhost:3000/admin.html) lets you:
- Browse all conversation sessions with timestamps and a first-message preview
- View the full transcript for any session
- Mark each session with a review status:
unreviewed/safe/risky/dangerous - Add free-text notes to flag specific issues
Review statuses are stored in a ConversationReview table and are intended to inform tuning of the system prompt, guardrails, and tool behaviour over time.
Raw API endpoints:
GET /admin-api/conversations # list all sessions with review status
GET /admin-api/conversations/{session_id} # full transcript + review for a session
POST /admin-api/conversations/{session_id}/review # set status and notes
uv run pytest tests/ -v| Method | Path | Description |
|---|---|---|
| POST | /chat/ |
Send a message to Aria |
| GET | /health |
Health check |
| GET | /admin-api/conversations |
List sessions with review status (admin) |
| GET | /admin-api/conversations/{id} |
Full transcript + review for a session (admin) |
| POST | /admin-api/conversations/{id}/review |
Set review status and notes (admin) |
- Refine prompts — review conversation logs to identify where Aria gives robotic or unclear responses; improve the system prompt and tool docstrings accordingly
- Confirm live bookings with Calendly — wire up
book_appointmentto create a real Calendly event via the scheduling links API or a Calendly webhook flow; currently bookings are recorded locally only - Test fraud prevention end-to-end — manually test PIN lockout, wrong-name rejection, and 3-booking-per-session limit with live data; verify bcrypt timing is acceptable
- Agent to ammend bookings if session same - Agent should be able to ammend or cancel bookings if the session is still the same, without requirment for PIN. PIN should only be for if there is a new session.
- Make Sure AI doesn't invent opening hours
- Edge case hunting — test corner cases: same patient name different email, booking on a Saturday[failed], cancelling an already-cancelled appointment, lookup when Calendly API is down, rescheduling for same time and day.
- Wire up Calendly available slots — confirm
get_available_slotsis returning real times fromGET /event_type_available_timesfor the correct event type - Reschedule flow — implement
reschedule_appointment(patient_name, pin, new_slot)tool; currently only cancel is supported. reschedule not supported in API from Calendly.
- Persistent MemorySaver — replace in-memory
MemorySaverwithlanggraph-checkpoint-sqliteso conversation context survives container restarts - Rate limiting — add per-IP or per-session message rate limiting to prevent API abuse
- Admin dashboard — UI to browse conversation logs, view full transcripts, and mark sessions as safe / risky / dangerous with notes to inform future guardrail and prompt tuning