Skip to content
Learn Agentic AI
Learn Agentic AI11 min read6 views

Building an AI Receptionist: Front Desk Automation for Small Offices

Learn how to build an AI receptionist agent that greets visitors, routes calls to the right staff member, manages visitor sign-ins, and handles package deliveries for small office environments.

The Modern Small Office Front Desk Problem

Small offices with five to fifty employees rarely justify a full-time receptionist, yet someone still needs to answer the phone, greet visitors, accept deliveries, and direct people to the right room. These tasks typically fall on whoever happens to be nearby — pulling accountants, engineers, or managers away from their actual work. An AI receptionist handles these routine interactions consistently, freeing the team to focus.

This guide builds a multi-function receptionist agent that manages calls, visitors, and deliveries through a unified interface.

Staff Directory and Routing Model

The receptionist needs to know who works in the office, their roles, their availability, and how to reach them. We model this as a staff directory with presence tracking.

Hear it before you finish reading

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

Try Live Demo →
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
from datetime import datetime

class PresenceStatus(Enum):
    AVAILABLE = "available"
    IN_MEETING = "in_meeting"
    OUT_OF_OFFICE = "out_of_office"
    DO_NOT_DISTURB = "do_not_disturb"
    LUNCH = "lunch"

@dataclass
class StaffMember:
    id: str
    name: str
    title: str
    department: str
    extension: str
    email: str
    status: PresenceStatus = PresenceStatus.AVAILABLE
    backup_contact: Optional[str] = None  # another staff ID

@dataclass
class VisitorRecord:
    name: str
    company: str
    visiting: str  # staff member ID
    purpose: str
    badge_number: Optional[str] = None
    check_in: datetime = field(default_factory=datetime.now)
    check_out: Optional[datetime] = None

@dataclass
class PackageRecord:
    tracking_number: str
    carrier: str
    recipient_id: str
    received_at: datetime = field(default_factory=datetime.now)
    picked_up: bool = False

Staff Directory Service

The directory service acts as the central lookup for the receptionist. It supports searching by name, department, or role.

class StaffDirectory:
    def __init__(self):
        self.staff: dict[str, StaffMember] = {}
        self.visitors: list[VisitorRecord] = []
        self.packages: list[PackageRecord] = []

    def add_member(self, member: StaffMember):
        self.staff[member.id] = member

    def find_by_name(self, query: str) -> list[StaffMember]:
        query_lower = query.lower()
        return [
            s for s in self.staff.values()
            if query_lower in s.name.lower()
            or query_lower in s.title.lower()
        ]

    def find_by_department(self, dept: str) -> list[StaffMember]:
        return [
            s for s in self.staff.values()
            if dept.lower() in s.department.lower()
        ]

    def get_routing_target(self, staff_id: str) -> dict:
        member = self.staff.get(staff_id)
        if not member:
            return {"action": "not_found"}
        if member.status == PresenceStatus.AVAILABLE:
            return {
                "action": "transfer",
                "extension": member.extension,
                "message": f"Connecting you to {member.name} now.",
            }
        if member.status == PresenceStatus.IN_MEETING:
            backup = self.staff.get(member.backup_contact)
            return {
                "action": "take_message",
                "message": (
                    f"{member.name} is in a meeting. "
                    + (f"I can connect you to {backup.name} instead, "
                       if backup else "")
                    + "or I can take a message."
                ),
            }
        return {
            "action": "take_message",
            "message": f"{member.name} is currently unavailable. Let me take a message.",
        }

directory = StaffDirectory()
directory.add_member(StaffMember(
    "m1", "Sarah Chen", "Managing Partner", "Leadership",
    "101", "[email protected]", backup_contact="m2"
))
directory.add_member(StaffMember(
    "m2", "James Rodriguez", "Office Manager", "Operations",
    "102", "[email protected]"
))
directory.add_member(StaffMember(
    "m3", "Priya Patel", "Senior Accountant", "Finance",
    "103", "[email protected]", backup_contact="m2"
))

Receptionist Agent Tools

from agents import Agent, Runner, function_tool

@function_tool
def lookup_staff(query: str) -> str:
    """Find a staff member by name, title, or department."""
    results = directory.find_by_name(query)
    if not results:
        results = directory.find_by_department(query)
    if not results:
        return "No staff member found matching that query."
    lines = []
    for s in results:
        lines.append(f"{s.name} - {s.title} ({s.department}) - Status: {s.status.value}")
    return "\n".join(lines)

@function_tool
def route_call(staff_id: str) -> str:
    """Route a call to a specific staff member based on their availability."""
    routing = directory.get_routing_target(staff_id)
    return routing.get("message", "Unable to route call.")

