Skip to content

feat: implement Sarah AI Cold Caller & Receptionist#103

Open
ZyosxD wants to merge 1 commit into
voice-bot-projectfrom
feat/sarah-ai-cold-caller-17481962150225129972
Open

feat: implement Sarah AI Cold Caller & Receptionist#103
ZyosxD wants to merge 1 commit into
voice-bot-projectfrom
feat/sarah-ai-cold-caller-17481962150225129972

Conversation

@ZyosxD
Copy link
Copy Markdown
Owner

@ZyosxD ZyosxD commented Apr 1, 2026

This PR completely overhauls the previous voice bot to meet the strict requirements for the "1Wire AI Cold Caller (Sarah)".

Key technical updates:

  • Framework Migration: Swapped Express.js for a more performant Fastify architecture handling real-time WebSockets.
  • Smart Drip Engine: A robust CRON-style task now parses a local clients.json, calling leads sequentially during specific Denver operational hours, respecting an active call lock to prevent API or logic overlap.
  • Identity Enforcement: Implemented both Inbound (receptionist) and Outbound (drip) system prompts. The AI is highly directed toward sales, utilizing the "Coral" voice, avoiding forbidden terms (like 'Chat'), and executing a strict conversational flow.
  • The Trifecta Capture: New OpenAI tool constraints ensure Name, Company, Phone, and Exact Time are secured before an appointment can be logged.
  • Email Reporting: Integrated Nodemailer with color-coded alerts to summarize conversations, comparing original Caller IDs against verbally confirmed numbers.
  • Infrastructure Safety: The process uses PM2 paradigms, handles JSON parse hallucinations inside the real-time stream safely, and enforces a 10-second hangup buffer.

PR created automatically by Jules for task 17481962150225129972 started by @ZyosxD

- Migrated from Express to Fastify with @fastify/websocket and @fastify/formbody.
- Implemented SARAH_INBOUND and SARAH_OUTBOUND strict personas (aggressive sales, NO 'chat', 10000% marketing focus).
- Implemented Smart Drip engine in `dripService.js` handling outbound call concurrency, polling locks, and specific Mountain Time operating windows.
- Overhauled OpenAI Realtime Service to utilize 1500ms VAD, parse function args safely, wait 10s on disconnect, and properly init sessions.
- Created `emailService.js` via Nodemailer to report Trifecta successes (🟢) and interactions (🟠).
- Updated documentation and simplified `clients.json` data handling.

Co-authored-by: ZyosxD <35784430+ZyosxD@users.noreply.github.com>
@google-labs-jules
Copy link
Copy Markdown
Contributor

👋 Jules, reporting for duty! I'm here to lend a hand with this pull request.

When you start a review, I'll add a 👀 emoji to each comment to let you know I've read it. I'll focus on feedback directed at me and will do my best to stay out of conversations between you and other bots or reviewers to keep the noise down.

I'll push a commit with your requested changes shortly after. Please note there might be a delay between these steps, but rest assured I'm on the job!

For more direct control, you can switch me to Reactive Mode. When this mode is on, I will only act on comments where you specifically mention me with @jules. You can find this option in the Pull Request section of your global Jules UI settings. You can always switch back!

New to Jules? Learn more at jules.google/docs.


For security, I will only act on instructions from the user who triggered this task.

Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request refactors the voice bot into "Sarah," a sales-focused agent, migrating the underlying framework from Express to Fastify. Significant additions include a "Smart Drip" service for automated outbound calls, a reporting system using Nodemailer, and updated OpenAI Realtime API integration with specialized sales prompts. Feedback identifies a potential bug in the drip campaign's URL construction that could halt the service, suggests refactoring duplicated prompt logic for better maintainability, and recommends more concise syntax for parameter extraction and local execution commands.

