zenaight commited on
Commit
a93b33d
·
1 Parent(s): f8c1312

Enhance AI chat functionality with image request handling and intent classification updates

Browse files

- Added direct handling for image requests in the chat function, providing immediate responses for users requesting images.
- Updated the intent classification logic to include detailed context analysis for image requests, improving accuracy in identifying user needs.
- Enhanced the end chat detection mechanism using AI to better understand user intent for ending conversations.
- Refactored the image request handling to streamline the process and improve user experience when multiple properties are discussed.
- Introduced additional debug logging for better traceability of user interactions and AI responses.

Files changed (2) hide show
  1. ai_chat.py +222 -18
  2. main.py +2 -14
ai_chat.py CHANGED
@@ -21,8 +21,22 @@ def chat_with_session_memory(state):
21
  # Get properties from state
22
  props = state.get("properties", [])
23
  search_status = state.get("search_status_message", "")
 
24
 
25
-
 
 
 
 
 
 
 
 
 
 
 
 
 
26
 
27
  # Add system message with user context
28
  system_message = (
@@ -331,16 +345,51 @@ async def classify_user_intent(state):
331
  """
332
  print("DEBUG - Starting classify_user_intent")
333
  user_message = state["user_message"]
 
 
 
 
 
 
 
 
 
 
 
 
334
  prompt = f"""
335
  Classify the user's message into exactly one of:
336
  - search_listings (user wants to see property listings)
337
  - request_images (user wants to see images/photos/pictures of a listing)
338
  - request_address (user wants the address/location of a listing)
339
  - request_details (user wants specific property info like price, features, floorplan, video, size, etc.)
340
- - other (anything else)
 
 
 
 
 
 
 
341
 
342
- If the user is asking for images and mentions a specific property (like "option 1", "the office", "warehouse", etc.),
343
- extract the property identifier and return: request_images:IDENTIFIER
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
344
 
345
  Examples:
346
  - "How much is this warehouse?" → request_details
@@ -348,10 +397,19 @@ Examples:
348
  - "What are the features?" → request_details
349
  - "How big is it?" → request_details
350
  - "Show me images" → request_images
 
 
 
 
 
351
  - "What do you have in JHB?" → search_listings
352
  - "Do you have any properties for sale?" → search_listings
353
  - "Any properties available?" → search_listings
354
  - "Show me properties" → search_listings
 
 
 
 
355
 
356
  Return only the tag (and identifier if applicable).
357
  Message: {user_message}
@@ -432,20 +490,57 @@ async def extract_and_search_properties(state):
432
 
433
  async def detect_end_chat(state):
434
  """
435
- Detect if the user wants to end the chat session.
436
  """
437
- user_message = state["user_message"].lower()
438
  session_id = state["session_id"]
439
 
440
- if any(phrase in user_message for phrase in ["thank you", "thanks", "bye", "goodbye", "end chat"]):
441
- await end_session(session_id)
442
- return {"response": "Thanks for chatting! I've ended this session. Goodbye!"}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
443
 
444
- return {"response": None}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
445
 
446
  # --- Build LangGraph ---
447
  graph = StateGraph(ChatState)
448
  graph.add_node("persona_update", RunnableLambda(extract_and_update_persona))
 
449
  graph.add_node("classify_intent", RunnableLambda(classify_user_intent))
450
  graph.add_node("intent_update", RunnableLambda(extract_and_update_intent))
451
  graph.add_node("property_search", RunnableLambda(extract_and_search_properties))
@@ -460,7 +555,11 @@ def should_continue(state):
460
  print(f"DEBUG - should_continue: response={state.get('response')}, has_response={has_response}, should_continue={not has_response}")
461
  return not has_response
462
 
463
- graph.add_edge("persona_update", "classify_intent")
 
 
 
 
464
  graph.add_edge("classify_intent", "intent_update")
465
  graph.add_conditional_edges("intent_update", should_continue, {
466
  True: "property_search",
@@ -500,6 +599,36 @@ async def process_message(user_message: str, user_info: dict = None, session_id:
500
  "properties": properties or []
501
  })
502
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
503
  # Save messages to database
504
  if session_id and wa_id and wamid:
505
  await save_message(session_id, wa_id, wamid, "user", user_message)
@@ -609,6 +738,58 @@ async def handle_image_request(state):
609
  if selected_property_context:
610
  selected_property = selected_property_context
611
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
612
  # Fallback: Use conversation context to find which property user was discussing
613
  if not selected_property and len(props) > 1:
614
  # Check conversation history for property context
@@ -639,13 +820,36 @@ async def handle_image_request(state):
639
  selected_property = props[most_mentioned[0]]
640
  print(f"DEBUG - Selected property from conversation context: {selected_property.get('title')}")
641
  else:
642
- # Multiple properties available - ask user to specify
643
- prop_options = []
644
- for i, prop in enumerate(props[:3], 1): # Show first 3 options
645
- prop_options.append(f"Option {i}: {prop.get('title')}")
646
-
647
- options_text = "\n".join(prop_options)
648
- return [f"I have multiple properties available. Which one would you like to see images of?\n\n{options_text}\n\nPlease let me know which option you'd like images for."]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
649
 
650
  # Final fallback: use first property if only one or no context found
651
  if not selected_property:
 
21
  # Get properties from state
22
  props = state.get("properties", [])
23
  search_status = state.get("search_status_message", "")
24
+ classification = state.get("classification", "")
25
 
26
+ # Check if this is an image request and handle it directly
27
+ if classification.startswith("request_images"):
28
+ print("DEBUG - Image request detected in chat function")
29
+ # This will be handled by the async wrapper that calls handle_image_request
30
+ # For now, just return a placeholder response
31
+ return {
32
+ "response": "I'll get those images for you right away!",
33
+ "user_message": user_message,
34
+ "ai_response": "I'll get those images for you right away!",
35
+ "session_id": session_id,
36
+ "wa_id": wa_id,
37
+ "wamid": wamid,
38
+ "classification": classification
39
+ }
40
 
41
  # Add system message with user context
42
  system_message = (
 
345
  """
346
  print("DEBUG - Starting classify_user_intent")
347
  user_message = state["user_message"]
348
+
349
+ # Get available properties for context
350
+ props = state.get("properties", [])
351
+ prop_titles = [p.get("title", "").lower() for p in props]
352
+
353
+ # Get recent conversation context
354
+ session_messages = state.get("session_messages", [])
355
+ recent_context = ""
356
+ if session_messages:
357
+ recent_messages = session_messages[-5:] # Last 5 messages
358
+ recent_context = "\n".join([f"{msg.get('role')}: {msg.get('content', '')}" for msg in recent_messages])
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
+
372
+ Recent conversation context:
373
+ {recent_context}
374
 
375
+ Property identifier extraction rules:
376
+ 1. If user says "option X" or "option X images" → request_images:option X
377
+ 2. If user mentions a property type (warehouse, office, space) and it matches a property title → request_images:PROPERTY_TYPE
378
+ 3. If user says "this property", "that property", "the property" → request_images:this
379
+ 4. If user says "show me images" without specific reference → request_images
380
+ 5. If user mentions specific location/area that matches a property → request_images:LOCATION
381
+ 6. If user asks for images in context where only one property is being discussed → request_images:this
382
+ 7. If user uses pronouns like "it", "this", "that" when asking for images → request_images:this
383
+
384
+ Context analysis:
385
+ - If the user has been discussing a specific property and now asks for images, classify as request_images:this
386
+ - If there's only one property available and user asks for images, classify as request_images:this
387
+ - If user asks for images after a property search, assume they want images of the most relevant property
388
+
389
+ 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
 
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}
 
490
 
491
  async def detect_end_chat(state):
492
  """
493
+ Detect if the user wants to end the chat session using AI.
494
  """
495
+ user_message = state["user_message"]
496
  session_id = state["session_id"]
497
 
498
+ # Use AI to detect if user wants to end the conversation
499
+ prompt = f"""
500
+ Analyze this user message and determine if they want to end the conversation or are saying goodbye.
501
+
502
+ User message: "{user_message}"
503
+
504
+ Consider these scenarios as "end conversation":
505
+ - User is satisfied and ending the conversation (e.g., "I'm all good", "that's all", "no thanks")
506
+ - User is saying goodbye (e.g., "bye", "goodbye", "see you", "take care")
507
+ - User is declining further assistance (e.g., "not interested", "maybe later", "I'll think about it")
508
+ - User is indicating they're done (e.g., "I'm done", "that's it", "nothing else")
509
+ - User is politely ending the conversation (e.g., "thanks for your help", "appreciate it")
510
+
511
+ Consider these scenarios as "continue conversation":
512
+ - User is asking questions about properties
513
+ - User is providing feedback but wants to continue
514
+ - User is making small talk but not ending
515
+ - User is asking for more information
516
+
517
+ Respond with ONLY:
518
+ - "end" if the user wants to end the conversation
519
+ - "continue" if the user wants to continue the conversation
520
+
521
+ Response:"""
522
 
523
+ try:
524
+ response = await llm.ainvoke([{"role": "user", "content": prompt}])
525
+ result = response.content.strip().lower()
526
+
527
+ print(f"DEBUG - End chat AI detection: '{result}' for message: '{user_message}'")
528
+
529
+ if result == "end":
530
+ await end_session(session_id)
531
+ return {"response": "Thanks for chatting! I've ended this session. Goodbye!"}
532
+
533
+ return {"response": None}
534
+
535
+ except Exception as e:
536
+ print(f"DEBUG - Error in end chat detection: {e}")
537
+ # Fallback to continue conversation if AI fails
538
+ return {"response": None}
539
 
540
  # --- Build LangGraph ---
541
  graph = StateGraph(ChatState)
542
  graph.add_node("persona_update", RunnableLambda(extract_and_update_persona))
543
+ graph.add_node("exit_check_early", RunnableLambda(detect_end_chat))
544
  graph.add_node("classify_intent", RunnableLambda(classify_user_intent))
545
  graph.add_node("intent_update", RunnableLambda(extract_and_update_intent))
546
  graph.add_node("property_search", RunnableLambda(extract_and_search_properties))
 
555
  print(f"DEBUG - should_continue: response={state.get('response')}, has_response={has_response}, should_continue={not has_response}")
556
  return not has_response
557
 
558
+ graph.add_edge("persona_update", "exit_check_early")
559
+ graph.add_conditional_edges("exit_check_early", should_continue, {
560
+ True: "classify_intent",
561
+ False: END
562
+ })
563
  graph.add_edge("classify_intent", "intent_update")
564
  graph.add_conditional_edges("intent_update", should_continue, {
565
  True: "property_search",
 
599
  "properties": properties or []
600
  })
601
 
602
+ # Check if this is an image request and handle it
603
+ classification = result.get("classification", "")
604
+ if classification.startswith("request_images"):
605
+ print("DEBUG - Processing image request in process_message")
606
+ # Create state for image request handling
607
+ image_state = {
608
+ "user_message": user_message,
609
+ "properties": result.get("properties", []),
610
+ "classification": classification,
611
+ "session_messages": session_messages
612
+ }
613
+
614
+ # Handle image request
615
+ image_messages = await handle_image_request(image_state)
616
+
617
+ if image_messages:
618
+ # Save the first text message to database
619
+ if session_id and wa_id and wamid:
620
+ await save_message(session_id, wa_id, wamid, "user", user_message)
621
+ # Save the first text response
622
+ if isinstance(image_messages[0], str):
623
+ await save_message(session_id, wa_id, f"{wamid}_ai", "assistant", image_messages[0])
624
+
625
+ return {
626
+ "response": image_messages[0] if isinstance(image_messages[0], str) else "Here are the images you requested!",
627
+ "properties": result.get("properties", []),
628
+ "classification": classification,
629
+ "image_messages": image_messages # Additional field for image handling
630
+ }
631
+
632
  # Save messages to database
633
  if session_id and wa_id and wamid:
634
  await save_message(session_id, wa_id, wamid, "user", user_message)
 
738
  if selected_property_context:
739
  selected_property = selected_property_context
740
 
741
+ # Enhanced context analysis for "this", "that", pronouns, etc.
742
+ if not selected_property:
743
+ # Check if user is using pronouns or demonstratives
744
+ user_message_lower = state["user_message"].lower()
745
+ is_pronoun_reference = any(word in user_message_lower for word in ["this", "that", "it", "the property", "the listing"])
746
+
747
+ if is_pronoun_reference:
748
+ print("DEBUG - User using pronoun/demonstrative reference, analyzing recent context")
749
+
750
+ # Look for the most recently discussed property in the conversation
751
+ recent_messages = session_messages[-10:] # Last 10 messages
752
+ property_mentions = {}
753
+
754
+ for msg in recent_messages:
755
+ content = msg.get("content", "").lower()
756
+ for i, prop in enumerate(props):
757
+ title = prop.get("title", "").lower()
758
+ location = prop.get("location", "").lower()
759
+ city = prop.get("city", "").lower()
760
+
761
+ # Check for property mentions with higher weight for recent messages
762
+ score = 0
763
+ title_words = title.split()
764
+
765
+ # Check if property title words are mentioned
766
+ for word in title_words[:3]: # First 3 words of title
767
+ if word in content:
768
+ score += 2
769
+
770
+ # Check if location is mentioned
771
+ if location in content:
772
+ score += 1
773
+
774
+ # Check if city is mentioned
775
+ if city in content:
776
+ score += 1
777
+
778
+ # Give higher weight to more recent messages
779
+ message_index = recent_messages.index(msg)
780
+ recency_weight = 10 - message_index # More recent = higher weight
781
+ score *= recency_weight
782
+
783
+ if score > 0:
784
+ property_mentions[i] = property_mentions.get(i, 0) + score
785
+ print(f"DEBUG - Found mention of property {i} (score {score}): {title}")
786
+
787
+ # Use most mentioned property from recent conversation
788
+ if property_mentions:
789
+ most_mentioned = max(property_mentions.items(), key=lambda x: x[1])
790
+ selected_property = props[most_mentioned[0]]
791
+ print(f"DEBUG - Selected property from pronoun context: {selected_property.get('title')}")
792
+
793
  # Fallback: Use conversation context to find which property user was discussing
794
  if not selected_property and len(props) > 1:
795
  # Check conversation history for property context
 
820
  selected_property = props[most_mentioned[0]]
821
  print(f"DEBUG - Selected property from conversation context: {selected_property.get('title')}")
822
  else:
823
+ # Check if user is asking for images in a general way (like "show me images")
824
+ # and there's only one property or clear context from recent messages
825
+ if len(props) == 1:
826
+ # Only one property available - use it
827
+ selected_property = props[0]
828
+ print(f"DEBUG - Only one property available, using: {selected_property.get('title')}")
829
+ else:
830
+ # Look for any recent property discussion in the last few messages
831
+ very_recent_messages = session_messages[-5:] # Last 5 messages
832
+ for msg in very_recent_messages:
833
+ if msg.get("role") == "assistant":
834
+ content = msg.get("content", "").lower()
835
+ # Check if any property was mentioned in the last AI response
836
+ for i, prop in enumerate(props):
837
+ title = prop.get("title", "").lower()
838
+ if any(word in content for word in title.split()[:3]): # Check first 3 words of title
839
+ selected_property = prop
840
+ print(f"DEBUG - Found recent mention of property: {prop.get('title')}")
841
+ break
842
+ if selected_property:
843
+ break
844
+
845
+ # If still no property selected, only then ask user to specify
846
+ if not selected_property:
847
+ prop_options = []
848
+ for i, prop in enumerate(props[:3], 1): # Show first 3 options
849
+ prop_options.append(f"Option {i}: {prop.get('title')}")
850
+
851
+ options_text = "\n".join(prop_options)
852
+ return [f"I have multiple properties available. Which one would you like to see images of?\n\n{options_text}\n\nPlease let me know which option you'd like images for."]
853
 
854
  # Final fallback: use first property if only one or no context found
855
  if not selected_property:
main.py CHANGED
@@ -5,7 +5,7 @@ from fastapi.responses import JSONResponse
5
  from config import supabase
6
  from database import get_or_create_user, update_user_activity, get_or_create_active_session, get_user_persona, get_or_create_user_intent
7
  from whatsapp import send_whatsapp_message, send_multiple_messages
8
- from ai_chat import process_message, handle_image_request
9
  from api_routes import router
10
 
11
  # --- FastAPI App Setup ---
@@ -65,23 +65,11 @@ async def receive_message(req: Request):
65
 
66
  ai_response = ai_result["response"]
67
  properties = ai_result.get("properties", [])
68
-
69
- # Check if this is an image request
70
- state = {
71
- "user_message": user_message,
72
- "properties": properties
73
- }
74
 
75
  # Check if this is an image request
76
  classification = ai_result.get("classification", "")
77
  print(f"DEBUG - main.py classification: '{classification}'")
78
- image_messages = None
79
-
80
- if classification.startswith("request_images") or "image" in user_message.lower() or "photo" in user_message.lower():
81
- print(f"DEBUG - Calling handle_image_request with classification: '{classification}'")
82
- # Ensure classification is passed to handle_image_request
83
- state["classification"] = classification
84
- image_messages = await handle_image_request(state)
85
 
86
  if image_messages:
87
  # Send images
 
5
  from config import supabase
6
  from database import get_or_create_user, update_user_activity, get_or_create_active_session, get_user_persona, get_or_create_user_intent
7
  from whatsapp import send_whatsapp_message, send_multiple_messages
8
+ from ai_chat import process_message
9
  from api_routes import router
10
 
11
  # --- FastAPI App Setup ---
 
65
 
66
  ai_response = ai_result["response"]
67
  properties = ai_result.get("properties", [])
68
+ image_messages = ai_result.get("image_messages")
 
 
 
 
 
69
 
70
  # Check if this is an image request
71
  classification = ai_result.get("classification", "")
72
  print(f"DEBUG - main.py classification: '{classification}'")
 
 
 
 
 
 
 
73
 
74
  if image_messages:
75
  # Send images