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
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.