Skip to content
Learn Agentic AI11 min read0 views

AI Agent for Photography Studios: Session Booking, Package Selection, and Gallery Delivery

Build an AI agent for photography studios that guides clients through package selection, schedules sessions with location coordination, and manages gallery delivery — turning inquiries into booked sessions.

Photography Studios Are Sales Businesses First

Professional photographers spend most of their time behind the camera, not behind a desk. But their business depends on converting inquiries into booked sessions, and every unanswered inquiry is revenue lost to a competitor. Photography clients have specific needs — the right package, the right location, the right time of day for lighting — and they want to feel guided through those choices. An AI agent acts as the studio's always-available booking coordinator, walking clients through package options, handling scheduling logistics, and managing gallery delivery after the shoot.

Photography Business Data Model

Photography studios sell packages that bundle session time, edited images, prints, and digital files. The data model captures these product offerings and client relationships.

from dataclasses import dataclass, field
from datetime import datetime, date, time, timedelta
from enum import Enum
from typing import Optional


class SessionType(Enum):
    PORTRAIT = "portrait"
    FAMILY = "family"
    WEDDING = "wedding"
    NEWBORN = "newborn"
    HEADSHOT = "headshot"
    EVENT = "event"
    PRODUCT = "product"


class BookingStatus(Enum):
    INQUIRY = "inquiry"
    QUOTED = "quoted"
    DEPOSIT_PAID = "deposit_paid"
    CONFIRMED = "confirmed"
    COMPLETED = "completed"
    GALLERY_DELIVERED = "gallery_delivered"
    ARCHIVED = "archived"


@dataclass
class Package:
    id: str
    name: str
    session_type: SessionType
    session_duration_hours: float
    edited_images: int
    includes_prints: bool
    digital_download: bool
    price: float
    description: str
    add_ons: list[str] = field(default_factory=list)


@dataclass
class Location:
    name: str
    address: str
    location_type: str  # "studio", "outdoor", "client_location"
    travel_fee: float = 0.0
    best_time: str = ""  # e.g., "golden hour (1 hr before sunset)"
    notes: str = ""


@dataclass
class PhotoSession:
    id: str
    client_name: str
    client_phone: str
    client_email: str
    session_type: SessionType
    package_id: str
    location: Optional[Location] = None
    session_date: Optional[datetime] = None
    status: BookingStatus = BookingStatus.INQUIRY
    deposit_amount: float = 0.0
    total_price: float = 0.0
    gallery_url: Optional[str] = None
    gallery_expiry: Optional[date] = None
    notes: str = ""

Package Catalog

Photography packages are the core product. We define them with enough detail for the agent to make personalized recommendations.

PACKAGES = {
    "portrait_mini": Package(
        "p1", "Mini Portrait Session", SessionType.PORTRAIT,
        0.5, 10, False, True, 195,
        "Perfect for headshots or quick individual portraits. "
        "30-minute studio session with 10 edited digital images.",
        add_ons=["Extra edited images ($15 each)", "Print package ($75)"],
    ),
    "portrait_full": Package(
        "p2", "Full Portrait Session", SessionType.PORTRAIT,
        1.5, 30, True, True, 450,
        "Extended portrait session with wardrobe changes. "
        "Includes 30 edited images, 5 prints, and digital downloads.",
        add_ons=["Canvas print ($125)", "Additional location ($100)"],
    ),
    "family_standard": Package(
        "p3", "Family Session", SessionType.FAMILY,
        1.0, 25, True, True, 395,
        "Outdoor family session for up to 6 people. "
        "Includes 25 edited images, a print set, and digital gallery.",
        add_ons=["Holiday cards (set of 25, $60)", "Extra people ($25 each)"],
    ),
    "wedding_essential": Package(
        "p4", "Wedding Essentials", SessionType.WEDDING,
        6.0, 300, False, True, 2800,
        "6 hours of coverage with 300+ edited images. "
        "Includes engagement session and online gallery.",
        add_ons=["Second photographer ($500)", "Album ($450)", "Extra hour ($350)"],
    ),
    "wedding_premium": Package(
        "p5", "Wedding Premium", SessionType.WEDDING,
        10.0, 600, True, True, 4500,
        "Full-day coverage with 600+ edited images. "
        "Includes engagement session, album, prints, and online gallery.",
        add_ons=["Video highlight reel ($800)", "Bridal session ($300)"],
    ),
    "headshot_pro": Package(
        "p6", "Professional Headshot", SessionType.HEADSHOT,
        0.5, 5, False, True, 175,
        "Studio headshot session for LinkedIn, websites, and business cards. "
        "5 retouched images with digital download.",
        add_ons=["Additional looks ($50 each)", "Rush delivery ($50)"],
    ),
}

