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.

Files changed (1) hide show
  1. 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
- must_have_list = intent_data.get('must_have', []) or []
58
- system_message += (
59
- f" They're looking for a property in {intent_data.get('location_preference','[any area]')}, "
60
- f"with a budget up to {intent_data.get('budget','[any amount]')} per month, "
61
- f"around {intent_data.get('size_preference_sqm','[size]')} sqm, "
62
- f"and must-haves: {', '.join(must_have_list) if must_have_list else '[none]'}. "
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 += "\n\nIMPORTANT: You may only reference listings passed in state['properties']. When users ask for images, photos, or pictures, let them know that images are available and will be sent separately. When users ask for the address, location, or where a property is located, provide the Google Maps address from the property data. The addresses are Google Maps compatible for navigation. If information is not available, respond with 'For this listing, I don't have [specific detail] available right now'."
 
 
 
 
 
 
 
 
 
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
- # b. Build a one-shot extraction prompt
153
- extraction_prompt = f"""
154
- Extract and normalize the user's language and tone preferences from this message:
155
- {user_message}
156
-
157
- Normalize any shorthand or typos before deciding language and tone.
158
- Return only a JSON object with keys "language" and "tone", and use null for unknown.
159
- """
160
-
161
- # c. Call the LLM
162
- response = await llm.ainvoke([{"role":"user","content":extraction_prompt}])
163
- import json
164
- import re
165
- extracted = {}
166
- try:
167
- # Clean up the response content (remove markdown formatting if present)
168
- content = response.content.strip()
169
- if content.startswith('```json'):
170
- content = content[7:] # Remove ```json
171
- if content.endswith('```'):
172
- content = content[:-3] # Remove ```
173
- content = content.strip()
174
-
175
- extracted = json.loads(content)
176
- except Exception as e:
177
- print("Failed to parse persona JSON:", response.content)
178
- print("Error:", e)
179
-
180
- # d. Update DB and in-memory state for any changed values
181
- for field in persona_fields:
182
- new_val = extracted.get(field)
183
- old_val = persona.get(field)
184
- if new_val is not None and new_val != old_val:
185
- await update_user_persona(wa_id, {field: new_val})
186
- persona[field] = new_val
 
 
 
 
 
 
 
187
 
188
  state["persona"] = persona
189
 
190
- # e. If any field still unset, ask a gentle follow-up
191
- missing = [f for f in persona_fields if state["persona"].get(f) is None]
192
- if missing:
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
- # User is setting preferences, ask for missing fields
326
- questions = {
327
- "location_preference": "Hi there! Which area or suburb are you interested in?",
328
- "budget": "Hi there! What is your monthly budget?",
329
- "size_preference_sqm": "Hi there! How many square metres do you need?",
330
- "must_have": "Hi there! What features are must-haves for you?"
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
- If the user is asking for images and mentions a specific property, extract the property identifier and return: request_images:IDENTIFIER
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}