Building a 24/7 Answering Service Agent for Small Businesses: Never Miss a Call
Learn how to build an AI-powered answering service agent that handles inbound calls around the clock, takes messages, answers FAQs, and routes urgent calls — so small businesses never lose a lead to voicemail.
The Cost of Missed Calls for Small Businesses
Research consistently shows that over 60 percent of callers who reach voicemail hang up without leaving a message. For a small business — a local plumber, a dental office, a boutique law firm — every missed call is a missed customer. Hiring a full-time receptionist costs $35,000 or more per year, and outsourced answering services charge per minute. An AI answering agent changes the equation entirely by providing around-the-clock coverage at a fraction of the cost.
In this tutorial you will build a production-ready answering service agent that handles inbound calls, answers frequently asked questions, captures caller information, and escalates urgent requests to on-call staff.
Core Architecture
The agent needs four capabilities: greeting and intent detection, FAQ answering, message taking, and after-hours routing. We model these as a state machine where each call progresses through stages.
from dataclasses import dataclass, field
from enum import Enum
from typing import Optional
import datetime
class CallStage(Enum):
GREETING = "greeting"
INTENT_DETECTION = "intent_detection"
FAQ_ANSWERING = "faq_answering"
MESSAGE_TAKING = "message_taking"
TRANSFER = "transfer"
WRAP_UP = "wrap_up"
@dataclass
class CallerInfo:
name: Optional[str] = None
phone: Optional[str] = None
email: Optional[str] = None
reason: Optional[str] = None
urgency: str = "normal"
timestamp: str = field(
default_factory=lambda: datetime.datetime.now().isoformat()
)
@dataclass
class CallSession:
caller: CallerInfo = field(default_factory=CallerInfo)
stage: CallStage = CallStage.GREETING
transcript: list[str] = field(default_factory=list)
faq_attempts: int = 0
needs_human: bool = False
Business Hours and Routing Logic
Small businesses operate on specific schedules, and the agent must behave differently during and after business hours. During open hours it can transfer to a live person. After hours it takes messages and flags emergencies.
from datetime import time
class BusinessHoursRouter:
def __init__(self, schedule: dict[str, tuple[time, time]]):
self.schedule = schedule
self.on_call_contacts = {}
def is_open(self) -> bool:
now = datetime.datetime.now()
day_name = now.strftime("%A").lower()
if day_name not in self.schedule:
return False
open_time, close_time = self.schedule[day_name]
return open_time <= now.time() <= close_time
def get_routing_action(self, urgency: str) -> dict:
if urgency == "emergency":
return {
"action": "transfer",
"target": self.on_call_contacts.get("emergency"),
"message": "Transferring you to our emergency line now.",
}
if self.is_open():
return {
"action": "transfer",
"target": "front_desk",
"message": "Let me connect you with someone who can help.",
}
return {
"action": "take_message",
"message": (
"We are currently closed. I will take a detailed "
"message and have someone call you back first thing."
),
}
router = BusinessHoursRouter(
schedule={
"monday": (time(8, 0), time(17, 0)),
"tuesday": (time(8, 0), time(17, 0)),
"wednesday": (time(8, 0), time(17, 0)),
"thursday": (time(8, 0), time(17, 0)),
"friday": (time(8, 0), time(16, 0)),
}
)
FAQ Knowledge Base
Rather than training a custom model, we store FAQ entries in a structured format and use semantic similarity to match caller questions. This approach keeps answers accurate and easy for business owners to update.
See AI Voice Agents Handle Real Calls
Book a free demo or calculate how much you can save with AI voice automation.
from agents import Agent, Runner, function_tool
FAQ_ENTRIES = [
{
"question": "What are your business hours?",
"answer": "We are open Monday through Thursday 8 AM to 5 PM, and Friday 8 AM to 4 PM.",
"keywords": ["hours", "open", "close", "schedule"],
},
{
"question": "Where are you located?",
"answer": "We are located at 123 Main Street, Suite 200, Springfield.",
"keywords": ["location", "address", "where", "directions"],
},
{
"question": "Do you accept walk-ins?",
"answer": "We accept walk-ins during business hours, but appointments are recommended to avoid wait times.",
"keywords": ["walk-in", "appointment", "drop in"],
},
]
@function_tool
def search_faq(query: str) -> str:
"""Search the business FAQ for an answer to the caller question."""
query_lower = query.lower()
for entry in FAQ_ENTRIES:
if any(kw in query_lower for kw in entry["keywords"]):
return entry["answer"]
return "NO_FAQ_MATCH"
@function_tool
def save_message(
name: str, phone: str, reason: str, urgency: str
) -> str:
"""Save a caller message for staff follow-up."""
# In production this writes to a database and sends notifications
return f"Message saved: {name} ({phone}) - {reason} [{urgency}]"
The Answering Agent
With tools defined, we assemble the agent with instructions that guide it through natural conversation while gathering the information a business needs.
answering_agent = Agent(
name="SmallBiz Answering Agent",
instructions="""You are a professional, friendly answering service agent
for a small business. Follow these rules:
1. Greet the caller warmly and ask how you can help.
2. If they ask a common question, use search_faq to find the answer.
3. If the FAQ does not have an answer, offer to take a message.
4. When taking a message, collect: full name, callback number, and
reason for calling.
5. If the caller describes an emergency (water leak, medical concern,
security issue), mark urgency as 'emergency' and explain you
will page someone immediately.
6. Always confirm details back to the caller before saving.
7. Be concise — callers value their time.""",
tools=[search_faq, save_message],
)
result = Runner.run_sync(
answering_agent,
"Hi, I have a leak in my basement and water is everywhere",
)
print(result.final_output)
The agent detects the emergency language, escalates urgency, and follows the routing logic to page on-call staff — all without custom NLP pipelines.
Message Delivery and Notifications
Taking a message is only useful if it reaches the right person promptly. A lightweight notification layer ensures messages are delivered via SMS or email within seconds.
import asyncio
async def deliver_message(message: dict, channel: str = "sms"):
"""Send captured message to staff via preferred channel."""
if channel == "sms":
# Integration with Twilio or similar
print(f"SMS to {message['staff_phone']}: New message from "
f"{message['caller_name']} - {message['reason']}")
elif channel == "email":
print(f"Email to {message['staff_email']}: {message['reason']}")
if message.get("urgency") == "emergency":
# Send to all on-call staff simultaneously
print("EMERGENCY: Paging all on-call staff")
FAQ
How many FAQ entries can the agent handle before performance degrades?
Keyword-based search works well up to a few hundred entries. For larger knowledge bases, switch to vector embedding search using a library like FAISS or a hosted solution like Pinecone. The agent tool interface stays the same — only the search implementation changes.
Can this agent handle multiple simultaneous calls?
Yes. Each call creates its own CallSession instance, and the agent framework handles concurrency. In production, deploy the agent behind an async web server like FastAPI so each inbound call webhook spawns an independent agent run.
How do I customize the agent for different types of businesses?
The two customization points are the FAQ entries and the agent instructions. A plumbing company emphasizes emergency detection, while a law firm focuses on confidentiality language. Update the instructions string and the FAQ_ENTRIES list — no code changes required.
#AIAnsweringService #SmallBusiness #CallHandling #VoiceAgent #Python #AgenticAI #LearnAI #AIEngineering
CallSphere Team
Expert insights on AI voice agents and customer communication automation.
Try CallSphere AI Voice Agents
See how AI voice agents work for your industry. Live demo available -- no signup required.