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.
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.
See AI Voice Agents Handle Real Calls
Book a free demo or calculate how much you can save with AI voice automation.
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", "sarah@firm.com", backup_contact="m2"
))
directory.add_member(StaffMember(
"m2", "James Rodriguez", "Office Manager", "Operations",
"102", "james@firm.com"
))
directory.add_member(StaffMember(
"m3", "Priya Patel", "Senior Accountant", "Finance",
"103", "priya@firm.com", 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.
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
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.