Add Phone Calls to Your CrewAI Agent
What You're Building
One of your crew's tasks requires calling a business by phone. Book an appointment, verify insurance, get a quote, follow up on a delivery.
This post gives you a PhoneCallTool for CrewAI and a dedicated "caller" agent. The caller handles phone interactions while other agents focus on research, analysis, or coordination.
Prerequisites:
- Python 3.10+
pip install crewai httpx- An AgentPhone API key (get one free — 5 calls, no credit card)
The Phone Tool
CrewAI tools extend BaseTool from crewai.tools. Here's the complete implementation:
import os
import time
import httpx
from crewai.tools import BaseTool
from pydantic import BaseModel, Field
AGENTPHONE_API_KEY = os.environ["AGENTPHONE_API_KEY"]
AGENTPHONE_BASE = "https://agentphone.app/api/v1"
AGENTPHONE_HEADERS = {
"x-api-key": AGENTPHONE_API_KEY,
"Content-Type": "application/json",
}
class PhoneCallInput(BaseModel):
to_phone_number: str = Field(
description="Phone number in E.164 format (e.g. +14155551234)"
)
objective: str = Field(
description="What the call should accomplish. Be specific — include "
"names, dates, reference numbers, and relevant details."
)
business_name: str = Field(
default="",
description="Name of the business being called."
)
class PhoneCallTool(BaseTool):
name: str = "Place Phone Call"
description: str = (
"Place a real phone call to a business or person. Use for booking "
"appointments, verifying information, following up on orders, getting "
"quotes, or checking availability. Returns the call outcome, a "
"summary of what happened, and the full transcript."
)
args_schema: type[BaseModel] = PhoneCallInput
def _run(
self,
to_phone_number: str,
objective: str,
business_name: str = "",
) -> str:
# Create the call
response = httpx.post(
f"{AGENTPHONE_BASE}/calls",
headers=AGENTPHONE_HEADERS,
json={
"to_phone_number": to_phone_number,
"objective": objective,
"business_name": business_name,
},
)
if response.status_code != 202:
return f"Failed to create call: {response.json()}"
call_id = response.json()["data"]["call_id"]
# Poll until done
while True:
time.sleep(4)
result = httpx.get(
f"{AGENTPHONE_BASE}/calls/{call_id}",
headers=AGENTPHONE_HEADERS,
).json()["data"]
if result["status"] in ("completed", "failed", "canceled"):
break
return (
f"Call Status: {result['status']}\n"
f"Outcome: {result.get('outcome', 'unknown')}\n"
f"Details: {result.get('outcome_details', '')}\n"
f"Summary: {result.get('summary', '')}\n"
f"Duration: {result.get('duration_seconds', 0)}s\n"
f"Transcript:\n{result.get('transcript', 'N/A')}"
)
CrewAI tools return strings. The formatted result gives the agent all the information it needs to continue the workflow.
The Caller Agent
CrewAI's strength is role specialization. Create a dedicated caller agent:
from crewai import Agent
phone_tool = PhoneCallTool()
caller = Agent(
role="Phone Coordinator",
goal="Place phone calls to businesses and extract the information or "
"confirmations the crew needs. Report outcomes clearly.",
backstory=(
"You're the crew member who handles all phone interactions. When "
"someone in the crew needs to call a business — to book, verify, "
"follow up, or gather information — you handle it. You're direct, "
"efficient, and always report back with specific details."
),
tools=[phone_tool],
verbose=True,
)
The role, goal, and backstory prime the agent for phone-specific tasks. It knows it's the phone specialist in the crew.
A Complete Crew: Appointment Scheduling
A researcher finds the business, a caller books the appointment, and a reporter compiles the results. The context parameter chains task outputs — the caller gets the researcher's findings, and the reporter gets everything.
from crewai import Agent, Task, Crew
from crewai_tools import SerperDevTool
search_tool = SerperDevTool()
phone_tool = PhoneCallTool()
researcher = Agent(
role="Business Researcher",
goal="Find business phone numbers, hours, and relevant details.",
backstory="You research local businesses to find accurate contact "
"information for the crew.",
tools=[search_tool],
verbose=True,
)
caller = Agent(
role="Phone Coordinator",
goal="Place phone calls to book appointments and gather information.",
backstory="You handle all phone calls for the crew. You're efficient "
"and always confirm key details before ending a call.",
tools=[phone_tool],
verbose=True,
)
reporter = Agent(
role="Results Reporter",
goal="Compile research and call results into a clear summary.",
backstory="You take the outputs from the researcher and caller and "
"present a clear, organized summary for the user.",
verbose=True,
)
research_task = Task(
description=(
"Find the phone number and business hours for {business_name} "
"in {location}. Get the closest location."
),
expected_output="Phone number in E.164 format and business hours.",
agent=researcher,
)
call_task = Task(
description=(
"Call {business_name} using the phone number from the researcher. "
"Objective: {objective}. Context: {context}."
),
expected_output="Call outcome with confirmed details.",
agent=caller,
context=[research_task],
)
summary_task = Task(
description="Compile research and call results into a clear summary.",
expected_output="Organized summary of what was accomplished.",
agent=reporter,
context=[research_task, call_task],
)
crew = Crew(
agents=[researcher, caller, reporter],
tasks=[research_task, call_task, summary_task],
verbose=True,
)
result = crew.kickoff(inputs={
"business_name": "Sunset Veterinary Clinic",
"location": "San Francisco",
"objective": "Schedule a wellness check for a 3-year-old Labrador",
"context": "Dog's name is Max. Owner name is Jamie. "
"Prefer next Tuesday or Wednesday afternoon.",
})
Multi-Call Crew: Insurance + Scheduling
CrewAI shines when the crew needs to make multiple sequential calls where each builds on the last:
verify_task = Task(
description=(
"Call the insurance company at {insurance_phone} to verify coverage "
"for {patient_name}. Check:\n"
"1. Is the plan active?\n"
"2. What's the copay for {procedure}?\n"
"3. Is pre-authorization required?\n"
"4. Get a reference number for the verification."
),
expected_output=(
"Coverage status, copay amount, pre-auth requirement, "
"and reference number."
),
agent=caller,
)
schedule_task = Task(
description=(
"Based on the insurance verification, call {provider_phone} to "
"schedule {procedure} for {patient_name}. Mention that insurance "
"was verified and provide the reference number from the previous call. "
"Preferred dates: {preferred_dates}."
),
expected_output=(
"Confirmed appointment date, time, provider name, "
"and any prep instructions."
),
agent=caller,
context=[verify_task],
)
The caller makes two calls in sequence. The second call uses information from the first — the reference number, coverage status, copay amount. The crew handles the dependency automatically through the context parameter.
What Crews Can Build
- Healthcare coordination — Verify insurance → schedule appointment → confirm with patient. Three sequential calls, each depending on the last.
- Vendor procurement — Research suppliers → call 3 for quotes → compare → call winner to place order.
- Event planning — Find venues → call for availability → negotiate pricing → book.
- Lead qualification — Pull leads from CRM → call to verify interest → update CRM with status.
- Property management — Identify maintenance needs → call contractors for quotes → schedule repairs.
Each workflow uses the same building blocks: a researcher, a caller, and a reporter (or coordinator). The caller makes calls; the other agents handle everything else.
Getting Started
Get an API key at agentphone.app (5 free calls, no credit card). API reference: agentphone.app/docs.
Other frameworks: OpenAI Agents SDK | LangChain | MCP
