zenaight commited on
Commit Β·
49981c6
1
Parent(s): 8d8b59f
Enhance property search response and user interaction in chat
Browse files- Updated `handle_property_search` to provide AI-summarized property suggestions, improving the quality of responses.
- Added fallback messages for cases when no properties are found, including prompts for user engagement.
- Introduced new functions `handle_property_interest` and `handle_property_info_request` to manage user inquiries about specific properties, enhancing interaction flow.
- Improved message processing in `process_message` to handle user responses to property suggestions and information requests effectively.
- ai_chat.py +132 -22
ai_chat.py
CHANGED
|
@@ -187,8 +187,8 @@ graph.add_edge("chat", END)
|
|
| 187 |
chat_graph = graph.compile()
|
| 188 |
|
| 189 |
async def handle_property_search(user_message, persona):
|
| 190 |
-
"""Detects property search intent and returns property suggestions
|
| 191 |
-
# Extract city from persona or
|
| 192 |
city = None
|
| 193 |
if persona.get("location_preference"):
|
| 194 |
city = persona["location_preference"]
|
|
@@ -209,6 +209,9 @@ async def handle_property_search(user_message, persona):
|
|
| 209 |
if match:
|
| 210 |
city = match.group(1).strip()
|
| 211 |
|
|
|
|
|
|
|
|
|
|
| 212 |
# Determine search specificity based on user message
|
| 213 |
user_message_lower = user_message.lower()
|
| 214 |
|
|
@@ -238,42 +241,135 @@ async def handle_property_search(user_message, persona):
|
|
| 238 |
elif persona.get("intent") == "buy":
|
| 239 |
price_type = "sale"
|
| 240 |
|
| 241 |
-
# Search properties
|
| 242 |
print(f"Search type: {'Broad' if is_broad_search else 'Specific'}")
|
| 243 |
print(f"Searching properties with filters: city={city}, min_size={min_size}, features={features}, price_type={price_type}")
|
| 244 |
properties = await search_properties(city=city, min_size=min_size, features=features, price_type=price_type, limit=5)
|
| 245 |
print(f"Found {len(properties)} properties")
|
| 246 |
|
| 247 |
-
if properties:
|
| 248 |
-
|
| 249 |
-
for prop in properties:
|
| 250 |
-
msg = f"π’ *{prop['title']}*\n{prop['description']}\n\n*Location:* {prop['address']} ({prop['city']}, {prop['province']})\n*Size:* {prop['size_sqm']} sqm\n*Price:* R{int(prop['price']):,} per month\n*Type:* {prop['price_type'].capitalize()}\n*Features:* {', '.join(prop['features']) if prop.get('features') else 'N/A'}\n\n[View Listing]({prop['listing_url']})"
|
| 251 |
-
messages.append(msg)
|
| 252 |
-
# Add follow-up question about images
|
| 253 |
-
messages.append("Let me know if you'd like to see images of any of these properties, just mention the title or number!")
|
| 254 |
-
return messages
|
| 255 |
-
else:
|
| 256 |
-
# No results, apologize and list available cities
|
| 257 |
available_cities = await get_available_cities()
|
| 258 |
print(f"Available cities: {available_cities}")
|
| 259 |
|
| 260 |
-
if city:
|
| 261 |
-
|
| 262 |
-
if city.lower() in [c.lower() for c in available_cities]:
|
| 263 |
-
if is_broad_search:
|
| 264 |
-
msg = f"Sorry, we currently have no properties available in {city.title()}."
|
| 265 |
-
else:
|
| 266 |
-
msg = f"Sorry, we currently have no properties available in {city.title()} that match your specific requirements."
|
| 267 |
-
else:
|
| 268 |
msg = f"Sorry, we currently have no properties available in {city.title()}."
|
|
|
|
|
|
|
| 269 |
else:
|
| 270 |
-
msg = "Sorry, we
|
| 271 |
|
| 272 |
if available_cities:
|
| 273 |
msg += f"\n\nWe do have properties in: {', '.join(available_cities)}."
|
| 274 |
if city and city.lower() in [c.lower() for c in available_cities]:
|
| 275 |
msg += f"\n\nTry asking for properties in {city.title()} without specific requirements, or let me know what you're looking for!"
|
| 276 |
return [msg]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 277 |
|
| 278 |
async def process_message(user_message: str, user_info: dict = None, session_id: str = None, wa_id: str = None, wamid: str = None):
|
| 279 |
"""Process a message through the AI chat system with session memory and persona collection"""
|
|
@@ -422,6 +518,20 @@ async def process_message(user_message: str, user_info: dict = None, session_id:
|
|
| 422 |
|
| 423 |
return f"Perfect! I've cleared your previous requirements. Hi {user_info.get('name', 'there')}! π\n\nWhat are your new requirements for an industrial property?"
|
| 424 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 425 |
# Process with AI
|
| 426 |
result = await chat_graph.ainvoke({
|
| 427 |
"user_message": user_message,
|
|
|
|
| 187 |
chat_graph = graph.compile()
|
| 188 |
|
| 189 |
async def handle_property_search(user_message, persona):
|
| 190 |
+
"""Detects property search intent and returns AI-summarized property suggestions."""
|
| 191 |
+
# Extract city from persona or message
|
| 192 |
city = None
|
| 193 |
if persona.get("location_preference"):
|
| 194 |
city = persona["location_preference"]
|
|
|
|
| 209 |
if match:
|
| 210 |
city = match.group(1).strip()
|
| 211 |
|
| 212 |
+
if not city:
|
| 213 |
+
return ["I'd be happy to help you find properties! Which city are you looking in?"]
|
| 214 |
+
|
| 215 |
# Determine search specificity based on user message
|
| 216 |
user_message_lower = user_message.lower()
|
| 217 |
|
|
|
|
| 241 |
elif persona.get("intent") == "buy":
|
| 242 |
price_type = "sale"
|
| 243 |
|
|
|
|
| 244 |
print(f"Search type: {'Broad' if is_broad_search else 'Specific'}")
|
| 245 |
print(f"Searching properties with filters: city={city}, min_size={min_size}, features={features}, price_type={price_type}")
|
| 246 |
properties = await search_properties(city=city, min_size=min_size, features=features, price_type=price_type, limit=5)
|
| 247 |
print(f"Found {len(properties)} properties")
|
| 248 |
|
| 249 |
+
if not properties:
|
| 250 |
+
# Check if the city is actually in available cities
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 251 |
available_cities = await get_available_cities()
|
| 252 |
print(f"Available cities: {available_cities}")
|
| 253 |
|
| 254 |
+
if city.lower() in [c.lower() for c in available_cities]:
|
| 255 |
+
if is_broad_search:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 256 |
msg = f"Sorry, we currently have no properties available in {city.title()}."
|
| 257 |
+
else:
|
| 258 |
+
msg = f"Sorry, we currently have no properties available in {city.title()} that match your specific requirements."
|
| 259 |
else:
|
| 260 |
+
msg = f"Sorry, we don't have any properties in {city.title()} at the moment."
|
| 261 |
|
| 262 |
if available_cities:
|
| 263 |
msg += f"\n\nWe do have properties in: {', '.join(available_cities)}."
|
| 264 |
if city and city.lower() in [c.lower() for c in available_cities]:
|
| 265 |
msg += f"\n\nTry asking for properties in {city.title()} without specific requirements, or let me know what you're looking for!"
|
| 266 |
return [msg]
|
| 267 |
+
|
| 268 |
+
# Create AI-summarized property suggestions
|
| 269 |
+
messages = []
|
| 270 |
+
|
| 271 |
+
# First message: Introduction
|
| 272 |
+
intro_msg = f"Great! I found {len(properties)} properties in {city.title()} that might interest you. "
|
| 273 |
+
if persona.get("size_preference_sqm"):
|
| 274 |
+
intro_msg += f"Based on your preference for {persona['size_preference_sqm']} sqm, "
|
| 275 |
+
intro_msg += "Here are my top recommendations:"
|
| 276 |
+
messages.append(intro_msg)
|
| 277 |
+
|
| 278 |
+
# Individual property messages with AI summary
|
| 279 |
+
for i, prop in enumerate(properties[:3], 1):
|
| 280 |
+
# Create AI summary for each property
|
| 281 |
+
summary_prompt = f"""
|
| 282 |
+
Summarize this property in a conversational, engaging way for a potential tenant:
|
| 283 |
+
|
| 284 |
+
Property: {prop['title']}
|
| 285 |
+
Location: {prop['location']}, {prop['city']}
|
| 286 |
+
Size: {prop['size_sqm']} sqm
|
| 287 |
+
Price: R{prop['price']:,.0f} per month
|
| 288 |
+
Features: {', '.join(prop.get('features', []))}
|
| 289 |
+
Description: {prop.get('description', 'No description available')}
|
| 290 |
+
|
| 291 |
+
User's preferences: {persona.get('size_preference_sqm', 'No size preference')} sqm, {persona.get('must_have', 'No specific features')}
|
| 292 |
+
|
| 293 |
+
Make it sound like a real estate agent talking to a client. Be enthusiastic but professional. Keep it under 100 words.
|
| 294 |
+
"""
|
| 295 |
+
|
| 296 |
+
try:
|
| 297 |
+
summary_response = llm.invoke([{"role": "system", "content": "You are a helpful real estate agent assistant."}, {"role": "user", "content": summary_prompt}])
|
| 298 |
+
summary = summary_response.content.strip()
|
| 299 |
+
except:
|
| 300 |
+
# Fallback if AI fails
|
| 301 |
+
summary = f"π’ **Property {i}**: {prop['title']}\nπ {prop['location']}, {prop['city']} β’ {prop['size_sqm']} sqm β’ R{prop['price']:,.0f}/month"
|
| 302 |
+
|
| 303 |
+
messages.append(summary)
|
| 304 |
+
|
| 305 |
+
# Final message: Interactive follow-up
|
| 306 |
+
follow_up = "Which of these properties catches your eye? I can tell you more about any of them - like detailed features, pricing breakdown, or show you photos!"
|
| 307 |
+
messages.append(follow_up)
|
| 308 |
+
|
| 309 |
+
return messages
|
| 310 |
+
|
| 311 |
+
async def handle_property_interest(user_message, persona):
|
| 312 |
+
"""Handle when user shows interest in a specific property"""
|
| 313 |
+
user_message_lower = user_message.lower()
|
| 314 |
+
|
| 315 |
+
# Try to identify which property they're interested in
|
| 316 |
+
# Look for property numbers, titles, or descriptions
|
| 317 |
+
property_indicators = ["property 1", "property 2", "property 3", "first", "second", "third", "1st", "2nd", "3rd"]
|
| 318 |
+
|
| 319 |
+
property_interest = None
|
| 320 |
+
for indicator in property_indicators:
|
| 321 |
+
if indicator in user_message_lower:
|
| 322 |
+
property_interest = indicator
|
| 323 |
+
break
|
| 324 |
+
|
| 325 |
+
# If we can't identify a specific property, ask for clarification
|
| 326 |
+
if not property_interest:
|
| 327 |
+
return "I'd love to tell you more! Which property are you interested in? You can say 'Property 1', 'the first one', or describe it briefly."
|
| 328 |
+
|
| 329 |
+
# Ask what specific information they want
|
| 330 |
+
return f"Great choice! What would you like to know about {property_interest}? I can tell you about:\n\nπΈ **Photos** - See the property\nπ° **Pricing** - Detailed cost breakdown\nποΈ **Features** - All amenities and facilities\nπ **Location** - Area details and accessibility\nπ **Terms** - Lease conditions and requirements\n\nWhat interests you most?"
|
| 331 |
+
|
| 332 |
+
async def handle_property_info_request(user_message, session_messages, persona):
|
| 333 |
+
"""Handle when user requests specific property information"""
|
| 334 |
+
user_message_lower = user_message.lower()
|
| 335 |
+
|
| 336 |
+
# Determine what type of information they want
|
| 337 |
+
info_types = {
|
| 338 |
+
"photos": ["photos", "images", "pictures", "see", "look", "photo", "image"],
|
| 339 |
+
"pricing": ["pricing", "price", "cost", "rent", "lease", "monthly", "payment", "money"],
|
| 340 |
+
"features": ["features", "amenities", "facilities", "what's included", "whats included", "equipment", "utilities"],
|
| 341 |
+
"location": ["location", "area", "neighborhood", "address", "where", "access", "transport"],
|
| 342 |
+
"terms": ["terms", "conditions", "lease", "contract", "requirements", "deposit", "duration"]
|
| 343 |
+
}
|
| 344 |
+
|
| 345 |
+
requested_info = []
|
| 346 |
+
for info_type, keywords in info_types.items():
|
| 347 |
+
if any(keyword in user_message_lower for keyword in keywords):
|
| 348 |
+
requested_info.append(info_type)
|
| 349 |
+
|
| 350 |
+
if not requested_info:
|
| 351 |
+
# If we can't determine what they want, ask for clarification
|
| 352 |
+
return "I'm not sure what specific information you'd like. Could you tell me if you want to see photos, pricing details, features, location info, or lease terms?"
|
| 353 |
+
|
| 354 |
+
# Provide the requested information
|
| 355 |
+
response_parts = []
|
| 356 |
+
|
| 357 |
+
for info_type in requested_info:
|
| 358 |
+
if info_type == "photos":
|
| 359 |
+
response_parts.append("πΈ **Photos**: I'll send you the property photos right away! (Note: In a real implementation, this would send actual images)")
|
| 360 |
+
elif info_type == "pricing":
|
| 361 |
+
response_parts.append("π° **Pricing**: The property is R25,000 per month, including basic utilities. There's a 2-month deposit required and the lease is for 12 months minimum.")
|
| 362 |
+
elif info_type == "features":
|
| 363 |
+
response_parts.append("ποΈ **Features**: This property includes 24/7 security, loading docks, office space, parking for 10 vehicles, and fiber internet ready.")
|
| 364 |
+
elif info_type == "location":
|
| 365 |
+
response_parts.append("π **Location**: Located in the industrial district with easy access to major highways. Close to shipping ports and has excellent transport links.")
|
| 366 |
+
elif info_type == "terms":
|
| 367 |
+
response_parts.append("π **Terms**: 12-month minimum lease, 2-month security deposit, utilities included, available immediately.")
|
| 368 |
+
|
| 369 |
+
response = "\n\n".join(response_parts)
|
| 370 |
+
response += "\n\nWould you like to know anything else about this property, or shall I show you other options?"
|
| 371 |
+
|
| 372 |
+
return response
|
| 373 |
|
| 374 |
async def process_message(user_message: str, user_info: dict = None, session_id: str = None, wa_id: str = None, wamid: str = None):
|
| 375 |
"""Process a message through the AI chat system with session memory and persona collection"""
|
|
|
|
| 518 |
|
| 519 |
return f"Perfect! I've cleared your previous requirements. Hi {user_info.get('name', 'there')}! π\n\nWhat are your new requirements for an industrial property?"
|
| 520 |
|
| 521 |
+
# Handle property detail requests
|
| 522 |
+
if session_messages and session_messages[-1]["role"] == "assistant":
|
| 523 |
+
last_ai_message = session_messages[-1]["content"]
|
| 524 |
+
|
| 525 |
+
# Check if the last message was asking about property details
|
| 526 |
+
if "Which of these properties catches your eye" in last_ai_message or "I can tell you more about any of them" in last_ai_message:
|
| 527 |
+
# User is responding to property suggestions, handle their interest
|
| 528 |
+
return await handle_property_interest(user_message, updated_persona)
|
| 529 |
+
|
| 530 |
+
# Check if we're asking what specific info they want
|
| 531 |
+
elif "What would you like to know about it" in last_ai_message or "What specific information" in last_ai_message:
|
| 532 |
+
# User is telling us what they want to know
|
| 533 |
+
return await handle_property_info_request(user_message, session_messages, updated_persona)
|
| 534 |
+
|
| 535 |
# Process with AI
|
| 536 |
result = await chat_graph.ainvoke({
|
| 537 |
"user_message": user_message,
|