STUDIO_LOCATIONS = [
    Location("Main Studio", "456 Oak Avenue", "studio",
             notes="Natural light studio with white and gray backdrops"),
    Location("City Park", "Riverside Park, Downtown", "outdoor",
             travel_fee=0, best_time="Golden hour (1 hr before sunset)"),
    Location("Botanical Garden", "Springfield Botanical Garden", "outdoor",
             travel_fee=50, best_time="Morning (9-11 AM) for soft light"),
    Location("Client Location", "Your chosen location", "client_location",
             travel_fee=75, notes="Travel fee applies for locations over 15 miles"),
]

Package Recommendation Logic

Different clients need different packages. A mother asking about newborn photos has different needs than a CEO wanting a headshot. The agent uses context clues to recommend the right package.

See AI Voice Agents Handle Real Calls

Book a free demo or calculate how much you can save with AI voice automation.

def recommend_packages(
    session_type: str, party_size: int = 1,
    budget_range: str = ""
) -> list[dict]:
    try:
        stype = SessionType(session_type.lower())
    except ValueError:
        return [{"error": f"Unknown session type. Available: {[s.value for s in SessionType]}"}]

    matching = [
        p for p in PACKAGES.values() if p.session_type == stype
    ]

    if budget_range:
        low, high = 0, float("inf")
        if budget_range == "budget":
            high = 300
        elif budget_range == "mid":
            low, high = 200, 1000
        elif budget_range == "premium":
            low = 800
        matching = [p for p in matching if low <= p.price <= high]

    results = []
    for pkg in sorted(matching, key=lambda p: p.price):
        add_on_text = "; ".join(pkg.add_ons) if pkg.add_ons else "None"
        results.append({
            "id": pkg.id,
            "name": pkg.name,
            "price": pkg.price,
            "duration": f"{pkg.session_duration_hours} hours",
            "images": pkg.edited_images,
            "includes_prints": pkg.includes_prints,
            "description": pkg.description,
            "add_ons": add_on_text,
        })
    return results

Agent Tools

from agents import Agent, Runner, function_tool


@function_tool
def browse_packages(
    session_type: str, budget: str = ""
) -> str:
    """Browse photography packages by session type and optional budget."""
    results = recommend_packages(session_type, budget_range=budget)
    if not results:
        return "No packages found matching your criteria."
    if "error" in results[0]:
        return results[0]["error"]
    lines = []
    for r in results:
        prints_note = "Prints included" if r["includes_prints"] else "Digital only"
        lines.append(
            f"\n{r['name']} - ${r['price']}\n"
            f"  {r['description']}\n"
            f"  Duration: {r['duration']} | Images: {r['images']} | {prints_note}\n"
            f"  Add-ons: {r['add_ons']}"
        )
    return "\n".join(lines)


@function_tool
def get_locations(session_type: str = "") -> str:
    """Get available session locations with details."""
    lines = []
    for loc in STUDIO_LOCATIONS:
        fee = f" (travel fee: ${loc.travel_fee})" if loc.travel_fee else ""
        best = f" | Best time: {loc.best_time}" if loc.best_time else ""
        lines.append(f"{loc.name} ({loc.location_type}){fee}{best}")
        if loc.notes:
            lines.append(f"  {loc.notes}")
    return "\n".join(lines)


