zenaight commited on
Commit Β·
ae927ca
1
Parent(s): 2cce083
trying more ai awareness
Browse files- ai_chat.py +6 -1
- persona_manager.py +42 -18
ai_chat.py
CHANGED
|
@@ -113,7 +113,12 @@ Key guidelines:
|
|
| 113 |
- If someone asks for clarification about property terms, explain clearly and helpfully
|
| 114 |
- Only ask about their property needs when they show interest in searching or viewing properties
|
| 115 |
- Keep responses conversational and not robotic
|
| 116 |
-
- If user says "hi" or "hello", respond with a greeting like "Hi [Name]! How can I help you with industrial properties today?"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 117 |
|
| 118 |
if user_info.get("name") and user_info["name"] != "Unknown":
|
| 119 |
system_message += f"\nThe user's name is {user_info['name']} - use their name to make it personal."
|
|
|
|
| 113 |
- If someone asks for clarification about property terms, explain clearly and helpfully
|
| 114 |
- Only ask about their property needs when they show interest in searching or viewing properties
|
| 115 |
- Keep responses conversational and not robotic
|
| 116 |
+
- If user says "hi" or "hello", respond with a greeting like "Hi [Name]! How can I help you with industrial properties today?"
|
| 117 |
+
- If someone mentions they need space for a specific business type (like "manufacturing plant"), understand the context and suggest appropriate sizes
|
| 118 |
+
- Don't keep asking the same question if the user has already given context about their needs
|
| 119 |
+
- Be understanding and helpful, not pushy or repetitive
|
| 120 |
+
- If someone says "big enough for manufacturing plant" or similar, understand they want a large industrial space and suggest appropriate sizes
|
| 121 |
+
- Don't be robotic - respond like a real human would in a conversation"""
|
| 122 |
|
| 123 |
if user_info.get("name") and user_info["name"] != "Unknown":
|
| 124 |
system_message += f"\nThe user's name is {user_info['name']} - use their name to make it personal."
|
persona_manager.py
CHANGED
|
@@ -197,19 +197,19 @@ async def fallback_parse_response(user_message: str, field: str) -> Tuple[bool,
|
|
| 197 |
if any(word in user_message_lower for word in affirmative_words):
|
| 198 |
# For affirmative responses, we need to get the value from context
|
| 199 |
# This will be handled by the LLM parsing, but we can provide a fallback
|
| 200 |
-
return True, "confirmed", "Got it! Thanks for confirming."
|
| 201 |
|
| 202 |
# Check for skip requests
|
| 203 |
skip_words = ["not sure", "skip", "later", "don't know", "maybe later", "pass", "no idea"]
|
| 204 |
if any(word in user_message_lower for word in skip_words):
|
| 205 |
-
return True, None, PERSONA_FIELDS[field]["skip_response"]
|
| 206 |
|
| 207 |
# Basic field-specific parsing
|
| 208 |
if field == "intent":
|
| 209 |
if any(word in user_message_lower for word in ["buy", "purchase", "own"]):
|
| 210 |
-
return True, "buy", "Got it, you're looking to buy. That's great!"
|
| 211 |
elif any(word in user_message_lower for word in ["lease", "rent"]):
|
| 212 |
-
return True, "lease", "Perfect, leasing gives you flexibility."
|
| 213 |
|
| 214 |
elif field == "budget":
|
| 215 |
import re
|
|
@@ -235,7 +235,7 @@ async def fallback_parse_response(user_message: str, field: str) -> Tuple[bool,
|
|
| 235 |
elif "m" in pattern or "million" in pattern:
|
| 236 |
budget *= 1000000
|
| 237 |
|
| 238 |
-
return True, budget, f"Thanks! I'll look for properties around ${budget:,}."
|
| 239 |
|
| 240 |
# Fallback: just look for numbers
|
| 241 |
numbers = re.findall(r'\d+', user_message)
|
|
@@ -245,7 +245,7 @@ async def fallback_parse_response(user_message: str, field: str) -> Tuple[bool,
|
|
| 245 |
budget *= 1000
|
| 246 |
elif "m" in user_message_lower or "million" in user_message_lower:
|
| 247 |
budget *= 1000000
|
| 248 |
-
return True, budget, f"Thanks! I'll look for properties around ${budget:,}."
|
| 249 |
|
| 250 |
elif field == "size_preference_sqm":
|
| 251 |
import re
|
|
@@ -266,13 +266,20 @@ async def fallback_parse_response(user_message: str, field: str) -> Tuple[bool,
|
|
| 266 |
# Convert sq ft to sqm if needed
|
| 267 |
if 'ft' in pattern and 'sq' in pattern:
|
| 268 |
size = int(size * 0.0929) # Convert sq ft to sqm
|
| 269 |
-
return True, size, f"Perfect! {size} sqm should give you good options."
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 270 |
|
| 271 |
# Fallback: just look for numbers
|
| 272 |
numbers = re.findall(r'\d+', user_message)
|
| 273 |
if numbers:
|
| 274 |
size = max(int(n) for n in numbers)
|
| 275 |
-
return True, size, f"Perfect! {size} sqm should give you good options."
|
| 276 |
|
| 277 |
elif field == "location_preference":
|
| 278 |
# Extract location names (basic approach)
|
|
@@ -284,10 +291,10 @@ async def fallback_parse_response(user_message: str, field: str) -> Tuple[bool,
|
|
| 284 |
break
|
| 285 |
|
| 286 |
if found_location:
|
| 287 |
-
return True, found_location, f"Great! {found_location.title()} has good options."
|
| 288 |
else:
|
| 289 |
# Assume the whole message is a location
|
| 290 |
-
return True, user_message.strip(), f"Got it! I'll look in {user_message.strip()}."
|
| 291 |
|
| 292 |
elif field == "must_have":
|
| 293 |
features = []
|
|
@@ -304,9 +311,9 @@ async def fallback_parse_response(user_message: str, field: str) -> Tuple[bool,
|
|
| 304 |
features.append(feature)
|
| 305 |
|
| 306 |
if features:
|
| 307 |
-
return True, features, f"Perfect! I'll make sure to find places with {', '.join(features)}."
|
| 308 |
|
| 309 |
-
return False, None, "I didn't quite understand. Could you try again?"
|
| 310 |
|
| 311 |
async def should_ask_persona_question(persona: Dict, conversation_context: str = "") -> Tuple[bool, Optional[str]]:
|
| 312 |
"""
|
|
@@ -352,6 +359,8 @@ Rules:
|
|
| 352 |
- If the user is asking about properties, locations, business needs, or showing interest in finding space β return "yes"
|
| 353 |
- If the user mentions locations (including abbreviations like cpt, pta, jhb) β return "yes"
|
| 354 |
- If the user mentions business types (clothing, manufacturing, etc.) β return "yes"
|
|
|
|
|
|
|
| 355 |
|
| 356 |
Return ONLY "yes" or "no"."""
|
| 357 |
|
|
@@ -415,17 +424,17 @@ Available persona fields:
|
|
| 415 |
- size_preference_sqm: Extract size in square meters (convert from sq ft if needed)
|
| 416 |
- must_have: Extract features as array (e.g., ["truck access", "office space"])
|
| 417 |
|
| 418 |
-
|
| 419 |
-
If
|
| 420 |
-
|
| 421 |
-
IMPORTANT: For location_preference, be very generous in extraction. If the user mentions ANY location (including "pretoria", "pta", "johannesburg", "jhb", "cape town", "cpt", etc.), extract it.
|
| 422 |
|
| 423 |
Examples:
|
| 424 |
- "I want a property in cpt" β {"location_preference": "cape town"}
|
| 425 |
- "Looking for warehouse in pta around 500k" β {"location_preference": "pretoria", "budget": 500000}
|
| 426 |
- "Looking for a property in pretoria for my clothing business" β {"location_preference": "pretoria"}
|
| 427 |
- "Need space for clothing business with office" β {"must_have": ["office"]}
|
| 428 |
-
"""
|
|
|
|
|
|
|
| 429 |
|
| 430 |
messages = [
|
| 431 |
{"role": "system", "content": system_prompt},
|
|
@@ -455,7 +464,22 @@ Examples:
|
|
| 455 |
|
| 456 |
except json.JSONDecodeError:
|
| 457 |
print(f"Failed to parse AI extraction response: {response.content}")
|
| 458 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 459 |
|
| 460 |
except Exception as e:
|
| 461 |
print(f"Error in AI persona extraction: {e}")
|
|
|
|
| 197 |
if any(word in user_message_lower for word in affirmative_words):
|
| 198 |
# For affirmative responses, we need to get the value from context
|
| 199 |
# This will be handled by the LLM parsing, but we can provide a fallback
|
| 200 |
+
return True, "confirmed", "Got it! Thanks for confirming.", False, False
|
| 201 |
|
| 202 |
# Check for skip requests
|
| 203 |
skip_words = ["not sure", "skip", "later", "don't know", "maybe later", "pass", "no idea"]
|
| 204 |
if any(word in user_message_lower for word in skip_words):
|
| 205 |
+
return True, None, PERSONA_FIELDS[field]["skip_response"], False, True
|
| 206 |
|
| 207 |
# Basic field-specific parsing
|
| 208 |
if field == "intent":
|
| 209 |
if any(word in user_message_lower for word in ["buy", "purchase", "own"]):
|
| 210 |
+
return True, "buy", "Got it, you're looking to buy. That's great!", False, False
|
| 211 |
elif any(word in user_message_lower for word in ["lease", "rent"]):
|
| 212 |
+
return True, "lease", "Perfect, leasing gives you flexibility.", False, False
|
| 213 |
|
| 214 |
elif field == "budget":
|
| 215 |
import re
|
|
|
|
| 235 |
elif "m" in pattern or "million" in pattern:
|
| 236 |
budget *= 1000000
|
| 237 |
|
| 238 |
+
return True, budget, f"Thanks! I'll look for properties around ${budget:,}.", False, False
|
| 239 |
|
| 240 |
# Fallback: just look for numbers
|
| 241 |
numbers = re.findall(r'\d+', user_message)
|
|
|
|
| 245 |
budget *= 1000
|
| 246 |
elif "m" in user_message_lower or "million" in user_message_lower:
|
| 247 |
budget *= 1000000
|
| 248 |
+
return True, budget, f"Thanks! I'll look for properties around ${budget:,}.", False, False
|
| 249 |
|
| 250 |
elif field == "size_preference_sqm":
|
| 251 |
import re
|
|
|
|
| 266 |
# Convert sq ft to sqm if needed
|
| 267 |
if 'ft' in pattern and 'sq' in pattern:
|
| 268 |
size = int(size * 0.0929) # Convert sq ft to sqm
|
| 269 |
+
return True, size, f"Perfect! {size} sqm should give you good options.", False, False
|
| 270 |
+
|
| 271 |
+
# Check for context-based size suggestions
|
| 272 |
+
if any(word in user_message_lower for word in ["manufacturing", "factory", "plant", "production"]):
|
| 273 |
+
if "large" in user_message_lower or "big" in user_message_lower:
|
| 274 |
+
return True, 5000, "Perfect! For a large manufacturing plant, I'd recommend around 5000 sqm. This gives you plenty of space for production lines, storage, and office areas.", False, False
|
| 275 |
+
else:
|
| 276 |
+
return True, 3000, "Great! For a manufacturing plant, I'd suggest around 3000 sqm. This should accommodate your production needs well.", False, False
|
| 277 |
|
| 278 |
# Fallback: just look for numbers
|
| 279 |
numbers = re.findall(r'\d+', user_message)
|
| 280 |
if numbers:
|
| 281 |
size = max(int(n) for n in numbers)
|
| 282 |
+
return True, size, f"Perfect! {size} sqm should give you good options.", False, False
|
| 283 |
|
| 284 |
elif field == "location_preference":
|
| 285 |
# Extract location names (basic approach)
|
|
|
|
| 291 |
break
|
| 292 |
|
| 293 |
if found_location:
|
| 294 |
+
return True, found_location, f"Great! {found_location.title()} has good options.", False, False
|
| 295 |
else:
|
| 296 |
# Assume the whole message is a location
|
| 297 |
+
return True, user_message.strip(), f"Got it! I'll look in {user_message.strip()}.", False, False
|
| 298 |
|
| 299 |
elif field == "must_have":
|
| 300 |
features = []
|
|
|
|
| 311 |
features.append(feature)
|
| 312 |
|
| 313 |
if features:
|
| 314 |
+
return True, features, f"Perfect! I'll make sure to find places with {', '.join(features)}.", False, False
|
| 315 |
|
| 316 |
+
return False, None, "I didn't quite understand. Could you try again?", False, False
|
| 317 |
|
| 318 |
async def should_ask_persona_question(persona: Dict, conversation_context: str = "") -> Tuple[bool, Optional[str]]:
|
| 319 |
"""
|
|
|
|
| 359 |
- If the user is asking about properties, locations, business needs, or showing interest in finding space β return "yes"
|
| 360 |
- If the user mentions locations (including abbreviations like cpt, pta, jhb) β return "yes"
|
| 361 |
- If the user mentions business types (clothing, manufacturing, etc.) β return "yes"
|
| 362 |
+
- If the user has already provided context about their needs (like "big enough for manufacturing plant") β return "no" (they've given enough context)
|
| 363 |
+
- If the user is frustrated or asking you to stop asking questions β return "no"
|
| 364 |
|
| 365 |
Return ONLY "yes" or "no"."""
|
| 366 |
|
|
|
|
| 424 |
- size_preference_sqm: Extract size in square meters (convert from sq ft if needed)
|
| 425 |
- must_have: Extract features as array (e.g., ["truck access", "office space"])
|
| 426 |
|
| 427 |
+
IMPORTANT: You MUST return a valid JSON object. If no information is found, return {} (empty object).
|
| 428 |
+
For location_preference, be very generous in extraction. If the user mentions ANY location (including "pretoria", "pta", "johannesburg", "jhb", "cape town", "cpt", etc.), extract it.
|
|
|
|
|
|
|
| 429 |
|
| 430 |
Examples:
|
| 431 |
- "I want a property in cpt" β {"location_preference": "cape town"}
|
| 432 |
- "Looking for warehouse in pta around 500k" β {"location_preference": "pretoria", "budget": 500000}
|
| 433 |
- "Looking for a property in pretoria for my clothing business" β {"location_preference": "pretoria"}
|
| 434 |
- "Need space for clothing business with office" β {"must_have": ["office"]}
|
| 435 |
+
- "I want one big enough for a phara manufacturing plant" β {"size_preference_sqm": 5000, "must_have": ["manufacturing"]}
|
| 436 |
+
|
| 437 |
+
Return ONLY the JSON object, no other text."""
|
| 438 |
|
| 439 |
messages = [
|
| 440 |
{"role": "system", "content": system_prompt},
|
|
|
|
| 464 |
|
| 465 |
except json.JSONDecodeError:
|
| 466 |
print(f"Failed to parse AI extraction response: {response.content}")
|
| 467 |
+
print(f"Response type: {type(response.content)}")
|
| 468 |
+
print(f"Response length: {len(response.content)}")
|
| 469 |
+
# Try to clean the response
|
| 470 |
+
cleaned_response = response.content.strip()
|
| 471 |
+
if cleaned_response.startswith('```json'):
|
| 472 |
+
cleaned_response = cleaned_response[7:]
|
| 473 |
+
if cleaned_response.endswith('```'):
|
| 474 |
+
cleaned_response = cleaned_response[:-3]
|
| 475 |
+
cleaned_response = cleaned_response.strip()
|
| 476 |
+
try:
|
| 477 |
+
extracted = json.loads(cleaned_response)
|
| 478 |
+
print(f"Successfully parsed after cleaning: {extracted}")
|
| 479 |
+
return extracted
|
| 480 |
+
except:
|
| 481 |
+
print(f"Still failed after cleaning: {cleaned_response}")
|
| 482 |
+
return {}
|
| 483 |
|
| 484 |
except Exception as e:
|
| 485 |
print(f"Error in AI persona extraction: {e}")
|