Files
LLM_Engineering_OLD/week2/community-contributions/hopeogbons/week2 EXERCISE.ipynb

1526 lines
62 KiB
Plaintext
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{
"cells": [
{
"cell_type": "markdown",
"id": "d006b2ea-9dfe-49c7-88a9-a5a0775185fd",
"metadata": {},
"source": [
"# 🏥 RoboCare AI Assistant\n",
"\n",
"## Why I Built This\n",
"\n",
"While working on a caregiver matching platform for **MyWoosah Inc** in the US, I faced a real challenge: how do you efficiently match families with the right caregivers when everyone has different needs?\n",
"\n",
"Families would ask things like:\n",
"- *\"I need someone for my mom on Monday mornings who speaks Spanish\"*\n",
"- *\"Can you find elder care in Boston under $30/hour with CPR certification?\"*\n",
"\n",
"Writing individual SQL queries for every combination of filters was exhausting and error-prone. I knew there had to be a better way.\n",
"\n",
"That's when I discovered the **Andela LLM Engineering program**. I saw an opportunity to transform this problem into a solution using AI. Instead of rigid queries, what if families could just... talk? And the AI would understand, search, and recommend?\n",
"\n",
"This project is my answer. It's not just an exercise—it's solving a real problem I encountered in the field.\n",
"\n",
"---\n",
"\n",
"## What This Does\n",
"\n",
"RoboCare helps families find caregivers through natural conversation. You tell it what you need, and it:\n",
"- 🔍 Searches the database intelligently\n",
"- 🎯 Finds the best matches\n",
"- 💬 Explains pros/cons in plain English \n",
"- 🔊 Speaks the results back to you\n",
"\n",
"**Tech:** OpenAI GPT-4o + Voice • Gradio UI • SQLite Database • Function Calling\n",
"\n",
"---\n",
"\n",
"**Note:** This is a demonstration. Always verify credentials independently."
]
},
{
"cell_type": "markdown",
"id": "4381c40c",
"metadata": {},
"source": [
"## Step 1: Libraries\n",
"\n",
"The essentials: OpenAI for the AI brain, Gradio for the interface, SQLite for data storage.\n"
]
},
{
"cell_type": "code",
"execution_count": 63,
"id": "185c6841",
"metadata": {},
"outputs": [],
"source": [
"# imports\n",
"\n",
"import os\n",
"from dotenv import load_dotenv\n",
"from openai import OpenAI\n",
"import gradio as gr\n",
"import sqlite3\n",
"import sqlite3\n",
"from textwrap import dedent\n",
"from contextlib import contextmanager\n",
"from typing import Optional, List, Dict, Any, Tuple"
]
},
{
"cell_type": "markdown",
"id": "2a366c15",
"metadata": {},
"source": [
"## Step 2: Setup\n",
"\n",
"Loading API keys securely (never hardcode them!), setting up the OpenAI client, and pointing to our database.\n"
]
},
{
"cell_type": "code",
"execution_count": 64,
"id": "0e731b96",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"OpenAI API Key exists and begins sk-proj-\n"
]
}
],
"source": [
"# Initialization\n",
"\n",
"load_dotenv(override=True)\n",
"\n",
"openai_api_key = os.getenv('OPENAI_API_KEY')\n",
"if openai_api_key:\n",
" print(f\"OpenAI API Key exists and begins {openai_api_key[:8]}\")\n",
"else:\n",
" print(\"OpenAI API Key not set\")\n",
" \n",
"MODEL = \"gpt-4o-mini\"\n",
"openai = OpenAI()\n",
"\n",
"DB_PATH = \"care_app.db\""
]
},
{
"cell_type": "markdown",
"id": "686fa27a",
"metadata": {},
"source": [
"## Step 3: The Database\n",
"\n",
"20 sample caregivers across major US cities with:\n",
"- Services they offer (elder care, child care, etc.)\n",
"- Languages, certifications, availability\n",
"- Personality traits\n",
"- Realistic pricing and schedules\n",
"\n",
"This mirrors the kind of data MyWoosah Inc would manage—except here, AI does the matching work.\n"
]
},
{
"cell_type": "code",
"execution_count": 65,
"id": "965d273d",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Seeded: care_app.db\n"
]
}
],
"source": [
"# Table creation and seeding\n",
"\n",
"SQL = '''\n",
"\n",
" CREATE TABLE IF NOT EXISTS caregivers (\n",
" id INTEGER PRIMARY KEY,\n",
" name TEXT NOT NULL,\n",
" gender TEXT,\n",
" years_experience INTEGER,\n",
" live_in INTEGER, -- 0/1\n",
" hourly_rate REAL,\n",
" currency TEXT,\n",
" city TEXT,\n",
" state_province TEXT,\n",
" country TEXT,\n",
" postal_code TEXT,\n",
" lat REAL,\n",
" lon REAL\n",
" );\n",
"\n",
" CREATE TABLE IF NOT EXISTS caregiver_services (\n",
" caregiver_id INTEGER,\n",
" care_type TEXT,\n",
" FOREIGN KEY (caregiver_id) REFERENCES caregivers(id)\n",
" );\n",
"\n",
" CREATE TABLE IF NOT EXISTS availability (\n",
" caregiver_id INTEGER,\n",
" day TEXT, -- e.g., 'Mon'\n",
" time_start TEXT, -- 'HH:MM'\n",
" time_end TEXT, -- 'HH:MM'\n",
" FOREIGN KEY (caregiver_id) REFERENCES caregivers(id)\n",
" );\n",
"\n",
" CREATE TABLE IF NOT EXISTS languages (\n",
" caregiver_id INTEGER,\n",
" language TEXT,\n",
" FOREIGN KEY (caregiver_id) REFERENCES caregivers(id)\n",
" );\n",
"\n",
" CREATE TABLE IF NOT EXISTS certifications (\n",
" caregiver_id INTEGER,\n",
" cert TEXT,\n",
" FOREIGN KEY (caregiver_id) REFERENCES caregivers(id)\n",
" );\n",
"\n",
" CREATE TABLE IF NOT EXISTS traits (\n",
" caregiver_id INTEGER,\n",
" trait TEXT,\n",
" FOREIGN KEY (caregiver_id) REFERENCES caregivers(id)\n",
" );\n",
"\n",
" ----------------------------------------------------------\n",
"\n",
" -- Clear old data (optional)\n",
"\n",
" DELETE FROM traits;\n",
" DELETE FROM certifications;\n",
" DELETE FROM languages;\n",
" DELETE FROM availability;\n",
" DELETE FROM caregiver_services;\n",
" DELETE FROM caregivers;\n",
"\n",
" -- Seed caregivers (20 examples, all USA)\n",
"\n",
" INSERT INTO caregivers\n",
" (id, name, gender, years_experience, live_in, hourly_rate, currency, city, state_province, country, postal_code, lat, lon)\n",
" VALUES\n",
" (1, 'Grace Williams', 'female', 6, 0, 28, 'USD', 'New York', 'NY', 'USA', '10001', 40.7128, -74.0060),\n",
" (2, 'Miguel Alvarez', 'male', 9, 1, 30, 'USD', 'Los Angeles', 'CA', 'USA', '90012', 34.0522, -118.2437),\n",
" (3, 'Ava Johnson', 'female', 4, 0, 24, 'USD', 'Chicago', 'IL', 'USA', '60601', 41.8781, -87.6298),\n",
" (4, 'Noah Robinson', 'male', 12, 0, 27, 'USD', 'Houston', 'TX', 'USA', '77002', 29.7604, -95.3698),\n",
" (5, 'Sophia Martinez', 'female', 8, 0, 29, 'USD', 'Phoenix', 'AZ', 'USA', '85004', 33.4484, -112.0740),\n",
" (6, 'Daniel Carter', 'male', 10, 1, 31, 'USD', 'Philadelphia', 'PA', 'USA', '19103', 39.9526, -75.1652),\n",
" (7, 'Emily Nguyen', 'female', 7, 0, 26, 'USD', 'San Antonio', 'TX', 'USA', '78205', 29.4241, -98.4936),\n",
" (8, 'Olivia Kim', 'female', 5, 0, 27, 'USD', 'San Diego', 'CA', 'USA', '92101', 32.7157, -117.1611),\n",
" (9, 'James Thompson', 'male', 15, 1, 34, 'USD', 'Dallas', 'TX', 'USA', '75201', 32.7767, -96.7970),\n",
" (10, 'Isabella Garcia', 'female', 3, 0, 22, 'USD', 'San Jose', 'CA', 'USA', '95113', 37.3382, -121.8863),\n",
" (11, 'Ethan Patel', 'male', 11, 1, 33, 'USD', 'Austin', 'TX', 'USA', '78701', 30.2672, -97.7431),\n",
" (12, 'Harper Brooks', 'female', 2, 0, 20, 'USD', 'Jacksonville', 'FL', 'USA', '32202', 30.3322, -81.6557),\n",
" (13, 'Logan White', 'male', 6, 0, 25, 'USD', 'Fort Worth', 'TX', 'USA', '76102', 32.7555, -97.3308),\n",
" (14, 'Amelia Davis', 'female', 9, 0, 28, 'USD', 'Columbus', 'OH', 'USA', '43215', 39.9612, -82.9988),\n",
" (15, 'Charlotte Reed', 'female', 14, 1, 32, 'USD', 'Charlotte', 'NC', 'USA', '28202', 35.2271, -80.8431),\n",
" (16, 'Jackson Lee', 'male', 5, 0, 26, 'USD', 'San Francisco', 'CA', 'USA', '94102', 37.7749, -122.4194),\n",
" (17, 'Avery Chen', 'female', 7, 0, 27, 'USD', 'Seattle', 'WA', 'USA', '98101', 47.6062, -122.3321),\n",
" (18, 'William Turner', 'male', 13, 1, 35, 'USD', 'Denver', 'CO', 'USA', '80202', 39.7392, -104.9903),\n",
" (19, 'Natalie O''Brien', 'female', 16, 0, 36, 'USD', 'Boston', 'MA', 'USA', '02108', 42.3601, -71.0589),\n",
" (20, 'Maya Robinson', 'female', 3, 0, 23, 'USD', 'Atlanta', 'GA', 'USA', '30303', 33.7488, -84.3880);\n",
"\n",
" -- Seed caregiver services\n",
"\n",
" INSERT INTO caregiver_services (caregiver_id, care_type) VALUES\n",
" (1, 'elder care'), (1, 'companionship'),\n",
" (2, 'post-op support'), (2, 'elder care'),\n",
" (3, 'child care'), (3, 'special needs'),\n",
" (4, 'respite care'), (4, 'elder care'),\n",
" (5, 'dementia care'), (5, 'companionship'),\n",
" (6, 'elder care'), (6, 'hospice support'),\n",
" (7, 'child care'), (7, 'respite care'),\n",
" (8, 'post-op support'), (8, 'companionship'),\n",
" (9, 'special needs'), (9, 'elder care'),\n",
" (10, 'child care'), (10, 'companionship'),\n",
" (11, 'dementia care'), (11, 'post-op support'),\n",
" (12, 'child care'), (12, 'special needs'),\n",
" (13, 'respite care'), (13, 'companionship'),\n",
" (14, 'elder care'), (14, 'post-op support'),\n",
" (15, 'hospice support'), (15, 'dementia care'),\n",
" (16, 'elder care'), (16, 'respite care'),\n",
" (17, 'special needs'), (17, 'companionship'),\n",
" (18, 'post-op support'), (18, 'elder care'),\n",
" (19, 'dementia care'), (19, 'hospice support'),\n",
" (20, 'child care'), (20, 'companionship');\n",
"\n",
" -- Seed availability (Mon-Sun samples)\n",
"\n",
" INSERT INTO availability (caregiver_id, day, time_start, time_end) VALUES\n",
" -- 1 Grace (NY): evenings + Sun\n",
" (1, 'Mon', '17:30', '22:00'),\n",
" (1, 'Thu', '17:30', '22:00'),\n",
" (1, 'Sun', '10:00', '16:00'),\n",
" -- 2 Miguel (LA): live-in, long blocks\n",
" (2, 'Tue', '08:00', '20:00'),\n",
" (2, 'Thu', '08:00', '20:00'),\n",
" (2, 'Sat', '09:00', '18:00'),\n",
" -- 3 Ava (CHI): weekdays 09-17\n",
" (3, 'Mon', '09:00', '17:00'),\n",
" (3, 'Wed', '09:00', '17:00'),\n",
" (3, 'Fri', '09:00', '17:00'),\n",
" -- 4 Noah (HOU): Tue-Fri 08-16\n",
" (4, 'Tue', '08:00', '16:00'),\n",
" (4, 'Wed', '08:00', '16:00'),\n",
" (4, 'Thu', '08:00', '16:00'),\n",
" -- 5 Sophia (PHX): Thu-Sun 10-18\n",
" (5, 'Thu', '10:00', '18:00'),\n",
" (5, 'Fri', '10:00', '18:00'),\n",
" (5, 'Sat', '10:00', '18:00'),\n",
" -- 6 Daniel (PHL): Mon-Thu 07-15\n",
" (6, 'Mon', '07:00', '15:00'),\n",
" (6, 'Tue', '07:00', '15:00'),\n",
" (6, 'Thu', '07:00', '15:00'),\n",
" -- 7 Emily (SAT): weekends\n",
" (7, 'Sat', '08:00', '17:00'),\n",
" (7, 'Sun', '09:00', '17:00'),\n",
" (7, 'Fri', '17:00', '21:00'),\n",
" -- 8 Olivia (SD): Mon, Wed evenings\n",
" (8, 'Mon', '16:00', '21:00'),\n",
" (8, 'Wed', '16:00', '21:00'),\n",
" (8, 'Sat', '10:00', '14:00'),\n",
" -- 9 James (DAL): live-in wide\n",
" (9, 'Mon', '07:00', '19:00'),\n",
" (9, 'Wed', '07:00', '19:00'),\n",
" (9, 'Sun', '09:00', '17:00'),\n",
" -- 10 Isabella (SJ): Tue-Thu 12-20\n",
" (10, 'Tue', '12:00', '20:00'),\n",
" (10, 'Wed', '12:00', '20:00'),\n",
" (10, 'Thu', '12:00', '20:00'),\n",
" -- 11 Ethan (ATX): nights\n",
" (11, 'Mon', '18:00', '23:00'),\n",
" (11, 'Tue', '18:00', '23:00'),\n",
" (11, 'Fri', '18:00', '23:00'),\n",
" -- 12 Harper (JAX): school hours\n",
" (12, 'Mon', '09:00', '14:00'),\n",
" (12, 'Wed', '09:00', '14:00'),\n",
" (12, 'Fri', '09:00', '14:00'),\n",
" -- 13 Logan (FTW): Thu-Sat\n",
" (13, 'Thu', '10:00', '18:00'),\n",
" (13, 'Fri', '10:00', '18:00'),\n",
" (13, 'Sat', '10:00', '18:00'),\n",
" -- 14 Amelia (CMH): Mon-Fri 08-16\n",
" (14, 'Mon', '08:00', '16:00'),\n",
" (14, 'Tue', '08:00', '16:00'),\n",
" (14, 'Thu', '08:00', '16:00'),\n",
" -- 15 Charlotte (CLT): live-in style\n",
" (15, 'Tue', '07:00', '19:00'),\n",
" (15, 'Thu', '07:00', '19:00'),\n",
" (15, 'Sat', '08:00', '16:00'),\n",
" -- 16 Jackson (SF): split shifts\n",
" (16, 'Mon', '07:00', '11:00'),\n",
" (16, 'Mon', '17:00', '21:00'),\n",
" (16, 'Sat', '12:00', '18:00'),\n",
" -- 17 Avery (SEA): Tue/Thu + Sun\n",
" (17, 'Tue', '10:00', '18:00'),\n",
" (17, 'Thu', '10:00', '18:00'),\n",
" (17, 'Sun', '11:00', '17:00'),\n",
" -- 18 William (DEN): Mon-Wed 06-14\n",
" (18, 'Mon', '06:00', '14:00'),\n",
" (18, 'Tue', '06:00', '14:00'),\n",
" (18, 'Wed', '06:00', '14:00'),\n",
" -- 19 Natalie (BOS): Tue-Fri 09-17\n",
" (19, 'Tue', '09:00', '17:00'),\n",
" (19, 'Wed', '09:00', '17:00'),\n",
" (19, 'Fri', '09:00', '17:00'),\n",
" -- 20 Maya (ATL): after-school + Sat\n",
" (20, 'Mon', '15:00', '20:00'),\n",
" (20, 'Wed', '15:00', '20:00'),\n",
" (20, 'Sat', '09:00', '15:00');\n",
"\n",
" -- Seed languages\n",
"\n",
" INSERT INTO languages (caregiver_id, language) VALUES\n",
" (1, 'English'), (1, 'Spanish'),\n",
" (2, 'English'), (2, 'Spanish'),\n",
" (3, 'English'),\n",
" (4, 'English'),\n",
" (5, 'English'), (5, 'Spanish'),\n",
" (6, 'English'),\n",
" (7, 'English'), (7, 'Vietnamese'),\n",
" (8, 'English'), (8, 'Korean'),\n",
" (9, 'English'),\n",
" (10,'English'), (10,'Spanish'),\n",
" (11,'English'), (11,'Hindi'),\n",
" (12,'English'),\n",
" (13,'English'),\n",
" (14,'English'), (14,'French'),\n",
" (15,'English'),\n",
" (16,'English'), (16,'Tagalog'),\n",
" (17,'English'), (17,'Mandarin'),\n",
" (18,'English'),\n",
" (19,'English'), (19,'Portuguese'),\n",
" (20,'English'), (20,'ASL');\n",
"\n",
" -- Seed certifications\n",
"\n",
" INSERT INTO certifications (caregiver_id, cert) VALUES\n",
" (1, 'CPR'), (1, 'First Aid'),\n",
" (2, 'CPR'), (2, 'BLS'),\n",
" (3, 'CPR'),\n",
" (4, 'First Aid'), (4, 'CNA'),\n",
" (5, 'CPR'), (5, 'Dementia Care'),\n",
" (6, 'HHA'), (6, 'CPR'),\n",
" (7, 'First Aid'),\n",
" (8, 'CPR'), (8, 'AED'),\n",
" (9, 'CNA'), (9, 'BLS'),\n",
" (10,'First Aid'),\n",
" (11,'CPR'), (11,'Medication Technician'),\n",
" (12,'CPR'),\n",
" (13,'First Aid'),\n",
" (14,'CPR'), (14,'CNA'),\n",
" (15,'Hospice Training'), (15,'CPR'),\n",
" (16,'First Aid'),\n",
" (17,'CPR'), (17,'Special Needs Training'),\n",
" (18,'BLS'), (18,'CPR'),\n",
" (19,'Dementia Care'), (19,'First Aid'),\n",
" (20,'CPR'), (20,'Childcare Safety');\n",
"\n",
" -- Seed traits\n",
"\n",
" INSERT INTO traits (caregiver_id, trait) VALUES\n",
" (1, 'empathetic'), (1, 'detail-oriented'),\n",
" (2, 'patient'), (2, 'communicative'),\n",
" (3, 'cheerful'), (3, 'reliable'),\n",
" (4, 'organized'), (4, 'professional'),\n",
" (5, 'compassionate'), (5, 'trustworthy'),\n",
" (6, 'calm under pressure'), (6, 'punctual'),\n",
" (7, 'adaptable'), (7, 'energetic'),\n",
" (8, 'friendly'), (8, 'respectful'),\n",
" (9, 'thorough'), (9, 'dependable'),\n",
" (10,'gentle'), (10,'attentive'),\n",
" (11,'proactive'), (11,'communicative'),\n",
" (12,'patient'), (12,'kind'),\n",
" (13,'flexible'), (13,'tidy'),\n",
" (14,'reliable'), (14,'punctual'),\n",
" (15,'compassionate'), (15,'detail-oriented'),\n",
" (16,'discreet'), (16,'organized'),\n",
" (17,'empathetic'), (17,'calm under pressure'),\n",
" (18,'professional'), (18,'thorough'),\n",
" (19,'trustworthy'), (19,'proactive'),\n",
" (20,'cheerful'), (20,'attentive');\n",
"\n",
"'''\n",
"\n",
"# Insert the data into the database\n",
"\n",
"sql = dedent(SQL)\n",
"con = sqlite3.connect(DB_PATH)\n",
"con.executescript(sql)\n",
"con.commit()\n",
"con.close()\n",
"print(\"Seeded:\", DB_PATH)\n"
]
},
{
"cell_type": "markdown",
"id": "3c0baa64",
"metadata": {},
"source": [
"## Step 4: Teaching the AI to Search\n",
"\n",
"Instead of the AI just talking, we teach it to actually *search* the database.\n",
"\n",
"When someone says *\"I need elder care in Boston for Mondays\"*, the AI translates that into:\n",
"```python\n",
"search_caregivers(city=\"Boston\", care_type=\"elder care\", day=\"Mon\")\n",
"```\n",
"\n",
"This schema defines all the filters the AI can use: location, services, budget, language, availability, and more.\n",
"\n",
"**This was the breakthrough.** No more writing custom queries—the AI figures it out.\n"
]
},
{
"cell_type": "code",
"execution_count": 66,
"id": "f2af7c67",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"[{'type': 'function',\n",
" 'function': {'name': 'search_caregivers',\n",
" 'description': 'Flexible multi-filter caregiver search. Any filter can be omitted. Supports location, service type, experience, pricing, live-in, language, certifications, day/time availability, and pagination.',\n",
" 'parameters': {'type': 'object',\n",
" 'properties': {'city': {'type': 'string',\n",
" 'description': 'City name to filter by (optional).'},\n",
" 'state_province': {'type': 'string',\n",
" 'description': 'State or province to filter by (optional).'},\n",
" 'country': {'type': 'string',\n",
" 'description': 'Country to filter by (optional).'},\n",
" 'care_type': {'type': 'string',\n",
" 'description': \"Service category, e.g., 'elder_care', 'child_care', 'pet_care', 'housekeeping' (optional).\"},\n",
" 'min_experience': {'type': 'integer',\n",
" 'minimum': 0,\n",
" 'description': 'Minimum years of experience (optional).'},\n",
" 'max_hourly_rate': {'type': 'number',\n",
" 'minimum': 0,\n",
" 'description': 'Maximum hourly rate in local currency (optional).'},\n",
" 'live_in': {'type': 'boolean',\n",
" 'description': 'Require live-in caregivers (optional).'},\n",
" 'language': {'type': 'string',\n",
" 'description': \"Required spoken language, e.g., 'English', 'Spanish' (optional).\"},\n",
" 'certification': {'type': 'string',\n",
" 'description': \"Required certification, e.g., 'CPR', 'CNA' (optional).\"},\n",
" 'day': {'type': 'string',\n",
" 'description': \"Day of week to match availability (optional), e.g., 'Monday', 'Tuesday', ... 'Sunday'.\"},\n",
" 'time_between': {'type': 'array',\n",
" 'description': \"Required availability window as ['HH:MM','HH:MM'] in 24h time. Matches caregivers whose availability window fully covers this range.\",\n",
" 'items': {'type': 'string',\n",
" 'pattern': '^\\\\d{2}:\\\\d{2}$',\n",
" 'description': \"Time in 'HH:MM' 24-hour format.\"},\n",
" 'minItems': 2,\n",
" 'maxItems': 2},\n",
" 'limit': {'type': 'integer',\n",
" 'minimum': 1,\n",
" 'maximum': 1000,\n",
" 'default': 50,\n",
" 'description': 'Max number of results to return (default 50).'},\n",
" 'offset': {'type': 'integer',\n",
" 'minimum': 0,\n",
" 'default': 0,\n",
" 'description': 'Number of results to skip for pagination (default 0).'}},\n",
" 'required': [],\n",
" 'additionalProperties': False}}}]"
]
},
"execution_count": 66,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"# Tool definition schema\n",
"\n",
"tools = [{\n",
" \"type\": \"function\",\n",
" \"function\": {\n",
" \"name\": \"search_caregivers\",\n",
" \"description\": (\n",
" \"Flexible multi-filter caregiver search. Any filter can be omitted. \"\n",
" \"Supports location, service type, experience, pricing, live-in, language, \"\n",
" \"certifications, day/time availability, and pagination.\"\n",
" ),\n",
" \"parameters\": {\n",
" \"type\": \"object\",\n",
" \"properties\": {\n",
" \"city\": {\n",
" \"type\": \"string\",\n",
" \"description\": \"City name to filter by (optional).\"\n",
" },\n",
" \"state_province\": {\n",
" \"type\": \"string\",\n",
" \"description\": \"State or province to filter by (optional).\"\n",
" },\n",
" \"country\": {\n",
" \"type\": \"string\",\n",
" \"description\": \"Country to filter by (optional).\"\n",
" },\n",
" \"care_type\": {\n",
" \"type\": \"string\",\n",
" \"description\": (\n",
" \"Service category, e.g., 'elder_care', 'child_care', \"\n",
" \"'pet_care', 'housekeeping' (optional).\"\n",
" )\n",
" },\n",
" \"min_experience\": {\n",
" \"type\": \"integer\",\n",
" \"minimum\": 0,\n",
" \"description\": \"Minimum years of experience (optional).\"\n",
" },\n",
" \"max_hourly_rate\": {\n",
" \"type\": \"number\",\n",
" \"minimum\": 0,\n",
" \"description\": \"Maximum hourly rate in local currency (optional).\"\n",
" },\n",
" \"live_in\": {\n",
" \"type\": \"boolean\",\n",
" \"description\": \"Require live-in caregivers (optional).\"\n",
" },\n",
" \"language\": {\n",
" \"type\": \"string\",\n",
" \"description\": \"Required spoken language, e.g., 'English', 'Spanish' (optional).\"\n",
" },\n",
" \"certification\": {\n",
" \"type\": \"string\",\n",
" \"description\": \"Required certification, e.g., 'CPR', 'CNA' (optional).\"\n",
" },\n",
" \"day\": {\n",
" \"type\": \"string\",\n",
" \"description\": (\n",
" \"Day of week to match availability (optional), e.g., \"\n",
" \"'Monday', 'Tuesday', ... 'Sunday'.\"\n",
" )\n",
" },\n",
" \"time_between\": {\n",
" \"type\": \"array\",\n",
" \"description\": (\n",
" \"Required availability window as ['HH:MM','HH:MM'] in 24h time. \"\n",
" \"Matches caregivers whose availability window fully covers this range.\"\n",
" ),\n",
" \"items\": {\n",
" \"type\": \"string\",\n",
" \"pattern\": \"^\\\\d{2}:\\\\d{2}$\",\n",
" \"description\": \"Time in 'HH:MM' 24-hour format.\"\n",
" },\n",
" \"minItems\": 2,\n",
" \"maxItems\": 2\n",
" },\n",
" \"limit\": {\n",
" \"type\": \"integer\",\n",
" \"minimum\": 1,\n",
" \"maximum\": 1000,\n",
" \"default\": 50,\n",
" \"description\": \"Max number of results to return (default 50).\"\n",
" },\n",
" \"offset\": {\n",
" \"type\": \"integer\",\n",
" \"minimum\": 0,\n",
" \"default\": 0,\n",
" \"description\": \"Number of results to skip for pagination (default 0).\"\n",
" }\n",
" },\n",
" \"required\": [],\n",
" \"additionalProperties\": False\n",
" }\n",
" }\n",
"}]\n",
"\n",
"tools"
]
},
{
"cell_type": "markdown",
"id": "76416da2",
"metadata": {},
"source": [
"## Step 5: Helper Functions\n",
"\n",
"**Voice:** The AI can speak its responses using OpenAI's text-to-speech.\n",
"\n",
"**Database functions:** All the queries we need—search, get profiles, check availability, etc. These are what the AI calls behind the scenes.\n"
]
},
{
"cell_type": "code",
"execution_count": 67,
"id": "2f50cc15",
"metadata": {},
"outputs": [],
"source": [
"\n",
"# Convert text to speech using OpenAI's TTS API\n",
"def announcements(message):\n",
" response = openai.audio.speech.create(\n",
" model=\"gpt-4o-mini-tts\",\n",
" voice=\"coral\", # Also, try replacing onyx with alloy or coral\n",
" input=message\n",
" )\n",
" return response.content\n",
"\n",
"# Context manager for database connection\n",
"@contextmanager\n",
"def _conn(dict_rows: bool = True):\n",
" conn = sqlite3.connect(DB_PATH)\n",
" if dict_rows:\n",
" conn.row_factory = _dict_factory\n",
" try:\n",
" yield conn\n",
" conn.commit()\n",
" finally:\n",
" conn.close()\n",
"\n",
"####################\n",
"# Helper functions #\n",
"####################\n",
"\n",
"# Converts SQLite query results from tuples into dictionaries\n",
"def _dict_factory(cursor, row):\n",
" return {col[0]: row[idx] for idx, col in enumerate(cursor.description)}\n",
"# A debug/logging function that prints database tool activity\n",
"def _print(msg: str):\n",
" print(f\"DATABASE TOOL CALLED: {msg}\", flush=True)\n",
"\n",
"################################\n",
"# Caregiver database functions #\n",
"################################\n",
"\n",
"# Counts the number of caregivers in the database\n",
"def get_caregiver_count() -> int:\n",
" _print(\"Counting caregivers\")\n",
" with _conn() as conn:\n",
" cur = conn.cursor()\n",
" cur.execute(\"SELECT COUNT(*) AS n FROM caregivers\")\n",
" return cur.fetchone()[\"n\"]\n",
"\n",
"# Fetches a caregiver's profile by their ID\n",
"def get_caregiver(caregiver_id: int) -> Optional[Dict[str, Any]]:\n",
" _print(f\"Fetching caregiver #{caregiver_id}\")\n",
" with _conn() as conn:\n",
" cur = conn.cursor()\n",
" cur.execute(\"SELECT * FROM caregivers WHERE id = ?\", (caregiver_id,))\n",
" return cur.fetchone()\n",
"\n",
"# Lists caregivers with pagination\n",
"def list_caregivers(limit: int = 20, offset: int = 0) -> List[Dict[str, Any]]:\n",
" _print(f\"Listing caregivers (limit={limit}, offset={offset})\")\n",
" with _conn() as conn:\n",
" cur = conn.cursor()\n",
" cur.execute(\"\"\"\n",
" SELECT * FROM caregivers\n",
" ORDER BY id\n",
" LIMIT ? OFFSET ?\n",
" \"\"\", (limit, offset))\n",
" return cur.fetchall()\n",
"\n",
"# Fetches the services a caregiver offers\n",
"def get_services(caregiver_id: int) -> List[str]:\n",
" _print(f\"Fetching services for caregiver #{caregiver_id}\")\n",
" with _conn() as conn:\n",
" cur = conn.cursor()\n",
" cur.execute(\"\"\"\n",
" SELECT care_type FROM caregiver_services WHERE caregiver_id = ?\n",
" ORDER BY care_type\n",
" \"\"\", (caregiver_id,))\n",
" return [r[\"care_type\"] for r in cur.fetchall()]\n",
"\n",
"# Fetches the languages a caregiver speaks\n",
"def get_languages(caregiver_id: int) -> List[str]:\n",
" _print(f\"Fetching languages for caregiver #{caregiver_id}\")\n",
" with _conn() as conn:\n",
" cur = conn.cursor()\n",
" cur.execute(\"\"\"\n",
" SELECT language FROM languages WHERE caregiver_id = ?\n",
" ORDER BY language\n",
" \"\"\", (caregiver_id,))\n",
" return [r[\"language\"] for r in cur.fetchall()]\n",
"\n",
"# Fetches the certifications a caregiver has\n",
"def get_certifications(caregiver_id: int) -> List[str]:\n",
" _print(f\"Fetching certifications for caregiver #{caregiver_id}\")\n",
" with _conn() as conn:\n",
" cur = conn.cursor()\n",
" cur.execute(\"\"\"\n",
" SELECT cert FROM certifications WHERE caregiver_id = ?\n",
" ORDER BY cert\n",
" \"\"\", (caregiver_id,))\n",
" return [r[\"cert\"] for r in cur.fetchall()]\n",
"\n",
"# Fetches the traits a caregiver has\n",
"def get_traits(caregiver_id: int) -> List[str]:\n",
" _print(f\"Fetching traits for caregiver #{caregiver_id}\")\n",
" with _conn() as conn:\n",
" cur = conn.cursor()\n",
" cur.execute(\"\"\"\n",
" SELECT trait FROM traits WHERE caregiver_id = ?\n",
" ORDER BY trait\n",
" \"\"\", (caregiver_id,))\n",
" return [r[\"trait\"] for r in cur.fetchall()]\n",
"\n",
"# Fetches the availability of a caregiver\n",
"def get_availability(caregiver_id: int) -> List[Dict[str, str]]:\n",
" _print(f\"Fetching availability for caregiver #{caregiver_id}\")\n",
" with _conn() as conn:\n",
" cur = conn.cursor()\n",
" cur.execute(\"\"\"\n",
" SELECT day, time_start, time_end\n",
" FROM availability\n",
" WHERE caregiver_id = ?\n",
" ORDER BY\n",
" CASE day\n",
" WHEN 'Mon' THEN 1 WHEN 'Tue' THEN 2 WHEN 'Wed' THEN 3\n",
" WHEN 'Thu' THEN 4 WHEN 'Fri' THEN 5 WHEN 'Sat' THEN 6\n",
" WHEN 'Sun' THEN 7 ELSE 8\n",
" END, time_start\n",
" \"\"\", (caregiver_id,))\n",
" return cur.fetchall()\n",
"\n",
"# Fetches a caregiver's full profile\n",
"def get_caregiver_profile(caregiver_id: int) -> Optional[Dict[str, Any]]:\n",
" _print(f\"Fetching full profile for caregiver #{caregiver_id}\")\n",
" base = get_caregiver(caregiver_id)\n",
" if not base:\n",
" return None\n",
" base[\"services\"] = get_services(caregiver_id)\n",
" base[\"languages\"] = get_languages(caregiver_id)\n",
" base[\"certifications\"] = get_certifications(caregiver_id)\n",
" base[\"traits\"] = get_traits(caregiver_id)\n",
" base[\"availability\"] = get_availability(caregiver_id)\n",
" return base\n",
"\n",
"###########################################\n",
"# Search caregivers with multiple filters #\n",
"###########################################\n",
"\n",
"def search_caregivers(\n",
" city: Optional[str] = None,\n",
" state_province: Optional[str] = None,\n",
" country: Optional[str] = None,\n",
" care_type: Optional[str] = None,\n",
" min_experience: Optional[int] = None,\n",
" max_hourly_rate: Optional[float] = None,\n",
" live_in: Optional[bool] = None,\n",
" language: Optional[str] = None,\n",
" certification: Optional[str] = None,\n",
" day: Optional[str] = None,\n",
" time_between: Optional[Tuple[str, str]] = None, # ('HH:MM', 'HH:MM')\n",
" limit: int = 50,\n",
" offset: int = 0\n",
") -> List[Dict[str, Any]]:\n",
" \"\"\"\n",
" Flexible multi-filter search. Any filter can be omitted.\n",
" \"\"\"\n",
" _print(\"Searching caregivers with multiple filters\")\n",
"\n",
" # base + optional joins\n",
" join_clauses = []\n",
" where = [\"1=1\"]\n",
" params: List[Any] = []\n",
"\n",
" if care_type:\n",
" join_clauses.append(\"JOIN caregiver_services s ON s.caregiver_id = c.id\")\n",
" where.append(\"LOWER(s.care_type) = LOWER(?)\")\n",
" params.append(care_type)\n",
"\n",
" if language:\n",
" join_clauses.append(\"JOIN languages l ON l.caregiver_id = c.id\")\n",
" where.append(\"LOWER(l.language) = LOWER(?)\")\n",
" params.append(language)\n",
"\n",
" if certification:\n",
" join_clauses.append(\"JOIN certifications cert ON cert.caregiver_id = c.id\")\n",
" where.append(\"LOWER(cert.cert) = LOWER(?)\")\n",
" params.append(certification)\n",
"\n",
" if day or time_between:\n",
" join_clauses.append(\"JOIN availability a ON a.caregiver_id = c.id\")\n",
" if day:\n",
" where.append(\"a.day = ?\")\n",
" params.append(day)\n",
" if time_between:\n",
" t0, t1 = time_between\n",
" # overlap check: caregiver window [start,end] must include [t0,t1]\n",
" where.append(\"a.time_start <= ? AND a.time_end >= ?\")\n",
" params.extend([t0, t1])\n",
"\n",
" if city:\n",
" where.append(\"LOWER(c.city) = LOWER(?)\")\n",
" params.append(city)\n",
" if state_province:\n",
" where.append(\"LOWER(c.state_province) = LOWER(?)\")\n",
" params.append(state_province)\n",
" if country:\n",
" where.append(\"LOWER(c.country) = LOWER(?)\")\n",
" params.append(country)\n",
" if min_experience is not None:\n",
" where.append(\"c.years_experience >= ?\")\n",
" params.append(min_experience)\n",
" if max_hourly_rate is not None:\n",
" where.append(\"c.hourly_rate <= ?\")\n",
" params.append(max_hourly_rate)\n",
" if live_in is not None:\n",
" where.append(\"c.live_in = ?\")\n",
" params.append(1 if live_in else 0)\n",
"\n",
" sql = f\"\"\"\n",
" SELECT DISTINCT c.*\n",
" FROM caregivers c\n",
" {' '.join(join_clauses)}\n",
" WHERE {' AND '.join(where)}\n",
" ORDER BY c.hourly_rate ASC, c.years_experience DESC, c.id\n",
" LIMIT ? OFFSET ?\n",
" \"\"\"\n",
" params.extend([limit, offset])\n",
"\n",
" with _conn() as conn:\n",
" cur = conn.cursor()\n",
" cur.execute(sql, tuple(params))\n",
" return cur.fetchall()"
]
},
{
"cell_type": "markdown",
"id": "6c526d05",
"metadata": {},
"source": [
"## Step 6: Quick Test\n",
"\n",
"Before connecting everything to the AI, let's make sure the database works. Run these examples to see sample caregivers and their profiles.\n"
]
},
{
"cell_type": "code",
"execution_count": 68,
"id": "98165a21",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"DATABASE TOOL CALLED: Searching caregivers with multiple filters\n",
"Found 1 elder care providers in New York:\n",
"- Grace Williams: $28.0/hr, 6 years experience\n",
"\n",
"============================================================\n",
"\n",
"DATABASE TOOL CALLED: Searching caregivers with multiple filters\n",
"Found 1 Spanish-speaking child care providers:\n",
"- Isabella Garcia in San Jose, CA\n",
"\n",
"============================================================\n",
"\n",
"DATABASE TOOL CALLED: Fetching full profile for caregiver #1\n",
"DATABASE TOOL CALLED: Fetching caregiver #1\n",
"DATABASE TOOL CALLED: Fetching services for caregiver #1\n",
"DATABASE TOOL CALLED: Fetching languages for caregiver #1\n",
"DATABASE TOOL CALLED: Fetching certifications for caregiver #1\n",
"DATABASE TOOL CALLED: Fetching traits for caregiver #1\n",
"DATABASE TOOL CALLED: Fetching availability for caregiver #1\n",
"Detailed profile for Grace Williams:\n",
" Services: companionship, elder care\n",
" Languages: English, Spanish\n",
" Certifications: CPR, First Aid\n",
" Traits: detail-oriented, empathetic\n",
" Availability: 3 time slots\n"
]
}
],
"source": [
"# Example 1: Search for elder care providers in New York\n",
"results = search_caregivers(\n",
" city=\"New York\",\n",
" care_type=\"elder care\",\n",
" max_hourly_rate=30.0,\n",
" limit=5\n",
")\n",
"\n",
"print(f\"Found {len(results)} elder care providers in New York:\")\n",
"for caregiver in results:\n",
" print(f\"- {caregiver['name']}: ${caregiver['hourly_rate']}/hr, {caregiver['years_experience']} years experience\")\n",
"\n",
"print(\"\\n\" + \"=\"*60 + \"\\n\")\n",
"\n",
"# Example 2: Search for Spanish-speaking child care providers\n",
"results2 = search_caregivers(\n",
" care_type=\"child care\",\n",
" language=\"Spanish\",\n",
" limit=3\n",
")\n",
"\n",
"print(f\"Found {len(results2)} Spanish-speaking child care providers:\")\n",
"for caregiver in results2:\n",
" print(f\"- {caregiver['name']} in {caregiver['city']}, {caregiver['state_province']}\")\n",
"\n",
"print(\"\\n\" + \"=\"*60 + \"\\n\")\n",
"\n",
"# Example 3: Get detailed profile of a specific caregiver\n",
"if results:\n",
" caregiver_id = results[0]['id']\n",
" profile = get_caregiver_profile(caregiver_id)\n",
" print(f\"Detailed profile for {profile['name']}:\")\n",
" print(f\" Services: {', '.join(profile['services'])}\")\n",
" print(f\" Languages: {', '.join(profile['languages'])}\")\n",
" print(f\" Certifications: {', '.join(profile['certifications'])}\")\n",
" print(f\" Traits: {', '.join(profile['traits'])}\")\n",
" print(f\" Availability: {len(profile['availability'])} time slots\")\n"
]
},
{
"cell_type": "markdown",
"id": "abfa81e6",
"metadata": {},
"source": [
"## Step 7: The AI's Instructions\n",
"\n",
"Here's where I learned prompt engineering matters *a lot*.\n",
"\n",
"The AI needs to know:\n",
"- What exact keywords to use (\"elder care\" not \"elderly care\", \"Mon\" not \"Monday\")\n",
"- How to map natural language to database values\n",
"- That it should give 2-3 recommendations with pros/cons\n",
"- To remind families to verify credentials independently\n",
"\n",
"**The lesson from MyWoosah:** Small keyword mismatches = zero results. This prompt prevents that.\n"
]
},
{
"cell_type": "code",
"execution_count": 69,
"id": "7bbe36e3",
"metadata": {},
"outputs": [],
"source": [
"# System prompt\n",
"\n",
"system_prompt = '''\n",
" You are a compassionate Caregiver Assistant that helps families quickly identify the most\n",
" suitable care provider by gathering requirements (care needs, schedule, budget, location,\n",
" language/cultural fit) and matching them to available profiles. Provide 2-3 best-fit options\n",
" with pros/cons, estimated costs, and next steps, and clearly state that credentials/background\n",
" checks are not verified by this sample app and should be confirmed by the family.\n",
"\n",
" CRITICAL: When searching the database, you MUST use these EXACT terms:\n",
"\n",
" CARE TYPES (use exactly as shown):\n",
" - \"elder care\" (for elderly, senior, old age, geriatric care)\n",
" - \"companionship\" (for companion, friendship, social support)\n",
" - \"post-op support\" (for post-surgery, post-operative, recovery care)\n",
" - \"child care\" (for children, kids, babysitting, nanny)\n",
" - \"special needs\" (for disabilities, autism, developmental needs)\n",
" - \"respite care\" (for temporary relief, break for family caregivers)\n",
" - \"dementia care\" (for Alzheimer's, memory care, cognitive decline)\n",
" - \"hospice support\" (for end-of-life, palliative, terminal care)\n",
"\n",
" If a user mentions any variation, map it to the closest match above. If unclear, ask clarifying questions.\n",
"\n",
" DAYS OF WEEK (use exactly as shown):\n",
" - \"Mon\" (for Monday)\n",
" - \"Tue\" (for Tuesday)\n",
" - \"Wed\" (for Wednesday)\n",
" - \"Thu\" (for Thursday)\n",
" - \"Fri\" (for Friday)\n",
" - \"Sat\" (for Saturday)\n",
" - \"Sun\" (for Sunday)\n",
"\n",
" STATES/PROVINCES (use 2-letter codes):\n",
" - Use standard US state abbreviations: \"NY\", \"CA\", \"TX\", \"FL\", \"MA\", etc.\n",
" - Convert full state names to abbreviations before searching\n",
"\n",
" COMMON LANGUAGES:\n",
" - \"English\", \"Spanish\", \"French\", \"Vietnamese\", \"Korean\", \"Hindi\", \"Mandarin\", \"Portuguese\", \"Tagalog\", \"ASL\"\n",
" - Capitalize properly (e.g., user says \"spanish\" → use \"Spanish\")\n",
"\n",
" CERTIFICATIONS:\n",
" - \"CPR\", \"First Aid\", \"CNA\", \"BLS\", \"HHA\", \"AED\", \"Medication Technician\", \"Hospice Training\", \n",
" \"Dementia Care\", \"Special Needs Training\", \"Childcare Safety\"\n",
" - Use exact capitalization and full names\n",
"\n",
" TRAITS:\n",
" - \"empathetic\", \"patient\", \"cheerful\", \"organized\", \"compassionate\", \"calm under pressure\", \n",
" \"adaptable\", \"friendly\", \"thorough\", \"gentle\", \"proactive\", \"flexible\", \"reliable\", \n",
" \"detail-oriented\", \"communicative\", \"energetic\", \"respectful\", \"dependable\", \"attentive\", \n",
" \"kind\", \"tidy\", \"punctual\", \"discreet\", \"professional\", \"trustworthy\"\n",
" - Use lowercase for all traits\n",
"\n",
" SEARCH STRATEGY:\n",
" 1. Listen carefully to user requirements\n",
" 2. Map their natural language to database terms above\n",
" 3. Use search_caregivers() with exact keyword matches\n",
" 4. If no results, suggest alternatives or broader searches\n",
" 5. After getting results, use get_caregiver_profile() for detailed information on top matches\n",
"\n",
" Always confirm your understanding by restating requirements using the exact database terms before searching.\n",
"'''"
]
},
{
"cell_type": "markdown",
"id": "0b8ae902",
"metadata": {},
"source": [
"## Step 8: Making it Work (and Not Crash)\n",
"\n",
"This is the engine room. When the AI wants to search, this code:\n",
"1. Validates the request\n",
"2. Calls the right database function\n",
"3. Handles errors gracefully (no crashes!)\n",
"4. Limits results to prevent overwhelming the AI\n",
"5. Generates the voice response\n",
"\n",
"**Defensive programming:** I learned the hard way that things break. This code expects problems and handles them elegantly.\n"
]
},
{
"cell_type": "code",
"execution_count": 70,
"id": "0d8accbc",
"metadata": {},
"outputs": [],
"source": [
"# Function registry: Maps tool names to actual Python functions\n",
"TOOL_REGISTRY = {\n",
" \"search_caregivers\": search_caregivers,\n",
" \"get_caregiver_count\": get_caregiver_count,\n",
" \"get_caregiver\": get_caregiver,\n",
" \"list_caregivers\": list_caregivers,\n",
" \"get_services\": get_services,\n",
" \"get_languages\": get_languages,\n",
" \"get_certifications\": get_certifications,\n",
" \"get_traits\": get_traits,\n",
" \"get_availability\": get_availability,\n",
" \"get_caregiver_profile\": get_caregiver_profile,\n",
"}\n",
"\n",
"def execute_tool_call(tool_call):\n",
" \"\"\"\n",
" Safely execute a single tool call with error handling.\n",
" Returns a properly formatted tool response.\n",
" \"\"\"\n",
" import json\n",
" \n",
" function_name = tool_call.function.name\n",
" \n",
" # Defensive check: Ensure function exists in registry\n",
" if function_name not in TOOL_REGISTRY:\n",
" return {\n",
" \"role\": \"tool\",\n",
" \"tool_call_id\": tool_call.id,\n",
" \"content\": json.dumps({\n",
" \"error\": f\"Unknown function: {function_name}\",\n",
" \"available_functions\": list(TOOL_REGISTRY.keys())\n",
" })\n",
" }\n",
" \n",
" try:\n",
" # Parse arguments\n",
" args = json.loads(tool_call.function.arguments)\n",
" \n",
" # Execute the function\n",
" func = TOOL_REGISTRY[function_name]\n",
" result = func(**args)\n",
" \n",
" # Format response based on result type with limit to prevent token overflow\n",
" if isinstance(result, list):\n",
" content = json.dumps({\n",
" \"count\": len(result),\n",
" \"results\": result[:10] if len(result) > 10 else result,\n",
" \"truncated\": len(result) > 10\n",
" })\n",
" elif isinstance(result, dict):\n",
" content = json.dumps(result)\n",
" elif isinstance(result, (int, float, str)):\n",
" content = json.dumps({\"result\": result})\n",
" else:\n",
" content = str(result)\n",
" \n",
" return {\n",
" \"role\": \"tool\",\n",
" \"tool_call_id\": tool_call.id,\n",
" \"content\": content\n",
" }\n",
" \n",
" except Exception as e:\n",
" # Defensive error handling\n",
" return {\n",
" \"role\": \"tool\",\n",
" \"tool_call_id\": tool_call.id,\n",
" \"content\": json.dumps({\n",
" \"error\": str(e),\n",
" \"function\": function_name,\n",
" \"args\": tool_call.function.arguments\n",
" })\n",
" }\n",
"\n",
"def process_tool_calls(message):\n",
" \"\"\"\n",
" Process all tool calls from the AI response.\n",
" Returns tool responses and extracted metadata.\n",
" \"\"\"\n",
" responses = []\n",
" metadata = {\n",
" \"cities\": set(),\n",
" \"caregiver_ids\": set(),\n",
" \"total_results\": 0\n",
" }\n",
" \n",
" if not message.tool_calls:\n",
" return responses, metadata\n",
" \n",
" for tool_call in message.tool_calls:\n",
" # Execute the tool call\n",
" response = execute_tool_call(tool_call)\n",
" responses.append(response)\n",
" \n",
" # Extract metadata for UI enhancements\n",
" try:\n",
" import json\n",
" content = json.loads(response[\"content\"])\n",
" \n",
" # Extract cities from search results\n",
" if \"results\" in content and isinstance(content[\"results\"], list):\n",
" for item in content[\"results\"]:\n",
" if isinstance(item, dict) and \"city\" in item:\n",
" metadata[\"cities\"].add(item[\"city\"])\n",
" if isinstance(item, dict) and \"id\" in item:\n",
" metadata[\"caregiver_ids\"].add(item[\"id\"])\n",
" \n",
" if \"count\" in content:\n",
" metadata[\"total_results\"] += content[\"count\"]\n",
" \n",
" except:\n",
" pass # Silently ignore metadata extraction errors\n",
" \n",
" return responses, metadata\n",
"\n",
"def generate_city_image(city):\n",
" \"\"\"\n",
" Generate or retrieve a city image (placeholder for future enhancement).\n",
" Could integrate with DALL-E, Unsplash API, or local image database.\n",
" \"\"\"\n",
" # Placeholder - can be enhanced with actual image generation\n",
" return None\n",
"\n",
"def chat(history):\n",
" \"\"\"\n",
" Main chat handler with multi-modal support and defensive error handling.\n",
" Handles conversation flow, tool calls, and response generation.\n",
" \"\"\"\n",
" # Normalize history format\n",
" history = [{\"role\": h[\"role\"], \"content\": h[\"content\"]} for h in history]\n",
" \n",
" # Initialize conversation with system prompt\n",
" messages = [{\"role\": \"system\", \"content\": system_prompt}] + history\n",
" \n",
" # Initialize metadata\n",
" image = None\n",
" selected_city = None\n",
" \n",
" try:\n",
" # Initial API call\n",
" response = openai.chat.completions.create(\n",
" model=MODEL,\n",
" messages=messages,\n",
" tools=tools\n",
" )\n",
" \n",
" # Tool calling loop (with safety limit)\n",
" max_iterations = 5\n",
" iteration = 0\n",
" \n",
" while response.choices[0].finish_reason == \"tool_calls\" and iteration < max_iterations:\n",
" iteration += 1\n",
" message = response.choices[0].message\n",
" \n",
" # Process all tool calls\n",
" tool_responses, metadata = process_tool_calls(message)\n",
" \n",
" # Track city for image generation\n",
" if metadata[\"cities\"]:\n",
" selected_city = list(metadata[\"cities\"])[0]\n",
" \n",
" # Add assistant message and tool responses to conversation\n",
" messages.append(message)\n",
" messages.extend(tool_responses)\n",
" \n",
" # Continue conversation\n",
" response = openai.chat.completions.create(\n",
" model=MODEL,\n",
" messages=messages,\n",
" tools=tools\n",
" )\n",
" \n",
" # Extract final reply\n",
" reply = response.choices[0].message.content\n",
" history.append({\"role\": \"assistant\", \"content\": reply})\n",
" \n",
" # Generate voice response\n",
" voice = announcements(reply)\n",
" \n",
" # Generate city image if applicable\n",
" if selected_city:\n",
" image = generate_city_image(selected_city)\n",
" \n",
" return history, voice, image\n",
" \n",
" except Exception as e:\n",
" # Defensive error handling for the entire chat flow\n",
" error_message = f\"I apologize, but I encountered an error: {str(e)}. Please try again.\"\n",
" history.append({\"role\": \"assistant\", \"content\": error_message})\n",
" return history, None, None"
]
},
{
"cell_type": "markdown",
"id": "451ed2e5",
"metadata": {},
"source": [
"## Step 9: The Interface\n",
"\n",
"A clean, professional web UI built with Gradio.\n",
"\n",
"Features:\n",
"- Chat interface with conversation history\n",
"- Voice responses that auto-play\n",
"- Settings sidebar (model selection, voice options)\n",
"- Clear instructions for families\n",
"\n",
"**Why Gradio?** At MyWoosah, I needed something non-technical staff could use immediately. Gradio made that possible without weeks of frontend work.\n",
"\n",
"**Run this cell to launch!** 🚀\n"
]
},
{
"cell_type": "code",
"execution_count": 71,
"id": "a07e7793-b8f5-44f4-aded-5562f633271a",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"* Running on local URL: http://127.0.0.1:7871\n",
"* To create a public link, set `share=True` in `launch()`.\n"
]
},
{
"data": {
"text/html": [
"<div><iframe src=\"http://127.0.0.1:7871/\" width=\"100%\" height=\"500\" allow=\"autoplay; camera; microphone; clipboard-read; clipboard-write;\" frameborder=\"0\" allowfullscreen></iframe></div>"
],
"text/plain": [
"<IPython.core.display.HTML object>"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"text/plain": []
},
"execution_count": 71,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"import gradio as gr\n",
"\n",
"# Gradio UI Setup\n",
"\n",
"def put_message_in_chatbot(message, history):\n",
" \"\"\"Add user message to chat history\"\"\"\n",
" return \"\", history + [{\"role\": \"user\", \"content\": message}]\n",
"\n",
"# Custom CSS for better styling\n",
"custom_css = \"\"\"\n",
"#chatbot {\n",
" border-radius: 10px;\n",
" box-shadow: 0 2px 8px rgba(0,0,0,0.1);\n",
"}\n",
"#message_box {\n",
" border-radius: 8px;\n",
"}\n",
".header {\n",
" text-align: center;\n",
" padding: 20px;\n",
" background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);\n",
" color: white;\n",
" border-radius: 10px;\n",
" margin-bottom: 20px;\n",
"}\n",
"\"\"\"\n",
"\n",
"with gr.Blocks(title=\"CareGiver AI Assistant\", css=custom_css, theme=gr.themes.Soft()) as ui:\n",
" \n",
" # Header\n",
" gr.Markdown(\"\"\"\n",
" <div class=\"header\">\n",
" <h1>🏥 RoboCare AI Assistant</h1>\n",
" <p>Find the perfect caregiver for your loved ones</p>\n",
" </div>\n",
" \"\"\")\n",
" \n",
" # Instructions\n",
" with gr.Accordion(\" Click here to learn more on how to use this AI\", open=False):\n",
" gr.Markdown(\"\"\"\n",
" **Tell me what you need:**\n",
" - Type of care (elder care, child care, companionship, etc.)\n",
" - Location (city, state)\n",
" - Schedule requirements (days/times)\n",
" - Budget constraints\n",
" - Language or certification needs\n",
" \n",
" **Example:** \"I need an elder care provider in Boston for Monday mornings who speaks Spanish and has CPR certification.\"\n",
" \n",
" ⚠️ **Note:** This is a demo app. Always verify credentials and conduct background checks independently.\n",
" \"\"\")\n",
" \n",
" # Main chat interface\n",
" with gr.Row():\n",
" with gr.Column(scale=2):\n",
" chatbot = gr.Chatbot(\n",
" height=500, \n",
" type=\"messages\",\n",
" elem_id=\"chatbot\",\n",
" label=\"Chat History\",\n",
" avatar_images=(None, \"🤖\")\n",
" )\n",
" \n",
" # Audio output\n",
" audio_output = gr.Audio(\n",
" label=\"Voice Response\",\n",
" autoplay=True,\n",
" visible=True,\n",
" interactive=False\n",
" )\n",
" \n",
" # Settings sidebar\n",
" with gr.Column(scale=1):\n",
" gr.Markdown(\"### ⚙️ Settings\")\n",
" \n",
" # Model selector (for future enhancement)\n",
" model_select = gr.Dropdown(\n",
" choices=[\"gpt-4o-mini\", \"gpt-4o\", \"gpt-4-turbo\"],\n",
" value=\"gpt-4o-mini\",\n",
" label=\"AI Model\",\n",
" interactive=True\n",
" )\n",
" \n",
" # Voice selector\n",
" voice_select = gr.Dropdown(\n",
" choices=[\"coral\", \"alloy\", \"echo\", \"fable\", \"onyx\", \"nova\", \"shimmer\"],\n",
" value=\"coral\",\n",
" label=\"Voice\",\n",
" interactive=True\n",
" )\n",
" \n",
" # Audio toggle\n",
" audio_enabled = gr.Checkbox(\n",
" label=\"Enable Voice Responses\",\n",
" value=True\n",
" )\n",
" \n",
" # Clear button\n",
" clear_btn = gr.Button(\"🗑️ Clear Conversation\", variant=\"secondary\")\n",
" \n",
" # Input section\n",
" with gr.Row():\n",
" message = gr.Textbox(\n",
" label=\"Your Message\",\n",
" placeholder=\"Type your question here... (e.g., 'I need elder care in Boston')\",\n",
" lines=2,\n",
" elem_id=\"message_box\",\n",
" scale=4\n",
" )\n",
" send_btn = gr.Button(\"Send\", variant=\"primary\", scale=1)\n",
" \n",
" # Event handlers\n",
" def chat_wrapper(history):\n",
" \"\"\"Wrapper to handle chat and extract only needed outputs\"\"\"\n",
" history_out, voice, image = chat(history)\n",
" return history_out, voice\n",
" \n",
" # Submit on enter or button click\n",
" submit_event = message.submit(\n",
" put_message_in_chatbot,\n",
" inputs=[message, chatbot],\n",
" outputs=[message, chatbot]\n",
" ).then(\n",
" chat_wrapper,\n",
" inputs=chatbot,\n",
" outputs=[chatbot, audio_output]\n",
" )\n",
" \n",
" send_btn.click(\n",
" put_message_in_chatbot,\n",
" inputs=[message, chatbot],\n",
" outputs=[message, chatbot]\n",
" ).then(\n",
" chat_wrapper,\n",
" inputs=chatbot,\n",
" outputs=[chatbot, audio_output]\n",
" )\n",
" \n",
" # Clear conversation\n",
" clear_btn.click(\n",
" lambda: ([], None),\n",
" outputs=[chatbot, audio_output]\n",
" )\n",
" \n",
" # Footer\n",
" gr.Markdown(\"\"\"\n",
" ---\n",
" <center>\n",
" <small>Powered by OpenAI & Gradio | Built by RoboOffice Ltd</small>\n",
" </center>\n",
" \"\"\")\n",
"\n",
"# Launch with better configuration\n",
"ui.launch(\n",
" inbrowser=True,\n",
" share=False,\n",
" show_error=True,\n",
" quiet=False\n",
")"
]
},
{
"cell_type": "markdown",
"id": "97d87d95",
"metadata": {},
"source": [
"---\n",
"\n",
"## Reflections\n",
"\n",
"This project started from frustration: *\"There has to be a better way to match families with caregivers.\"*\n",
"\n",
"Through the Andela program, I learned that AI + thoughtful engineering = solutions to real problems.\n",
"\n",
"### What Worked:\n",
"- **Function calling** eliminated the need for custom queries\n",
"- **Prompt engineering** prevented keyword mismatches\n",
"- **Defensive coding** made it robust\n",
"- **Gradio** made it accessible\n",
"\n",
"### What I'd Do Next:\n",
"- Add speech input (families could call and talk)\n",
"- Connect to actual MyWoosah database\n",
"- Add background check API integration\n",
"- Deploy for real users\n",
"\n",
"### The Bigger Picture:\n",
"\n",
"This isn't just about caregiving. The same pattern works for:\n",
"- Healthcare appointments\n",
"- Legal services\n",
"- Tutoring platforms\n",
"- Any matching problem where natural language beats forms\n",
"\n",
"AI doesn't replace good database design—it makes it accessible to everyone.\n",
"\n",
"---\n",
"\n",
"**For MyWoosah Inc and beyond:** This is proof that AI can transform how we connect people with the care they need.\n",
"\n",
"*Built during Week 2 of the Andela LLM Engineering Program*\n"
]
}
],
"metadata": {
"kernelspec": {
"display_name": ".venv",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.12.12"
}
},
"nbformat": 4,
"nbformat_minor": 5
}