Spaces:
Running
Running
Commit
·
5e404a3
1
Parent(s):
84f1fc6
Refine intent analysis and lead data export
Browse filesUpdated intent analysis logic in app.py to enforce 'never give up' objection handling and clarified rules for MOVE, STAY, and EXIT. Improved lead export in leads_manager.py by adding an 'AI Insights' column and using escaped newlines in transcripts. Removed ambiguous transitions from 'hook_rejection' in sales_script.json for clearer flow.
- app.py +20 -18
- leads_manager.py +4 -3
- sales_script.json +0 -10
app.py
CHANGED
|
@@ -110,8 +110,10 @@ def get_predicted_path(graph, start_id, target_id, id_to_node, node_to_id):
|
|
| 110 |
if dist[target_id] == float('inf'): return []
|
| 111 |
path = [target_id]
|
| 112 |
curr = target_id
|
|
|
|
| 113 |
while curr != start_id and attempts < 200:
|
| 114 |
found = False
|
|
|
|
| 115 |
for u in range(graph.num_vertices):
|
| 116 |
for v, w in graph.adj_list[u]:
|
| 117 |
if v == curr and dist[v] == dist[u] + w:
|
|
@@ -122,32 +124,34 @@ def get_predicted_path(graph, start_id, target_id, id_to_node, node_to_id):
|
|
| 122 |
|
| 123 |
def analyze_full_context(model, user_input, current_node, chat_history):
|
| 124 |
"""
|
| 125 |
-
Аналізує
|
| 126 |
"""
|
| 127 |
-
history_text = "\n".join([f"{m['role']}: {m['content']}" for m in chat_history[-4:]])
|
| 128 |
|
| 129 |
prompt = f"""
|
| 130 |
-
ROLE:
|
| 131 |
|
| 132 |
CONTEXT:
|
| 133 |
Current Step: "{current_node}"
|
| 134 |
-
|
| 135 |
-
{history_text}
|
| 136 |
-
User just said: "{user_input}"
|
| 137 |
|
| 138 |
-
TASK
|
| 139 |
-
- DRIVER (Direct, impatient, results-oriented)
|
| 140 |
-
- ANALYST (Detail-oriented, asks 'how', skeptical)
|
| 141 |
-
- EXPRESSIVE (Emotional, enthusiastic, visionary)
|
| 142 |
-
- CONSERVATIVE (Risk-averse, slow, likes stability)
|
| 143 |
|
| 144 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 145 |
|
| 146 |
OUTPUT JSON format:
|
| 147 |
{{
|
| 148 |
"archetype": "DRIVER" | "ANALYST" | "EXPRESSIVE" | "CONSERVATIVE",
|
| 149 |
"intent": "MOVE" | "STAY" | "EXIT",
|
| 150 |
-
"reasoning": "Why
|
| 151 |
}}
|
| 152 |
"""
|
| 153 |
try:
|
|
@@ -155,7 +159,8 @@ def analyze_full_context(model, user_input, current_node, chat_history):
|
|
| 155 |
clean_text = response.text.replace("```json", "").replace("```", "").strip()
|
| 156 |
return json.loads(clean_text)
|
| 157 |
except:
|
| 158 |
-
|
|
|
|
| 159 |
|
| 160 |
def generate_response(model, instruction_text, user_input, intent, lead_info, archetype):
|
| 161 |
"""
|
|
@@ -603,11 +608,8 @@ elif st.session_state.page == "chat":
|
|
| 603 |
# --- ГЕНЕРАЦІЯ ПЕРШОГО ПОВІДОМЛЕННЯ ---
|
| 604 |
if not st.session_state.messages:
|
| 605 |
with st.spinner("AI готується до дзвінка..."):
|
| 606 |
-
start_instruction = nodes["start"]
|
| 607 |
# Викликаємо AI для генерації живого привітання
|
| 608 |
-
|
| 609 |
-
# Just in case, defaults from setup form are used.
|
| 610 |
-
greeting = generate_greeting(model, start_instruction, st.session_state.lead_info)
|
| 611 |
|
| 612 |
st.session_state.messages.append({"role": "assistant", "content": greeting})
|
| 613 |
st.rerun()
|
|
|
|
| 110 |
if dist[target_id] == float('inf'): return []
|
| 111 |
path = [target_id]
|
| 112 |
curr = target_id
|
| 113 |
+
attempts = 0
|
| 114 |
while curr != start_id and attempts < 200:
|
| 115 |
found = False
|
| 116 |
+
attempts += 1
|
| 117 |
for u in range(graph.num_vertices):
|
| 118 |
for v, w in graph.adj_list[u]:
|
| 119 |
if v == curr and dist[v] == dist[u] + w:
|
|
|
|
| 124 |
|
| 125 |
def analyze_full_context(model, user_input, current_node, chat_history):
|
| 126 |
"""
|
| 127 |
+
Аналізує Інтент з налаштуванням "NEVER GIVE UP".
|
| 128 |
"""
|
| 129 |
+
history_text = "\n".join([f"{m['role']}: {m['content']}" for m in chat_history[-4:]])
|
| 130 |
|
| 131 |
prompt = f"""
|
| 132 |
+
ROLE: World-Class Sales Psychologist.
|
| 133 |
|
| 134 |
CONTEXT:
|
| 135 |
Current Step: "{current_node}"
|
| 136 |
+
User said: "{user_input}"
|
|
|
|
|
|
|
| 137 |
|
| 138 |
+
TASK: Determine Intent (MOVE, STAY, EXIT).
|
|
|
|
|
|
|
|
|
|
|
|
|
| 139 |
|
| 140 |
+
CRITICAL RULES FOR INTENT:
|
| 141 |
+
1. **EXIT** triggers ONLY if user is HOSTILE or EXPLICITLY ends the call.
|
| 142 |
+
- Examples: "Stop calling me", "Fuck off", "Put me on blacklist", "Bye", "Hang up".
|
| 143 |
+
|
| 144 |
+
2. **STAY** (Objection Handling) triggers for ANY resistance.
|
| 145 |
+
- Examples: "Not interested", "No time", "We have a vendor", "Too expensive", "Send info to mail".
|
| 146 |
+
- Even if they say "No" to the first question -> It is NOT an exit. It is an objection to handle!
|
| 147 |
+
|
| 148 |
+
3. **MOVE** triggers only if user agrees or answers a question positively.
|
| 149 |
|
| 150 |
OUTPUT JSON format:
|
| 151 |
{{
|
| 152 |
"archetype": "DRIVER" | "ANALYST" | "EXPRESSIVE" | "CONSERVATIVE",
|
| 153 |
"intent": "MOVE" | "STAY" | "EXIT",
|
| 154 |
+
"reasoning": "Why?"
|
| 155 |
}}
|
| 156 |
"""
|
| 157 |
try:
|
|
|
|
| 159 |
clean_text = response.text.replace("```json", "").replace("```", "").strip()
|
| 160 |
return json.loads(clean_text)
|
| 161 |
except:
|
| 162 |
+
# За замовчуванням STAY! Краще зайвий раз перепитати, ніж кинути слухавку.
|
| 163 |
+
return {"archetype": "UNKNOWN", "intent": "STAY", "reasoning": "Fallback safety"}
|
| 164 |
|
| 165 |
def generate_response(model, instruction_text, user_input, intent, lead_info, archetype):
|
| 166 |
"""
|
|
|
|
| 608 |
# --- ГЕНЕРАЦІЯ ПЕРШОГО ПОВІДОМЛЕННЯ ---
|
| 609 |
if not st.session_state.messages:
|
| 610 |
with st.spinner("AI готується до дзвінка..."):
|
|
|
|
| 611 |
# Викликаємо AI для генерації живого привітання
|
| 612 |
+
greeting = generate_greeting(model, nodes["start"], st.session_state.lead_info)
|
|
|
|
|
|
|
| 613 |
|
| 614 |
st.session_state.messages.append({"role": "assistant", "content": greeting})
|
| 615 |
st.rerun()
|
leads_manager.py
CHANGED
|
@@ -43,14 +43,14 @@ def save_lead_to_db(lead_info, chat_history, outcome):
|
|
| 43 |
if not sheet.get_all_values():
|
| 44 |
sheet.append_row([
|
| 45 |
"Date", "Name", "Company", "Type", "Context",
|
| 46 |
-
"Pain Point", "Budget", "Outcome", "Transcript"
|
| 47 |
])
|
| 48 |
except:
|
| 49 |
pass # Таблиця може бути новою
|
| 50 |
|
| 51 |
# Формуємо рядок даних
|
| 52 |
# Збираємо весь текст діалогу для навчання
|
| 53 |
-
transcript = "
|
| 54 |
|
| 55 |
row = [
|
| 56 |
datetime.now().strftime("%Y-%m-%d %H:%M"),
|
|
@@ -61,7 +61,8 @@ def save_lead_to_db(lead_info, chat_history, outcome):
|
|
| 61 |
"AI Pending", # Тут можна додати AI аналіз
|
| 62 |
"Unknown",
|
| 63 |
outcome,
|
| 64 |
-
transcript
|
|
|
|
| 65 |
]
|
| 66 |
|
| 67 |
# Додаємо рядок
|
|
|
|
| 43 |
if not sheet.get_all_values():
|
| 44 |
sheet.append_row([
|
| 45 |
"Date", "Name", "Company", "Type", "Context",
|
| 46 |
+
"Pain Point", "Budget", "Outcome", "Transcript", "AI Insights"
|
| 47 |
])
|
| 48 |
except:
|
| 49 |
pass # Таблиця може бути новою
|
| 50 |
|
| 51 |
# Формуємо рядок даних
|
| 52 |
# Збираємо весь текст діалогу для навчання
|
| 53 |
+
transcript = "\\n".join([f"{msg['role']}: {msg['content']}" for msg in chat_history])
|
| 54 |
|
| 55 |
row = [
|
| 56 |
datetime.now().strftime("%Y-%m-%d %H:%M"),
|
|
|
|
| 61 |
"AI Pending", # Тут можна додати AI аналіз
|
| 62 |
"Unknown",
|
| 63 |
outcome,
|
| 64 |
+
transcript,
|
| 65 |
+
"" # AI Insights placeholder
|
| 66 |
]
|
| 67 |
|
| 68 |
# Додаємо рядок
|
sales_script.json
CHANGED
|
@@ -35,16 +35,6 @@
|
|
| 35 |
"to": "exit_fail",
|
| 36 |
"weight": 1000
|
| 37 |
},
|
| 38 |
-
{
|
| 39 |
-
"from": "hook_rejection",
|
| 40 |
-
"to": "qualification_needs",
|
| 41 |
-
"weight": 1
|
| 42 |
-
},
|
| 43 |
-
{
|
| 44 |
-
"from": "hook_rejection",
|
| 45 |
-
"to": "exit_fail",
|
| 46 |
-
"weight": 50
|
| 47 |
-
},
|
| 48 |
{
|
| 49 |
"from": "qualification_needs",
|
| 50 |
"to": "pain_followup",
|
|
|
|
| 35 |
"to": "exit_fail",
|
| 36 |
"weight": 1000
|
| 37 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 38 |
{
|
| 39 |
"from": "qualification_needs",
|
| 40 |
"to": "pain_followup",
|