A voice assistant powered by Vapi that creates Google Calendar events through natural conversation.
Live Demo: https://voice-scheduling-agent-2hsn.onrender.com
Watch the demo: https://www.loom.com/share/600bf0cb30164ed1a7c5eae536916ccb
Vapi Assistant: https://vapi.ai/?demo=true&shareKey=a75efaa4-3334-4843-89ee-7b9991507859&assistantId=4a5eabd1-8c9a-4573-b931-cbfca500a9e5
This will launch the Vapi demo page where you can talk to the assistant directly.
The voice assistant successfully:
- Listens to your voice
- Collects name, date, time, and meeting title
- Confirms details with you
- Creates a Google Calendar event
User → Vapi Widget → Groq LLM → Tool Call → FastAPI (/webhook/vapi) → Google Calendar
- Frontend: Vapi Widget (HTML/JS) connects to Vapi
- Voice: Vapi handles speech-to-text and text-to-speech
- LLM: Groq (llama-3.3-70b-versatile) processes conversation
- Backend: FastAPI receives tool calls and creates calendar events
- Calendar: Google Calendar API creates the event
sequenceDiagram
participant User
participant Vapi
participant Groq
participant FastAPI
participant GoogleCalendar
User->>Vapi: "Hi, I want to schedule a meeting"
Vapi->>Groq: Send transcript
Groq->>Vapi: Ask for name
Vapi->>User: "What's your name?"
User->>Vapi: "My name is John"
Vapi->>Groq: Send transcript
Groq->>Vapi: Ask for date
Vapi->>User: "What date?"
User->>Vapi: "March 20"
Vapi->>Groq: Send transcript
Groq->>Vapi: Ask for time
Vapi->>User: "What time?"
User->>Vapi: "3 PM"
Vapi->>Groq: Send transcript
Groq->>Vapi: Ask for title (optional)
User->>Vapi: "Team meeting"
Vapi->>Groq: Send transcript
Groq->>Vapi: Confirm details
Vapi->>User: "John, March 20 at 3 PM, Team meeting. Confirm?"
User->>Vapi: "Yes"
Vapi->>Groq: Tool call with all data
Groq->>FastAPI: POST /webhook/vapi
FastAPI->>GoogleCalendar: Create event
GoogleCalendar->>FastAPI: Event link
FastAPI->>Vapi: Success response
Vapi->>User: "Event created successfully!"
| Feature | Status | Implementation | Why Important |
|---|---|---|---|
| Calendar API failures | ✅ | main.py:288 - Returns "Sorry, I couldn't create the event. Please try again." |
Prevents user confusion when Google Calendar is unavailable |
| Invalid date inputs | ✅ | main.py:263 - "Could not understand the date..." |
Voice input is error-prone; users may say "next Tuesday" or unclear dates |
| Invalid time inputs | ✅ | main.py:268 - "Could not understand the time..." |
Same as dates - natural speech varies widely |
| Feature | Status | Implementation | Why Important |
|---|---|---|---|
| State machine | ✅ | session_manager.py |
Ensures consistent flow: name → date → time → title → confirmation |
| Confirmation before event | ✅ | System prompt enforcement | Prevents accidental event creation with wrong details |
| Skip optional title | ✅ | session_manager.py:315-317 |
Not all meetings need titles; gives user flexibility |
| Cancel/Restart | ✅ | session_manager.py:345-352 |
Users may change their mind mid-conversation |
The backend includes several safeguards to ensure reliable and safe event creation:
Why: LLMs can sometimes call tools prematurely, before collecting all required information.
Implementation:
- Session must be in
ready_for_toolstate confirmation_statusmust beTrue- If not confirmed → returns error message to user
Why: Users might accidentally confirm multiple times, creating duplicate events.
Implementation:
- Session tracks
event_createdflag - If event already created → returns existing event link
- Prevents duplicate bookings
Why: LLMs may hallucinate dates or users might say ambiguous dates.
Implementation:
- Validates date is not in the past
- Validates date is within 1 year
- Rejects invalid formats with user-friendly messages
Philosophy:
- Internal logs contain full error details (for debugging)
- External responses are user-friendly (no technical jargon)
- Google Calendar API failures are caught gracefully
| Feature | Benefit | Trade-off |
|---|---|---|
| Strict validation | Prevents bad data | May reject ambiguous human input |
| Idempotency | Prevents duplicates | Requires session tracking |
| Confirmation check | Prevents premature execution | Slightly more complex flow |
| Feature | Status | Implementation | Why Important |
|---|---|---|---|
| Comprehensive logging | ✅ | main.py:31-35 |
Debug issues in production; trace conversation flow |
| Environment validation | ✅ | calendar_service.py:42-45 |
Fail fast on missing credentials |
| Health check endpoint | ✅ | main.py:291-302 - GET /health |
Monitor service status; used by load balancers |
pip install -r requirements.txtCreate .env file:
PORT=8000
# Google Calendar API Credentials
GOOGLE_CLIENT_ID=your_client_id
GOOGLE_CLIENT_SECRET=your_client_secret
GOOGLE_REFRESH_TOKEN=your_refresh_token
# Vapi Configuration
VAPI_API_KEY=your_vapi_api_key
# Groq Configuration
GROQ_API_KEY=your_groq_api_keypython main.pyThe server runs at http://localhost:8000
ngrok http 8000Copy the ngrok URL (e.g., https://abc123.ngrok.io)
- Go to Vapi Dashboard
- Create or select your assistant
- Add the Tool:
- Type: Function
- Name:
function_tool - Server URL:
https://iconological-clerklier-amelia.ngrok-free.dev/webhook/vapi - Timeout: 30 seconds
- Set Model: Groq (llama-3.3-70b-versatile)
- Set Voice: Cartesia (or your preferred voice)
IMPORTANT: Use a local server, not file://
Option A: Use ngrok URL (recommended)
https://iconological-clerklier-amelia.ngrok-free.dev/
Make sure backend is running with ngrok tunnel active.
Option B: Run locally
python main.pyThen open: http://localhost:8000
Option C: Python HTTP server
python -m http.server 3000Then open: http://localhost:3000
https://iconological-clerklier-amelia.ngrok-free.dev/webhook/vapi
{
"name": "function_tool",
"description": "Create a calendar event",
"parameters": {
"type": "object",
"properties": {
"name": { "type": "string" },
"date": { "type": "string" },
"time": { "type": "string" },
"title": { "type": "string" }
},
"required": ["name", "date", "time"]
}
}You are a voice scheduling assistant.
Ask for user's name, then date, then time, then title (optional).
Confirm all details, then call function_tool.
Do NOT skip steps.
Assistant: "Thank you for calling. How may I help you?"
User: "Hi, I want to schedule a meeting"
Assistant: "What's your name?"
User: "My name is John"
Assistant: "What date would you like?"
User: "March 20"
Assistant: "What time?"
User: "3 PM"
Assistant: "Would you like a title?"
User: "Team meeting"
Assistant: "Let me confirm. Your name is John. Meeting is on March 20 at 3 PM, title is Team meeting. Should I create it?"
User: "Yes"
Assistant: "Event created successfully!"
- Backend starts without errors (
python main.py) - Health check works (
GET /health) - ngrok tunnel is active
- Vapi tool is configured with ngrok URL
- Voice assistant responds to voice
- Calendar event is created in Google Calendar
| Method | Endpoint | Description |
|---|---|---|
| POST | /webhook/vapi |
Vapi webhook (tool calls) |
| GET | /health |
Health check |
| GET | /api/sessions/{id} |
Session info |
| POST | /api/tools/create_event |
Direct API (testing) |
├── main.py # FastAPI backend
├── calendar_service.py # Google Calendar API
├── session_manager.py # Session state machine
├── index.html # Voice widget
├── requirements.txt # Dependencies
├── .env.example # Environment template
└── README.md # This file
- Check internet connection
- Try refreshing the page
- Verify GOOGLE_CLIENT_ID is correct in
.env
- Make sure the backend has python-dateutil installed
- Verify ngrok URL is set in Vapi Dashboard tool settings
- Check that Server URL ends with
/webhook/vapi
- Vapi: https://vapi.ai
- Groq: https://groq.com