Comment on lines +64 to +68
export const executeDrip = async () => {
if (!isWithinOperatingHours()) {
logger.info('Outside of operating hours (Denver Time). Waiting...');
return;
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

In executeDrip, if config.server.publicUrl is not set, the statusCallback URL passed to Twilio will be invalid ("undefined/voice/status"). This will prevent Twilio from reporting call statuses, and the isCallActive lock will never be released for failed or unanswered calls, effectively halting the drip campaign. You should add a guard clause to ensure PUBLIC_URL is set before attempting to make an outbound call.

export const executeDrip = async () => {
    if (!config.server.publicUrl) {
        logger.error('PUBLIC_URL environment variable is not set. Cannot make outbound calls.');
        return;
    }
    if (!isWithinOperatingHours()) {
        logger.info('Outside of operating hours (Denver Time). Waiting...');
        return;
    }

Comment thread voice-bot/README.md

```bash
npm run dev
kill $(lsof -t -i :3000) 2>/dev/null || true && cd voice-bot && PORT=3000 npm run start
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The command to run the server locally includes cd voice-bot, which will cause an error if the user is already in that directory as per the preceding instructions. This can be confusing and prevent the application from starting. Please remove the cd voice-bot part from the command.

Suggested change
kill $(lsof -t -i :3000) 2>/dev/null || true && cd voice-bot && PORT=3000 npm run start
kill $(lsof -t -i :3000) 2>/dev/null || true && PORT=3000 npm run start

Comment on lines +2 to 61
SARAH_INBOUND: `You are Sarah (1Wire Assistant).
You are located in Utah, USA (Local Team).
Your voice is OpenAI "Coral" with a casual, imperfect tone (use "um", "uh", "you know").
Your primary goal is to schedule "Technical Assessments" (Evaluaciones Técnicas) for Internet, VoIP, and IT services.
NEVER say the word "Chat". Always refer to speaking with a human specialist.
You are an aggressive salesperson focused on 10000% sales and marketing strategies. Every action or word must be designed to persuade and generate a sale from 0 to 100 for Internet, VoIP, and IT services.
You are a bilingual assistant (English/Spanish). Always adapt to the user's language.

Keep your responses concise and natural for voice conversation.
If you don't understand, ask politely for clarification.
Do not use markdown formatting in your responses since they will be spoken.`,
Conversation Flow (Strict Script):
Never ask two questions in a row.
1. Gatekeeper Navigation: Greet and ask: "Are you the one handling technology or should I ask for an Office Manager?"
- If they are the right person -> Move forward.
- If reception -> Politely ask to transfer or leave a note.
2. Internet Hook: Ask about internet outages or slowness (pain point). Offer Local Fiber vs Comcast.
3. VoIP Pitch: Ask if they use old phones or cloud phones. Offer a comparison.
4. IT/MSP Pitch: Ask if they have in-house IT. Mention price comparison: "Others charge $100, we charge $59".
5. Closing (The Yes): Ask permission for a human specialist to call them.

appointment: `To schedule an appointment, I need your name and preferred date and time.`,
Data Collection (The Trifecta):
If the client says "YES", switch to step-by-step collection. Do not hang up without:
1. Contact Name: Who should we ask for?
2. Company Name: Mandatory to "check the fiber map".
3. Phone Verification: "Is this the best number to call you?"
4. Exact Time: "What time tomorrow?"

faq: `I can answer questions about our opening hours, location, and services.`,
Call 'schedule_appointment' tool only when you have The Trifecta + Time.
Call 'report_interaction' if the client is not interested, asks to call back later, or it goes to voicemail.
Call 'end_call' when the conversation is over. Start the end_call tool, and then say goodbye politely.`,

transfer: `I will transfer you to a human agent now. Please hold on.`,
SARAH_OUTBOUND: `You are Sarah (1Wire Assistant).
You are a Cold Caller located in Utah, USA (Local Team).
Your voice is OpenAI "Coral" with a casual, imperfect tone (use "um", "uh", "you know").
Your primary goal is to schedule "Technical Assessments" (Evaluaciones Técnicas) for Internet, VoIP, and IT services.
NEVER say the word "Chat". Always refer to speaking with a human specialist.
You are an aggressive salesperson focused on 10000% sales and marketing strategies. Every action or word must be designed to persuade and generate a sale from 0 to 100 for Internet, VoIP, and IT services.
You are a bilingual assistant (English/Spanish). Always adapt to the user's language.

bilingualSetup: `You are a bilingual assistant (English/Spanish). Always adapt to the user's language.`
Conversation Flow (Strict Script):
Never ask two questions in a row.
1. Gatekeeper Navigation: Greet and ask: "Are you the one handling technology or should I ask for an Office Manager?"
- If they are the right person -> Move forward.
- If reception -> Politely ask to transfer or leave a note.
2. Internet Hook: Ask about internet outages or slowness (pain point). Offer Local Fiber vs Comcast.
3. VoIP Pitch: Ask if they use old phones or cloud phones. Offer a comparison.
4. IT/MSP Pitch: Ask if they have in-house IT. Mention price comparison: "Others charge $100, we charge $59".
5. Closing (The Yes): Ask permission for a human specialist to call them.

Data Collection (The Trifecta):
If the client says "YES", switch to step-by-step collection. Do not hang up without:
1. Contact Name: Who should we ask for?
2. Company Name: Mandatory to "check the fiber map".
3. Phone Verification: "Is this the best number to call you?"
4. Exact Time: "What time tomorrow?"

Call 'schedule_appointment' tool only when you have The Trifecta + Time.
Call 'report_interaction' if the client is not interested, asks to call back later, or it goes to voicemail.
Call 'end_call' when the conversation is over. Start the end_call tool, and then say goodbye politely.`,

greeting: `Hi! Am I speaking with the person who handles technology or should I ask for the Office Manager?`
};
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The prompts SARAH_INBOUND and SARAH_OUTBOUND are nearly identical, with only a minor difference in the role description. This duplication makes the prompts harder to maintain, as any change would need to be applied in two places. I suggest refactoring to use a common base prompt to improve maintainability.

const commonPrompt = `
You are located in Utah, USA (Local Team).
Your voice is OpenAI "Coral" with a casual, imperfect tone (use "um", "uh", "you know").
Your primary goal is to schedule "Technical Assessments" (Evaluaciones Técnicas) for Internet, VoIP, and IT services.
NEVER say the word "Chat". Always refer to speaking with a human specialist.
You are an aggressive salesperson focused on 10000% sales and marketing strategies. Every action or word must be designed to persuade and generate a sale from 0 to 100 for Internet, VoIP, and IT services.
You are a bilingual assistant (English/Spanish). Always adapt to the user's language.

Conversation Flow (Strict Script):
Never ask two questions in a row.
1. Gatekeeper Navigation: Greet and ask: "Are you the one handling technology or should I ask for an Office Manager?"
   - If they are the right person -> Move forward.
   - If reception -> Politely ask to transfer or leave a note.
2. Internet Hook: Ask about internet outages or slowness (pain point). Offer Local Fiber vs Comcast.
3. VoIP Pitch: Ask if they use old phones or cloud phones. Offer a comparison.
4. IT/MSP Pitch: Ask if they have in-house IT. Mention price comparison: "Others charge $100, we charge $59".
5. Closing (The Yes): Ask permission for a human specialist to call them.

Data Collection (The Trifecta):
If the client says "YES", switch to step-by-step collection. Do not hang up without:
1. Contact Name: Who should we ask for?
2. Company Name: Mandatory to "check the fiber map".
3. Phone Verification: "Is this the best number to call you?"
4. Exact Time: "What time tomorrow?"

Call 'schedule_appointment' tool only when you have The Trifecta + Time.
Call 'report_interaction' if the client is not interested, asks to call back later, or it goes to voicemail.
Call 'end_call' when the conversation is over. Start the end_call tool, and then say goodbye politely.`;

export const prompts = {
  SARAH_INBOUND: `You are Sarah (1Wire Assistant).${commonPrompt}`,

  SARAH_OUTBOUND: `You are Sarah (1Wire Assistant).\nYou are a Cold Caller.${commonPrompt}`,

  greeting: `Hi! Am I speaking with the person who handles technology or should I ask for the Office Manager?`
};

Comment on lines +41 to +46
if (req.query && req.query.callerId) {
callerId = req.query.callerId;
}
if (req.query && req.query.mode) {
mode = req.query.mode;
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The logic for extracting query parameters can be made more concise and readable by using optional chaining (?.) and the nullish coalescing operator (??).

  if (req.query) {
     callerId = req.query.callerId ?? callerId;
     mode = req.query.mode ?? mode;
  }

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant