Skip to content
Learn Agentic AI11 min read0 views

Building a Property Inquiry Agent: Answering Buyer Questions About Listings 24/7

Learn how to build an AI agent that answers buyer questions about property listings around the clock, including database lookups, FAQ handling, photo sharing, and automated showing scheduling.

Why Real Estate Needs 24/7 Inquiry Agents

The average buyer browses listings at 9 PM on a Tuesday. By the time an agent responds the next morning, that buyer has already messaged three competitors. A property inquiry agent eliminates this gap by answering questions about listings, sharing photos, and scheduling showings instantly — no matter the hour.

In this guide, we will build a property inquiry agent that connects to a listing database, handles common buyer questions, serves property photos, and books showings automatically.

Designing the Listing Database Layer

Every property inquiry agent starts with structured access to listing data. We will use a simple schema and a retrieval layer that the agent can call as a tool.

import asyncpg
from dataclasses import dataclass
from typing import Optional

@dataclass
class PropertyListing:
    listing_id: str
    address: str
    price: float
    bedrooms: int
    bathrooms: float
    sqft: int
    description: str
    photo_urls: list[str]
    status: str  # active, pending, sold
    listing_agent: str

class ListingDatabase:
    def __init__(self, pool: asyncpg.Pool):
        self.pool = pool

    async def search_listings(
        self,
        min_price: Optional[float] = None,
        max_price: Optional[float] = None,
        min_beds: Optional[int] = None,
        city: Optional[str] = None,
        limit: int = 10,
    ) -> list[PropertyListing]:
        conditions = ["status = 'active'"]
        params = []
        idx = 1

        if min_price is not None:
            conditions.append(f"price >= ${idx}")
            params.append(min_price)
            idx += 1
        if max_price is not None:
            conditions.append(f"price <= ${idx}")
            params.append(max_price)
            idx += 1
        if min_beds is not None:
            conditions.append(f"bedrooms >= ${idx}")
            params.append(min_beds)
            idx += 1
        if city is not None:
            conditions.append(f"LOWER(city) = LOWER(${idx})")
            params.append(city)
            idx += 1

        where_clause = " AND ".join(conditions)
        query = f"""
            SELECT * FROM listings
            WHERE {where_clause}
            ORDER BY created_at DESC
            LIMIT {limit}
        """
        rows = await self.pool.fetch(query, *params)
        return [PropertyListing(**dict(r)) for r in rows]

    async def get_listing(self, listing_id: str) -> Optional[PropertyListing]:
        row = await self.pool.fetchrow(
            "SELECT * FROM listings WHERE listing_id = $1",
            listing_id,
        )
        return PropertyListing(**dict(row)) if row else None

This layer gives the agent parameterized search capabilities. The key design choice is returning structured data rather than raw SQL rows so the agent can format responses naturally.

Building the Agent with Tools

Now we wire the database into an agent using tool functions. Each tool handles a specific buyer intent.

See AI Voice Agents Handle Real Calls

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

from agents import Agent, Runner, function_tool

listing_db: ListingDatabase  # initialized at startup

@function_tool
async def search_properties(
    city: str,
    max_price: float = None,
    min_bedrooms: int = None,
) -> str:
    """Search available properties by city, price range, and bedroom count."""
    results = await listing_db.search_listings(
        city=city,
        max_price=max_price,
        min_beds=min_bedrooms,
        limit=5,
    )
    if not results:
        return "No matching properties found. Try broadening your search."
    lines = []
    for p in results:
        lines.append(
            f"- {p.address}: {p.bedrooms}bd/{p.bathrooms}ba, "
            f"{p.sqft} sqft, ${p.price:,.0f} (ID: {p.listing_id})"
        )
    return "\n".join(lines)

@function_tool
async def get_property_details(listing_id: str) -> str:
    """Get full details and photos for a specific listing."""
    p = await listing_db.get_listing(listing_id)
    if not p:
        return "Listing not found."
    photos = "\n".join(p.photo_urls[:5])
    return (
        f"Address: {p.address}\n"
        f"Price: ${p.price:,.0f}\n"
        f"Beds/Baths: {p.bedrooms}/{p.bathrooms}\n"
        f"Sqft: {p.sqft}\n"
        f"Description: {p.description}\n"
        f"Photos:\n{photos}"
    )

@function_tool
async def schedule_showing(
    listing_id: str,
    buyer_name: str,
    buyer_phone: str,
    preferred_date: str,
) -> str:
    """Schedule a property showing for a buyer."""
    # In production, this writes to a calendar/CRM system
    return (
        f"Showing scheduled for {buyer_name} at listing "
        f"{listing_id} on {preferred_date}. "
        f"A confirmation will be sent to {buyer_phone}."
    )

property_agent = Agent(
    name="PropertyInquiryAgent",
    instructions="""You are a helpful real estate assistant. Answer
    questions about available properties using the search and detail
    tools. When a buyer is interested, offer to schedule a showing.
    Always be accurate — never invent property details.""",
    tools=[search_properties, get_property_details, schedule_showing],
)

Handling FAQs with a Knowledge Base

Many buyer questions are not about specific listings but about process — closing costs, inspection timelines, mortgage pre-approval. We handle these with a lightweight FAQ retrieval tool.

FAQ_DATA = {
    "closing_costs": "Typical closing costs range from 2-5% of the purchase price...",
    "inspection": "Home inspections usually occur within 7-10 days of accepted offer...",
    "preapproval": "Mortgage pre-approval typically requires pay stubs, tax returns...",
}

@function_tool
async def lookup_faq(topic: str) -> str:
    """Look up common real estate FAQs by topic keyword."""
    topic_lower = topic.lower()
    for key, answer in FAQ_DATA.items():
        if key in topic_lower or topic_lower in key:
            return answer
    return "I do not have a specific FAQ on that topic. Let me connect you with an agent."

This approach keeps the agent grounded in verified information rather than hallucinating answers about legal or financial topics.

Running the Agent

import asyncio

async def main():
    result = await Runner.run(
        property_agent,
        input="I am looking for a 3-bedroom house in Austin under $500k",
    )
    print(result.final_output)

asyncio.run(main())

The agent will call search_properties with the extracted parameters and present matching listings in a conversational format.

FAQ

How does the agent handle questions about properties not in the database?

The agent is instructed to never fabricate details. If a listing is not found, it responds honestly and suggests broadening the search or contacting a human agent for off-market properties.

Can this agent handle multiple languages for international buyers?

Yes. Since the underlying LLM supports multilingual input and output, you can add an instruction to detect the buyer's language and respond accordingly. The database queries remain the same — only the presentation layer changes.

What happens when the agent cannot answer a question?

The FAQ tool returns a fallback message suggesting human escalation. You can extend this by adding a handoff to a live agent tool that creates a callback request in your CRM.


#RealEstateAI #PropertyInquiry #AgenticAI #Python #Chatbot #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.