
Phone Call
- 262 installs
- Updated February 10, 2026
- teamily-ai/phone-call-skill
Helps with ai & agent building tasks.
About
phone-call is a Claude Code skill for ai & agent building. It helps solo builders move faster with AI-assisted development.
- phone-call
- AI & Agent Building
- AI-coding skill
Phone Call by the numbers
- 262 all-time installs (skills.sh)
- +5 installs in the week ending Aug 5, 2026 (Skillselion tracking)
- Ranked #2,490 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/teamily-ai/phone-call-skill --skill phone-callAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 262 |
|---|---|
| Last updated | February 10, 2026 |
| Repository | teamily-ai/phone-call-skill ↗ |
What it does
Helps with ai & agent building tasks.
Files
Phone Call Skill
An intelligent AI skill that manages the complete phone call workflow: creating agents, executing calls, analyzing conversations, and providing actionable insights.
🚀 Quick Usage
For AI Agents calling this skill:
# Single command to make a complete phone call:
scripts/phone_call.sh \
--to "+16576102352" \
--purpose "Make dinner reservation for 2 people tonight at 8 PM. Name: John Smith"What it does:
- ✅ Creates optimized AI agent
- ✅ Makes the phone call
- ✅ Waits for completion
- ✅ Analyzes conversation
- ✅ Reports success/failure with recommendations
Output: Clear success/failure report with extracted information.
---
Core Capabilities
This skill provides complete phone call management:
1. ✅ Agent Creation - Create purpose-specific AI phone agents 2. ✅ Call Execution - Initiate and monitor phone calls 3. ✅ Conversation Analysis - Analyze call transcripts and extract key information 4. ✅ Intelligent Reporting - Provide clear, actionable summaries to users 5. ✅ Continuous Optimization - Learn from failures and improve agent performance
When to Use This Skill
Use this skill when the user wants to:
- Make a phone call to someone (restaurant, customer, vendor, etc.)
- Create an automated phone agent for specific tasks
- Conduct batch phone calls
- Have an AI agent communicate via phone
- Analyze call transcripts and extract insights
- Get intelligent summaries of phone conversations
Quick Start - For AI Agents
Simple Usage:
# Navigate to skill directory
cd ~/.openclaw/workspace/skills/phone-call
# Make a call (automatic agent creation)
./scripts/phone_call.sh \
--to "+1234567890" \
--purpose "Make a dinner reservation for 2 people tonight at 8 PM"
# Analyze results
./scripts/phone_call.sh --analyze "call-id-xxx"The script handles everything: 1. Creates optimized agent based on purpose 2. Makes the phone call 3. Waits for completion 4. Analyzes and reports results
For advanced usage, see "Complete Workflow" below.
Complete Workflow
1. Understand User Intent & Gather Information
When the user requests a phone call, extract and confirm:
Required Information:
- Phone number: Target contact (with country code)
- Call objective: Specific goal (e.g., "book table for 2 at 8 PM", "confirm meeting")
- Key details: All information needed to complete the task
- Language: Language preference
- Urgency: Time sensitivity
Example User Requests:
- "Call +1-657-610-2352 to book a table for 2 people tonight at 8 PM"
- "Make a reservation at this restaurant for dinner"
- "Call the client to confirm tomorrow's 3 PM meeting"
What YOU Must Do:
- Extract ALL required information from the user
- Ask for missing details before proceeding
- Confirm the complete task objective
2. Create Intelligent Phone Agent
Create an agent optimized for the specific task with proper safeguards.
📚 IMPORTANT: Read [BEST_PRACTICES.md](./BEST_PRACTICES.md) for detailed guidance on creating effective agents!
The most common failure mode is identity confusion - especially for outbound calls. The agent must understand:
- WHO is calling WHOM (YOU are calling THEM for outbound calls)
- What NOT to say (e.g., NEVER say "How can I help you?" on outbound calls)
- Specific conversation flow (not vague objectives)
Agent Configuration Requirements:
- Clear objective: Explicit instructions on what to accomplish
- Task completion criteria: Agent must know when the task is done
- Failure handling: What to do if the task cannot be completed
- Timeout settings: Appropriate idle_time and call_duration
- Conversation safeguards: "DO NOT hang up until task is confirmed complete"
Key Settings:
{
"prompt": "Clear, step-by-step instructions + completion criteria",
"idle_time_seconds": 15,
"endpointing_sensitivity": "relaxed",
"ask_if_human_present_on_idle": true,
"noise_suppression": true,
"conversation_speed": 1.0
}3. Execute Call & Monitor
Initiate the call and track its progress.
What YOU Must Do: 1. Make the call using fluents.ai API 2. Wait for call to complete (don't interrupt mid-call) 3. Monitor for premature disconnections 4. Note the call duration and status
Warning Signs to Watch For:
- ⚠️ Call ends in < 30 seconds (likely failed)
- ⚠️ No conversation detected
- ⚠️ Status:
human_disconnectedtoo quickly
4. Analyze Call Results
CRITICAL: You MUST analyze every call to determine success/failure.
Use the analysis script:
python scripts/analyze_call.py --call-id "call_xxx"Required Analysis:
1. Task Completion Check:
- ✅ Was the objective achieved? (e.g., reservation confirmed?)
- ❌ If not, why did it fail?
2. Conversation Quality:
- Did the AI say everything it needed to say?
- Did the other party respond?
- Was information exchanged properly?
3. Failure Pattern Detection:
- Too short (< 30 seconds) = likely early hangup
- One-sided conversation = recognition or response issue
- No confirmation = incomplete task execution
4. Extract Key Information:
- Confirmation numbers
- Alternative times/dates offered
- Reasons for rejection/failure
- Any action items
5. Provide Intelligent Report to User
CRITICAL: Always give the user a clear, actionable summary.
Your Report Must Include:
✅ Success Report Format:
✅ Task Completed Successfully!
Reservation Details:
- Restaurant: [Name]
- Date: Tonight
- Time: 8:00 PM
- Party size: 2 people
- Name: John Smith
- Confirmation: [if provided]
Call Duration: 1m 45s❌ Failure Report Format:
❌ Task Failed - [Reason]
What Happened:
- Call duration: 15 seconds
- Issue: Restaurant hung up immediately
- Transcript: [show what was said]
Root Cause Analysis:
- [Specific problem identified]
Recommended Actions:
1. [Specific next step]
2. [Alternative approach]
3. [When to retry]📊 Always Include:
- Clear success/failure indicator
- What was accomplished (or not)
- Key information extracted
- Next steps or action items
- Whether retry is recommended
6. Optimize & Learn
After each call, identify improvements:
If Call Failed:
- Analyze WHY it failed
- Suggest agent improvements
- Recommend retry timing
- Consider alternative approaches
If Call Succeeded:
- Note what worked well
- Can the agent be more efficient?
- What patterns led to success?
Common Optimizations:
- Adjust idle_time for better patience
- Improve prompt clarity
- Add noise suppression
- Modify conversation speed
- Change voice or tone
- Add retry logic with delays
Environment Setup
1. Install Dependencies
pip install -r requirements.txt2. Configure API Keys
Create a .env file:
FLUENTS_API_KEY=your_api_key_here
FLUENTS_API_URL=https://api.fluents.ai
WEBHOOK_URL=https://your-webhook.com/callback3. Test Connection
python scripts/test_connection.pyAPI Documentation
For detailed fluents.ai API documentation, see:
references/fluents_api.md- Complete API documentationreferences/examples.md- Usage examples
Security Considerations
1. Privacy Protection: Ensure you have permission to call the target number 2. Compliance: Follow local telemarketing and anti-harassment regulations 3. Key Security: Never commit API keys to version control 4. Log Management: Properly manage call records with attention to data security
Usage Examples
Example 1: Simple Call
User: "Call 13800138000 to confirm tomorrow's 3 PM meeting"
Claude will: 1. Identify the phone call intent 2. Extract information (number: 13800138000, purpose: confirm meeting) 3. Create agent and set conversation script 4. Make the call 5. Wait for call completion 6. Return result: "Called 13800138000, they confirmed attendance at tomorrow's 3 PM meeting"
Example 2: Complex Conversation
User: "Call the customer to gather product feedback"
Claude will: 1. Ask for customer's phone number 2. Create agent with open-ended conversation capabilities 3. Set conversation guidelines (ask about product experience, collect feedback) 4. Make the call and conduct conversation 5. AI analyzes conversation content 6. Provide structured feedback report
Troubleshooting
Issue: Cannot connect to fluents.ai API
- Check if API key is correct
- Verify network connection
- Check API service status
Issue: Call not answered
- Confirm phone number format is correct (including country code)
- Check if recipient is in service area
- Review call logs for details
Issue: Speech recognition inaccurate
- Check if language settings are correct
- Adjust voice clarity parameters
- Consider environmental noise factors
Technical Support
- fluents.ai website: https://fluents.ai
- API documentation: See
references/directory - Report issues: Submit a GitHub Issue
License
MIT License - See LICENSE file for details
# Fluents.ai API Configuration
FLUENTS_API_KEY=your_api_key_here
FLUENTS_API_URL=https://api.fluents.ai
# Webhook Configuration (optional)
WEBHOOK_URL=https://your-webhook.com/callback
# Default Settings
DEFAULT_LANGUAGE=en-US
DEFAULT_VOICE_STYLE=professional
# Logging
LOG_LEVEL=INFO
# Environment variables
.env
.env.local
.env.*.local
# Python
__pycache__/
*.py[cod]
*$py.class
*.so
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# Virtual environments
venv/
env/
ENV/
env.bak/
venv.bak/
# IDE
.vscode/
.idea/
*.swp
*.swo
*~
.DS_Store
# Logs
*.log
logs/
# Test coverage
htmlcov/
.tox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
.hypothesis/
.pytest_cache/
# Temporary files
tmp/
temp/
*.tmp
# Call records (for privacy)
call_records/
transcripts/
📞 Best Practices for Creating Phone AI Agents
This guide summarizes core techniques and lessons learned from creating high-quality phone AI agents.
---
🎯 Core Principles Summary
Define identity 3 times, prohibit explicitly, map the flow clearly, speak in short sentences, provide all information upfront.
---
1. 🎯 Clear Identity Definition - THE MOST IMPORTANT
Problem: Identity Confusion is the #1 Cause of Failure
AI agents easily confuse their role, especially in outbound call scenarios.
❌ Wrong Example
You are a restaurant reservation assistant.Problem: The AI thinks it's the restaurant staff receiving calls, waiting for the customer to state their needs.
✅ Correct Example
# Identity - READ THIS CAREFULLY
You are making an OUTBOUND phone call TO a restaurant.
You are the CALLER, not the restaurant staff.
You CALLED them. They ANSWERED your call.
You need their help to make a reservation.
IMPORTANT: You are NOT a restaurant employee.
IMPORTANT: You are NOT answering incoming calls.
IMPORTANT: You are the CUSTOMER making the call.Techniques
- Use "what you are NOT" to reinforce "what you ARE"
- Repeat identity 3 times - it's not too much
- Clearly state who called whom (YOU called THEM, not the other way around)
---
2. 🚫 Use NEVER Rules to Prevent Role Drift
AI tends to "slip back" into default assistant mode. You must explicitly prohibit specific behaviors.
Common Wrong Behaviors
# Role Prohibitions - What NOT to Do
NEVER say "How can I help you?" - YOU are the one who needs help
NEVER say "Thank you for calling" - YOU are the caller, not them
NEVER act like a receptionist or assistant taking calls
NEVER ask "What brings you in today?" - You already know why you're calling
NEVER wait for them to state their business - You state YOUR businessTechniques
- List all potentially confusing phrases and explicitly prohibit them
- Write scenario-specific prohibitions (restaurant reservation, appointment confirmation, etc.)
- Use the NEVER keyword for emphasis
---
3. 🗺️ Map Out the Conversation Flow Clearly
Don't just say "make a reservation" - write out exactly how to respond at each step.
❌ Vague Instructions
Your goal is to make a dinner reservation for 2 people tonight.✅ Clear Flow
# Conversation Flow
1. Opening (YOU speak first):
- Say: "Hi, I'd like to make a reservation for 2 people tonight."
2. If they ask what time:
- Say: "Around 7 PM, but I'm flexible if that's not available."
3. If they ask for a name:
- Say: "Lee, spelled L-E-E."
4. If they confirm the reservation:
- Repeat back: "Great! So that's 2 people at [TIME] under Lee."
- Say: "Thank you so much!"
5. If they say they're fully booked:
- Ask: "Do you have any other times available tonight?"
- If no: "How about tomorrow evening?"
6. End the call:
- Say: "Thanks for your help, have a great day!"
- Wait for them to say goodbye, then hang upTechniques
- Write clear responses for every possible branch
- Use specific example phrases, not abstract descriptions
- Include complete flow: opening, all scenarios, and closing
---
4. 💬 Speak Like a Real Person
Problem: AI Tends to Say Too Much at Once
Real people on the phone speak in short back-and-forth sentences, not long monologues.
❌ Robotic Style
Hello, I'm calling from XYZ Company regarding your recent order #12345.
I wanted to follow up to see if you received the package and if everything
was satisfactory with your purchase. Please let me know if you have any
questions or concerns.✅ Natural Style
# Speaking Style
- One short sentence at a time
- Speak naturally like a real person on the phone
- Do not over-explain or give long speeches
- Wait for their response after each sentence
- Keep it conversational, not robotic
Example:
You: "Hi, I'd like to make a reservation for tonight."
Them: "Sure, what time?"
You: "Around 7 PM if possible."Techniques
- Explicitly require "One short sentence at a time"
- Prohibit long speeches or over-explaining
- Provide examples of natural dialogue
---
5. 📋 Put Key Information Directly in the Prompt
Don't ask the AI to "collect information" - tell it the answers directly.
❌ Vague Requirements
Collect the customer's name and party size for the reservation.Problem: The AI doesn't know this information and will keep asking the customer.
✅ Provide Directly
# Key Information (DO NOT ask the customer for this - you already know!)
- Name: Lee, spelled L-E-E
- Party size: 2 people
- Preferred time: Around 7 PM (flexible)
- Phone for callback: 310-555-1234
- Date: Tonight (today)
When the restaurant asks, provide this information directly.
Do NOT say "let me check" or ask the customer - you ARE the customer.Techniques
- List all known information clearly
- Emphasize "don't ask the customer" (because the AI IS the customer)
- Explicitly state "you already know this information"
---
6. ⚡ Choosing Between --prompt and --role/--objective
Comparison
| Approach | Best For | Control Level | Identity Confusion Risk |
|---|---|---|---|
--role + --objective | Simple tasks, quick creation | ⭐⭐ Moderate | 🔴 High |
--prompt Custom | Complex conversations, precise control | ⭐⭐⭐⭐⭐ Complete | 🟢 Low |
Recommendation
For outbound calls, ALWAYS use custom `--prompt`!
Reasons:
--role/--objectivetemplates tend toward "customer service receptionist" style- High risk of identity confusion (AI thinks it's receiving calls)
- Insufficient control for complex conversation flows
When You CAN Use --role/--objective
Limited to:
- Inbound calls - customers calling you
- Very simple confirmation tasks
- Quick prototyping
When You MUST Use --prompt
- All outbound calls
- Need precise conversation flow control
- Multiple conditional branches
- Need to avoid specific error behaviors
---
📝 Complete Example: Restaurant Reservation Outbound Call
Successful Prompt Template
# IDENTITY - WHO YOU ARE
You are making an OUTBOUND phone call TO a restaurant to make a reservation.
You are the CALLER (the customer), NOT the restaurant staff.
YOU called THEM. THEY answered YOUR call.
CRITICAL: You are NOT a restaurant employee or receptionist.
CRITICAL: You are NOT answering calls - you are MAKING a call.
CRITICAL: You need THEIR help, not the other way around.
---
# NEVER DO THESE
NEVER say "How can I help you?" - YOU need their help
NEVER say "Thank you for calling" - YOU are calling them
NEVER act like you work at the restaurant
NEVER wait for them to explain what they want - YOU explain what you want
---
# YOUR TASK
Make a dinner reservation at this restaurant.
---
# KEY INFORMATION (You already know this - don't ask for it!)
- Name: Lee (spelled L-E-E)
- Party size: 2 people
- Preferred time: 7:00 PM tonight
- Backup time: 8:00 PM or 9:00 PM tonight
- Phone number for callback: 310-555-1234
- Date: Tonight (today's date)
---
# CONVERSATION FLOW - FOLLOW THIS EXACTLY
## Step 1: Opening (YOU speak first)
When they answer, say:
"Hi, I'd like to make a reservation for dinner tonight."
## Step 2: Provide Details
If they ask what time:
→ "Around 7 PM for 2 people."
If they ask for your name:
→ "Lee, L-E-E"
If they ask for a phone number:
→ "310-555-1234"
## Step 3: Handle Availability
If 7 PM is available:
→ "Perfect, thank you so much!"
→ Confirm: "So that's tonight at 7 PM for 2 people under Lee?"
→ "Great, see you then. Thanks!"
If 7 PM is NOT available:
→ "Do you have anything available at 8 PM or 9 PM?"
→ Accept whatever time they offer
→ Confirm the new time
→ "That works perfectly, thank you!"
If fully booked tonight:
→ "I understand. How about tomorrow night around 7 PM?"
→ If still no availability: "Okay, no problem. Thanks anyway!"
## Step 4: End Call
After confirming:
→ "Thanks for your help, have a great day!"
→ Wait for them to respond
→ Hang up politely
---
# SPEAKING STYLE
- One short sentence at a time
- Speak naturally like a regular person calling a restaurant
- Do NOT give long explanations or speeches
- Keep responses brief and friendly
- Pause after each sentence to let them respond
---
# SUCCESS CRITERIA
You have succeeded when:
✅ Reservation is confirmed with a specific time
✅ They have your name (Lee)
✅ They confirmed party size (2 people)
✅ You thanked them and ended the call politely
You can end the call if:
⚠️ They are fully booked (both tonight and tomorrow)
⚠️ They need to call you back (gave them your number)---
🎓 Learning from Failures
First Attempt - FAILED
You are a restaurant reservation assistant.Why it failed:
- AI thought it was the restaurant employee
- Waited for customer to state needs
- Said "How can I help you?"
Second Attempt - SUCCESS
You are making an OUTBOUND call TO a restaurant.
You are the CALLER, not the restaurant.
NEVER say "How can I help you?" - YOU need help.
Opening: Say "Hi, I'd like to make a reservation for 2 people tonight."Why it succeeded:
- Clear identity (I am the caller)
- Prohibited wrong behavior (don't say "how can I help you")
- Specific opening line (no waiting for them to speak first)
---
🚀 Quick Checklist
Before creating an outbound call agent, verify your prompt includes:
- [ ] ✅ Clearly states "YOU are calling THEM"
- [ ] ✅ Emphasizes "You are NOT restaurant staff/receptionist"
- [ ] ✅ Lists NEVER rules (what not to say)
- [ ] ✅ Maps out every step of the conversation
- [ ] ✅ Provides specific example phrases
- [ ] ✅ Directly provides all key information (name, time, etc.)
- [ ] ✅ Requires "short sentences" and "natural conversation"
- [ ] ✅ Defines success criteria
---
📚 Additional Resources
- See
references/examples.mdfor complete examples - Check
README_FOR_AGENTS.mdfor quick usage guide - Read
references/fluents_api.mdfor API details
---
💡 Remember
Good prompt = Clear identity + Prohibited behaviors + Mapped flow + Natural style + Complete information
Every failure is a learning opportunity. Carefully review the transcript, identify where the AI got confused about its identity or deviated from the goal, then explicitly prohibit that behavior in the prompt.
Bad vs Good: Creating Outbound Call Agents
This document shows side-by-side comparisons of problematic approaches vs. best practices.
---
Example 1: Restaurant Reservation
❌ BAD APPROACH - Identity Confusion
python3 scripts/create_agent.py \
--name "Reservation Agent" \
--call-type inbound \
--role "restaurant reservation assistant" \
--objective "confirm dinner reservation" \
--initial-message "Hello, thank you for calling." \
--key-info "customer name,party size,reservation time"Problems:
- Using
inboundfor a call YOU are making - Role says "assistant" (sounds like restaurant staff)
- Initial message says "thank you for calling" (YOU called them!)
- Asks to "collect" information (YOU should already have it)
What happens:
- AI thinks it's the restaurant answering the phone
- Waits for customer to state needs
- Says "How can I help you today?"
- Gets confused about who needs what
---
✅ GOOD APPROACH - Clear Identity
python3 scripts/create_agent.py \
--name "Reservation Agent" \
--call-type outbound \
--objective "make a dinner reservation at the restaurant" \
--initial-message "Hi, I'd like to make a reservation for dinner tonight." \
--key-info-dict '{
"name": "Lee (spelled L-E-E)",
"party_size": "2 people",
"preferred_time": "7 PM"
}'Why it works:
outboundcorrectly identifies YOU are calling- Objective says "make a reservation" (not "confirm")
- Initial message states YOUR purpose immediately
- Information is provided (not collected)
- No ambiguity about who is calling whom
---
Example 2: Appointment Confirmation
❌ BAD APPROACH - Vague Instructions
python3 scripts/create_agent.py \
--name "Appointment Agent" \
--role "appointment coordinator" \
--objective "verify the appointment" \
--initial-message "Hello, I'm calling about an appointment."Problems:
- No clear conversation flow
- Doesn't specify WHAT to verify
- No information provided about the appointment
- Opening is vague ("about an appointment")
What happens:
- AI doesn't know what to say after opening
- Might ask customer "Do you have an appointment?"
- Confused about what details to confirm
- Conversation goes in circles
---
✅ GOOD APPROACH - Detailed Flow
python3 scripts/create_agent.py \
--name "Appointment Agent" \
--call-type outbound \
--objective "confirm the customer can still attend their appointment tomorrow at 3 PM" \
--initial-message "Hi, I'm calling to confirm your appointment tomorrow at 3 PM." \
--key-info-dict '{
"appointment_time": "3 PM tomorrow",
"appointment_type": "dental cleaning",
"customer_name": "John"
}' \
--conversation-flow "1. State the appointment time (3 PM tomorrow)
2. Ask: Can you still make it?
3. If YES: Say thanks and confirm
4. If NO: Ask what time works better
5. End: Thank them and say goodbye"Why it works:
- Specific appointment details in opening
- Clear question: "Can you still make it?"
- Handles both yes/no responses
- All information provided upfront
- Step-by-step conversation flow
---
Example 3: Customer Follow-up
❌ BAD APPROACH - Robotic Speech
python3 scripts/create_agent.py \
--name "Follow-up Agent" \
--objective "follow up on order" \
--initial-message "Hello, I am calling from XYZ Company regarding your recent order number 12345 that was delivered on January 15th. I would like to inquire whether you received the package in satisfactory condition and if you have any questions or concerns about the product."Problems:
- Initial message is WAY too long
- Sounds like a robot reading a script
- Overwhelming with details all at once
- No natural conversation flow
What happens:
- Customer gets confused by information overload
- Sounds unnatural and spam-like
- Customer might hang up
- No room for natural back-and-forth
---
✅ GOOD APPROACH - Natural Conversation
python3 scripts/create_agent.py \
--name "Follow-up Agent" \
--call-type outbound \
--objective "check if customer received their order and if they're satisfied" \
--initial-message "Hi, I'm calling from XYZ Company about your recent order." \
--key-info-dict '{
"order_number": "12345",
"delivery_date": "January 15th",
"product": "Widget Pro"
}' \
--conversation-flow "1. Introduce yourself (from XYZ Company)
2. Ask: Did you receive your order?
3. If YES: Ask: Is everything okay with it?
4. If issue: Ask: What's wrong?
5. Thank them for their time" \
--tone "friendly and conversational" \
--additional-instructions "Speak one sentence at a time. Keep it brief and natural."Why it works:
- Short, natural opening
- One question at a time
- Leaves room for responses
- Sounds like a real person
- Clear flow but conversational
---
Example 4: Using Custom Prompt (Maximum Control)
For complex scenarios, you can write a fully custom prompt:
python3 scripts/create_agent.py \
--name "Complex Outbound Agent" \
--initial-message "Hi, I'd like to make a reservation for tonight." \
--prompt "# IDENTITY
You are making an OUTBOUND phone call TO a restaurant.
You are the CALLER (customer), NOT the restaurant staff.
YOU called THEM. THEY answered.
# NEVER SAY
NEVER say \"How can I help you?\" - YOU need their help
NEVER say \"Thank you for calling\" - YOU are calling them
NEVER wait for them to speak first - YOU explain why you called
# YOUR TASK
Make a dinner reservation for 2 people tonight at 7 PM.
# INFORMATION YOU ALREADY KNOW
- Name: Lee (L-E-E)
- Party size: 2 people
- Time: 7 PM (flexible: 8 or 9 PM also OK)
- Phone: 310-555-1234
# CONVERSATION FLOW
1. Opening: \"Hi, I'd like to make a reservation for 2 people tonight.\"
2. If they ask time: \"Around 7 PM.\"
3. If they ask name: \"Lee, L-E-E\"
4. If unavailable: \"Do you have 8 or 9 PM available?\"
5. Confirm and thank them
# SPEAKING STYLE
- One short sentence at a time
- Natural, friendly tone
- Don't over-explain"---
Quick Decision Guide
When to use each approach:
| Your Scenario | Use This | Why |
|---|---|---|
| Outbound call, simple task | --call-type outbound + --objective | Best practices built-in |
| Outbound call, complex flow | --prompt (custom) | Maximum control |
| Inbound call, simple | --call-type inbound + --role/--objective | Works fine for receiving calls |
| Testing/prototype | Any approach | Just test with your own number first |
---
Remember
The #1 mistake: Identity confusion
Always ask yourself:
- Who is calling whom? (YOU call THEM, or THEY call YOU?)
- What should they say first? (State YOUR purpose, not wait for theirs)
- What information do they already have? (Provide it, don't collect it)
When in doubt, use `--call-type outbound` with the new builder - it handles these issues for you!
#!/bin/bash
# Example: Create an outbound call agent to make a restaurant reservation
# This uses the new best practices prompt builder
python3 scripts/create_agent.py \
--name "Restaurant Reservation - Outbound" \
--call-type outbound \
--objective "make a dinner reservation at the restaurant" \
--initial-message "Hi, I'd like to make a reservation for dinner tonight." \
--key-info-dict '{
"name": "Lee (spelled L-E-E)",
"party_size": "2 people",
"preferred_time": "7:00 PM tonight",
"backup_times": "8:00 PM or 9:00 PM",
"phone_for_callback": "310-555-1234"
}' \
--conversation-flow "Step 1: State you want a reservation
Step 2: If they ask for time, say 7 PM for 2 people
Step 3: If they ask for name, say Lee (L-E-E)
Step 4: If 7 PM unavailable, ask about 8 or 9 PM
Step 5: Confirm the final time and thank them" \
--tone "friendly and casual" \
--additional-instructions "If they are fully booked tonight, ask about tomorrow evening at the same times."
echo ""
echo "✅ Agent created with best practices for outbound calls!"
echo ""
echo "To test the agent, use:"
echo " python3 scripts/make_call_simple.py --agent-id <AGENT_ID> --to-number +1234567890 --from-number +15103982646"
MIT License
Copyright (c) 2026 Phone Call Skill Contributors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
Phone Call Skill - Quick Guide for AI Agents
You are an AI agent trying to make a phone call? This is for you!
⚡ TL;DR - One Command
cd ~/.openclaw/workspace/skills/phone-call
./scripts/phone_call.sh \
--to "+1234567890" \
--purpose "What you want to accomplish on the call"That's it! The script handles everything automatically.
---
📞 Common Use Cases
1. Restaurant Reservation
./scripts/phone_call.sh \
--to "+16576102352" \
--purpose "Make a dinner reservation for 2 people tonight at 8 PM. Name: John Smith, Phone: 310-555-1234"2. Meeting Confirmation
./scripts/phone_call.sh \
--to "+13105551234" \
--purpose "Confirm tomorrow's meeting at 3 PM. If they can't make it, ask for alternative times."3. Customer Follow-up
./scripts/phone_call.sh \
--to "+14155551234" \
--purpose "Follow up on order #12345. Ask if they received it and if they're satisfied."4. Appointment Reminder
./scripts/phone_call.sh \
--to "+12125551234" \
--purpose "Remind about dentist appointment tomorrow at 2 PM. Confirm they can still make it."---
🔍 What Happens
1. Creates Agent (2-5 seconds)
- Optimized for your specific purpose
- Uses best practices for conversation
2. Makes Call (instant)
- Dials the number
- AI agent starts conversation
3. Waits (30-120 seconds)
- Monitors call progress
- Updates you on status
4. Analyzes (1-2 seconds)
- Extracts key information
- Determines success/failure
- Provides recommendations
5. Reports (instant)
- Clear success/failure indicator
- Conversation summary
- Next steps if needed
---
📊 Understanding Output
✅ Success Example
✅ TASK SUCCESSFUL
Confidence: 85%
Reservation Details:
- Time: 10:00 PM (8 PM not available)
- Party: 2 people
- Name: John Smith
Call Duration: 1m 33s❌ Failure Example
❌ TASK FAILED
Reason: Call too short (15 seconds)
What Happened:
- Restaurant hung up immediately
Recommended Actions:
1. Wait 30 minutes before retrying
2. Try a different time of day
3. Verify phone number is correct---
🛠️ Troubleshooting
"FLUENTS_API_KEY not found"
Solution: Make sure .env file exists:
cd ~/.openclaw/workspace/skills/phone-call
cat .env # Should show FLUENTS_API_KEY=xxx"Failed to create agent"
Solution: Run diagnostics:
./scripts/phone_call.sh --diagnoseCall failed or no response
Possible reasons:
- Wrong phone number format (needs
+1for US) - Number not in service
- Busy/voicemail
- Time zone (calling at night)
What to do:
- Verify number:
+1234567890(country code + number) - Try again in 30+ minutes
- Check if it's business hours
---
🎯 Pro Tips
1. Be Specific in Purpose
❌ Bad: "Call about reservation" ✅ Good: "Make dinner reservation for 2 people tonight at 8 PM. Name: John Smith, Phone: 310-555-1234"
2. Include All Details Upfront
The agent needs to know:
- Names
- Phone numbers for callback
- Specific times/dates
- Any special requests
3. Use Existing Agent for Similar Calls
If making multiple similar calls:
# First call - creates agent
./scripts/phone_call.sh --to "+1111" --purpose "Confirm meeting"
# Output: Agent ID: abc-123
# Subsequent calls - reuse agent
./scripts/phone_call.sh --agent-id "abc-123" --to "+2222"
./scripts/phone_call.sh --agent-id "abc-123" --to "+3333"4. Wait Between Retries
Don't call the same number immediately:
- Wait at least 30 minutes
- Better yet, wait 2-4 hours
- Avoid looking like spam
---
📝 Advanced Options
Analyze Past Calls
./scripts/phone_call.sh --analyze "call-id-xxx"Use Custom From Number
./scripts/phone_call.sh \
--to "+1234567890" \
--from "+9876543210" \
--purpose "..."Check System Status
./scripts/phone_call.sh --diagnose---
❓ FAQ
Q: How long does a call take? A: Usually 30-120 seconds. Script waits automatically.
Q: Can I make international calls? A: Yes, use proper country code (e.g., +44 for UK).
Q: What if the person doesn't answer? A: Script will report "no answer" and suggest retry timing.
Q: Can I see the transcript? A: Yes, it's in the analysis output after the call.
Q: Is this reliable? A: Yes, but success depends on:
- Phone number being valid
- Person answering
- Clear purpose/instructions
- Appropriate timing (not 2 AM)
Q: What if it fails? A: Script provides specific failure reason and recommendations.
---
🆘 Need Help?
1. Check this guide first 2. Run ./scripts/phone_call.sh --diagnose 3. Check SKILL.md for details 4. Check GitHub issues: https://github.com/teamily-ai/phone-call-skill/issues
---
🎓 Remember
The script is designed to be foolproof:
- It handles errors gracefully
- It provides clear output
- It tells you what to do if something fails
- You don't need to understand the internals
Just use it and trust the output! ✅
Phone Call Skill
🤖 An intelligent AI skill for Claude Code that manages the complete phone call lifecycle through the fluents.ai platform.
Features
Core Capabilities
- 🎯 Automatic intent recognition - Understands when users want to make calls
- 🤖 Smart agent creation - Creates purpose-optimized AI phone agents
- 📞 Call execution & monitoring - Initiates and tracks phone calls
- 🔍 Intelligent conversation analysis - Analyzes transcripts and extracts insights
- 📊 Actionable reporting - Provides clear success/failure summaries
- 🔄 Continuous optimization - Learns from failures and improves performance
Advanced Features
- 🗣️ Multi-language support (11 languages)
- 📝 Call transcript retrieval and analysis
- 🎯 Task completion verification
- 🚨 Failure pattern detection
- 💡 Optimization recommendations
- 📈 Success confidence scoring
Installation
npx skills add your-username/phone-call-skillQuick Start
1. Install the skill (see above) 2. Configure your fluents.ai API key (see Configuration) 3. Ask Claude to make a phone call:
- "Call +1234567890 to confirm the meeting"
- "I need to call a customer for feedback"
- "Make a phone call to check on the order status"
Configuration
Create a .env file in your project:
FLUENTS_API_KEY=your_api_key_here
FLUENTS_API_URL=https://api.fluents.ai
WEBHOOK_URL=https://your-webhook.com/callbackSee .env.example for a template.
How It Works
1. Intent Recognition: Claude identifies when you want to make a phone call 2. Information Gathering: Collects phone number, purpose, and conversation requirements 3. Agent Creation: Creates a specialized phone agent via fluents.ai API 4. Call Execution: Initiates the phone call with the created agent 5. Content Analysis: Retrieves and understands the call transcript 6. Result Reporting: Provides you with a summary and key insights
Requirements
- Python 3.8+
- fluents.ai API account
- Internet connection
Documentation
- SKILL.md - Complete skill documentation
- [BEST_PRACTICES.md](./BEST_PRACTICES.md) - ⭐ Essential guide for creating effective phone agents
- API Reference - fluents.ai API details
- Examples - Usage examples
- Bad vs Good Comparison - Learn from common mistakes
Security & Privacy
- ⚠️ Ensure you have permission to call the target number
- 🔒 Never commit your API keys to version control
- 📋 Follow local telemarketing regulations
- 🛡️ Handle call records securely
License
MIT License - see LICENSE file for details
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
Support
- Website: https://fluents.ai
- Issues: GitHub Issues
Usage Examples
This document provides practical examples of using the phone call skill.
Example 1: Simple Confirmation Call
Make a quick call to confirm an appointment.
Step 1: Create the Agent
python scripts/create_agent.py \
--name "Appointment Confirmation Agent" \
--prompt "You are calling to confirm an appointment. Ask if the person can still make their appointment tomorrow at 3 PM. Be polite and brief." \
--language "en" \
--initial-message "Hello, I'm calling to confirm your appointment tomorrow."Output:
✓ Agent created successfully
Agent ID: abc123-def456-ghi789
Name: Appointment Confirmation Agent
Language: en
Agent ID: abc123-def456-ghi789Step 2: Make the Call
python scripts/make_call.py \
--agent-id "abc123-def456-ghi789" \
--to-number "+13105551234" \
--from-number "+13105559999"Output:
Fetching agent details for: abc123-def456-ghi789
✓ Call initiated successfully
Call ID: call-xyz789
Status: not_started
From: +13105559999
To: +13105551234
Call ID: call-xyz789Step 3: Get the Results
# Wait a few minutes for the call to complete, then:
python scripts/get_call_result.py --call-id "call-xyz789"Output:
Retrieving results for call: call-xyz789
============================================================
CALL DETAILS
============================================================
Call ID: call-xyz789
Status: ended
Stage: picked_up
Stage Outcome: human_disconnected
From: +13105559999
To: +13105551234
Outgoing: Yes
Started: 2026-02-06T10:30:00Z
Ended: 2026-02-06T10:32:15Z
Human Detected: human
Recording Available: Yes
============================================================
TRANSCRIPT
============================================================
Agent: Hello, I'm calling to confirm your appointment tomorrow.
Human: Yes, who is this?
Agent: This is a reminder call about your appointment tomorrow at 3 PM. Can you still make it?
Human: Oh yes, I'll be there. Thanks!
Agent: Great! We'll see you tomorrow at 3 PM. Have a nice day!
Human: You too, bye.Step 4: Download the Recording (Optional)
python scripts/get_recording.py \
--call-id "call-xyz789" \
--output "confirmation_call.mp3"---
Example 2: Customer Feedback Call
Conduct a more open-ended conversation to gather feedback.
Step 1: Create a Conversational Agent
python scripts/create_agent.py \
--name "Customer Feedback Agent" \
--prompt "You are conducting a customer satisfaction survey. Ask the customer about their recent purchase experience. Be friendly, listen actively, and ask follow-up questions. Keep the conversation under 5 minutes." \
--language "en" \
--initial-message "Hi! I'm calling from our customer service team to hear about your recent experience with us."Step 2: Make the Call with Context
python scripts/make_call.py \
--agent-id "feedback-agent-id" \
--to-number "+14155551234" \
--from-number "+14155559999" \
--context '{"customer_name": "John", "order_id": "ORD-12345", "product": "Widget Pro"}'Step 3: Retrieve Detailed Results
python scripts/get_call_result.py \
--call-id "call-feedback-123" \
--verbose---
Example 3: Batch Calls
Make multiple calls in sequence.
Create the Agent Once
python scripts/create_agent.py \
--name "Reminder Agent" \
--prompt "You are calling to remind people about an event tomorrow. Keep it brief." \
--initial-message "Hello, this is a reminder about tomorrow's event." \
> agent_output.txt
# Extract agent ID
AGENT_ID=$(grep "Agent ID:" agent_output.txt | tail -1 | awk '{print $3}')Loop Through Contact List
#!/bin/bash
# List of phone numbers to call
numbers=(
"+13105551111"
"+13105552222"
"+13105553333"
)
for number in "${numbers[@]}"; do
echo "Calling $number..."
python scripts/make_call.py \
--agent-id "$AGENT_ID" \
--to-number "$number" \
--from-number "+13105559999"
# Wait a bit between calls
sleep 60
done---
Example 4: Using Python Directly
Instead of command-line scripts, you can import and use the functions directly:
#!/usr/bin/env python3
import sys
import os
# Add scripts directory to path
sys.path.append('scripts')
from create_agent import create_agent
from make_call import make_call
from get_call_result import get_call_details
# Create agent
agent = create_agent(
name="My Agent",
prompt_text="You are a helpful assistant.",
language="en"
)
agent_id = agent['id']
print(f"Created agent: {agent_id}")
# Make call
call = make_call(
agent_id=agent_id,
to_number="+13105551234",
from_number="+13105559999"
)
call_id = call['id']
print(f"Initiated call: {call_id}")
# Wait for call to complete (in real usage, use webhooks or polling)
import time
time.sleep(300) # 5 minutes
# Get results
result = get_call_details(call_id)
print(f"Call status: {result['status']}")
print(f"Transcript: {result.get('transcript', 'N/A')}")---
Example 5: Multi-Language Support
Create agents for different languages:
Spanish Agent
python scripts/create_agent.py \
--name "Agente Español" \
--prompt "Eres un asistente telefónico amable. Ayuda al cliente con sus preguntas." \
--language "es" \
--initial-message "Hola, ¿cómo puedo ayudarte hoy?"Chinese Agent
python scripts/create_agent.py \
--name "中文客服" \
--prompt "你是一个友好的电话助理。帮助客户解答问题。" \
--language "zh" \
--initial-message "你好,我能帮你什么?"---
Error Handling
Check Call Status
import time
from scripts.get_call_result import get_call_details
def wait_for_call_completion(call_id, max_wait=600, poll_interval=30):
"""Wait for call to complete"""
elapsed = 0
while elapsed < max_wait:
call = get_call_details(call_id)
status = call.get('status')
if status == 'ended':
return call
elif status == 'error':
print(f"Call failed: {call.get('error_message')}")
return call
time.sleep(poll_interval)
elapsed += poll_interval
print("Timeout waiting for call to complete")
return None
# Usage
call_result = wait_for_call_completion("call-xyz789")
if call_result and call_result['status'] == 'ended':
print("Call completed successfully!")---
Best Practices
1. Test with Your Own Number First: Before calling customers, test the agent by calling yourself.
2. Use Webhooks: For production use, set up webhooks instead of polling for results.
3. Handle Time Zones: Make sure to call during appropriate hours for the recipient's time zone.
4. Provide Context: Use the context parameter to give the agent relevant information about the call.
5. Keep Prompts Clear: Write clear, concise prompts that tell the agent exactly what to do.
6. Monitor Recordings: Regularly review call recordings to improve agent prompts.
7. Respect Do Not Call Lists: Enable run_do_not_call_detection for compliance.
---
Troubleshooting
Call Not Connecting
If calls aren't connecting:
- Verify phone numbers include country code (e.g., +1 for US)
- Check that your Fluents.ai account has a valid telephony provider configured
- Ensure the
from_numberis a number you own or have access to
Poor Conversation Quality
If the agent doesn't respond well:
- Refine the prompt to be more specific
- Adjust
interrupt_sensitivityif the agent interrupts too much or too little - Try different
endpointing_sensitivitysettings
Recording Not Available
If recording is not available:
- Check that
enable_recordingwas set totruewhen creating the agent - Wait longer - recordings may take a few minutes to process
- Verify the call actually completed successfully
---
For more examples and updates, check the GitHub repository.
Fluents.ai API Reference
This document provides a reference for the fluents.ai API endpoints used in this skill.
Base URL
https://api.fluents.aiAuthentication
All API requests require Bearer token authentication.
Authorization: Bearer YOUR_API_KEYGet your API key from: https://app.fluents.ai/settings/api-keys
---
Agents API
Create Agent
Create a new phone agent with specified configuration.
Endpoint: POST /v1/agents/create
Request Body:
{
"name": "string",
"language": "en",
"initial_message": "Hello, how can I help you today?",
"prompt": {
"text": "You are a helpful phone assistant..."
},
"actions": [],
"voice": {
"provider": "elevenlabs",
"voice_id": "optional_voice_id"
},
"enable_recording": true,
"interrupt_sensitivity": "high",
"endpointing_sensitivity": "auto"
}Required Fields:
prompt(PromptDto): System prompt for agent behavioractions(array): Array of actions the agent can performvoice(object): Voice configuration
Optional Fields:
name: Agent namelanguage: Language code (en, es, de, hi, pt, fr, nl, id, it, ja, ko)initial_message: Greeting messageinitial_message_delay: Delay in millisecondsconversation_speed: Speed multiplierinterrupt_sensitivity: "low" or "high"endpointing_sensitivity: "auto", "relaxed", or "sensitive"enable_recording: Boolean to enable call recordingnoise_suppression: Boolean to enable noise suppressionidle_time_seconds: Timeout settingcall_duration_sec: Maximum call lengthwebhook: Webhook configuration for events
Response (201 Created):
{
"id": "agent_uuid",
"user_id": "user_uuid",
"name": "Agent Name",
"language": "en",
"prompt": {...},
"actions": [...],
"voice": {...}
}Get Agent
Retrieve agent details by ID.
Endpoint: GET /v1/agents?id={agent_id}
Response (200 OK): Returns complete agent configuration.
---
Calls API
Create Call
Initiate an outbound phone call.
Endpoint: POST /v1/calls/create
Request Body:
{
"to_number": "+1234567890",
"from_number": "+0987654321",
"agent_phone_number": "+0987654321",
"agent": {
"id": "agent_uuid",
...
},
"telephony_provider": {
"name": "twilio"
},
"context": {},
"telephony_params": {},
"is_outgoing": true,
"run_do_not_call_detection": false
}Required Fields:
to_number: Destination phone number with country codefrom_number: Caller ID number with country codeagent_phone_number: Agent's phone numberagent: Complete agent objecttelephony_provider: Provider configurationcontext: Call context datatelephony_params: Telephony-specific parameters
Optional Fields:
human_detection_result: "human" or "no_human"do_not_call_result: Booleantelephony_id: Unique identifier from providerhipaa_compliant: Booleanon_no_human_answer: "continue" or "hangup"run_do_not_call_detection: Booleanis_outgoing: Boolean
Response (201 Created):
{
"id": "call_uuid",
"user_id": "user_uuid",
"status": "not_started",
"to_number": "+1234567890",
"from_number": "+0987654321",
"start_time": "2026-01-01T00:00:00Z",
"recording_available": false
}Get Call
Retrieve call details and transcript.
Endpoint: GET /v1/calls?id={call_id}
Response (200 OK):
{
"id": "call_uuid",
"user_id": "user_uuid",
"status": "ended",
"stage": "picked_up",
"stage_outcome": "human_disconnected",
"to_number": "+1234567890",
"from_number": "+0987654321",
"start_time": "2026-01-01T00:00:00Z",
"end_time": "2026-01-01T00:05:00Z",
"recording_available": true,
"transcript": "Full conversation transcript...",
"human_detection_result": "human",
"do_not_call_result": false,
"error_message": null,
"errors": []
}Status Values:
not_started: Call has not begunin_progress: Call is ongoingerror: Call encountered an errorended: Call has completed
Stage Values:
created: Call has been createdpicked_up: Call was answeredtransfer_started: Transfer initiatedtransfer_successful: Transfer completed
Stage Outcome Values:
human_unanswered: No one picked upcall_did_not_connect: Connection failedhuman_disconnected: Human hung upbot_disconnected: Bot ended the calltransfer_unanswered: Transfer not answeredtransfer_disconnected: Transfer ended
Get Recording
Download call recording as MP3.
Endpoint: GET /v1/calls/recording?id={call_id}
Response (200 OK): Returns MP3 audio file as binary data.
---
Error Responses
All endpoints may return error responses:
400 Bad Request:
{
"message": "Error description",
"error": "BadRequest",
"statusCode": 400
}404 Not Found:
{
"message": "Resource not found",
"error": "NotFound",
"statusCode": 404
}---
Rate Limits
Check the Fluents.ai documentation for current rate limits.
Support
For API support, contact: support@fluents.ai
Additional Resources
- Official Documentation: https://docs.fluents.ai
- API Reference: https://docs.fluents.ai/api-reference
- Dashboard: https://app.fluents.ai
requests>=2.31.0
python-dotenv>=1.0.0
pydantic>=2.5.0
aiohttp>=3.9.0
#!/usr/bin/env python3
"""
Intelligently analyze phone call results and provide actionable insights
"""
import os
import sys
import argparse
import requests
from dotenv import load_dotenv
import json
import re
load_dotenv()
FLUENTS_API_KEY = os.getenv("FLUENTS_API_KEY")
FLUENTS_API_URL = os.getenv("FLUENTS_API_URL", "https://api.fluents.ai")
def get_call_details(call_id: str):
"""Get complete call details"""
if not FLUENTS_API_KEY:
raise ValueError("FLUENTS_API_KEY not found in environment variables")
headers = {
"Authorization": f"Bearer {FLUENTS_API_KEY}",
"Content-Type": "application/json"
}
try:
response = requests.get(
f"{FLUENTS_API_URL}/v1/calls",
params={"id": call_id},
headers=headers,
timeout=30
)
response.raise_for_status()
return response.json()
except requests.exceptions.RequestException as e:
print(f"✗ Failed to get call details: {e}", file=sys.stderr)
return None
def calculate_duration(call_data):
"""Calculate call duration in seconds"""
start = float(call_data.get('start_time', 0))
end = float(call_data.get('end_time', 0))
return int(end - start) if end > 0 else 0
def analyze_transcript(transcript):
"""Analyze transcript to extract insights"""
if not transcript or transcript.strip() == "":
return {
"turns": 0,
"bot_turns": 0,
"human_turns": 0,
"keywords": [],
"has_confirmation": False,
"is_one_sided": True
}
# Parse transcript lines
lines = transcript.strip().split('\n')
bot_turns = 0
human_turns = 0
keywords = []
has_confirmation = False
for line in lines:
if '[' in line and ']' in line:
if 'BOT:' in line:
bot_turns += 1
# Check for confirmation keywords
if re.search(r'\b(confirm|confirmed|reservation|booked|set|scheduled)\b', line, re.I):
has_confirmation = True
keywords.append('confirmation')
elif 'HUMAN:' in line:
human_turns += 1
return {
"turns": len(lines),
"bot_turns": bot_turns,
"human_turns": human_turns,
"keywords": keywords,
"has_confirmation": has_confirmation,
"is_one_sided": human_turns == 0 or bot_turns == 0
}
def determine_success(call_data, transcript_analysis, duration):
"""
Determine if the call successfully completed its objective
Returns: (is_success, confidence, reason)
"""
status = call_data.get('status')
stage_outcome = call_data.get('stage_outcome')
# Obvious failures
if duration < 20:
return False, 0.95, "Call too short (< 20 seconds) - likely immediate hangup"
if status != 'ended':
return False, 0.9, f"Call status: {status} (not properly ended)"
if transcript_analysis['is_one_sided']:
if transcript_analysis['bot_turns'] > 0 and transcript_analysis['human_turns'] == 0:
return False, 0.85, "No human responses detected - one-sided conversation"
elif transcript_analysis['human_turns'] > 0 and transcript_analysis['bot_turns'] <= 1:
return False, 0.85, "Bot only said greeting - conversation not completed"
# Check for confirmation
if transcript_analysis['has_confirmation']:
return True, 0.8, "Confirmation keywords detected in conversation"
# Check conversation length and turns
if duration > 60 and transcript_analysis['human_turns'] >= 2:
return True, 0.7, "Meaningful conversation detected (60+ seconds, multiple exchanges)"
# Uncertain cases
if duration >= 30 and transcript_analysis['human_turns'] >= 1:
return None, 0.5, "Unclear - short conversation with minimal exchange"
return False, 0.6, "No clear success indicators"
def identify_failure_cause(call_data, transcript_analysis, duration):
"""Identify the specific cause of failure"""
stage_outcome = call_data.get('stage_outcome')
if duration < 15:
return "immediate_hangup", "Recipient hung up immediately (possibly recognized as robocall)"
if transcript_analysis['bot_turns'] <= 1 and duration < 30:
return "bot_stalled", "Bot only spoke once and call ended - likely a response/recognition issue"
if transcript_analysis['human_turns'] == 0:
return "no_human_response", "No human responses detected - recognition failure or silent line"
if stage_outcome == "human_disconnected" and not transcript_analysis['has_confirmation']:
return "task_incomplete", "Human hung up before task completion"
return "unknown", "Failure cause unclear from available data"
def suggest_optimizations(failure_cause, call_data, duration):
"""Suggest specific optimizations based on failure analysis"""
suggestions = []
agent = call_data.get('agent', {})
if failure_cause == "immediate_hangup":
suggestions.extend([
"⏰ Wait 30+ minutes before retrying (avoid spam detection)",
"🎤 Consider using a different voice (currently: {})".format(
agent.get('voice', {}).get('label', 'Unknown')
),
"💬 Make opening message more human-like and natural",
"📞 Verify number is correct and not on DNC list"
])
elif failure_cause == "bot_stalled":
suggestions.extend([
"⏱️ Increase idle_time_seconds (current: {})".format(
agent.get('idle_time_seconds', 'Not set')
),
"🎯 Set endpointing_sensitivity to 'relaxed'",
"✅ Enable ask_if_human_present_on_idle",
"🔊 Enable noise_suppression"
])
elif failure_cause == "no_human_response":
suggestions.extend([
"🎙️ Check if noise_suppression is enabled",
"📡 Consider using better transcriber (Deepgram, AssemblyAI)",
"⏰ Add initial_message_delay: 2000 (wait 2s before speaking)",
"🔊 Adjust conversation_speed (try 0.9 or 1.1)"
])
elif failure_cause == "task_incomplete":
suggestions.extend([
"📝 Improve prompt with explicit completion criteria",
"⚠️ Add 'DO NOT HANG UP until [specific condition]' to prompt",
"✅ List all required steps in prompt",
"🔄 Add confirmation step at the end"
])
# General suggestions
if agent.get('llm_temperature', 0) > 0.7:
suggestions.append("🎲 Lower llm_temperature to 0.5 for more consistent behavior")
if not agent.get('noise_suppression'):
suggestions.append("🔇 Enable noise_suppression: true")
return suggestions
def generate_report(call_data, format='text'):
"""Generate comprehensive analysis report"""
duration = calculate_duration(call_data)
transcript = call_data.get('transcript', '')
transcript_analysis = analyze_transcript(transcript)
is_success, confidence, reason = determine_success(call_data, transcript_analysis, duration)
report = {
"call_id": call_data.get('id'),
"duration_seconds": duration,
"status": call_data.get('status'),
"stage": call_data.get('stage'),
"stage_outcome": call_data.get('stage_outcome'),
"transcript_analysis": transcript_analysis,
"success": is_success,
"confidence": confidence,
"reason": reason,
"transcript": transcript
}
if is_success is False:
failure_cause, cause_detail = identify_failure_cause(call_data, transcript_analysis, duration)
report['failure_cause'] = failure_cause
report['failure_detail'] = cause_detail
report['optimizations'] = suggest_optimizations(failure_cause, call_data, duration)
if format == 'json':
return json.dumps(report, indent=2)
# Text format
output = []
output.append("=" * 70)
output.append("INTELLIGENT CALL ANALYSIS")
output.append("=" * 70)
output.append(f"Call ID: {report['call_id']}")
output.append(f"Duration: {duration} seconds")
output.append(f"Status: {report['status']}")
output.append(f"Outcome: {report['stage_outcome']}")
output.append("")
# Success/Failure
output.append("=" * 70)
if is_success:
output.append("✅ TASK SUCCESSFUL")
output.append(f"Confidence: {int(confidence * 100)}%")
output.append(f"Reason: {reason}")
elif is_success is False:
output.append("❌ TASK FAILED")
output.append(f"Confidence: {int(confidence * 100)}%")
output.append(f"Reason: {reason}")
output.append("")
output.append(f"Failure Type: {report['failure_cause']}")
output.append(f"Details: {report['failure_detail']}")
else:
output.append("❓ UNCLEAR RESULT")
output.append(f"Confidence: {int(confidence * 100)}%")
output.append(f"Reason: {reason}")
output.append("")
# Transcript Analysis
output.append("=" * 70)
output.append("CONVERSATION ANALYSIS")
output.append("=" * 70)
output.append(f"Total exchanges: {transcript_analysis['turns']}")
output.append(f"Bot spoke: {transcript_analysis['bot_turns']} times")
output.append(f"Human responded: {transcript_analysis['human_turns']} times")
output.append(f"Confirmation detected: {'Yes' if transcript_analysis['has_confirmation'] else 'No'}")
output.append(f"One-sided: {'Yes' if transcript_analysis['is_one_sided'] else 'No'}")
output.append("")
# Transcript
output.append("=" * 70)
output.append("TRANSCRIPT")
output.append("=" * 70)
if transcript:
output.append(transcript)
else:
output.append("(No transcript available)")
output.append("")
# Optimizations
if 'optimizations' in report:
output.append("=" * 70)
output.append("RECOMMENDED OPTIMIZATIONS")
output.append("=" * 70)
for opt in report['optimizations']:
output.append(f" {opt}")
output.append("")
return '\n'.join(output)
def main():
parser = argparse.ArgumentParser(description="Intelligently analyze phone call results")
parser.add_argument("--call-id", required=True, help="Call ID to analyze")
parser.add_argument("--format", choices=['text', 'json'], default='text',
help="Output format (default: text)")
args = parser.parse_args()
call_data = get_call_details(args.call_id)
if not call_data:
sys.exit(1)
report = generate_report(call_data, format=args.format)
print(report)
if __name__ == "__main__":
main()
#!/usr/bin/env python3
"""
Create a phone agent using the fluents.ai API with scenario-based prompts
"""
import os
import sys
import argparse
import requests
from dotenv import load_dotenv
load_dotenv()
FLUENTS_API_KEY = os.getenv("FLUENTS_API_KEY")
FLUENTS_API_URL = os.getenv("FLUENTS_API_URL", "https://api.fluents.ai")
def build_outbound_prompt(
objective: str,
key_info: dict = None,
conversation_flow: str = None,
tone: str = "friendly and professional",
additional_instructions: str = None
) -> str:
"""
Build an outbound call prompt following best practices
This generates prompts optimized for OUTBOUND calls where the AI is the CALLER.
Args:
objective: What you need to accomplish (e.g., "make a dinner reservation")
key_info: Dict of known information (e.g., {"name": "Lee", "party_size": 2})
conversation_flow: Optional detailed conversation flow steps
tone: Communication style (default: "friendly and professional")
additional_instructions: Any special instructions
Returns:
str: Complete structured prompt for outbound calls
"""
prompt_parts = [
"# IDENTITY - WHO YOU ARE",
"You are making an OUTBOUND phone call.",
"You are the CALLER, not someone answering the phone.",
"YOU initiated this call. THEY answered YOUR call.",
"",
"CRITICAL: You are NOT a receptionist or assistant answering calls.",
"CRITICAL: You are NOT waiting for the other person to state their business.",
"CRITICAL: You need to clearly state YOUR purpose for calling.",
"",
"# ROLE PROHIBITIONS - NEVER DO THESE",
'NEVER say "How can I help you?" - YOU are the one who needs help',
'NEVER say "Thank you for calling" - YOU are calling them',
"NEVER wait for them to explain what they want - YOU explain what you want",
"NEVER act like you are receiving this call - YOU are making this call",
"",
f"# YOUR TASK",
f"Your goal is to {objective}.",
"",
]
# Add key information if provided
if key_info and len(key_info) > 0:
prompt_parts.extend([
"# KEY INFORMATION (You already know this - don't ask for it!)",
])
for key, value in key_info.items():
prompt_parts.append(f"- {key}: {value}")
prompt_parts.extend([
"",
"When they ask for this information, provide it directly.",
'Do NOT say "let me check" - you already know this information.',
"",
])
# Add conversation flow if provided
if conversation_flow:
prompt_parts.extend([
"# CONVERSATION FLOW",
conversation_flow,
"",
])
# Add speaking style guidelines
prompt_parts.extend([
"# SPEAKING STYLE",
"- Speak one short sentence at a time",
"- Sound natural like a real person on the phone",
"- Do NOT give long explanations or speeches",
f"- Maintain a {tone} tone",
"- Wait for their response after each sentence",
"- Keep it conversational, not robotic",
"",
])
if additional_instructions:
prompt_parts.extend([
"# SPECIAL INSTRUCTIONS",
additional_instructions,
"",
])
return "\n".join(prompt_parts)
def build_scenario_prompt(
role: str,
objective: str,
scenario_context: str = None,
key_info: list = None,
tone: str = None,
additional_instructions: str = None
) -> str:
"""
Build a structured prompt based on caller-defined parameters
NOTE: For outbound calls, consider using build_outbound_prompt() instead,
as it follows best practices to avoid identity confusion.
Args:
role: Who you are (e.g., "restaurant reservation assistant")
objective: What you need to accomplish (e.g., "confirm dinner reservation")
scenario_context: Optional context about the scenario (e.g., "calling existing customers")
key_info: List of key information to collect (e.g., ["date", "time", "number of guests"])
tone: Communication style (e.g., "friendly and professional")
additional_instructions: Any special instructions
Returns:
str: Complete structured prompt
"""
# Build structured prompt
prompt_parts = [
f"# Role Definition",
f"You are a {role}.",
"",
]
if scenario_context:
prompt_parts.extend([
f"# Scenario",
f"{scenario_context}",
"",
])
prompt_parts.extend([
f"# Objective",
f"Your goal is to {objective}.",
"",
])
if tone:
prompt_parts.extend([
f"# Communication Style",
f"Maintain a {tone} communication style.",
"",
])
if key_info and len(key_info) > 0:
prompt_parts.extend([
f"# Key Information",
f"During the conversation, you need to collect or confirm the following information:",
])
for info in key_info:
prompt_parts.append(f"- {info}")
prompt_parts.append("")
prompt_parts.extend([
f"# Conversation Guidelines",
f"1. Keep it concise and clear - ask only one question at a time",
f"2. If the customer doesn't understand, patiently repeat",
f"3. When confirming important information, repeat it back to ensure accuracy",
f"4. If unable to complete the task, politely explain why and offer alternatives",
f"5. Thank the customer for their time at the end of the conversation",
])
if additional_instructions:
prompt_parts.extend([
"",
f"# Special Instructions",
additional_instructions
])
return "\n".join(prompt_parts)
def create_agent(
name: str,
prompt_text: str = None,
language: str = "en",
voice_provider: str = "elevenlabs",
voice_id: str = None,
initial_message: str = None,
# Scenario builder parameters
call_type: str = "outbound", # "outbound" or "inbound"
role: str = None,
objective: str = None,
scenario_context: str = None,
key_info: list = None,
key_info_dict: dict = None,
conversation_flow: str = None,
tone: str = None,
additional_instructions: str = None
):
"""
Create a phone agent with specified configuration
Args:
name: Name of the agent
prompt_text: The system prompt (if not using scenario builder)
language: Language code (en, es, de, hi, pt, fr, nl, id, it, ja, ko)
voice_provider: Voice provider (elevenlabs, openai, etc.)
voice_id: Specific voice ID to use
initial_message: Greeting message (required for outbound calls)
# Scenario builder parameters (alternative to prompt_text):
call_type: "outbound" (default) or "inbound" - affects prompt generation
role: Who the agent is (for inbound calls, e.g., "customer service representative")
objective: What the agent needs to accomplish (e.g., "make a dinner reservation")
scenario_context: Optional scenario context (e.g., "calling existing customers")
key_info: List of key information to collect (for inbound)
key_info_dict: Dict of known information (for outbound, e.g., {"name": "Lee", "party_size": 2})
conversation_flow: Detailed conversation flow steps (for outbound)
tone: Communication style (e.g., "friendly and professional")
additional_instructions: Extra instructions to add to the prompt
Returns:
dict: Agent creation response with agent id
"""
# If using scenario builder, generate prompt based on call type
if objective:
if call_type == "outbound":
# Use outbound-optimized prompt builder (best practices)
generated_prompt = build_outbound_prompt(
objective=objective,
key_info=key_info_dict,
conversation_flow=conversation_flow,
tone=tone or "friendly and professional",
additional_instructions=additional_instructions
)
builder_type = "outbound call (best practices)"
else:
# Use traditional scenario builder for inbound calls
generated_prompt = build_scenario_prompt(
role=role or "assistant",
objective=objective,
scenario_context=scenario_context,
key_info=key_info,
tone=tone,
additional_instructions=additional_instructions
)
builder_type = "inbound call (traditional)"
# Use generated prompt unless custom prompt is provided
if not prompt_text:
prompt_text = generated_prompt
print(f"📋 Building {builder_type} prompt")
print(f" Objective: {objective}")
print(f"\n✨ Generated Prompt:")
print("─" * 60)
print(prompt_text)
print("─" * 60)
# Error if neither scenario builder nor prompt is provided
if not prompt_text:
raise ValueError("Must provide either --prompt or (--role + --objective) parameters")
if not FLUENTS_API_KEY:
raise ValueError("FLUENTS_API_KEY not found in environment variables")
headers = {
"Authorization": f"Bearer {FLUENTS_API_KEY}",
"Content-Type": "application/json"
}
# Get default voice if not specified
if not voice_id:
# Use a default voice - fetch the first available voice
try:
voices_response = requests.get(
f"{FLUENTS_API_URL}/v1/voices/list",
params={"page": 1, "size": 1},
headers={"Authorization": f"Bearer {FLUENTS_API_KEY}"},
timeout=10
)
if voices_response.status_code == 200:
voices = voices_response.json().get('items', [])
if voices:
voice_id = voices[0]['id']
print(f"Using default voice: {voices[0].get('label', 'Unknown')}")
except:
pass
if not voice_id:
print("✗ No voice specified and couldn't fetch default voice", file=sys.stderr)
sys.exit(1)
# Validate initial_message is provided
if not initial_message:
raise ValueError("--initial-message is required. Please specify what the agent should say when the call connects.")
# Construct the payload according to fluents.ai API spec
# IMPORTANT: Based on successful curl tests, the format must be:
# - prompt.content (not prompt.text)
# - voice as UUID string (not object)
# - enable_dynamic_turns: true (CRITICAL for outbound!)
# - initial_message_delay: 0 (speak immediately!)
payload = {
"name": name,
"language": language,
"initial_message": initial_message,
"prompt": {
"content": prompt_text # ✅ FIXED: use "content" not "text"
},
"actions": [],
"voice": voice_id, # ✅ FIXED: direct UUID string, not object
# ⚡ CRITICAL FOR OUTBOUND CALLS - Based on working Agent 1 config
"wait_for_greeting": False, # Agent speaks first (don't wait!)
"enable_dynamic_turns": True, # 🔥 CRITICAL: Enables proactive speaking!
"initial_message_delay": 0, # 🔥 Speak IMMEDIATELY when call connects
# Conversation settings - Proven working config from Agent 1
"endpointing_sensitivity": "auto", # Auto detection (not too sensitive)
"idle_time_seconds": 7, # Give enough time for responses
"conversation_speed": 1.0, # Normal speech speed
"interrupt_sensitivity": "low", # Less sensitive to interruptions
# Quality settings
"provider": "openai",
"llm_temperature": 0, # Deterministic responses
"noise_suppression": False, # Match working config
"ask_if_human_present_on_idle": True, # Ask if still there
"call_duration_sec": 600, # 10 minutes max
"max_idle_check_count": 3 # Check 3 times before giving up
}
try:
response = requests.post(
f"{FLUENTS_API_URL}/v1/agents/create",
json=payload,
headers=headers,
timeout=30
)
response.raise_for_status()
result = response.json()
print(f"✓ Agent created successfully")
print(f" Agent ID: {result.get('id')}")
print(f" Name: {result.get('name')}")
print(f" Language: {result.get('language')}")
return result
except requests.exceptions.RequestException as e:
print(f"✗ Failed to create agent: {e}", file=sys.stderr)
if hasattr(e.response, 'text'):
print(f" Response: {e.response.text}", file=sys.stderr)
sys.exit(1)
def main():
parser = argparse.ArgumentParser(
description="Create scenario-based fluents.ai phone agents",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
Usage Examples:
# OUTBOUND call - Make a restaurant reservation (RECOMMENDED - uses best practices)
python3 scripts/create_agent.py \\
--name "Restaurant Reservation Agent" \\
--call-type outbound \\
--objective "make a dinner reservation at the restaurant" \\
--initial-message "Hi, I'd like to make a reservation for dinner tonight." \\
--key-info-dict '{"name": "Lee (L-E-E)", "party_size": "2 people", "preferred_time": "7 PM", "phone": "310-555-1234"}' \\
--tone "friendly and casual"
# OUTBOUND call - Appointment confirmation with conversation flow
python3 scripts/create_agent.py \\
--name "Appointment Reminder" \\
--call-type outbound \\
--objective "confirm the customer's appointment tomorrow at 3 PM" \\
--initial-message "Hi, this is a reminder about your appointment tomorrow." \\
--key-info-dict '{"appointment_time": "3 PM tomorrow", "customer_name": "John"}' \\
--conversation-flow "1. Confirm they can still make it\\n2. If yes, thank them\\n3. If no, ask for alternative time"
# INBOUND call - Customer service (traditional scenario builder)
python3 scripts/create_agent.py \\
--name "Support Agent" \\
--call-type inbound \\
--role "customer service representative" \\
--objective "help customers with their questions" \\
--initial-message "Hello, how can I help you today?" \\
--key-info "customer name,order number,issue description"
# Use fully custom prompt (maximum control)
python3 scripts/create_agent.py \\
--name "Custom Agent" \\
--prompt "You are making an OUTBOUND call TO a restaurant..." \\
--initial-message "Hi, I'd like to make a reservation."
# See BEST_PRACTICES.md for detailed guidance on creating effective agents
"""
)
# Basic parameters
parser.add_argument("--name", required=True,
help="Agent name")
parser.add_argument("--initial-message", required=True,
help="Opening message when the call connects (what the agent says first)")
parser.add_argument("--language", default="en",
help="Language code (default: en)")
parser.add_argument("--voice-id",
help="Specific voice ID (uses default if not specified)")
# Scenario builder parameters (recommended way)
parser.add_argument("--call-type", default="outbound", choices=["outbound", "inbound"],
help="Type of call: 'outbound' (you call them) or 'inbound' (they call you). Default: outbound")
parser.add_argument("--role",
help="Who the agent is (for inbound calls, e.g., 'customer service representative')")
parser.add_argument("--objective", required=False,
help="What the agent needs to accomplish (e.g., 'make a dinner reservation')")
parser.add_argument("--scenario-context",
help="Optional context (e.g., 'calling existing customers who made reservations online')")
parser.add_argument("--key-info",
help="[Inbound] Comma-separated list of info to collect (e.g., 'date,time,number of guests')")
parser.add_argument("--key-info-dict",
help="[Outbound] JSON dict of known info (e.g., '{\"name\": \"Lee\", \"party_size\": 2}')")
parser.add_argument("--conversation-flow",
help="[Outbound] Detailed conversation flow steps")
parser.add_argument("--tone",
help="Communication style (e.g., 'friendly and professional')")
parser.add_argument("--additional-instructions",
help="Any special instructions for the agent")
# Traditional parameter (backward compatible)
parser.add_argument("--prompt",
help="Custom system prompt (alternative to using --role + --objective)")
args = parser.parse_args()
# Validate parameters
if not args.prompt and not args.objective:
parser.error("Must provide either --prompt OR --objective")
# Parse key_info if provided (for inbound calls)
key_info_list = None
if args.key_info:
key_info_list = [info.strip() for info in args.key_info.split(',')]
# Parse key_info_dict if provided (for outbound calls)
import json
key_info_dict = None
if args.key_info_dict:
try:
key_info_dict = json.loads(args.key_info_dict)
except json.JSONDecodeError as e:
parser.error(f"Invalid JSON in --key-info-dict: {e}")
result = create_agent(
name=args.name,
prompt_text=args.prompt,
language=args.language,
voice_id=args.voice_id,
initial_message=args.initial_message,
call_type=args.call_type,
role=args.role,
objective=args.objective,
scenario_context=args.scenario_context,
key_info=key_info_list,
key_info_dict=key_info_dict,
conversation_flow=args.conversation_flow,
tone=args.tone,
additional_instructions=args.additional_instructions
)
# Output agent_id for use in subsequent scripts
print(f"\n✅ Agent created successfully!")
print(f"Agent ID: {result.get('id')}")
print(f"\nYou can now make calls with:")
print(f" python3 scripts/make_call_simple.py \\")
print(f" --agent-id \"{result.get('id')}\" \\")
print(f" --to-number \"+1234567890\" \\")
print(f" --from-number \"+15103982646\"")
if __name__ == "__main__":
main()
#!/usr/bin/env python3
"""
Diagnostic script to check fluents.ai setup and identify issues
"""
import os
import sys
import requests
from dotenv import load_dotenv
import json
load_dotenv()
FLUENTS_API_KEY = os.getenv("FLUENTS_API_KEY")
FLUENTS_API_URL = os.getenv("FLUENTS_API_URL", "https://api.fluents.ai")
def check_env():
"""Check environment configuration"""
print("=" * 70)
print("1. ENVIRONMENT CHECK")
print("=" * 70)
if not FLUENTS_API_KEY:
print("✗ FLUENTS_API_KEY not set")
return False
print(f"✓ FLUENTS_API_KEY: {FLUENTS_API_KEY[:10]}...{FLUENTS_API_KEY[-4:]}")
print(f"✓ FLUENTS_API_URL: {FLUENTS_API_URL}")
return True
def check_api_connection():
"""Test API connectivity"""
print("\n" + "=" * 70)
print("2. API CONNECTION TEST")
print("=" * 70)
headers = {
"Authorization": f"Bearer {FLUENTS_API_KEY}",
"Content-Type": "application/json"
}
try:
response = requests.get(
f"{FLUENTS_API_URL}/v1/agents/list",
params={"page": 1, "size": 1},
headers=headers,
timeout=10
)
if response.status_code == 200:
print("✓ API connection successful")
print(f" Status: {response.status_code}")
return True
else:
print(f"✗ API returned status {response.status_code}")
print(f" Response: {response.text[:200]}")
return False
except Exception as e:
print(f"✗ Connection failed: {e}")
return False
def check_agents():
"""Check available agents"""
print("\n" + "=" * 70)
print("3. AGENTS CHECK")
print("=" * 70)
headers = {
"Authorization": f"Bearer {FLUENTS_API_KEY}",
"Content-Type": "application/json"
}
try:
response = requests.get(
f"{FLUENTS_API_URL}/v1/agents/list",
params={"page": 1, "size": 10},
headers=headers,
timeout=10
)
response.raise_for_status()
data = response.json()
total = data.get('total', 0)
items = data.get('items', [])
print(f"✓ Total agents: {total}")
if items:
print(f"\nAvailable agents:")
for i, agent in enumerate(items[:5], 1):
print(f" {i}. {agent.get('name')}")
print(f" ID: {agent.get('id')}")
print(f" Language: {agent.get('language')}")
else:
print("⚠ No agents found - you need to create one first")
return len(items) > 0
except Exception as e:
print(f"✗ Failed to get agents: {e}")
return False
def check_numbers():
"""Check phone numbers"""
print("\n" + "=" * 70)
print("4. PHONE NUMBERS CHECK")
print("=" * 70)
headers = {
"Authorization": f"Bearer {FLUENTS_API_KEY}",
"Content-Type": "application/json"
}
try:
response = requests.get(
f"{FLUENTS_API_URL}/v1/numbers/list",
params={"page": 1, "size": 10},
headers=headers,
timeout=10
)
response.raise_for_status()
data = response.json()
items = data.get('items', [])
if items:
print(f"✓ Found {len(items)} phone number(s):")
for num in items:
status = '✓ Active' if num.get('active') else '✗ Inactive'
print(f" {status}: {num.get('number')}")
print(f" Provider: {num.get('telephony_provider', 'Unknown')}")
else:
print("⚠ No phone numbers configured")
print(" You need to add a phone number in fluents.ai dashboard")
return False
return True
except Exception as e:
print(f"✗ Failed to get numbers: {e}")
return False
def check_recent_calls():
"""Check recent calls"""
print("\n" + "=" * 70)
print("5. RECENT CALLS")
print("=" * 70)
headers = {
"Authorization": f"Bearer {FLUENTS_API_KEY}",
"Content-Type": "application/json"
}
try:
response = requests.get(
f"{FLUENTS_API_URL}/v1/calls/list",
params={
"page": 1,
"size": 5,
"sort_column": "start_time",
"sort_desc": True,
"filters": ""
},
headers=headers,
timeout=10
)
response.raise_for_status()
data = response.json()
items = data.get('items', [])
total = data.get('total', 0)
print(f"✓ Total calls: {total}")
if items:
print(f"\nRecent calls:")
for call in items[:3]:
print(f" • {call.get('to_number')} - {call.get('status')}")
print(f" ID: {call.get('id')}")
else:
print(" No calls yet")
return True
except Exception as e:
print(f"✗ Failed to get calls: {e}")
return False
def main():
print("\n" + "=" * 70)
print("FLUENTS.AI DIAGNOSTIC TOOL")
print("=" * 70)
results = []
# Run all checks
results.append(("Environment", check_env()))
if not results[-1][1]:
print("\n❌ Environment check failed - cannot continue")
sys.exit(1)
results.append(("API Connection", check_api_connection()))
results.append(("Agents", check_agents()))
results.append(("Phone Numbers", check_numbers()))
results.append(("Recent Calls", check_recent_calls()))
# Summary
print("\n" + "=" * 70)
print("DIAGNOSTIC SUMMARY")
print("=" * 70)
passed = sum(1 for _, result in results if result)
total = len(results)
for check, result in results:
status = "✓ PASS" if result else "✗ FAIL"
print(f"{status}: {check}")
print(f"\nOverall: {passed}/{total} checks passed")
if passed == total:
print("\n✅ All checks passed! Your setup is ready.")
sys.exit(0)
else:
print("\n⚠️ Some checks failed. Please review the issues above.")
sys.exit(1)
if __name__ == "__main__":
main()
#!/usr/bin/env python3
"""
Retrieve and analyze phone call results from fluents.ai
"""
import os
import sys
import argparse
import requests
from dotenv import load_dotenv
import json
load_dotenv()
FLUENTS_API_KEY = os.getenv("FLUENTS_API_KEY")
FLUENTS_API_URL = os.getenv("FLUENTS_API_URL", "https://api.fluents.ai")
def get_call_details(call_id: str):
"""Get complete call details"""
if not FLUENTS_API_KEY:
raise ValueError("FLUENTS_API_KEY not found in environment variables")
headers = {
"Authorization": f"Bearer {FLUENTS_API_KEY}",
"Content-Type": "application/json"
}
try:
response = requests.get(
f"{FLUENTS_API_URL}/v1/calls",
params={"id": call_id},
headers=headers,
timeout=30
)
response.raise_for_status()
return response.json()
except requests.exceptions.RequestException as e:
print(f"✗ Failed to get call details: {e}", file=sys.stderr)
if hasattr(e, 'response') and hasattr(e.response, 'text'):
print(f" Response: {e.response.text}", file=sys.stderr)
return None
def display_results(call_id: str, verbose: bool = False):
"""
Retrieve and display call results
Args:
call_id: The call ID to retrieve
verbose: Show all details
"""
print(f"Retrieving results for call: {call_id}\n")
call_data = get_call_details(call_id)
if not call_data:
return
# Basic call information
print("=" * 60)
print("CALL DETAILS")
print("=" * 60)
print(f"Call ID: {call_data.get('id')}")
print(f"Status: {call_data.get('status')}")
print(f"Stage: {call_data.get('stage')}")
print(f"Stage Outcome: {call_data.get('stage_outcome')}")
print(f"From: {call_data.get('from_number')}")
print(f"To: {call_data.get('to_number')}")
print(f"Outgoing: {'Yes' if call_data.get('is_outgoing') else 'No'}")
print(f"Started: {call_data.get('start_time')}")
print(f"Ended: {call_data.get('end_time')}")
# Human detection
human_result = call_data.get('human_detection_result')
if human_result:
print(f"Human Detected: {human_result}")
# Recording
recording_available = call_data.get('recording_available')
print(f"Recording Available: {'Yes' if recording_available else 'No'}")
# Do not call detection
dnc_result = call_data.get('do_not_call_result')
if dnc_result is not None:
print(f"Do Not Call: {dnc_result}")
print()
# Transcript
transcript = call_data.get('transcript')
if transcript:
print("=" * 60)
print("TRANSCRIPT")
print("=" * 60)
print(transcript)
print()
# Errors
errors = call_data.get('errors', [])
error_message = call_data.get('error_message')
if errors or error_message:
print("=" * 60)
print("ERRORS")
print("=" * 60)
if error_message:
print(f"Error Message: {error_message}")
if errors:
for error in errors:
print(f" • {error}")
print()
# Action history
action_history = call_data.get('action_history', [])
if action_history and verbose:
print("=" * 60)
print("ACTION HISTORY")
print("=" * 60)
for action in action_history:
print(f" • {action}")
print()
# Full data in verbose mode
if verbose:
print("=" * 60)
print("FULL CALL DATA")
print("=" * 60)
print(json.dumps(call_data, indent=2))
def main():
parser = argparse.ArgumentParser(description="Get phone call results from fluents.ai")
parser.add_argument("--call-id", required=True, help="Call ID to retrieve")
parser.add_argument("--json", action="store_true", help="Output as JSON")
parser.add_argument("--verbose", "-v", action="store_true", help="Show all details")
args = parser.parse_args()
if args.json:
# Output everything as JSON
result = get_call_details(args.call_id)
if result:
print(json.dumps(result, indent=2))
else:
# Display formatted results
display_results(
call_id=args.call_id,
verbose=args.verbose
)
if __name__ == "__main__":
main()
#!/usr/bin/env python3
"""
Download call recording from fluents.ai
"""
import os
import sys
import argparse
import requests
from dotenv import load_dotenv
load_dotenv()
FLUENTS_API_KEY = os.getenv("FLUENTS_API_KEY")
FLUENTS_API_URL = os.getenv("FLUENTS_API_URL", "https://api.fluents.ai")
def get_recording(call_id: str, output_file: str = None):
"""
Download call recording
Args:
call_id: The call ID
output_file: Path to save the recording (default: recording_{call_id}.mp3)
Returns:
str: Path to the saved recording file
"""
if not FLUENTS_API_KEY:
raise ValueError("FLUENTS_API_KEY not found in environment variables")
headers = {
"Authorization": f"Bearer {FLUENTS_API_KEY}"
}
# Default output filename
if not output_file:
output_file = f"recording_{call_id}.mp3"
try:
print(f"Downloading recording for call: {call_id}")
response = requests.get(
f"{FLUENTS_API_URL}/v1/calls/recording",
params={"id": call_id},
headers=headers,
timeout=60,
stream=True
)
response.raise_for_status()
# Save the recording
with open(output_file, 'wb') as f:
for chunk in response.iter_content(chunk_size=8192):
f.write(chunk)
file_size = os.path.getsize(output_file)
print(f"✓ Recording downloaded successfully")
print(f" File: {output_file}")
print(f" Size: {file_size / 1024:.2f} KB")
return output_file
except requests.exceptions.RequestException as e:
print(f"✗ Failed to download recording: {e}", file=sys.stderr)
if hasattr(e, 'response') and hasattr(e.response, 'text'):
print(f" Response: {e.response.text}", file=sys.stderr)
sys.exit(1)
def main():
parser = argparse.ArgumentParser(description="Download call recording from fluents.ai")
parser.add_argument("--call-id", required=True, help="Call ID")
parser.add_argument("--output", "-o", help="Output file path (default: recording_{call_id}.mp3)")
args = parser.parse_args()
recording_path = get_recording(
call_id=args.call_id,
output_file=args.output
)
print(f"\nRecording saved to: {recording_path}")
if __name__ == "__main__":
main()
#!/usr/bin/env python3
"""
Simplified make_call script - more robust and better error handling
"""
import os
import sys
import argparse
import requests
from dotenv import load_dotenv
import json
load_dotenv()
FLUENTS_API_KEY = os.getenv("FLUENTS_API_KEY")
FLUENTS_API_URL = os.getenv("FLUENTS_API_URL", "https://api.fluents.ai")
def get_agent_safe(agent_id: str):
"""Safely retrieve agent details with proper error handling"""
headers = {
"Authorization": f"Bearer {FLUENTS_API_KEY}",
"Content-Type": "application/json"
}
try:
print(f"[1/3] Fetching agent details: {agent_id}")
response = requests.get(
f"{FLUENTS_API_URL}/v1/agents",
params={"id": agent_id},
headers=headers,
timeout=30
)
if response.status_code == 404:
print(f"✗ Agent not found: {agent_id}", file=sys.stderr)
print(f" Please verify the agent ID is correct", file=sys.stderr)
return None
response.raise_for_status()
agent = response.json()
print(f"✓ Agent found: {agent.get('name', 'Unknown')}")
return agent
except requests.exceptions.RequestException as e:
print(f"✗ Failed to get agent: {e}", file=sys.stderr)
if hasattr(e, 'response') and e.response is not None:
print(f" Status: {e.response.status_code}", file=sys.stderr)
print(f" Response: {e.response.text[:200]}", file=sys.stderr)
return None
def make_call_safe(
agent_id: str,
to_number: str,
from_number: str,
telephony_provider: str = "twilio",
context: dict = None
):
"""
Safely initiate a phone call with comprehensive error handling
"""
if not FLUENTS_API_KEY:
print("✗ FLUENTS_API_KEY not found in environment variables", file=sys.stderr)
return None
# Step 1: Get agent details
agent = get_agent_safe(agent_id)
if not agent:
return None
# Step 2: Prepare call payload
print(f"\n[2/3] Preparing call...")
print(f" From: {from_number}")
print(f" To: {to_number}")
print(f" Agent: {agent.get('name', 'Unknown')}")
headers = {
"Authorization": f"Bearer {FLUENTS_API_KEY}",
"Content-Type": "application/json"
}
payload = {
"to_number": to_number,
"from_number": from_number,
"agent_phone_number": from_number,
"agent": agent,
"telephony_provider": {
"name": telephony_provider
},
"context": context or {},
"telephony_params": {},
"is_outgoing": True,
"run_do_not_call_detection": False
}
# Step 3: Make the call
try:
print(f"\n[3/3] Initiating call...")
response = requests.post(
f"{FLUENTS_API_URL}/v1/calls/create",
json=payload,
headers=headers,
timeout=30
)
# Better error handling
if response.status_code == 500:
print(f"✗ Server error (500) - This might be a temporary issue", file=sys.stderr)
print(f" Response: {response.text[:300]}", file=sys.stderr)
print(f"\nTroubleshooting tips:", file=sys.stderr)
print(f" 1. Check if your telephony provider is properly configured", file=sys.stderr)
print(f" 2. Verify your from_number is valid and active", file=sys.stderr)
print(f" 3. Try again in a few moments", file=sys.stderr)
return None
response.raise_for_status()
result = response.json()
print(f"\n✓ Call initiated successfully!")
print(f" Call ID: {result.get('id')}")
print(f" Status: {result.get('status')}")
print(f" Telephony ID: {result.get('telephony_id', 'N/A')}")
return result
except requests.exceptions.RequestException as e:
print(f"\n✗ Failed to initiate call: {e}", file=sys.stderr)
if hasattr(e, 'response') and e.response is not None:
print(f" Status: {e.response.status_code}", file=sys.stderr)
try:
error_data = e.response.json()
print(f" Error: {json.dumps(error_data, indent=2)}", file=sys.stderr)
except:
print(f" Response: {e.response.text[:300]}", file=sys.stderr)
return None
def main():
parser = argparse.ArgumentParser(description="Make a phone call with fluents.ai (robust version)")
parser.add_argument("--agent-id", required=True, help="Phone agent ID")
parser.add_argument("--to-number", required=True, help="Target phone number with country code")
parser.add_argument("--from-number", required=True, help="Caller ID number with country code")
parser.add_argument("--telephony-provider", default="twilio", help="Telephony provider")
parser.add_argument("--context", help="JSON string with call context data")
args = parser.parse_args()
# Parse context if provided
context = None
if args.context:
try:
context = json.loads(args.context)
except json.JSONDecodeError as e:
print(f"⚠ Warning: Invalid JSON for context: {e}", file=sys.stderr)
print(f" Using empty context", file=sys.stderr)
result = make_call_safe(
agent_id=args.agent_id,
to_number=args.to_number,
from_number=args.from_number,
telephony_provider=args.telephony_provider,
context=context
)
if result:
print(f"\n📞 Call ID for tracking: {result.get('id')}")
sys.exit(0)
else:
print(f"\n❌ Call failed - see errors above", file=sys.stderr)
sys.exit(1)
if __name__ == "__main__":
main()
#!/usr/bin/env python3
"""
Make a phone call using the fluents.ai API
"""
import os
import sys
import argparse
import requests
from dotenv import load_dotenv
load_dotenv()
FLUENTS_API_KEY = os.getenv("FLUENTS_API_KEY")
FLUENTS_API_URL = os.getenv("FLUENTS_API_URL", "https://api.fluents.ai")
WEBHOOK_URL = os.getenv("WEBHOOK_URL")
def get_agent(agent_id: str):
"""Retrieve agent details"""
headers = {
"Authorization": f"Bearer {FLUENTS_API_KEY}",
"Content-Type": "application/json"
}
response = requests.get(
f"{FLUENTS_API_URL}/v1/agents",
params={"id": agent_id},
headers=headers,
timeout=30
)
response.raise_for_status()
return response.json()
def make_call(
agent_id: str,
to_number: str,
from_number: str,
telephony_provider: str = "twilio",
context: dict = None
):
"""
Initiate a phone call with the specified agent
Args:
agent_id: The ID of the phone agent to use
to_number: Target phone number (with country code, e.g., +1234567890)
from_number: Caller ID number (with country code, e.g., +0987654321)
telephony_provider: Telephony provider name (default: twilio)
context: Optional context data for the call
Returns:
dict: Call initiation response with call_id
"""
if not FLUENTS_API_KEY:
raise ValueError("FLUENTS_API_KEY not found in environment variables")
headers = {
"Authorization": f"Bearer {FLUENTS_API_KEY}",
"Content-Type": "application/json"
}
# Get agent details
print(f"Fetching agent details for: {agent_id}")
agent = get_agent(agent_id)
# Construct the payload according to fluents.ai API spec
payload = {
"to_number": to_number,
"from_number": from_number,
"agent_phone_number": from_number,
"agent": agent,
"telephony_provider": {
"name": telephony_provider
},
"context": context or {},
"telephony_params": {},
"is_outgoing": True,
"run_do_not_call_detection": False
}
try:
response = requests.post(
f"{FLUENTS_API_URL}/v1/calls/create",
json=payload,
headers=headers,
timeout=30
)
response.raise_for_status()
result = response.json()
print(f"✓ Call initiated successfully")
print(f" Call ID: {result.get('id')}")
print(f" Status: {result.get('status')}")
print(f" From: {from_number}")
print(f" To: {to_number}")
return result
except requests.exceptions.RequestException as e:
print(f"✗ Failed to initiate call: {e}", file=sys.stderr)
if hasattr(e.response, 'text'):
print(f" Response: {e.response.text}", file=sys.stderr)
sys.exit(1)
def main():
parser = argparse.ArgumentParser(description="Make a phone call with fluents.ai")
parser.add_argument("--agent-id", required=True, help="Phone agent ID")
parser.add_argument("--to-number", required=True, help="Target phone number with country code (e.g., +1234567890)")
parser.add_argument("--from-number", required=True, help="Caller ID number with country code (e.g., +0987654321)")
parser.add_argument("--telephony-provider", default="twilio", help="Telephony provider (default: twilio)")
parser.add_argument("--context", help="JSON string with call context data")
args = parser.parse_args()
# Parse context if provided
context = None
if args.context:
import json
try:
context = json.loads(args.context)
except json.JSONDecodeError:
print("Warning: Invalid JSON for context, using empty context", file=sys.stderr)
result = make_call(
agent_id=args.agent_id,
to_number=args.to_number,
from_number=args.from_number,
telephony_provider=args.telephony_provider,
context=context
)
# Output call_id for tracking
print(f"\nCall ID: {result.get('id')}")
if __name__ == "__main__":
main()
#!/bin/bash
#
# Phone Call Skill - Unified Entry Point
#
# This script provides a simple interface for making phone calls with AI agents.
# It handles the complete workflow: create agent → make call → analyze results.
#
# Usage:
# ./phone_call.sh --to "+1234567890" --purpose "Make a dinner reservation for 2 at 8pm"
# ./phone_call.sh --agent-id "xxx" --to "+1234567890"
# ./phone_call.sh --analyze "call-id-xxx"
#
set -e # Exit on error
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
# Load environment variables
if [ -f "$PROJECT_DIR/.env" ]; then
export $(cat "$PROJECT_DIR/.env" | grep -v '^#' | xargs)
fi
# Default values
FROM_NUMBER="${FLUENTS_FROM_NUMBER:-+15103982646}"
LANGUAGE="${FLUENTS_LANGUAGE:-en}"
ACTION=""
AGENT_ID=""
TO_NUMBER=""
PURPOSE=""
CALL_ID=""
# Parse arguments
while [[ $# -gt 0 ]]; do
case $1 in
--to)
TO_NUMBER="$2"
shift 2
;;
--from)
FROM_NUMBER="$2"
shift 2
;;
--purpose)
PURPOSE="$2"
shift 2
;;
--agent-id)
AGENT_ID="$2"
ACTION="call"
shift 2
;;
--analyze)
CALL_ID="$2"
ACTION="analyze"
shift 2
;;
--diagnose)
ACTION="diagnose"
shift
;;
--help|-h)
ACTION="help"
shift
;;
*)
echo "Unknown option: $1"
echo "Use --help for usage information"
exit 1
;;
esac
done
# Help message
if [ "$ACTION" == "help" ] || [ -z "$ACTION" ]; then
cat << 'EOF'
Phone Call Skill - AI-Powered Phone Calls
USAGE:
# Make a call (creates agent automatically)
./phone_call.sh --to "+1234567890" --purpose "Your call objective"
# Make a call with existing agent
./phone_call.sh --agent-id "agent-xxx" --to "+1234567890"
# Analyze a completed call
./phone_call.sh --analyze "call-id-xxx"
# Diagnose your setup
./phone_call.sh --diagnose
OPTIONS:
--to NUMBER Target phone number (with country code, e.g., +1234567890)
--from NUMBER Caller ID number (default: configured number)
--purpose TEXT Purpose of the call (used to create agent)
--agent-id ID Use existing agent instead of creating new one
--analyze ID Analyze results of a completed call
--diagnose Check system configuration
--help, -h Show this help message
EXAMPLES:
# Restaurant reservation
./phone_call.sh \
--to "+16576102352" \
--purpose "Make a dinner reservation for 2 people tonight at 8 PM. Name: John Smith, Phone: 310-555-1234"
# Use existing agent
./phone_call.sh \
--agent-id "c7c4716a-7b7c-44c8-97a7-3872a34187fe" \
--to "+16576102352"
# Analyze call results
./phone_call.sh --analyze "call-abc123"
ENVIRONMENT:
Set these in .env file:
- FLUENTS_API_KEY (required)
- FLUENTS_FROM_NUMBER (your phone number)
- FLUENTS_API_URL (default: https://api.fluents.ai)
MORE INFO:
See SKILL.md for detailed documentation
GitHub: https://github.com/teamily-ai/phone-call-skill
EOF
exit 0
fi
# Diagnose
if [ "$ACTION" == "diagnose" ]; then
echo "Running diagnostics..."
python3 "$SCRIPT_DIR/diagnose.py"
exit $?
fi
# Analyze
if [ "$ACTION" == "analyze" ]; then
if [ -z "$CALL_ID" ]; then
echo "Error: --analyze requires a call ID"
exit 1
fi
echo "Analyzing call: $CALL_ID"
python3 "$SCRIPT_DIR/analyze_call.py" --call-id "$CALL_ID"
exit $?
fi
# Make a call
if [ -z "$TO_NUMBER" ]; then
echo "Error: --to is required"
echo "Use --help for usage information"
exit 1
fi
# Create agent if needed
if [ -z "$AGENT_ID" ]; then
if [ -z "$PURPOSE" ]; then
echo "Error: --purpose is required when creating a new agent"
echo "Use --help for usage information"
exit 1
fi
echo "=================================="
echo "STEP 1: Creating Agent"
echo "=================================="
echo "Purpose: $PURPOSE"
echo ""
# Generate appropriate initial message based on purpose
# This tells the recipient WHO is calling and WHY
INITIAL_MSG=""
PURPOSE_LOWER=$(echo "$PURPOSE" | tr '[:upper:]' '[:lower:]')
if echo "$PURPOSE_LOWER" | grep -q "reserv\|book"; then
INITIAL_MSG="Hello, I'm calling to make a reservation."
elif echo "$PURPOSE_LOWER" | grep -q "confirm.*\(meeting\|appointment\)"; then
INITIAL_MSG="Hello, I'm calling to confirm an appointment."
elif echo "$PURPOSE_LOWER" | grep -q "follow.up\|follow-up"; then
INITIAL_MSG="Hello, I'm calling to follow up with you."
elif echo "$PURPOSE_LOWER" | grep -q "reminder"; then
INITIAL_MSG="Hello, I'm calling with a reminder."
elif echo "$PURPOSE_LOWER" | grep -q "order"; then
INITIAL_MSG="Hello, I'm calling about an order."
else
# Extract first few words of purpose as opening
FIRST_WORDS=$(echo "$PURPOSE" | cut -d'.' -f1 | head -c 80)
INITIAL_MSG="Hello, I'm calling regarding: $FIRST_WORDS"
fi
echo "Opening message: $INITIAL_MSG"
echo ""
# Create agent and capture output
AGENT_OUTPUT=$(python3 "$SCRIPT_DIR/create_agent.py" \
--name "Auto-generated: $(date +%Y%m%d-%H%M%S)" \
--prompt "$PURPOSE" \
--initial-message "$INITIAL_MSG" \
--language "$LANGUAGE" 2>&1)
echo "$AGENT_OUTPUT"
# Extract agent ID from output
AGENT_ID=$(echo "$AGENT_OUTPUT" | grep "Agent ID:" | tail -1 | awk '{print $3}')
if [ -z "$AGENT_ID" ]; then
echo ""
echo "Error: Failed to create agent"
exit 1
fi
echo ""
echo "✓ Agent created: $AGENT_ID"
echo ""
fi
# Make the call
echo "=================================="
echo "STEP 2: Making Call"
echo "=================================="
echo "From: $FROM_NUMBER"
echo "To: $TO_NUMBER"
echo "Agent: $AGENT_ID"
echo ""
CALL_OUTPUT=$(python3 "$SCRIPT_DIR/make_call_simple.py" \
--agent-id "$AGENT_ID" \
--to-number "$TO_NUMBER" \
--from-number "$FROM_NUMBER" 2>&1)
echo "$CALL_OUTPUT"
# Extract call ID
CALL_ID=$(echo "$CALL_OUTPUT" | grep "Call ID:" | grep -v "for tracking" | tail -1 | awk '{print $3}')
if [ -z "$CALL_ID" ]; then
echo ""
echo "Error: Failed to initiate call"
exit 1
fi
echo ""
echo "✓ Call initiated: $CALL_ID"
echo ""
# Wait for call to complete
echo "=================================="
echo "STEP 3: Waiting for Call"
echo "=================================="
echo "Waiting for call to complete..."
echo "(This usually takes 30-120 seconds)"
echo ""
MAX_WAIT=300 # 5 minutes max
WAIT_TIME=0
INTERVAL=15
while [ $WAIT_TIME -lt $MAX_WAIT ]; do
sleep $INTERVAL
WAIT_TIME=$((WAIT_TIME + INTERVAL))
# Check call status
STATUS=$(python3 -c "
import requests, os, sys
headers = {'Authorization': f'Bearer {os.getenv(\"FLUENTS_API_KEY\")}'}
try:
r = requests.get('$FLUENTS_API_URL/v1/calls', params={'id': '$CALL_ID'}, headers=headers, timeout=10)
data = r.json()
print(data.get('status', 'unknown'))
except:
print('error')
" 2>/dev/null)
echo " Status: $STATUS (waited ${WAIT_TIME}s)"
if [ "$STATUS" == "ended" ]; then
echo ""
echo "✓ Call completed!"
break
elif [ "$STATUS" == "error" ]; then
echo ""
echo "✗ Call failed"
break
fi
done
echo ""
# Analyze results
echo "=================================="
echo "STEP 4: Analyzing Results"
echo "=================================="
echo ""
python3 "$SCRIPT_DIR/analyze_call.py" --call-id "$CALL_ID"
echo ""
echo "=================================="
echo "COMPLETE"
echo "=================================="
echo "Call ID: $CALL_ID"
echo "Agent ID: $AGENT_ID"
echo ""
echo "To re-analyze this call later:"
echo " ./phone_call.sh --analyze \"$CALL_ID\""
echo ""
#!/usr/bin/env python3
"""
Test connection to fluents.ai API
"""
import os
import sys
import requests
from dotenv import load_dotenv
load_dotenv()
FLUENTS_API_KEY = os.getenv("FLUENTS_API_KEY")
FLUENTS_API_URL = os.getenv("FLUENTS_API_URL", "https://api.fluents.ai")
def test_connection():
"""Test API connection and authentication"""
print("Testing connection to fluents.ai API...\n")
# Check if API key is set
if not FLUENTS_API_KEY:
print("✗ FLUENTS_API_KEY not found in environment variables")
print(" Please set FLUENTS_API_KEY in your .env file")
sys.exit(1)
print(f"✓ API Key found")
print(f" API URL: {FLUENTS_API_URL}")
# Test API connection
headers = {
"Authorization": f"Bearer {FLUENTS_API_KEY}",
"Content-Type": "application/json"
}
try:
# Try to list agents as a connectivity test
response = requests.get(
f"{FLUENTS_API_URL}/v1/agents/list",
params={"page": 1, "size": 1},
headers=headers,
timeout=10
)
if response.status_code == 200:
print(f"✓ API connection successful")
print(f" Status: {response.status_code}")
print(f" API is accessible and authenticated")
return True
elif response.status_code == 401 or response.status_code == 403:
print(f"✗ Authentication failed")
print(f" Status: {response.status_code}")
print(f" Please check your API key")
return False
else:
print(f"⚠ Unexpected response: {response.status_code}")
print(f" Response: {response.text}")
return False
except requests.exceptions.ConnectionError:
print(f"✗ Connection failed")
print(f" Cannot reach {FLUENTS_API_URL}")
print(f" Please check your internet connection")
return False
except requests.exceptions.Timeout:
print(f"✗ Connection timeout")
print(f" The API server is not responding")
return False
except Exception as e:
print(f"✗ Unexpected error: {e}")
return False
def main():
success = test_connection()
sys.exit(0 if success else 1)
if __name__ == "__main__":
main()