zenaight commited on
Commit
a822a4f
·
1 Parent(s): 997047c
Files changed (2) hide show
  1. .DS_Store +0 -0
  2. ai_chat.py +101 -405
.DS_Store ADDED
Binary file (8.2 kB). View file
 
ai_chat.py CHANGED
@@ -16,7 +16,28 @@ from config import supabase
16
  from datetime import datetime
17
  import re
18
 
19
- def chat_with_session_memory(state):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
20
  """Chat function with session-based memory and persona collection"""
21
  user_message = state["user_message"]
22
  user_info = state.get("user_info", {})
@@ -24,8 +45,6 @@ def chat_with_session_memory(state):
24
  wa_id = state.get("wa_id")
25
  wamid = state.get("wamid")
26
  persona = state.get("persona", {})
27
- is_persona_question = state.get("is_persona_question", False)
28
- current_field = state.get("current_field")
29
 
30
  # Get conversation history from database
31
  session_messages = []
@@ -33,133 +52,86 @@ def chat_with_session_memory(state):
33
  # This will be populated by the async wrapper
34
  session_messages = state.get("session_messages", [])
35
 
36
- # Check if we should handle persona collection
37
- if is_persona_question and current_field:
38
- # Handle persona question response
39
- is_valid, parsed_value, response_message = state.get("parsed_response", (False, None, ""))
40
-
41
- if is_valid:
42
- # Update persona in database (handled by async wrapper)
43
- # Check if there are more persona questions to ask
44
- updated_persona = persona.copy()
45
- updated_persona[current_field] = parsed_value
46
- missing_fields = [f for f in PERSONA_FIELDS.keys() if not updated_persona.get(f)]
47
-
48
- if missing_fields:
49
- # Ask next persona question
50
- next_field = missing_fields[0]
51
- next_question = PERSONA_FIELDS[next_field]["prompt"]
52
- return {
53
- "response": f"{response_message}\n\n{next_question}",
54
- "user_message": user_message,
55
- "ai_response": f"{response_message}\n\n{next_question}",
56
- "session_id": session_id,
57
- "wa_id": wa_id,
58
- "wamid": wamid,
59
- "update_persona": True,
60
- "persona_field": current_field,
61
- "persona_value": parsed_value,
62
- "ask_persona_question": True,
63
- "current_field": next_field
64
- }
65
- else:
66
- # Persona complete - continue with natural conversation
67
- return {
68
- "response": f"{response_message}\n\nGreat! Now I have a good understanding of what you're looking for. How can I help you find the right industrial property?",
69
- "user_message": user_message,
70
- "ai_response": f"{response_message}\n\nGreat! Now I have a good understanding of what you're looking for. How can I help you find the right industrial property?",
71
- "session_id": session_id,
72
- "wa_id": wa_id,
73
- "wamid": wamid,
74
- "update_persona": True,
75
- "persona_field": current_field,
76
- "persona_value": parsed_value
77
- }
78
- else:
79
- # Invalid response, ask for clarification or re-ask
80
- return {
81
- "response": response_message,
82
- "user_message": user_message,
83
- "ai_response": response_message,
84
- "session_id": session_id,
85
- "wa_id": wa_id,
86
- "wamid": wamid,
87
- "ask_persona_question": True,
88
- "current_field": current_field
89
- }
90
-
91
- # Regular conversation - check if we should ask persona questions
92
- should_ask, field_to_ask = state.get("persona_check", (False, None))
93
-
94
- if should_ask and field_to_ask:
95
- # Ask persona question
96
- question = PERSONA_FIELDS[field_to_ask]["prompt"]
97
- return {
98
- "response": question,
99
- "user_message": user_message,
100
- "ai_response": question,
101
- "session_id": session_id,
102
- "wa_id": wa_id,
103
- "wamid": wamid,
104
- "ask_persona_question": True,
105
- "current_field": field_to_ask
106
- }
107
 
