zenaight commited on
Commit ·
22f2680
1
Parent(s): 9dfa030
Enhance debugging and property search logic in AI chat
Browse files- Added debug print statements in `chat_with_session_memory` and `extract_and_search_properties` to log the number of properties found, specific property details, and search filters applied, improving traceability during the property search process.
- Implemented logic to clear restrictive filters for general area searches, enhancing the flexibility of property recommendations based on user intent.
- Refined the property search query in `search_properties` to prioritize city matches before falling back to location searches, optimizing the search process and improving user experience.
- ai_chat.py +21 -0
- database.py +15 -7
ai_chat.py
CHANGED
|
@@ -39,7 +39,9 @@ def chat_with_session_memory(state):
|
|
| 39 |
|
| 40 |
# Direct listing summary (bypass LLM when properties are found)
|
| 41 |
props = state.get("properties", [])
|
|
|
|
| 42 |
if props:
|
|
|
|
| 43 |
# a) Build a direct summary string
|
| 44 |
response_text = ""
|
| 45 |
# include any search status
|
|
@@ -247,10 +249,24 @@ async def extract_and_update_intent(state):
|
|
| 247 |
except Exception as e:
|
| 248 |
extracted = {}
|
| 249 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 250 |
for field in intent_fields:
|
| 251 |
new_val = extracted.get(field)
|
| 252 |
old_val = intent.get(field)
|
| 253 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 254 |
if new_val is not None and new_val != old_val:
|
| 255 |
# Handle must_have field as array (LLM decides the logic)
|
| 256 |
if field == "must_have" and new_val:
|
|
@@ -289,12 +305,14 @@ async def extract_and_search_properties(state):
|
|
| 289 |
Search for properties based on user intent and store results in state.
|
| 290 |
"""
|
| 291 |
intent = state.get("intent", {})
|
|
|
|
| 292 |
|
| 293 |
# Check if we have the minimum required field for property search
|
| 294 |
location = intent.get("location_preference")
|
| 295 |
|
| 296 |
if not location:
|
| 297 |
# Missing location, skip property search
|
|
|
|
| 298 |
return {"response": None}
|
| 299 |
|
| 300 |
# Prepare filters for property search
|
|
@@ -314,11 +332,14 @@ async def extract_and_search_properties(state):
|
|
| 314 |
filters["must_have"] = must_have
|
| 315 |
|
| 316 |
# Search for properties
|
|
|
|
| 317 |
properties = await search_properties(filters)
|
| 318 |
state["properties"] = properties
|
|
|
|
| 319 |
|
| 320 |
# Pass 1: strict filters
|
| 321 |
if properties:
|
|
|
|
| 322 |
return {"response": None}
|
| 323 |
|
| 324 |
# Pass 2: drop must-haves
|
|
|
|
| 39 |
|
| 40 |
# Direct listing summary (bypass LLM when properties are found)
|
| 41 |
props = state.get("properties", [])
|
| 42 |
+
print(f"DEBUG - chat_with_session_memory: {len(props)} properties in state")
|
| 43 |
if props:
|
| 44 |
+
print(f"DEBUG - first property: {props[0].get('title')} in {props[0].get('city')}")
|
| 45 |
# a) Build a direct summary string
|
| 46 |
response_text = ""
|
| 47 |
# include any search status
|
|
|
|
| 249 |
except Exception as e:
|
| 250 |
extracted = {}
|
| 251 |
|
| 252 |
+
# Check if this is a general area search (like "what do you have in [area]")
|
| 253 |
+
is_general_area_search = (
|
| 254 |
+
"what do you have" in user_message.lower() and
|
| 255 |
+
any(word in user_message.lower() for word in ["in ", "area", "jhb", "johannesburg", "cape town", "durban"])
|
| 256 |
+
)
|
| 257 |
+
|
| 258 |
for field in intent_fields:
|
| 259 |
new_val = extracted.get(field)
|
| 260 |
old_val = intent.get(field)
|
| 261 |
|
| 262 |
+
# For general area searches, clear restrictive filters
|
| 263 |
+
if is_general_area_search and field in ["budget", "size_preference_sqm", "must_have"]:
|
| 264 |
+
if old_val is not None:
|
| 265 |
+
print(f"DEBUG - Clearing restrictive field {field} for general area search")
|
| 266 |
+
await update_user_intent(session_id, {field: None})
|
| 267 |
+
intent[field] = None
|
| 268 |
+
continue
|
| 269 |
+
|
| 270 |
if new_val is not None and new_val != old_val:
|
| 271 |
# Handle must_have field as array (LLM decides the logic)
|
| 272 |
if field == "must_have" and new_val:
|
|
|
|
| 305 |
Search for properties based on user intent and store results in state.
|
| 306 |
"""
|
| 307 |
intent = state.get("intent", {})
|
| 308 |
+
print(f"DEBUG - extract_and_search_properties intent: {intent}")
|
| 309 |
|
| 310 |
# Check if we have the minimum required field for property search
|
| 311 |
location = intent.get("location_preference")
|
| 312 |
|
| 313 |
if not location:
|
| 314 |
# Missing location, skip property search
|
| 315 |
+
print("DEBUG - No location found, skipping property search")
|
| 316 |
return {"response": None}
|
| 317 |
|
| 318 |
# Prepare filters for property search
|
|
|
|
| 332 |
filters["must_have"] = must_have
|
| 333 |
|
| 334 |
# Search for properties
|
| 335 |
+
print(f"DEBUG - Searching with filters: {filters}")
|
| 336 |
properties = await search_properties(filters)
|
| 337 |
state["properties"] = properties
|
| 338 |
+
print(f"DEBUG - Found {len(properties)} properties with strict filters")
|
| 339 |
|
| 340 |
# Pass 1: strict filters
|
| 341 |
if properties:
|
| 342 |
+
print("DEBUG - Properties found, returning None to continue to chat")
|
| 343 |
return {"response": None}
|
| 344 |
|
| 345 |
# Pass 2: drop must-haves
|
database.py
CHANGED
|
@@ -294,18 +294,21 @@ async def search_properties(filters: dict) -> list:
|
|
| 294 |
return []
|
| 295 |
|
| 296 |
try:
|
|
|
|
| 297 |
query = supabase.table("properties").select("*").eq("is_active", True)
|
| 298 |
|
| 299 |
# a. Location: try city first, then location field
|
| 300 |
loc = filters.get("location_preference")
|
| 301 |
if loc:
|
| 302 |
-
|
| 303 |
-
|
| 304 |
-
|
| 305 |
-
|
| 306 |
-
|
| 307 |
-
|
| 308 |
-
|
|
|
|
|
|
|
| 309 |
|
| 310 |
# b. Budget cap
|
| 311 |
budget = filters.get("budget")
|
|
@@ -327,7 +330,12 @@ async def search_properties(filters: dict) -> list:
|
|
| 327 |
|
| 328 |
# f. Execute and return
|
| 329 |
resp = query.execute()
|
|
|
|
|
|
|
|
|
|
|
|
|
| 330 |
return resp.data or []
|
| 331 |
|
| 332 |
except Exception as e:
|
|
|
|
| 333 |
return []
|
|
|
|
| 294 |
return []
|
| 295 |
|
| 296 |
try:
|
| 297 |
+
# Start with base query
|
| 298 |
query = supabase.table("properties").select("*").eq("is_active", True)
|
| 299 |
|
| 300 |
# a. Location: try city first, then location field
|
| 301 |
loc = filters.get("location_preference")
|
| 302 |
if loc:
|
| 303 |
+
# Try city match first
|
| 304 |
+
city_query = query.ilike("city", f"%{loc}%")
|
| 305 |
+
city_resp = city_query.execute()
|
| 306 |
+
|
| 307 |
+
if city_resp.data:
|
| 308 |
+
query = city_query
|
| 309 |
+
else:
|
| 310 |
+
# Fallback to location field
|
| 311 |
+
query = supabase.table("properties").select("*").eq("is_active", True).ilike("location", f"%{loc}%")
|
| 312 |
|
| 313 |
# b. Budget cap
|
| 314 |
budget = filters.get("budget")
|
|
|
|
| 330 |
|
| 331 |
# f. Execute and return
|
| 332 |
resp = query.execute()
|
| 333 |
+
print(f"DEBUG - search_properties filters: {filters}")
|
| 334 |
+
print(f"DEBUG - properties returned: {len(resp.data) if resp.data else 0}")
|
| 335 |
+
if resp.data:
|
| 336 |
+
print(f"DEBUG - first property: {resp.data[0].get('title')} in {resp.data[0].get('city')}")
|
| 337 |
return resp.data or []
|
| 338 |
|
| 339 |
except Exception as e:
|
| 340 |
+
print(f"DEBUG - search_properties error: {e}")
|
| 341 |
return []
|