By Sagar Shankaran, Founder of CallSphere
Build an AI venue management agent that handles event inquiries, provides venue details, schedules tours, generates customized proposals, and manages automated follow-up sequences.
Key takeaways
Event venues receive dozens of inquiries daily — couples planning weddings, companies booking conferences, organizations hosting galas. Each inquiry requires understanding the event type, matching it to appropriate spaces, providing pricing, scheduling a site tour, and following up persistently. The average venue converts only 15 to 20 percent of inquiries because slow response times and inconsistent follow-up let prospects go cold.
An AI venue agent responds instantly to every inquiry, qualifies the lead, matches them to the right space, generates a customized proposal, and nurtures the relationship through automated follow-up — increasing conversion rates dramatically.
Hear it before you finish reading
Talk to a live CallSphere AI voice agent in your browser — 60 seconds, no signup.
from dataclasses import dataclass, field
from datetime import date, datetime, time, timedelta
from enum import Enum
from typing import Optional
class EventType(Enum):
WEDDING = "wedding"
CORPORATE = "corporate"
GALA = "gala"
CONFERENCE = "conference"
BIRTHDAY = "birthday"
FUNDRAISER = "fundraiser"
SOCIAL = "social"
class InquiryStatus(Enum):
NEW = "new"
QUALIFIED = "qualified"
TOUR_SCHEDULED = "tour_scheduled"
PROPOSAL_SENT = "proposal_sent"
NEGOTIATING = "negotiating"
BOOKED = "booked"
LOST = "lost"
@dataclass
class VenueSpace:
space_id: str
name: str
capacity_seated: int
capacity_standing: int
square_feet: int
indoor: bool
features: list[str] = field(default_factory=list)
suitable_for: list[EventType] = field(default_factory=list)
base_rental: float = 0.0
peak_rental: float = 0.0 # weekends, holidays
booked_dates: list[date] = field(default_factory=list)
def is_available(self, event_date: date) -> bool:
return event_date not in self.booked_dates
def get_rental_rate(self, event_date: date) -> float:
if event_date.weekday() in (4, 5, 6): # Fri-Sun
return self.peak_rental
return self.base_rental
@dataclass
class CateringOption:
name: str
price_per_person: float
description: str
min_guests: int = 20
@dataclass
class AddOn:
name: str
price: float
unit: str # flat, per_hour, per_person
description: str
@dataclass
class EventInquiry:
inquiry_id: str
contact_name: str
contact_email: str
contact_phone: str
event_type: EventType
event_date: Optional[date] = None
guest_count: int = 0
budget: float = 0.0
notes: str = ""
status: InquiryStatus = InquiryStatus.NEW
matched_spaces: list[str] = field(default_factory=list)
tour_datetime: Optional[datetime] = None
follow_ups: list[dict] = field(default_factory=list)
created_at: datetime = field(default_factory=datetime.now)
@dataclass
class Proposal:
inquiry_id: str
space: VenueSpace
event_date: date
guest_count: int
rental_fee: float
catering_total: float
addons_total: float
discount: float = 0.0
@property
def subtotal(self) -> float:
return self.rental_fee + self.catering_total + self.addons_total
@property
def total(self) -> float:
return round(self.subtotal * (1 - self.discount), 2)
from agents import Agent, function_tool
venue_spaces = [
VenueSpace("GH", "Grand Hall", 300, 500, 5000, True,
["stage", "dance floor", "chandeliers", "bridal suite"],
[EventType.WEDDING, EventType.GALA, EventType.FUNDRAISER],
5000.0, 8000.0),
VenueSpace("GR", "Garden Terrace", 150, 250, 3500, False,
["fountain", "string lights", "pergola", "garden views"],
[EventType.WEDDING, EventType.SOCIAL, EventType.BIRTHDAY],
3500.0, 5500.0),
VenueSpace("BC", "Business Center", 200, 100, 4000, True,
["AV system", "breakout rooms", "projectors", "podium"],
[EventType.CORPORATE, EventType.CONFERENCE],
3000.0, 4000.0),
VenueSpace("RL", "Rooftop Lounge", 80, 120, 2000, False,
["skyline view", "bar", "lounge furniture", "fire pits"],
[EventType.SOCIAL, EventType.BIRTHDAY, EventType.CORPORATE],
2500.0, 4000.0),
]
catering_options = [
CateringOption("Cocktail Reception", 45.0, "Passed hors d'oeuvres and drink stations"),
CateringOption("Plated Dinner", 85.0, "Three-course plated dinner with wine service"),
CateringOption("Buffet", 65.0, "Chef-attended buffet stations with variety"),
CateringOption("Brunch", 55.0, "Morning event with breakfast and lunch options"),
]
addons = [
AddOn("DJ & Sound System", 1200.0, "flat", "Professional DJ for up to 5 hours"),
AddOn("Floral Arrangements", 25.0, "per_person", "Centerpieces and ceremony florals"),
AddOn("Photography", 2500.0, "flat", "8 hours of professional event photography"),
AddOn("Valet Parking", 15.0, "per_person", "Full valet service for all guests"),
]
inquiries_db: list[EventInquiry] = []
@function_tool
def qualify_inquiry(
contact_name: str, contact_email: str, contact_phone: str,
event_type: str, event_date: str, guest_count: int, budget: float = 0.0,
notes: str = ""
) -> str:
inquiry = EventInquiry(
inquiry_id=f"INQ-{len(inquiries_db)+1:04d}",
contact_name=contact_name,
contact_email=contact_email,
contact_phone=contact_phone,
event_type=EventType(event_type),
event_date=date.fromisoformat(event_date) if event_date else None,
guest_count=guest_count,
budget=budget,
notes=notes,
status=InquiryStatus.QUALIFIED,
)
inquiries_db.append(inquiry)
return f"Inquiry {inquiry.inquiry_id} created for {contact_name}. Event: {event_type}, {guest_count} guests on {event_date}."
@function_tool
def find_matching_spaces(event_type: str, guest_count: int, event_date: str) -> str:
evt = EventType(event_type)
target = date.fromisoformat(event_date)
matches = [
s for s in venue_spaces
if evt in s.suitable_for
and s.capacity_seated >= guest_count
and s.is_available(target)
]
if not matches:
return "No available spaces match your requirements for that date."
lines = []
for s in matches:
rate = s.get_rental_rate(target)
lines.append(
f"- **{s.name}** (seats {s.capacity_seated}, stands {s.capacity_standing})\n"
f" Features: {', '.join(s.features)}\n"
f" Rental: ${rate:,.2f} | {s.square_feet} sq ft"
)
return "\n".join(lines)
@function_tool
def schedule_tour(inquiry_id: str, tour_date: str, tour_time: str) -> str:
inquiry = next((i for i in inquiries_db if i.inquiry_id == inquiry_id), None)
if not inquiry:
return f"Inquiry {inquiry_id} not found."
tour_dt = datetime.strptime(f"{tour_date} {tour_time}", "%Y-%m-%d %H:%M")
inquiry.tour_datetime = tour_dt
inquiry.status = InquiryStatus.TOUR_SCHEDULED
return (
f"Tour scheduled for {inquiry.contact_name} on "
f"{tour_dt.strftime('%B %d at %I:%M %p')}. "
f"A confirmation email will be sent to {inquiry.contact_email}."
)
@function_tool
def generate_proposal(
inquiry_id: str, space_id: str, catering_choice: str,
addon_names: list[str] = []
) -> str:
inquiry = next((i for i in inquiries_db if i.inquiry_id == inquiry_id), None)
if not inquiry or not inquiry.event_date:
return "Inquiry not found or event date not set."
space = next((s for s in venue_spaces if s.space_id == space_id), None)
if not space:
return f"Space {space_id} not found."
rental = space.get_rental_rate(inquiry.event_date)
catering = next((c for c in catering_options if c.name.lower() == catering_choice.lower()), None)
catering_total = catering.price_per_person * inquiry.guest_count if catering else 0.0
addon_total = 0.0
selected_addons = []
for addon_name in addon_names:
addon = next((a for a in addons if a.name.lower() == addon_name.lower()), None)
if addon:
cost = addon.price if addon.unit == "flat" else addon.price * inquiry.guest_count
addon_total += cost
selected_addons.append(f"{addon.name}: ${cost:,.2f}")
proposal = Proposal(
inquiry_id=inquiry_id,
space=space,
event_date=inquiry.event_date,
guest_count=inquiry.guest_count,
rental_fee=rental,
catering_total=catering_total,
addons_total=addon_total,
)
inquiry.status = InquiryStatus.PROPOSAL_SENT
addons_str = "\n".join(f" {a}" for a in selected_addons) if selected_addons else " None"
catering_str = f"{catering.name} (${catering.price_per_person}/person)" if catering else "None"
return (
f"=== PROPOSAL for {inquiry.contact_name} ===\n"
f"Event: {inquiry.event_type.value} | {inquiry.event_date.isoformat()}\n"
f"Guests: {inquiry.guest_count}\n"
f"Space: {space.name}\n\n"
f" Venue rental: ${rental:,.2f}\n"
f" Catering ({catering_str}): ${catering_total:,.2f}\n"
f" Add-ons:\n{addons_str}\n\n"
f" TOTAL: ${proposal.total:,.2f}\n\n"
f"This proposal is valid for 14 days."
)
@function_tool
def get_follow_up_queue() -> str:
needs_followup = [
i for i in inquiries_db
if i.status in (InquiryStatus.QUALIFIED, InquiryStatus.PROPOSAL_SENT)
]
if not needs_followup:
return "No inquiries need follow-up at this time."
lines = []
for inq in needs_followup:
days_old = (datetime.now() - inq.created_at).days
lines.append(
f"- {inq.inquiry_id}: {inq.contact_name} | {inq.event_type.value} | "
f"Status: {inq.status.value} | {days_old} days old"
)
return "\n".join(lines)
venue_agent = Agent(
name="Venue Management Agent",
instructions="""You are a venue sales agent. Help event planners find the
right space for their events. Qualify every inquiry by collecting event
type, date, guest count, and budget. Match them to appropriate spaces,
schedule tours, and generate detailed proposals. Follow up on open
inquiries proactively. Be enthusiastic but not pushy.""",
tools=[qualify_inquiry, find_matching_spaces, schedule_tour,
generate_proposal, get_follow_up_queue],
)
Venue sales depend on persistent follow-up. The agent triggers a sequence after each stage transition.
flowchart LR
INPUT(["User intent"])
PARSE["Parse plus<br/>classify"]
PLAN["Plan and tool<br/>selection"]
AGENT["Agent loop<br/>LLM plus tools"]
GUARD{"Guardrails<br/>and policy"}
EXEC["Execute and<br/>verify result"]
OBS[("Trace and metrics")]
OUT(["Outcome plus<br/>next action"])
INPUT --> PARSE --> PLAN --> AGENT --> GUARD
GUARD -->|Pass| EXEC --> OUT
GUARD -->|Fail| AGENT
AGENT --> OBS
style AGENT fill:#4f46e5,stroke:#4338ca,color:#fff
style GUARD fill:#f59e0b,stroke:#d97706,color:#1f2937
style OBS fill:#ede9fe,stroke:#7c3aed,color:#1e1b4b
style OUT fill:#059669,stroke:#047857,color:#fff
from datetime import timedelta
FOLLOW_UP_SEQUENCES = {
InquiryStatus.QUALIFIED: [
{"delay": timedelta(hours=1), "action": "Send venue brochure PDF via email"},
{"delay": timedelta(days=1), "action": "Invite to schedule a tour"},
{"delay": timedelta(days=3), "action": "Share testimonials from similar events"},
{"delay": timedelta(days=7), "action": "Check in on decision timeline"},
],
InquiryStatus.PROPOSAL_SENT: [
{"delay": timedelta(days=2), "action": "Ask if they have questions about the proposal"},
{"delay": timedelta(days=5), "action": "Offer to adjust the proposal"},
{"delay": timedelta(days=10), "action": "Mention limited date availability"},
{"delay": timedelta(days=14), "action": "Final follow-up before proposal expires"},
],
InquiryStatus.TOUR_SCHEDULED: [
{"delay": timedelta(days=-1), "action": "Send tour reminder with directions"},
{"delay": timedelta(hours=2), "action": "Post-tour thank you and proposal offer"},
],
}
def get_next_follow_up(inquiry: EventInquiry) -> dict | None:
sequence = FOLLOW_UP_SEQUENCES.get(inquiry.status, [])
completed = len(inquiry.follow_ups)
if completed < len(sequence):
return sequence[completed]
return None
The agent qualifies the inquiry with a flexible date range and presents availability across multiple weekends. It uses the venue's booking calendar to highlight dates that are filling up fast, creating gentle urgency without being pushy. The agent saves the inquiry as qualified and schedules follow-up to check in once the client narrows their date options.
The agent follows a first-come-first-served policy for confirmed bookings, but can hold a date for 48 to 72 hours with a deposit. When a second inquiry requests an already-held date, the agent transparently communicates that the date is tentatively reserved, suggests alternative dates or spaces, and offers to place them on a waitlist in case the hold expires without a deposit.
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.
The initial proposal uses standard pricing. When a client negotiates, the agent can apply pre-approved discount tiers: 5 percent for off-peak dates, 10 percent for multi-event contracts, and case-by-case discounts up to 15 percent with manager approval. Beyond that threshold, the agent escalates the negotiation to a human sales manager while keeping the client informed that a senior team member is reviewing their request.
#EventVenue #VenueManagement #ProposalGeneration #AgenticAI #Python #LearnAI #AIEngineering

Written by
Sagar Shankaran· Founder, CallSphere
LinkedInSagar Shankaran is the founder of CallSphere, where he builds production AI voice and chat agents deployed across healthcare, hospitality, real estate, and home services. He writes about agentic AI, LLM engineering, and shipping voice agents that handle real calls in production.
See how AI voice agents work for your industry. Live demo available -- no signup required.
The 2026 desktop AI agent landscape — ServiceNow Project Arc, Anthropic Claude offerings, OpenAI agents, and Google Mariner. A buyer's map.
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.
An agentic-AI perspective on Anthropic Skills system, covering orchestration patterns, tool use, and how agent tooling fits production agent stacks.
Enterprise CIO Guide perspective on Comet's general-availability launch put an agentic browser in front of millions of consumers, and it works better than the demos suggested.
Enterprise CIO Guide perspective on Harvey AI's enterprise rollout numbers show legal agents have moved past the pilot stage at AmLaw 100 firms.
Enterprise CIO Guide perspective on Hippocratic AI's deployment numbers show healthcare voice agents are moving from pilot to production across major US health systems.
© 2026 CallSphere Inc. All rights reserved.
Made within San Francisco