108
- # Add system message with user context and persona
109
- system_message = """You are a friendly, professional industrial property agent assistant. Be conversational and human-like in your responses.
 
 
 
110
 
111
  Key guidelines:
112
- - ALWAYS respond naturally to greetings (hi, hello, etc.) with a warm greeting back FIRST
113
- - Use the user's name if available to make it personal
114
  - Be helpful and informative about industrial properties
115
- - Don't immediately jump to asking questions - have a natural conversation first
116
- - If someone asks for clarification about property terms, explain clearly and helpfully
117
- - Only ask about their property needs when they show interest in searching or viewing properties
118
  - Keep responses conversational and not robotic
119
- - If user says "hi" or "hello", respond with a greeting like "Hi [Name]! How can I help you with industrial properties today?"
120
- - If someone mentions they need space for a specific business type (like "manufacturing plant"), understand the context and suggest appropriate sizes
121
- - Don't keep asking the same question if the user has already given context about their needs
122
- - Be understanding and helpful, not pushy or repetitive
123
- - If someone says "big enough for manufacturing plant" or similar, understand they want a large industrial space and suggest appropriate sizes
124
- - Don't be robotic - respond like a real human would in a conversation"""
125
-
126
- if user_info.get("name") and user_info["name"] != "Unknown":
127
- system_message += f"\nThe user's name is {user_info['name']} - use their name to make it personal."
128
-
129
- # Add persona context if available
130
- persona_summary = get_persona_summary(persona)
131
- if persona_summary != "New user - profile incomplete":
132
- system_message += f"\nUser profile: {persona_summary}. Use this information to provide relevant property advice."
133
-
134
- # Build messages array with history
135
- messages = [{"role": "system", "content": system_message}]
136
-
137
- # Add conversation history (last 30 messages)
138
- for msg in session_messages[-30:]:
139
- messages.append({"role": msg["role"], "content": msg["content"]})
140
-
141
- # Add current user message
142
- messages.append({"role": "user", "content": user_message})
143
 
 
 
 
144
  try:
145
- if not OPENAI_API_KEY:
146
- return {"response": "Sorry, AI chat is not available. Please check your OpenAI API key configuration."}
 
 
 
 
 
147
 
148
  response = llm.invoke(messages)
149
- ai_response = response.content
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
150
 
151
- # Save messages to database (this will be handled by the async wrapper)
152
  return {
153
  "response": ai_response,
154
  "user_message": user_message,
155
  "ai_response": ai_response,
156
  "session_id": session_id,
157
  "wa_id": wa_id,
158
- "wamid": wamid
 
 
159
  }
 
160
  except Exception as e:
161
- print(f"Error in chat_with_session_memory: {e}")
162
- return {"response": "Sorry, something went wrong: " + str(e)}
 
 
 
 
 
 
 
163
 
164
  class ChatState(TypedDict):
165
  user_message: str
@@ -170,18 +142,12 @@ class ChatState(TypedDict):
170
  wamid: str
171
  session_messages: list
172
  persona: dict
173
- is_persona_question: bool
174
- current_field: str
175
- parsed_response: tuple
176
- persona_check: tuple
177
- update_persona: bool
178
- persona_field: str
179
- persona_value: any
180
- ask_persona_question: bool
181
 
182
  # --- Build LangGraph ---
183
  graph = StateGraph(ChatState)
184
- graph.add_node("chat", RunnableLambda(chat_with_session_memory))
185
  graph.set_entry_point("chat")
186
  graph.add_edge("chat", END)
187
  chat_graph = graph.compile()
