zenaight commited on
Commit ·
41e7566
1
Parent(s): bcd0eb8
more humanlike
Browse files- ai_chat.py +54 -18
- persona_manager.py +44 -15
ai_chat.py
CHANGED
|
@@ -36,17 +36,41 @@ def chat_with_session_memory(state):
|
|
| 36 |
|
| 37 |
if is_valid:
|
| 38 |
# Update persona in database (handled by async wrapper)
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
"
|
| 48 |
-
|
| 49 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 50 |
else:
|
| 51 |
# Invalid response, ask for clarification or re-ask
|
| 52 |
return {
|
|
@@ -78,14 +102,24 @@ def chat_with_session_memory(state):
|
|
| 78 |
}
|
| 79 |
|
| 80 |
# Add system message with user context and persona
|
| 81 |
-
system_message = "You are a
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 82 |
if user_info.get("name") and user_info["name"] != "Unknown":
|
| 83 |
-
system_message += f"
|
| 84 |
|
| 85 |
# Add persona context if available
|
| 86 |
persona_summary = get_persona_summary(persona)
|
| 87 |
if persona_summary != "New user - profile incomplete":
|
| 88 |
-
system_message += f"
|
| 89 |
|
| 90 |
# Build messages array with history
|
| 91 |
messages = [{"role": "system", "content": system_message}]
|
|
@@ -155,10 +189,6 @@ async def process_message(user_message: str, user_info: dict = None, session_id:
|
|
| 155 |
if session_id:
|
| 156 |
session_messages = await get_session_messages(session_id, limit=30)
|
| 157 |
|
| 158 |
-
# Check if we should ask persona questions
|
| 159 |
-
conversation_context = " ".join([msg["content"] for msg in session_messages[-5:]]) + " " + user_message
|
| 160 |
-
should_ask, field_to_ask = await should_ask_persona_question(persona, conversation_context)
|
| 161 |
-
|
| 162 |
# Check if this is a response to a persona question
|
| 163 |
is_persona_question = False
|
| 164 |
current_field = None
|
|
@@ -175,6 +205,12 @@ async def process_message(user_message: str, user_info: dict = None, session_id:
|
|
| 175 |
parsed_response = await parse_user_response(user_message, field)
|
| 176 |
break
|
| 177 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 178 |
# Process with AI
|
| 179 |
result = await chat_graph.ainvoke({
|
| 180 |
"user_message": user_message,
|
|
|
|
| 36 |
|
| 37 |
if is_valid:
|
| 38 |
# Update persona in database (handled by async wrapper)
|
| 39 |
+
# Check if there are more persona questions to ask
|
| 40 |
+
updated_persona = persona.copy()
|
| 41 |
+
updated_persona[current_field] = parsed_value
|
| 42 |
+
missing_fields = [f for f in PERSONA_FIELDS.keys() if not updated_persona.get(f)]
|
| 43 |
+
|
| 44 |
+
if missing_fields:
|
| 45 |
+
# Ask next persona question
|
| 46 |
+
next_field = missing_fields[0]
|
| 47 |
+
next_question = PERSONA_FIELDS[next_field]["prompt"]
|
| 48 |
+
return {
|
| 49 |
+
"response": f"{response_message}\n\n{next_question}",
|
| 50 |
+
"user_message": user_message,
|
| 51 |
+
"ai_response": f"{response_message}\n\n{next_question}",
|
| 52 |
+
"session_id": session_id,
|
| 53 |
+
"wa_id": wa_id,
|
| 54 |
+
"wamid": wamid,
|
| 55 |
+
"update_persona": True,
|
| 56 |
+
"persona_field": current_field,
|
| 57 |
+
"persona_value": parsed_value,
|
| 58 |
+
"ask_persona_question": True,
|
| 59 |
+
"current_field": next_field
|
| 60 |
+
}
|
| 61 |
+
else:
|
| 62 |
+
# Persona complete - continue with natural conversation
|
| 63 |
+
return {
|
| 64 |
+
"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?",
|
| 65 |
+
"user_message": user_message,
|
| 66 |
+
"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?",
|
| 67 |
+
"session_id": session_id,
|
| 68 |
+
"wa_id": wa_id,
|
| 69 |
+
"wamid": wamid,
|
| 70 |
+
"update_persona": True,
|
| 71 |
+
"persona_field": current_field,
|
| 72 |
+
"persona_value": parsed_value
|
| 73 |
+
}
|
| 74 |
else:
|
| 75 |
# Invalid response, ask for clarification or re-ask
|
| 76 |
return {
|
|
|
|
| 102 |
}
|
| 103 |
|
| 104 |
# Add system message with user context and persona
|
| 105 |
+
system_message = """You are a friendly, professional industrial property agent assistant. Be conversational and human-like in your responses.
|
| 106 |
+
|
| 107 |
+
Key guidelines:
|
| 108 |
+
- Always respond naturally to greetings (hi, hello, etc.) with a warm greeting back
|
| 109 |
+
- Use the user's name if available to make it personal
|
| 110 |
+
- Be helpful and informative about industrial properties
|
| 111 |
+
- Don't immediately jump to asking questions - have a natural conversation first
|
| 112 |
+
- If someone asks for clarification about property terms, explain clearly and helpfully
|
| 113 |
+
- Only ask about their property needs when they show interest in searching or viewing properties
|
| 114 |
+
- Keep responses conversational and not robotic"""
|
| 115 |
+
|
| 116 |
if user_info.get("name") and user_info["name"] != "Unknown":
|
| 117 |
+
system_message += f"\nThe user's name is {user_info['name']} - use their name to make it personal."
|
| 118 |
|
| 119 |
# Add persona context if available
|
| 120 |
persona_summary = get_persona_summary(persona)
|
| 121 |
if persona_summary != "New user - profile incomplete":
|
| 122 |
+
system_message += f"\nUser profile: {persona_summary}. Use this information to provide relevant property advice."
|
| 123 |
|
| 124 |
# Build messages array with history
|
| 125 |
messages = [{"role": "system", "content": system_message}]
|
|
|
|
| 189 |
if session_id:
|
| 190 |
session_messages = await get_session_messages(session_id, limit=30)
|
| 191 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 192 |
# Check if this is a response to a persona question
|
| 193 |
is_persona_question = False
|
| 194 |
current_field = None
|
|
|
|
| 205 |
parsed_response = await parse_user_response(user_message, field)
|
| 206 |
break
|
| 207 |
|
| 208 |
+
# Only check for new persona questions if this isn't a response to one
|
| 209 |
+
should_ask, field_to_ask = (False, None)
|
| 210 |
+
if not is_persona_question:
|
| 211 |
+
conversation_context = " ".join([msg["content"] for msg in session_messages[-5:]]) + " " + user_message
|
| 212 |
+
should_ask, field_to_ask = await should_ask_persona_question(persona, conversation_context)
|
| 213 |
+
|
| 214 |
# Process with AI
|
| 215 |
result = await chat_graph.ainvoke({
|
| 216 |
"user_message": user_message,
|
persona_manager.py
CHANGED
|
@@ -5,28 +5,28 @@ from config import supabase, llm, OPENAI_API_KEY
|
|
| 5 |
# Persona field definitions and their collection prompts
|
| 6 |
PERSONA_FIELDS = {
|
| 7 |
"intent": {
|
| 8 |
-
"prompt": "Are you looking to buy a place, or maybe lease for now?",
|
| 9 |
-
"clarification": "Great question
|
| 10 |
"skip_response": "No problem at all. We can always revisit that later."
|
| 11 |
},
|
| 12 |
"location_preference": {
|
| 13 |
"prompt": "Any specific areas you'd prefer to be based in?",
|
| 14 |
-
"clarification": "I can help you find properties in any area.
|
| 15 |
"skip_response": "No problem at all. We can always revisit that later."
|
| 16 |
},
|
| 17 |
"size_preference_sqm": {
|
| 18 |
"prompt": "Do you have a rough size in mind? For example, 1500 or 3000 sqm?",
|
| 19 |
-
"clarification": "
|
| 20 |
"skip_response": "No problem at all. We can always revisit that later."
|
| 21 |
},
|
| 22 |
"budget": {
|
| 23 |
"prompt": "Is there a budget you'd like me to work within?",
|
| 24 |
-
"clarification": "Budget helps me find the right properties for you. We can discuss options at any price point
|
| 25 |
"skip_response": "No problem at all. We can always revisit that later."
|
| 26 |
},
|
| 27 |
"must_have": {
|
| 28 |
"prompt": "Anything important that's a must-have? Like truck access or yard space?",
|
| 29 |
-
"clarification": "Must-haves are features you really need
|
| 30 |
"skip_response": "No problem at all. We can always revisit that later."
|
| 31 |
}
|
| 32 |
}
|
|
@@ -159,13 +159,34 @@ async def fallback_parse_response(user_message: str, field: str) -> Tuple[bool,
|
|
| 159 |
"""Fallback parsing when LLM parsing fails"""
|
| 160 |
user_message_lower = user_message.lower()
|
| 161 |
|
| 162 |
-
# Check for clarification requests
|
| 163 |
-
clarification_words = [
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 164 |
if any(word in user_message_lower for word in clarification_words):
|
| 165 |
return False, None, PERSONA_FIELDS[field]["clarification"]
|
| 166 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 167 |
# Check for skip requests
|
| 168 |
-
skip_words = ["not sure", "skip", "later", "don't know", "maybe later"]
|
| 169 |
if any(word in user_message_lower for word in skip_words):
|
| 170 |
return True, None, PERSONA_FIELDS[field]["skip_response"]
|
| 171 |
|
|
@@ -243,18 +264,26 @@ async def should_ask_persona_question(persona: Dict, conversation_context: str =
|
|
| 243 |
# Check if user is asking about properties (indicates they want to search)
|
| 244 |
search_indicators = [
|
| 245 |
"property", "warehouse", "industrial", "space", "building",
|
| 246 |
-
"available", "looking for", "need", "find", "search"
|
| 247 |
]
|
| 248 |
|
| 249 |
conversation_lower = conversation_context.lower()
|
| 250 |
is_searching = any(indicator in conversation_lower for indicator in search_indicators)
|
| 251 |
|
| 252 |
-
#
|
| 253 |
-
|
| 254 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 255 |
|
| 256 |
-
#
|
| 257 |
-
if
|
|
|
|
|
|
|
|
|
|
|
|
|
| 258 |
return True, missing_fields[0]
|
| 259 |
|
| 260 |
return False, None
|
|
|
|
| 5 |
# Persona field definitions and their collection prompts
|
| 6 |
PERSONA_FIELDS = {
|
| 7 |
"intent": {
|
| 8 |
+
"prompt": "Great! Are you looking to buy a place, or maybe lease for now?",
|
| 9 |
+
"clarification": "Great question! Buying gives you long-term control and equity, but requires a larger upfront investment. Leasing gives you flexibility and lower initial costs, but you don't build equity. Both have their benefits - what feels right for your situation?",
|
| 10 |
"skip_response": "No problem at all. We can always revisit that later."
|
| 11 |
},
|
| 12 |
"location_preference": {
|
| 13 |
"prompt": "Any specific areas you'd prefer to be based in?",
|
| 14 |
+
"clarification": "I can help you find properties in any area! Some popular industrial areas include downtown, suburbs, industrial districts, and warehouse zones. Each has different benefits - downtown for accessibility, suburbs for space, industrial areas for zoning. What kind of location works best for your business?",
|
| 15 |
"skip_response": "No problem at all. We can always revisit that later."
|
| 16 |
},
|
| 17 |
"size_preference_sqm": {
|
| 18 |
"prompt": "Do you have a rough size in mind? For example, 1500 or 3000 sqm?",
|
| 19 |
+
"clarification": "Property sizes can vary a lot! Small workshops might be 500-1000 sqm, medium warehouses 1500-3000 sqm, and large facilities 5000+ sqm. Think about your current space needs and future growth. What kind of operations will you be running?",
|
| 20 |
"skip_response": "No problem at all. We can always revisit that later."
|
| 21 |
},
|
| 22 |
"budget": {
|
| 23 |
"prompt": "Is there a budget you'd like me to work within?",
|
| 24 |
+
"clarification": "Budget helps me find the right properties for you! Industrial properties can range from $200k for small units to millions for large facilities. We can discuss options at any price point - what's comfortable for your business?",
|
| 25 |
"skip_response": "No problem at all. We can always revisit that later."
|
| 26 |
},
|
| 27 |
"must_have": {
|
| 28 |
"prompt": "Anything important that's a must-have? Like truck access or yard space?",
|
| 29 |
+
"clarification": "Must-haves are features you really need for your business! Common ones include loading docks, high ceilings, truck access, yard space, office areas, parking, or specific zoning. What features are essential for your operations?",
|
| 30 |
"skip_response": "No problem at all. We can always revisit that later."
|
| 31 |
}
|
| 32 |
}
|
|
|
|
| 159 |
"""Fallback parsing when LLM parsing fails"""
|
| 160 |
user_message_lower = user_message.lower()
|
| 161 |
|
| 162 |
+
# Check for clarification requests - more comprehensive
|
| 163 |
+
clarification_words = [
|
| 164 |
+
"what", "how", "explain", "difference", "mean", "clarify", "tell me",
|
| 165 |
+
"what do you mean", "what does", "how does", "can you explain",
|
| 166 |
+
"i don't understand", "not sure what", "what's the difference"
|
| 167 |
+
]
|
| 168 |
+
|
| 169 |
+
# Check for questions about the field specifically
|
| 170 |
+
field_specific_clarifications = {
|
| 171 |
+
"intent": ["buy", "lease", "purchase", "rent", "owning", "owning vs leasing"],
|
| 172 |
+
"size_preference_sqm": ["size", "sqm", "square meters", "how big", "dimensions"],
|
| 173 |
+
"budget": ["budget", "cost", "price", "how much", "expensive"],
|
| 174 |
+
"location_preference": ["location", "area", "where", "place"],
|
| 175 |
+
"must_have": ["must have", "features", "requirements", "need", "essential"]
|
| 176 |
+
}
|
| 177 |
+
|
| 178 |
+
# Check for general clarification requests
|
| 179 |
if any(word in user_message_lower for word in clarification_words):
|
| 180 |
return False, None, PERSONA_FIELDS[field]["clarification"]
|
| 181 |
|
| 182 |
+
# Check for field-specific clarification requests
|
| 183 |
+
if field in field_specific_clarifications:
|
| 184 |
+
field_words = field_specific_clarifications[field]
|
| 185 |
+
if any(word in user_message_lower for word in field_words):
|
| 186 |
+
return False, None, PERSONA_FIELDS[field]["clarification"]
|
| 187 |
+
|
| 188 |
# Check for skip requests
|
| 189 |
+
skip_words = ["not sure", "skip", "later", "don't know", "maybe later", "pass"]
|
| 190 |
if any(word in user_message_lower for word in skip_words):
|
| 191 |
return True, None, PERSONA_FIELDS[field]["skip_response"]
|
| 192 |
|
|
|
|
| 264 |
# Check if user is asking about properties (indicates they want to search)
|
| 265 |
search_indicators = [
|
| 266 |
"property", "warehouse", "industrial", "space", "building",
|
| 267 |
+
"available", "looking for", "need", "find", "search", "show me", "what do you have"
|
| 268 |
]
|
| 269 |
|
| 270 |
conversation_lower = conversation_context.lower()
|
| 271 |
is_searching = any(indicator in conversation_lower for indicator in search_indicators)
|
| 272 |
|
| 273 |
+
# Check for greetings or casual conversation - don't ask persona questions
|
| 274 |
+
casual_indicators = [
|
| 275 |
+
"hi", "hello", "hey", "good morning", "good afternoon", "good evening",
|
| 276 |
+
"how are you", "what's up", "thanks", "thank you", "bye", "goodbye"
|
| 277 |
+
]
|
| 278 |
+
|
| 279 |
+
is_casual = any(indicator in conversation_lower for indicator in casual_indicators)
|
| 280 |
|
| 281 |
+
# Don't ask persona questions during casual conversation
|
| 282 |
+
if is_casual:
|
| 283 |
+
return False, None
|
| 284 |
+
|
| 285 |
+
# Only ask if user is actively searching for properties
|
| 286 |
+
if is_searching:
|
| 287 |
return True, missing_fields[0]
|
| 288 |
|
| 289 |
return False, None
|