@function_tool
def book_session(
    client_name: str, client_phone: str, client_email: str,
    package_id: str, preferred_date: str,
    location_name: str = "Main Studio"
) -> str:
    """Book a photography session with a specific package and date."""
    pkg = next((p for p in PACKAGES.values() if p.id == package_id), None)
    if not pkg:
        return "Package not found."
    location = next(
        (l for l in STUDIO_LOCATIONS
         if location_name.lower() in l.name.lower()), None
    )
    travel_fee = location.travel_fee if location else 0
    total = pkg.price + travel_fee
    deposit = total * 0.3

    return (
        f"Session booked!\n"
        f"Client: {client_name}\n"
        f"Package: {pkg.name} (${pkg.price})\n"
        f"Location: {location.name if location else location_name}"
        f"{f' (+${travel_fee} travel)' if travel_fee else ''}\n"
        f"Date: {preferred_date}\n"
        f"Total: ${total:.0f}\n"
        f"Deposit required: ${deposit:.0f} (30%)\n"
        f"Deposit link sent to {client_email}.\n\n"
        f"Preparation tips will be emailed 3 days before your session."
    )


@function_tool
def check_gallery_status(client_name: str) -> str:
    """Check the status of a client's photo gallery after their session."""
    # In production this queries the gallery management system
    return (
        f"Gallery status for {client_name}:\n"
        f"Session: Completed\n"
        f"Editing: In progress (estimated delivery: 2-3 weeks after session)\n"
        f"You will receive an email with your private gallery link once ready.\n"
        f"Gallery will be available for download for 90 days."
    )


@function_tool
def send_preparation_guide(session_type: str, client_email: str) -> str:
    """Send a session preparation guide to the client."""
    guides = {
        "portrait": "Wear solid colors, avoid busy patterns. Bring 2-3 outfit options.",
        "family": "Coordinate outfits (not matching). Bring snacks for kids. Plan for golden hour.",
        "wedding": "Timeline consultation scheduled separately. Bring your shot list.",
        "headshot": "Bring a lint roller. Solid professional attire in 2 colors.",
    }
    guide = guides.get(session_type, "General prep guide sent.")
    return f"Preparation guide sent to {client_email}:\n{guide}"


photography_agent = Agent(
    name="Studio Booking Coordinator",
    instructions="""You are the booking coordinator for Luminous Photography Studio.

1. When a potential client inquires, ask about the type of session
   they need (portrait, family, wedding, headshot, etc.) and their budget.
2. Use browse_packages to present options. Recommend the package
   that best fits their needs and explain what is included.
3. Share location options with get_locations. For outdoor sessions,
   mention the best time of day for lighting.
4. Once they choose a package and date, use book_session to confirm.
   Explain the deposit requirement.
5. Use send_preparation_guide to help them prepare for the session.
6. For returning clients checking on their gallery, use
   check_gallery_status.

STYLE:
- Be warm and excited about their milestone (wedding, new baby, etc.).
- Help them visualize the experience, not just the price.
- If budget is a concern, start with the most affordable option and
  explain how add-ons can enhance it later.""",
    tools=[browse_packages, get_locations, book_session, check_gallery_status, send_preparation_guide],
)

result = Runner.run_sync(
    photography_agent,
    "Hi, I am getting married in October and looking for a photographer.",
)
print(result.final_output)

FAQ

How does the agent handle wedding consultations that require detailed planning?

Wedding bookings are more complex than standard sessions — they involve timelines, venue logistics, and multi-hour coverage. The agent handles the initial package selection and booking. Once the deposit is paid, it schedules a separate planning consultation (either in-person or video call) where the photographer discusses the timeline, shot list, and venue details. The agent collects the initial information; the photographer handles the creative planning.

Yes. Add a place_print_order tool that accepts the gallery URL, selected image numbers, print sizes, and quantities. The tool calculates pricing from a print price list and generates an order. This turns the gallery delivery email into a revenue opportunity — the agent follows up after gallery viewing to ask if the client would like prints, canvases, or albums.

How do I handle seasonal pricing or mini-session events?

Create a promotions layer that the browse_packages tool checks before returning results. Seasonal mini-sessions (holiday minis, spring portraits) are temporary packages with their own pricing, duration, and availability windows. Add them to the PACKAGES dictionary with a start and end date, and filter them out automatically once the event period ends.


#PhotographyBusiness #SessionBooking #PackageSelection #GalleryDelivery #Python #AgenticAI #LearnAI #AIEngineering

Share this article
C

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.