@@ -371,305 +337,35 @@ async def process_message(user_message: str, user_info: dict = None, session_id:
371
  if user_message.lower() in ["hi", "hello", "hey", "good morning", "good afternoon", "good evening", "morning", "afternoon", "evening"]:
372
  return f"Hi {user_info.get('name', 'there')}! 👋\n\nI'm your property agent assistant. I can help you find industrial properties, warehouses, offices, and commercial spaces. What are you looking for today?"
373
 
374
- # Detect if user is expressing interest in a specific property
375
- property_1_phrases = ["property 1", "first", "1", "one", "office", "19 sqm", "2300", "edenvale"]
376
- property_2_phrases = ["property 2", "second", "2", "two", "warehouse", "304 sqm", "22000", "rutland"]
377
- user_response_lower = user_message.lower()
378
- if any(phrase in user_response_lower for phrase in property_1_phrases):
379
- return "Great choice! The 19 sqm office space in Eastleigh Exchange is perfect for 2-3 people. It's available for R2,300/month with all-inclusive amenities. Would you like me to tell you more about the features, show you photos, or help you schedule a viewing?"
380
- if any(phrase in user_response_lower for phrase in property_2_phrases):
381
- return "Excellent! The 304 sqm warehouse at Rutland Works is ideal for manufacturing. It features high roller shutter access, dual-level layout, and office space. Available for R22,000/month. Would you like more details about the features, photos, or to schedule a viewing?"
382
-
383
  # Get user persona
384
  persona = await get_or_create_persona(wa_id) if wa_id else {}
385
 
386
- # Get session messages for context
387
- session_messages = []
388
- if session_id:
389
- session_messages = await get_session_messages(session_id, limit=30)
390
-
391
- # Check if we should ask persona questions
392
- conversation_context = " ".join([msg["content"] for msg in session_messages[-5:]]) + " " + user_message
393
- should_ask, field_to_ask = await should_ask_persona_question(persona, conversation_context)
394
-
395
- # Check if this is a response to a persona question
396
- is_persona_question = False
397
- current_field = None
398
- parsed_response = (False, None, "")
399
-
400
- # Check if the last AI message was a persona question
401
- if session_messages and session_messages[-1]["role"] == "assistant":
402
- last_ai_message = session_messages[-1]["content"]
403
- for field, field_info in PERSONA_FIELDS.items():
404
- if field_info["prompt"] in last_ai_message:
405
- is_persona_question = True
406
- current_field = field
407
- # Parse user response
408
- parsed_response = await parse_user_response(user_message, field)
409
- break
410
-
411
- # Proactive persona extraction from any message
412
- extracted_persona = {}
413
- if wa_id:
414
- # Try to extract persona fields from the current message (ALWAYS)
415
- extracted_persona = await extract_persona_from_message(user_message, persona)
416
- print(f"Extracted persona from message: {extracted_persona}")
417
-
418
- # Update persona with extracted information for persona_check calculation
419
  updated_persona = persona.copy()
420
  for field, value in extracted_persona.items():
421
  if value is not None:
422
  updated_persona[field] = value
423
 
424
- # Recalculate persona check with updated persona (but only if we extracted something)
425
- if extracted_persona:
426
- conversation_context = " ".join([msg["content"] for msg in session_messages[-5:]]) + " " + user_message
427
- should_ask, field_to_ask = await should_ask_persona_question(updated_persona, conversation_context)
428
-
429
- # Check for property search intent - be more flexible
430
- property_search_phrases = [
431
- "show me properties", "property in", "warehouse in", "industrial in", "listings in", "toon my eiendomme", "warehouse", "factory", "show me warehouses", "show me listings", "show me", "show", "do you have", "have any", "properties like", "any properties", "any listings", "suggestions", "options", "results", "found", "search", "looking for", "where are", "what about", "what do you have"
432
- ]
433
- is_property_search = any(phrase in user_message.lower() for phrase in property_search_phrases)
434
-
435
- # Only trigger property search if user explicitly asks for properties, NOT for greetings
436
- greeting_phrases = ["hi", "hello", "hey", "good morning", "good afternoon", "good evening", "morning", "afternoon", "evening"]
437
- is_greeting = any(phrase in user_message.lower() for phrase in greeting_phrases)
438
-
439
- print(f"Debug - is_property_search: {is_property_search}, is_greeting: {is_greeting}")
440
- print(f"Debug - user_message: '{user_message}'")
441
-
442
- # Check if user is asking for promised property suggestions (only if last message was about searching)
443
- if session_messages and session_messages[-1]["role"] == "assistant" and updated_persona.get("location_preference"):
444
- last_ai_message = session_messages[-1]["content"]
445
- if any(phrase in last_ai_message.lower() for phrase in ["gather some options", "keep you posted", "suitable listings", "looking for properties", "search for properties"]):
446
- # User is asking for the property suggestions that were promised
447
- print("User is asking for promised property suggestions")
448
- property_msgs = await handle_property_search(user_message, updated_persona)
449
- for msg in property_msgs:
450
- print(f"Property suggestion: {msg}")
451
- if property_msgs:
452
- # Add some visual separators to make it more readable
453
- formatted_messages = []
454
- for i, msg in enumerate(property_msgs):
455
- if i == 0: # Intro message
456
- formatted_messages.append(msg)
457
- elif i == len(property_msgs) - 1: # Last message (follow-up)
458
- formatted_messages.append(f"\n{msg}")
459
- else: # Property descriptions
460
- formatted_messages.append(f"\n🏢 **Property {i}**\n{msg}")
461
-
462
- return "\n\n".join(formatted_messages)
463
- return "Let me know if you'd like to see images or more details!"
464
-
465
- # Check if user is expressing interest in a specific property
466
- if session_messages and session_messages[-1]["role"] == "assistant":
467
- last_ai_message = session_messages[-1]["content"]
468
- if "Which of these properties catches your eye" in last_ai_message:
469
- # User is responding to property suggestions
470
- user_response_lower = user_message.lower()
471
-
472
- # Check for property selection
473
- if any(phrase in user_response_lower for phrase in ["property 1", "first", "1", "one", "office", "19 sqm", "2300", "edenvale"]):
474
- return "Great choice! The 19 sqm office space in Eastleigh Exchange is perfect for 2-3 people. It's available for R2,300/month with all-inclusive amenities. Would you like me to tell you more about the features, show you photos, or help you schedule a viewing?"
475
-
476
- elif any(phrase in user_response_lower for phrase in ["property 2", "second", "2", "two", "warehouse", "304 sqm", "22000", "rutland"]):
477
- return "Excellent! The 304 sqm warehouse at Rutland Works is ideal for manufacturing. It features high roller shutter access, dual-level layout, and office space. Available for R22,000/month. Would you like more details about the features, photos, or to schedule a viewing?"
478
-
479
- elif any(phrase in user_response_lower for phrase in ["both", "all", "show me", "more", "details", "photos", "pictures"]):
480
- return "I'd be happy to show you more details! Which property would you like to know more about first - the office space (Property 1) or the warehouse (Property 2)?"
481
-
482
- else:
483
- return "I'm not sure which property you're interested in. Could you please specify 'Property 1' (the office space) or 'Property 2' (the warehouse)?"
484
-
485
- # Check if user is asking about property suggestions/results in general
486
- if any(phrase in user_message.lower() for phrase in ["where are", "suggestions", "options", "results", "found", "what about", "what do you have", "show me what", "any properties"]):
487
- print("User is asking about property suggestions/results")
488
- property_msgs = await handle_property_search(user_message, updated_persona)
489
- for msg in property_msgs:
490
- print(f"Property suggestion: {msg}")
491
- if property_msgs:
492
- # Add some visual separators to make it more readable
493
- formatted_messages = []
494
- for i, msg in enumerate(property_msgs):
495
- if i == 0: # Intro message
496
- formatted_messages.append(msg)
497
- elif i == len(property_msgs) - 1: # Last message (follow-up)
498
- formatted_messages.append(f"\n{msg}")
499
- else: # Property descriptions
500
- formatted_messages.append(f"\n🏢 **Property {i}**\n{msg}")
501
-
502
- return "\n\n".join(formatted_messages)
503
- return "Let me know if you'd like to see images or more details!"
504
-
505
- # Handle confusion or clarification requests
506
- if user_message.lower() in ["?", "huh", "what", "confused", "not sure", "what do you mean"]:
507
- if session_messages and session_messages[-1]["role"] == "assistant":
508
- last_ai_message = session_messages[-1]["content"]
509
- if "Which of these properties catches your eye" in last_ai_message:
510
- return "I just showed you a property in Edenvale! It's a 304 sqm warehouse with office space, parking, and 24/7 security. Would you like me to tell you more about it, or would you prefer to see other options?"
511
- elif "property" in last_ai_message.lower():
512
- return "I'm here to help you find industrial properties! I can search for warehouses, factories, or commercial spaces. What type of property are you looking for?"
513
- return "I'm here to help you find industrial properties! What can I assist you with today?"
514
-
515
- if is_property_search and not is_greeting:
516
- print(f"Property search detected: {user_message}")
517
- property_msgs = await handle_property_search(user_message, updated_persona)
518
- for msg in property_msgs:
519
- # Send each property message (simulate WhatsApp message send)
520
- print(f"Property suggestion: {msg}")
521
- # Return all messages as a single response (they will be sent sequentially)
522
- if property_msgs:
523
- # Add some visual separators to make it more readable
524
- formatted_messages = []
525
- for i, msg in enumerate(property_msgs):
526
- if i == 0: # Intro message
527
- formatted_messages.append(msg)
528
- elif i == len(property_msgs) - 1: # Last message (follow-up)
529
- formatted_messages.append(f"\n{msg}")
530
- else: # Property descriptions
531
- formatted_messages.append(f"\n🏢 **Property {i}**\n{msg}")
532
-
533
- return "\n\n".join(formatted_messages)
534
- return "Let me know if you'd like to see images or more details!"
535
-
536
- # Handle greetings with persona context
537
- if is_greeting and updated_persona.get("location_preference"):
538
- # User has a persona, ask if they're still looking
539
- persona_summary = get_persona_summary(updated_persona)
540
- greeting_msg = f"Hi {user_info.get('name', 'there')}! 👋\n\nI see you were previously looking for properties in {updated_persona['location_preference'].title()}"
541
-
542
- if updated_persona.get("size_preference_sqm"):
543
- greeting_msg += f" around {updated_persona['size_preference_sqm']} sqm"
544
-
545
- if updated_persona.get("budget"):
546
- greeting_msg += f" with a budget of R{int(updated_persona['budget']):,} per month"
547
-
548
- greeting_msg += ".\n\nAre you still looking for properties with these requirements, or would you like to start fresh?"
549
-
550
- return greeting_msg
551
-
552
- # Handle simple greetings without persona
553
- if is_greeting:
554
- return f"Hi {user_info.get('name', 'there')}! 👋\n\nI'm your property agent assistant. I can help you find industrial properties, warehouses, offices, and commercial spaces. What are you looking for today?"
555
-
556
- # Handle responses to the greeting question
557
- if session_messages and session_messages[-1]["role"] == "assistant":
558
- last_ai_message = session_messages[-1]["content"]
559
- if "Are you still looking for properties with these requirements" in last_ai_message:
560
- user_response_lower = user_message.lower()
561
-
562
- # Check for affirmative responses
563
- affirmative_words = ["yes", "yeah", "yep", "sure", "ok", "okay", "correct", "right", "that's right", "exactly", "still looking", "still searching"]
564
- negative_words = ["no", "nope", "not", "start fresh", "new search", "different", "change", "reset"]
565
-
566
- if any(word in user_response_lower for word in affirmative_words):
567
- # User wants to continue with current persona, search for properties
568
- property_msgs = await handle_property_search(user_message, updated_persona)
569
- for msg in property_msgs:
570
- print(f"Property suggestion: {msg}")
571
- if property_msgs:
572
- # Add some visual separators to make it more readable
573
- formatted_messages = []
574
- for i, msg in enumerate(property_msgs):
575
- if i == 0: # Intro message
576
- formatted_messages.append(msg)
577
- elif i == len(property_msgs) - 1: # Last message (follow-up)
578
- formatted_messages.append(f"\n{msg}")
579
- else: # Property descriptions
580
- formatted_messages.append(f"\n🏢 **Property {i}**\n{msg}")
581
-
582
- return "\n\n".join(formatted_messages)
583
- return "Let me know if you'd like to see images or more details!"
584
-
585
- elif any(word in user_response_lower for word in negative_words):
586
- # User wants to start fresh, reset persona
587
- if wa_id:
588
- # Reset persona fields
589
- reset_data = {
590
- "location_preference": None,
591
- "size_preference_sqm": None,
592
- "budget": None,
593
- "must_have": None,
594
- "intent": None,
595
- "updated_at": datetime.utcnow().isoformat()
596
- }
597
- try:
598
- supabase.table("user_personas").update(reset_data).eq("wa_id", wa_id).execute()
599
- print(f"Reset persona for user {wa_id}")
600
- except Exception as e:
601
- print(f"Error resetting persona: {e}")
602
-
603
- return f"Perfect! Let's start fresh. Hi {user_info.get('name', 'there')}! 👋\n\nHow can I help you find the perfect industrial property today?"
604
-
605
- # Handle "new requirements" or "start fresh" requests
606
- new_requirements_phrases = ["new requirements", "new search", "start fresh", "different requirements", "change requirements", "reset", "clear", "new criteria"]
607
- if any(phrase in user_message.lower() for phrase in new_requirements_phrases):
608
- if wa_id:
609
- # Reset persona fields
610
- reset_data = {
611
- "location_preference": None,
612
- "size_preference_sqm": None,
613
- "budget": None,
614
- "must_have": None,
615
- "intent": None,
616
- "updated_at": datetime.utcnow().isoformat()
617
- }
618
- try:
619
- supabase.table("user_personas").update(reset_data).eq("wa_id", wa_id).execute()
620
- print(f"Reset persona for user {wa_id} due to new requirements")
621
- except Exception as e:
622
- print(f"Error resetting persona: {e}")
623
-
624
- return f"Perfect! I've cleared your previous requirements. Hi {user_info.get('name', 'there')}! 👋\n\nWhat are your new requirements for an industrial property?"
625
-
626
- # Handle property detail requests
627
- if session_messages and session_messages[-1]["role"] == "assistant":
628
- last_ai_message = session_messages[-1]["content"]
629
-
630
- # Check if the last message was asking about property details
631
- if "Which of these properties catches your eye" in last_ai_message or "I can tell you more about any of them" in last_ai_message:
632
- # User is responding to property suggestions, handle their interest
633
- return await handle_property_interest(user_message, updated_persona)
634
-
635
- # Check if we're asking what specific info they want
636
- elif "What would you like to know about it" in last_ai_message or "What specific information" in last_ai_message:
637
- # User is telling us what they want to know
638
- return await handle_property_info_request(user_message, session_messages, updated_persona)
639
 
640
- # Process with AI
641
- result = await chat_graph.ainvoke({
642
  "user_message": user_message,
643
  "user_info": user_info,
644
  "session_id": session_id,
645
  "wa_id": wa_id,
646
  "wamid": wamid,
647
  "session_messages": session_messages,
648
- "persona": updated_persona, # Use updated persona
649
- "is_persona_question": is_persona_question,
650
- "current_field": current_field,
651
- "parsed_response": parsed_response,
652
- "persona_check": (should_ask, field_to_ask)
653
- })
654
-
655
- print("LangGraph result:", result) # Debugging
656
 
657
- # Save extracted persona fields first
658
- for field, value in extracted_persona.items():
659
- if value is not None:
660
- print(f"Saving extracted persona field {field} with value {value} for user {wa_id}")
661
- await update_persona_field(wa_id, field, value)
662
-
663
- # Save persona field if present in result
664
- if result.get("persona_field") and result.get("persona_value") is not None and wa_id:
665
- print(f"Saving persona field {result['persona_field']} with value {result['persona_value']} for user {wa_id}")
666
- await update_persona_field(wa_id, result["persona_field"], result["persona_value"])
667
- elif result.get("update_persona") and wa_id:
668
- await update_persona_field(wa_id, result["persona_field"], result["persona_value"])
669
-
670
- # Save messages to database
671
- if session_id and wa_id and wamid:
672
- await save_message(session_id, wa_id, wamid, "user", user_message)
673
- await save_message(session_id, wa_id, f"{wamid}_ai", "assistant", result["response"])
674
 
675
  return result["response"]
 
16
  from datetime import datetime
17
  import re
18
 
19
+ async def search_properties_for_ai(city=None, min_size=None, features=None, price_type=None, limit=5):
20
+ """Search properties for AI responses"""
21
+ try:
22
+ properties = await search_properties(city=city, min_size=min_size, features=features, price_type=price_type, limit=limit)
23
+ return properties
24
+ except Exception as e:
25
+ print(f"Error searching properties: {e}")
26
+ return []
27
+
28
+ async def get_property_details_for_ai(property_id):
29
+ """Get detailed property information for AI responses"""
30
+ try:
31
+ properties = await search_properties(limit=100) # Get all properties
32
+ for prop in properties:
33
+ if prop['id'] == property_id:
34
+ return prop
35
+ return None
36
+ except Exception as e:
37
+ print(f"Error getting property details: {e}")
38
+ return None
39
+
40
+ async def chat_with_session_memory(state):
41
  """Chat function with session-based memory and persona collection"""
42
  user_message = state["user_message"]
43
  user_info = state.get("user_info", {})
 
45
  wa_id = state.get("wa_id")
46
  wamid = state.get("wamid")
47
  persona = state.get("persona", {})
 
 
48
 
49
  # Get conversation history from database
50
  session_messages = []
 
52
  # This will be populated by the async wrapper
53
  session_messages = state.get("session_messages", [])
54
 
55
+ # Build context for the AI
56
+ context = f"User: {user_info.get('name', 'there')}\n"
57
+ if persona:
58
+ context += f"Persona: {persona}\n"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
59
 
60
+ # Add system message with context
61
+ system_message = f"""You are a friendly, professional industrial property agent assistant. Be conversational and human-like in your responses.
62
+
63
+ Context:
64
+ {context}
65
 
66
  Key guidelines:
 
 
67
  - Be helpful and informative about industrial properties
 
 
 
68
  - Keep responses conversational and not robotic
69
+ - If user asks for property search, help them find properties
70
+ - Always respond naturally and conversationally
71
+ - If user asks for specific property information (URL, photos, features, viewing), you can search the database for that information
72
+ - Use the conversation context to understand which property they're referring to
73
+
74
+ Available actions you can take:
75
+ - Search for properties by city, size, features, or price
76
+ - Get detailed information about specific properties
77
+ - Provide property URLs, photos, features, and viewing arrangements
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
78
 
79
+ Respond naturally to the user's message: {user_message}"""
80
+
81
+ # Use LLM to generate response
82
  try:
83
+ messages = [{"role": "system", "content": system_message}]
84
+
85
+ # Add recent conversation history for context
86
+ for msg in session_messages[-5:]: # Last 5 messages for context
87
+ messages.append({"role": msg["role"], "content": msg["content"]})
88
+
89
+ messages.append({"role": "user", "content": user_message})
90
 
91
  response = llm.invoke(messages)
92
+ ai_response = response.content.strip()
93
+
94
+ # If the AI response indicates it needs property information, search the database
95
+ if any(keyword in user_message.lower() for keyword in ["property", "properties", "warehouse", "office", "space", "listing", "url", "photos", "features"]):
96
+ # Extract potential search parameters from user message
97
+ city = None
98
+ if persona.get("location_preference"):
99
+ city = persona["location_preference"]
100
+
101
+ # Search for properties
102
+ properties = await search_properties_for_ai(city=city, limit=5)
103
+
104
+ if properties:
105
+ # Add property information to the response
106
+ property_info = "\n\nAvailable properties:\n"
107
+ for i, prop in enumerate(properties[:3], 1):
108
+ property_info += f"{i}. {prop['title']} - {prop['location']}, {prop['city']} - {prop['size_sqm']} sqm - R{prop['price']:,.0f}/month\n"
109
+ property_info += f" URL: {prop.get('listing_url', 'N/A')}\n"
110
+ property_info += f" Features: {', '.join(prop.get('features', [])[:5])}\n\n"
111
+
112
+ ai_response += property_info
113
 
 
114
  return {
115
  "response": ai_response,
116
  "user_message": user_message,
117
  "ai_response": ai_response,
118
  "session_id": session_id,
119
  "wa_id": wa_id,
120
+ "wamid": wamid,
121
+ "current_property": None,
122
+ "property_details": {}
123
  }
124
+
125
  except Exception as e:
126
+ print(f"Error in LangGraph: {e}")
127
+ return {
128
+ "response": "I'm having trouble processing your request right now. Could you please try again?",
129
+ "user_message": user_message,
130
+ "ai_response": "I'm having trouble processing your request right now. Could you please try again?",
131
+ "session_id": session_id,
132
+ "wa_id": wa_id,
133
+ "wamid": wamid
134
+ }
135
 
136
  class ChatState(TypedDict):
137
  user_message: str
 
142
  wamid: str
143
  session_messages: list
144
  persona: dict
145
+ current_property: str
146
+ property_details: dict
 
 
 
 
 
 
147
 
148
  # --- Build LangGraph ---
149
  graph = StateGraph(ChatState)
150
+ graph.add_node("chat", chat_with_session_memory)
151
  graph.set_entry_point("chat")
152
  graph.add_edge("chat", END)
153
  chat_graph = graph.compile()
 
337
  if user_message.lower() in ["hi", "hello", "hey", "good morning", "good afternoon", "good evening", "morning", "afternoon", "evening"]:
338
  return f"Hi {user_info.get('name', 'there')}! 👋\n\nI'm your property agent assistant. I can help you find industrial properties, warehouses, offices, and commercial spaces. What are you looking for today?"
339
 
 
 
 
 
 
 
 
 
 
340
  # Get user persona
341
  persona = await get_or_create_persona(wa_id) if wa_id else {}
342
 
343
+ # Extract persona information from message
344
+ extracted_persona = await extract_persona_from_message(user_message, persona)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
345
  updated_persona = persona.copy()
346
  for field, value in extracted_persona.items():
347
  if value is not None:
348
  updated_persona[field] = value
349
 
350
+ # Get session messages
351
+ session_messages = []
352
+ if session_id:
353
+ session_messages = await get_session_messages(session_id, limit=30)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
354
 
355
+ # Prepare state for LangGraph - let the AI handle everything
356
+ state = {
357
  "user_message": user_message,
358
  "user_info": user_info,
359
  "session_id": session_id,
360
  "wa_id": wa_id,
361
  "wamid": wamid,
362
  "session_messages": session_messages,
363
+ "persona": updated_persona,
364
+ "current_property": None,
365
+ "property_details": {}
366
+ }
 
 
 
 
367
 
368
+ # Run LangGraph - the AI will handle property detection and responses naturally
369
+ result = await chat_graph.ainvoke(state)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
370
 
371
  return result["response"]