Skip to content
Building a 24/7 Answering Service Agent for Small Businesses: Never Miss a Call
Learn Agentic AI11 min read8 views

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.

flowchart LR
    subgraph HUMAN["Human Receptionist"]
        H1["8 hours per day<br/>limited language coverage"]
        H2["Salary plus benefits<br/>3,000 to 5,000 per month"]
        H3["Sick days, holidays,<br/>turnover"]
        H4["Call notes typed<br/>manually into CRM"]
    end
    subgraph AI["CallSphere AI Voice Agent"]
        A1["24 by 7 coverage<br/>57 plus languages"]
        A2["Flat fee from 199<br/>per month, unlimited calls"]
        A3["Zero turnover, instant<br/>script updates"]
        A4["Auto written CRM<br/>notes plus sentiment"]
    end
    H1 -.->|Upgrade| A1
    H2 -.->|Upgrade| A2
    H3 -.->|Upgrade| A3
    H4 -.->|Upgrade| A4
    style HUMAN fill:#fee2e2,stroke:#dc2626,color:#7f1d1d
    style AI fill:#dcfce7,stroke:#059669,color:#064e3b
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.

Hear it before you finish reading

Talk to a live CallSphere AI voice agent in your browser — 60 seconds, no signup.

Try Live Demo →
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.

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.

Still reading? Stop comparing — try CallSphere live.

CallSphere ships complete AI voice agents per industry — 14 tools for healthcare, 10 agents for real estate, 4 specialists for salons. See how it actually handles a call before you book a demo.

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

Share

Try CallSphere AI Voice Agents

See how AI voice agents work for your industry. Live demo available -- no signup required.