@function_tool
def check_in_visitor(
    visitor_name: str, company: str,
    host_staff_id: str, purpose: str
) -> str:
    """Register a visitor and notify the host staff member."""
    host = directory.staff.get(host_staff_id)
    if not host:
        return "Host not found. Please verify the name."
    record = VisitorRecord(
        name=visitor_name, company=company,
        visiting=host_staff_id, purpose=purpose,
        badge_number=f"V-{len(directory.visitors) + 1:03d}",
    )
    directory.visitors.append(record)
    return (
        f"Welcome, {visitor_name}. Your visitor badge is {record.badge_number}. "
        f"I have notified {host.name} that you have arrived. "
        f"Please have a seat in the lobby."
    )

@function_tool
def log_package(
    tracking_number: str, carrier: str, recipient_name: str
) -> str:
    """Log an incoming package and notify the recipient."""
    results = directory.find_by_name(recipient_name)
    if not results:
        return f"No staff member named '{recipient_name}' found."
    recipient = results[0]
    record = PackageRecord(
        tracking_number=tracking_number,
        carrier=carrier,
        recipient_id=recipient.id,
    )
    directory.packages.append(record)
    return (
        f"Package logged: {carrier} tracking {tracking_number} "
        f"for {recipient.name}. Notification sent to {recipient.email}."
    )

The Receptionist Agent

receptionist = Agent(
    name="Office Receptionist",
    instructions="""You are the front desk receptionist for a small professional office.

For phone calls:
1. Greet the caller professionally.
2. Ask who they are trying to reach. Use lookup_staff to find the person.
3. Use route_call to connect them or offer to take a message.

For visitors:
1. Welcome them and ask their name, company, and who they are visiting.
2. Use check_in_visitor to register them and issue a badge.

For deliveries:
1. Ask for the tracking number, carrier, and recipient name.
2. Use log_package to record the delivery and notify the recipient.

Always be warm but professional. If unsure who a caller needs,
ask clarifying questions about the nature of their inquiry to narrow
down the right department.""",
    tools=[lookup_staff, route_call, check_in_visitor, log_package],
)

result = Runner.run_sync(
    receptionist,
    "Hi, I have a meeting with Sarah about our quarterly taxes.",
)
print(result.final_output)

Handling Ambiguous Requests

Callers rarely say "Connect me to staff ID m3." They say "I need to talk to someone about my taxes" or "Is the boss available?" The agent instructions handle this naturally — the LLM maps "taxes" to the Finance department and "the boss" to the Managing Partner. The lookup_staff tool supports searching by title and department, not just name, which covers most ambiguous cases.

FAQ

How does the agent handle multiple visitors arriving at the same time?

Each visitor interaction is an independent agent run. If the system receives multiple check-in requests simultaneously, they execute in parallel, each producing its own badge number and notification. The visitor list is append-only, so there are no concurrency conflicts in the check-in process itself.

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.

Can I integrate this with a real calendar system?

Yes. Replace the static PresenceStatus with a live lookup against Google Calendar or Microsoft Outlook via their APIs. Before routing a call, the agent tool queries the calendar to determine whether the staff member is in a meeting, then updates the routing decision accordingly.

How do I handle sensitive visitor information for compliance?

Add a data retention policy to the VisitorRecord model — automatically purge records after 90 days. For HIPAA or SOC 2 environments, encrypt the visitor log at rest and restrict access to the visitors list through role-based permissions on the API layer.


#AIReceptionist #OfficeAutomation #CallRouting #VisitorManagement #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.

Related Articles You May Like

AI Strategy

Total Cost of Ownership: AI Receptionist Over 24 Months in 2026

AI receptionist TCO can swing 10x by pricing model. Most SMBs pay $199-$299/month for full-featured, and a 24-month all-in TCO lands at $4.7K-$7.2K — vs $100K+ for a human seat. Here is the line-by-line model.

Agentic AI

Building Your First Agent with the OpenAI Agents SDK in 2026: A Hands-On Walkthrough

Step-by-step build of a working agent with the OpenAI Agents SDK — Agent class, tools, handoffs, tracing — plus an eval pipeline that catches regressions before merge.

Agentic AI

Smolagents: Hugging Face's Code-First Agent Framework Reviewed

Smolagents lets agents write Python instead of JSON. Why code-as-action reduces tool errors and where the security trade-offs are for production deployments.

AI Voice Agents

How Retail Stores in Las Vegas Use AI Voice Agents in 2026

Las Vegas retail inventory hit 70.7M SF in Q1 2026 with a 4.3% vacancy rate. Tourism + locals drive a unique multilingual call mix. Here is how a 2026 voice agent runs your storefront line.

AI Infrastructure

Deploy a Voice Agent on Modal with Python and Serverless GPU

Modal turns a Python function into autoscaling serverless compute with optional GPU. Deploy a LiveKit Agent with one command and get pay-per-second billing.

AI Voice Agents

How Logistics Companies in Austin Use AI Voice Agents in 2026

Texas freight is a $144B market in 2026, with 1,680 shippers in Austin alone. CHIPS Act fabs and construction freight drive premium calls. Here is the 2026 dispatcher-grade voice playbook.