zenaight commited on
Commit Β·
4a4adb5
1
Parent(s): a93b33d
Refactor chat_with_session_memory function to improve user interaction and intent handling
Browse files- Enhanced the system message construction to provide clearer property search preferences, including location, budget, size, and must-haves, only when relevant.
- Updated the guidelines for user interactions to ensure a more conversational tone and to avoid asking for preferences unless the user is explicitly inquiring.
- Improved intent classification logic to allow for a more natural flow of conversation, reducing unnecessary prompts for user preferences.
- Added detailed examples for intent classification to clarify user requests and improve response accuracy.
- ai_chat.py +103 -74
ai_chat.py
CHANGED
|
@@ -45,6 +45,7 @@ def chat_with_session_memory(state):
|
|
| 45 |
"If the user requests more detail, respond with whatever is in that listing dict (URL, images, features, etc.). "
|
| 46 |
"Always base property recommendations solely on listings in our database."
|
| 47 |
)
|
|
|
|
| 48 |
if user_info.get("name") and user_info["name"] != "Unknown":
|
| 49 |
system_message += f" The user's name is {user_info['name']}."
|
| 50 |
|
|
@@ -54,13 +55,18 @@ def chat_with_session_memory(state):
|
|
| 54 |
)
|
| 55 |
|
| 56 |
intent_data = state.get("intent", {})
|
| 57 |
-
|
| 58 |
-
|
| 59 |
-
|
| 60 |
-
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 64 |
|
| 65 |
# Include search status if present
|
| 66 |
if search_status:
|
|
@@ -68,7 +74,7 @@ def chat_with_session_memory(state):
|
|
| 68 |
|
| 69 |
# Include property data if available (without showing images or addresses in listings)
|
| 70 |
if props:
|
| 71 |
-
system_message += "\n\nAvailable listings:\n"
|
| 72 |
for p in props[:5]:
|
| 73 |
system_message += (
|
| 74 |
f"- {p.get('title')} in {p.get('location')}, {p.get('city')}: "
|
|
@@ -95,7 +101,16 @@ def chat_with_session_memory(state):
|
|
| 95 |
if available_extras:
|
| 96 |
system_message += f" Available on request: {', '.join(available_extras)}\n"
|
| 97 |
|
| 98 |
-
system_message +=
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 99 |
|
| 100 |
# Build messages array with history
|
| 101 |
messages = [{"role": "system", "content": system_message}]
|
|
@@ -149,53 +164,54 @@ async def extract_and_update_persona(state):
|
|
| 149 |
wa_id = state["wa_id"]
|
| 150 |
persona = state.get("persona", {})
|
| 151 |
|
| 152 |
-
#
|
| 153 |
-
|
| 154 |
-
|
| 155 |
-
|
| 156 |
-
|
| 157 |
-
|
| 158 |
-
|
| 159 |
-
|
| 160 |
-
|
| 161 |
-
|
| 162 |
-
|
| 163 |
-
|
| 164 |
-
|
| 165 |
-
|
| 166 |
-
|
| 167 |
-
|
| 168 |
-
|
| 169 |
-
|
| 170 |
-
|
| 171 |
-
|
| 172 |
-
|
| 173 |
-
|
| 174 |
-
|
| 175 |
-
|
| 176 |
-
|
| 177 |
-
|
| 178 |
-
|
| 179 |
-
|
| 180 |
-
|
| 181 |
-
|
| 182 |
-
|
| 183 |
-
|
| 184 |
-
|
| 185 |
-
|
| 186 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 187 |
|
| 188 |
state["persona"] = persona
|
| 189 |
|
| 190 |
-
#
|
| 191 |
-
|
| 192 |
-
|
| 193 |
-
state["response"] = f"Hi there! What is your {missing[0]} preference?"
|
| 194 |
-
print(f"DEBUG - Persona update returning response: {state['response']}")
|
| 195 |
-
return state
|
| 196 |
-
|
| 197 |
-
# f. All persona fields presentβproceed to chat
|
| 198 |
-
print("DEBUG - Persona update returning None")
|
| 199 |
return {"response": None}
|
| 200 |
|
| 201 |
async def extract_and_update_intent(state):
|
|
@@ -322,16 +338,13 @@ async def extract_and_update_intent(state):
|
|
| 322 |
"classification": classification
|
| 323 |
}
|
| 324 |
|
| 325 |
-
#
|
| 326 |
-
|
| 327 |
-
|
| 328 |
-
|
| 329 |
-
"
|
| 330 |
-
"
|
| 331 |
}
|
| 332 |
-
state["response"] = questions.get(missing[0], f"Hi there! Could you tell me your {missing[0]}?")
|
| 333 |
-
print(f"DEBUG - Intent update returning response: {state['response']}")
|
| 334 |
-
return state
|
| 335 |
|
| 336 |
print("DEBUG - Intent update returning None")
|
| 337 |
return {
|
|
@@ -359,13 +372,13 @@ async def classify_user_intent(state):
|
|
| 359 |
|
| 360 |
prompt = f"""
|
| 361 |
Classify the user's message into exactly one of:
|
| 362 |
-
- search_listings (user wants to see property listings)
|
| 363 |
- request_images (user wants to see images/photos/pictures of a listing)
|
| 364 |
- request_address (user wants the address/location of a listing)
|
| 365 |
- request_details (user wants specific property info like price, features, floorplan, video, size, etc.)
|
| 366 |
-
- other (anything else, including greetings, goodbyes, general conversation, end of conversation)
|
| 367 |
|
| 368 |
-
|
| 369 |
|
| 370 |
Available properties for context: {prop_titles}
|
| 371 |
|
|
@@ -390,26 +403,42 @@ End of conversation indicators (classify as "other"):
|
|
| 390 |
- Goodbyes: "bye", "goodbye", "see you", "take care"
|
| 391 |
- Completion: "I'm all good", "that's all", "no thanks", "I'm done"
|
| 392 |
- General conversation: greetings, casual chat, non-property related topics
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 393 |
|
| 394 |
-
Examples:
|
| 395 |
- "How much is this warehouse?" β request_details
|
| 396 |
- "What is the price?" β request_details
|
| 397 |
- "What are the features?" β request_details
|
| 398 |
- "How big is it?" β request_details
|
|
|
|
|
|
|
| 399 |
- "Show me images" β request_images
|
| 400 |
- "Show me images of option 1" β request_images:option 1
|
| 401 |
- "I want to see the warehouse images" β request_images:warehouse
|
| 402 |
- "Show me pictures of this property" β request_images:this
|
| 403 |
- "Can I see images of it?" β request_images:this
|
| 404 |
- "Show me the images" β request_images:this
|
| 405 |
-
- "What do you have in JHB?" β search_listings
|
| 406 |
-
- "Do you have any properties for sale?" β search_listings
|
| 407 |
-
- "Any properties available?" β search_listings
|
| 408 |
-
- "Show me properties" β search_listings
|
| 409 |
-
- "I'm all good" β other
|
| 410 |
-
- "Goodbye" β other
|
| 411 |
-
- "Thanks for your help" β other
|
| 412 |
-
- "That's all I need" β other
|
| 413 |
|
| 414 |
Return only the tag (and identifier if applicable).
|
| 415 |
Message: {user_message}
|
|
|
|
| 45 |
"If the user requests more detail, respond with whatever is in that listing dict (URL, images, features, etc.). "
|
| 46 |
"Always base property recommendations solely on listings in our database."
|
| 47 |
)
|
| 48 |
+
|
| 49 |
if user_info.get("name") and user_info["name"] != "Unknown":
|
| 50 |
system_message += f" The user's name is {user_info['name']}."
|
| 51 |
|
|
|
|
| 55 |
)
|
| 56 |
|
| 57 |
intent_data = state.get("intent", {})
|
| 58 |
+
if intent_data.get('location_preference'):
|
| 59 |
+
must_have_list = intent_data.get('must_have', []) or []
|
| 60 |
+
system_message += (
|
| 61 |
+
f"\n\nUser's property search preferences (only mention if relevant to conversation):\n"
|
| 62 |
+
f"- Location: {intent_data.get('location_preference','[not set]')}\n"
|
| 63 |
+
)
|
| 64 |
+
if intent_data.get('budget'):
|
| 65 |
+
system_message += f"- Budget: {intent_data.get('budget')} per month\n"
|
| 66 |
+
if intent_data.get('size_preference_sqm'):
|
| 67 |
+
system_message += f"- Size: {intent_data.get('size_preference_sqm')} sqm\n"
|
| 68 |
+
if must_have_list:
|
| 69 |
+
system_message += f"- Must-haves: {', '.join(must_have_list)}\n"
|
| 70 |
|
| 71 |
# Include search status if present
|
| 72 |
if search_status:
|
|
|
|
| 74 |
|
| 75 |
# Include property data if available (without showing images or addresses in listings)
|
| 76 |
if props:
|
| 77 |
+
system_message += "\n\nAvailable property listings:\n"
|
| 78 |
for p in props[:5]:
|
| 79 |
system_message += (
|
| 80 |
f"- {p.get('title')} in {p.get('location')}, {p.get('city')}: "
|
|
|
|
| 101 |
if available_extras:
|
| 102 |
system_message += f" Available on request: {', '.join(available_extras)}\n"
|
| 103 |
|
| 104 |
+
system_message += (
|
| 105 |
+
f"\n\nIMPORTANT GUIDELINES:\n"
|
| 106 |
+
f"- Be conversational and friendly, not pushy or sales-focused\n"
|
| 107 |
+
f"- Only reference properties if user asks about them or if they're actively searching\n"
|
| 108 |
+
f"- For casual greetings or location mentions, respond conversationally\n"
|
| 109 |
+
f"- When users ask for images, let them know images will be sent separately\n"
|
| 110 |
+
f"- When users ask for addresses, provide the Google Maps address from property data\n"
|
| 111 |
+
f"- If information isn't available, politely say 'I don't have that information available'\n"
|
| 112 |
+
f"- Don't immediately jump to asking about property preferences unless user is actively looking"
|
| 113 |
+
)
|
| 114 |
|
| 115 |
# Build messages array with history
|
| 116 |
messages = [{"role": "system", "content": system_message}]
|
|
|
|
| 164 |
wa_id = state["wa_id"]
|
| 165 |
persona = state.get("persona", {})
|
| 166 |
|
| 167 |
+
# Check if user is explicitly asking about or setting preferences
|
| 168 |
+
is_preference_related = any(word in user_message.lower() for word in [
|
| 169 |
+
"language", "tone", "prefer", "speak", "formal", "casual", "friendly"
|
| 170 |
+
])
|
| 171 |
+
|
| 172 |
+
# Only extract persona if user is actually setting preferences or this is preference-related
|
| 173 |
+
if is_preference_related:
|
| 174 |
+
# b. Build a one-shot extraction prompt
|
| 175 |
+
extraction_prompt = f"""
|
| 176 |
+
Extract and normalize the user's language and tone preferences from this message:
|
| 177 |
+
{user_message}
|
| 178 |
+
|
| 179 |
+
Normalize any shorthand or typos before deciding language and tone.
|
| 180 |
+
Return only a JSON object with keys "language" and "tone", and use null for unknown.
|
| 181 |
+
"""
|
| 182 |
+
|
| 183 |
+
# c. Call the LLM
|
| 184 |
+
response = await llm.ainvoke([{"role":"user","content":extraction_prompt}])
|
| 185 |
+
import json
|
| 186 |
+
import re
|
| 187 |
+
extracted = {}
|
| 188 |
+
try:
|
| 189 |
+
# Clean up the response content (remove markdown formatting if present)
|
| 190 |
+
content = response.content.strip()
|
| 191 |
+
if content.startswith('```json'):
|
| 192 |
+
content = content[7:] # Remove ```json
|
| 193 |
+
if content.endswith('```'):
|
| 194 |
+
content = content[:-3] # Remove ```
|
| 195 |
+
content = content.strip()
|
| 196 |
+
|
| 197 |
+
extracted = json.loads(content)
|
| 198 |
+
except Exception as e:
|
| 199 |
+
print("Failed to parse persona JSON:", response.content)
|
| 200 |
+
print("Error:", e)
|
| 201 |
+
|
| 202 |
+
# d. Update DB and in-memory state for any changed values
|
| 203 |
+
for field in persona_fields:
|
| 204 |
+
new_val = extracted.get(field)
|
| 205 |
+
old_val = persona.get(field)
|
| 206 |
+
if new_val is not None and new_val != old_val:
|
| 207 |
+
await update_user_persona(wa_id, {field: new_val})
|
| 208 |
+
persona[field] = new_val
|
| 209 |
|
| 210 |
state["persona"] = persona
|
| 211 |
|
| 212 |
+
# Don't ask for preferences unless user explicitly asks about them
|
| 213 |
+
# Let conversation flow naturally
|
| 214 |
+
print("DEBUG - Persona update returning None - letting conversation flow naturally")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 215 |
return {"response": None}
|
| 216 |
|
| 217 |
async def extract_and_update_intent(state):
|
|
|
|
| 338 |
"classification": classification
|
| 339 |
}
|
| 340 |
|
| 341 |
+
# If user is not actively searching for properties, don't ask preference questions
|
| 342 |
+
# Let conversation flow naturally - only ask preferences when they're actually looking
|
| 343 |
+
print(f"DEBUG - User not actively searching for properties (classification: {classification}), letting conversation flow naturally")
|
| 344 |
+
return {
|
| 345 |
+
"response": None,
|
| 346 |
+
"classification": classification
|
| 347 |
}
|
|
|
|
|
|
|
|
|
|
| 348 |
|
| 349 |
print("DEBUG - Intent update returning None")
|
| 350 |
return {
|
|
|
|
| 372 |
|
| 373 |
prompt = f"""
|
| 374 |
Classify the user's message into exactly one of:
|
| 375 |
+
- search_listings (user explicitly wants to see property listings)
|
| 376 |
- request_images (user wants to see images/photos/pictures of a listing)
|
| 377 |
- request_address (user wants the address/location of a listing)
|
| 378 |
- request_details (user wants specific property info like price, features, floorplan, video, size, etc.)
|
| 379 |
+
- other (anything else, including greetings, goodbyes, general conversation, end of conversation, casual location mentions)
|
| 380 |
|
| 381 |
+
IMPORTANT: Only classify as "search_listings" if the user is EXPLICITLY asking to see properties, listings, or available options.
|
| 382 |
|
| 383 |
Available properties for context: {prop_titles}
|
| 384 |
|
|
|
|
| 403 |
- Goodbyes: "bye", "goodbye", "see you", "take care"
|
| 404 |
- Completion: "I'm all good", "that's all", "no thanks", "I'm done"
|
| 405 |
- General conversation: greetings, casual chat, non-property related topics
|
| 406 |
+
- Simple location mentions without asking for properties: "I'm in Johannesburg", "I live in Cape Town"
|
| 407 |
+
|
| 408 |
+
Examples of search_listings (user explicitly asking for properties):
|
| 409 |
+
- "What properties do you have in JHB?"
|
| 410 |
+
- "Show me available listings"
|
| 411 |
+
- "Do you have any properties for sale?"
|
| 412 |
+
- "Any properties available?"
|
| 413 |
+
- "Show me properties"
|
| 414 |
+
- "What do you have available?"
|
| 415 |
+
- "I'm looking for properties in Cape Town"
|
| 416 |
+
|
| 417 |
+
Examples of other (NOT property searches):
|
| 418 |
+
- "Hi" β other
|
| 419 |
+
- "Hello" β other
|
| 420 |
+
- "I'm in Johannesburg" β other
|
| 421 |
+
- "Johannesburg" β other
|
| 422 |
+
- "Cape Town" β other
|
| 423 |
+
- "How are you?" β other
|
| 424 |
+
- "I'm all good" β other
|
| 425 |
+
- "Goodbye" β other
|
| 426 |
+
- "Thanks for your help" β other
|
| 427 |
+
- "That's all I need" β other
|
| 428 |
|
| 429 |
+
Examples of request_details:
|
| 430 |
- "How much is this warehouse?" β request_details
|
| 431 |
- "What is the price?" β request_details
|
| 432 |
- "What are the features?" β request_details
|
| 433 |
- "How big is it?" β request_details
|
| 434 |
+
|
| 435 |
+
Examples of request_images:
|
| 436 |
- "Show me images" β request_images
|
| 437 |
- "Show me images of option 1" β request_images:option 1
|
| 438 |
- "I want to see the warehouse images" β request_images:warehouse
|
| 439 |
- "Show me pictures of this property" β request_images:this
|
| 440 |
- "Can I see images of it?" β request_images:this
|
| 441 |
- "Show me the images" β request_images:this
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 442 |
|
| 443 |
Return only the tag (and identifier if applicable).
|
| 444 |
Message: {user_message}
|