WhatsApp AI Intake Agent
WhatsApp AI intake agent: inbound webhook, Groq tool-calling, appointment creation
WhatsApp AI Intake Agent
This page details the architecture and implementation of the AI-driven WhatsApp intake agent. It covers the handling of inbound WhatsApp messages, the use of Groq for tool-calling to understand user intent, and the process of creating appointments based on these interactions.
Inbound Webhook
The system processes inbound WhatsApp messages via a webhook endpoint. This endpoint is responsible for receiving messages from the WhatsApp provider, extracting relevant message data, and initiating the AI intake flow.
The implementation details for the src/app/api/whatsapp/webhook endpoint are not available in the provided code snippet.
Groq Tool-Calling Agent
The core logic for the AI intake agent resides in src/lib/ai-intake.ts. This module implements a two-pass LLM pattern, similar to the /api/ai/chat endpoint, where the first LLM call determines if a tool is needed, and the second generates a final response using tool results.
The agent uses Groq's LLM with a predefined set of tools to interact with the database and manage the booking process. The Groq client is instantiated via getGroq() using the GROQ_API_KEY environment variable.
Tool Definitions
The TOOLS array in src/lib/ai-intake.ts:40 defines the functions the Groq LLM can call. These include:
get_available_slots: Retrieves available appointment times for a specific date.book_appointment: Creates a new appointment after confirming details.get_professional_info: Fetches details about the professional, such as session duration, price, and working hours.
const TOOLS: Groq.Chat.Completions.ChatCompletionTool[] = [
{
type: "function",
function: {
name: "get_available_slots",
description: "Retorna horários disponíveis para agendamento em uma data específica. Use SEMPRE antes de sugerir horários.",
parameters: {
type: "object",
properties: {
date: { type: "string", description: "Data no formato YYYY-MM-DD" },
},
required: ["date"],
},
},
},
{
type: "function",
function: {
name: "book_appointment",
description: "Cria um agendamento. Use SOMENTE após confirmar data, horário e nome do cliente.",
parameters: {
type: "object",
properties: {
date: { type: "string", description: "Data no formato YYYY-MM-DD" },
time: { type: "string", description: "Horário no formato HH:mm (ex: 14:00)" },
client_name: { type: "string", description: "Nome completo do cliente" },
client_phone: { type: "string", description: "Telefone do cliente no formato 5511999999999" },
},
required: ["date", "time", "client_name", "client_phone"],
},
},
},
{
type: "function",
function: {
name: "get_professional_info",
description: "Retorna informações do profissional: nome, profissão, duração e preço da sessão, e horários de atendimento.",
parameters: { type: "object", properties: {}, required: [] },
},
},
]getAvailableSlots Implementation
The getAvailableSlots function in src/lib/ai-intake.ts is responsible for calculating and returning available time slots for a given professional and date.
- Professional Lookup: It first retrieves the professional's details from the
professionalstable usingdb.select().from(professionals). - Date Validation & Day of Week: The input
dateStris validated forYYYY-MM-DDformat. The day of the week is determined in the "America/Sao_Paulo" timezone usingtoLocaleDateString. - Availability Rules: It queries the
availabilitytable to find the professional's working hours for that specific day usingdb.select().from(availability). - Existing Appointments: It fetches existing appointments for the given date from the
appointmentstable, excludingrejectedorcancelledstatuses, usingdb.select().from(appointments). - Slot Generation: It iterates through the availability rules, generating potential slots based on the
professional.sessionDuration. Each potential slot is checked against existing appointments to prevent double-bookings and ensures only future slots are offered.
Brazilian time zone handling is explicit, with saoPauloDate, formatSaoPauloTime, and formatSaoPauloDate helpers ensuring consistency. Brazil abolished DST in 2019, so São Paulo is consistently UTC-3.
bookAppointment Implementation
The bookAppointment function in src/lib/ai-intake.ts creates a new appointment in the database. It takes the professional ID, date, time, client name, and client phone as arguments.
- Professional Lookup: Retrieves professional details from the
professionalstable. - Date & Time Calculation: Calculates
startDateandendDatefor the appointment usingsaoPauloDateandaddMinutes. - Conflict Check: Performs a conflict check against existing appointments in the
appointmentstable, similar to the logic in thePOST /api/bookingendpoint, to prevent double-bookings. - Client Upsert: Finds an existing client by
phone(normalized usingnormalizeBrazilianPhone) scoped to theprofessionalId. If no client is found, a new client record is inserted into theclientstable. - Appointment Creation: Inserts a new appointment into the
appointmentstable with a status ofconfirmedifprofessional.autoConfirmis true, otherwisepending_confirmation. - Notifications: Triggers three types of notifications:
- An in-app notification inserted into the
notificationstable for the professional. - A WhatsApp message sent to the professional via
sendWhatsAppusing themsgBookingRequesttemplate. - An email notification sent to the professional via
sendBookingRequestEmailToProfessionalifprofessional.emailis available.
- An in-app notification inserted into the
getProfessionalInfo Implementation
The getProfessionalInfo function in src/lib/ai-intake.ts retrieves and formats information about a professional.
- Professional Lookup: Fetches the professional's details from the
professionalstable. - Availability Rules: Queries the
availabilitytable to get all working hours for the professional. - Schedule Formatting: Formats the availability rules into a human-readable schedule, mapping
dayOfWeekintegers to Brazilian day names. - Return Data: Returns the professional's name, profession,
sessionDuration,sessionPrice(converted from cents), formatted schedule, andslug.
Conversation Management
The src/lib/conversation.ts module handles the state of ongoing conversations with clients via WhatsApp. This is crucial for maintaining context across multiple messages and enabling the AI agent to have coherent dialogues.
State Storage
Conversation state is stored in Redis, keyed by conv:{professionalId}:{clientPhone} (where clientPhone is normalized to digits-only by stripDigits). Each ConversationState object includes:
professionalIdandclientPhonefor identification.messages: An array ofConversationMessageobjects, trimmed toMAX_MESSAGES(20) to manage context window size.context: An object holding transient information likependingDate,pendingSlot, andappointmentCreated.createdAtandupdatedAttimestamps.
Conversations have a TTL of 30 minutes (30 * 60 seconds), after which they expire from Redis. The getConversation, saveConversation, and deleteConversation functions manage this state.
Client Activity Tracking
To prevent transactional reminders from interrupting an active AI conversation, src/lib/conversation.ts maintains a client_active:{phone} key in Redis. This key is touched on every saveConversation call and has an ACTIVE_TTL of 15 minutes (15 * 60 seconds). The isClientActive function allows other parts of the system (e.g., reminder cron jobs) to check if a client is currently engaged with the AI. In case of Redis failure, isClientActive fails open, allowing reminders to proceed.
Outbound Context
The setOutboundContext and getOutboundContext functions in src/lib/conversation.ts store and retrieve the context of the last transactional message sent to a client. This OutboundContext includes the professionalId and optionally an appointmentId. It has an OUTBOUND_TTL of 2 hours (2 * 60 * 60 seconds). This allows inbound replies like "I need to reschedule" to be automatically routed to the correct professional and appointment without requiring the client to re-specify details.
Why this shape
The design of the WhatsApp AI intake agent prioritizes a robust, stateful interaction model. The two-pass LLM pattern in src/lib/ai-intake.ts allows for explicit tool-calling, ensuring that complex actions like booking appointments are handled systematically. Storing conversation state in Redis (src/lib/conversation.ts) enables the AI to maintain context across messages, providing a more natural and efficient user experience. The explicit handling of São Paulo time zones in src/lib/ai-intake.ts ensures accurate scheduling without relying on server-side timezone configurations. The client_active and last_outbound Redis keys provide critical context for managing client interactions and preventing notification interference.
Known gaps
- Missing webhook implementation: The
src/app/api/whatsapp/webhookfile content was not available, so the specific implementation details of how inbound WhatsApp messages are received and processed before being handed off to the AI agent are not documented here.