LevinAleksey commited on
Commit
6d87b73
·
verified ·
1 Parent(s): 44e3920

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +148 -39
app.py CHANGED
@@ -1,5 +1,6 @@
1
  import os
2
  from typing import List, Dict, Optional
 
3
 
4
  import chainlit as cl
5
  from huggingface_hub import InferenceClient
@@ -15,7 +16,10 @@ HF_TOKEN = os.getenv("HF_TOKEN")
15
  QDRANT_URL = os.getenv("QDRANT_URL")
16
  QDRANT_API_KEY = os.getenv("QDRANT_API_KEY")
17
 
18
- MODEL_ID = "Qwen/Qwen2.5-7B-Instruct"
 
 
 
19
  QDRANT_COLLECTION = "sales_knowledge"
20
 
21
  HISTORY_KEEP = 20
@@ -27,38 +31,135 @@ RAG_MAX_CHARS = 2500
27
 
28
 
29
  # ================================
30
- # ELITE SALES SYSTEM PROMPT
31
  # ================================
32
 
33
- SALES_SYSTEM_PROMPT = """
34
- Ты элитный AI-архитектор и консультант по автоматизации бизнеса.
35
- Ты ведешь интеллектуальную беседу, а не допрос.
 
 
 
36
 
37
- ТВОЯ ЦЕЛЬ:
38
- Быть полезным экспертом. Помогать клиенту разобраться в его хаосе.
39
- Квалификация происходит нативно, через диалог, а не через анкету.
40
 
41
- СТИЛЬ:
42
- - Уверенный, спокойный, "дорогой".
43
- - Лаконичный (3-5 предложений).
44
- - Ты партнер, а не назойливый менеджер.
45
 
46
- ПРАВИЛА ДИАЛОГА (ВАЖНО):
47
- 1. НЕ заканчивай каждое сообщение вопросом. Это запрещено.
48
- 2. Если клиент задал вопрос — сначала ответь качественно и полно. Не отвечай вопросом на вопрос.
49
- 3. Задавай встречный вопрос только тогда, когда это логично вытекает из контекста.
50
- 4. Если данных мало дай гипотезу или пример, а не просто требуй информацию.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
51
 
52
- АНТИ-ДОПРОС:
53
- - Вместо "Какой у вас бюджет?" спроси: "Обычно такие решения стоят от X до Y, это вписывается в ваши ожидания?"
54
- - Вместо "Сколько у вас сотрудников?" скажи: "Это решение идеально для команд от 50 человек. Это ваш случай?"
55
 
56
- RAG (БАЗА ЗНАНИЙ):
57
- Используй контекст. Если ответа нет в базе — скажи честно, предложи обсудить на аудите.
 
58
 
59
- ПОЗИЦИОНИРОВАНИЕ:
60
- Ты архитектор. Ты даешь ценность в каждой реплике.
61
- """.strip()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
62
 
63
 
64
  # ================================
@@ -98,7 +199,6 @@ def get_context(
98
  for hit in hits:
99
  score = getattr(hit, "score", 0.0)
100
 
101
- # агрессивная фильтрация
102
  if score < RAG_SCORE_THRESHOLD:
103
  continue
104
 
@@ -133,7 +233,7 @@ async def start():
133
  check_env()
134
 
135
  await cl.Message(
136
- content="👋 Приве��! Я AI-архитектор. Помогаю компаниям внедрять ИИ и автоматизацию. Расскажи, какую задачу хочешь решить?"
137
  ).send()
138
 
139
  hf_client = InferenceClient(MODEL_ID, token=HF_TOKEN)
@@ -174,9 +274,18 @@ async def main(message: cl.Message):
174
  user_text = (message.content or "").strip()
175
 
176
  if not user_text:
177
- await cl.Message(content="Напиши вопрос 🙂").send()
178
  return
179
 
 
 
 
 
 
 
 
 
 
180
  # =========================
181
  # RAG
182
  # =========================
@@ -191,24 +300,23 @@ async def main(message: cl.Message):
191
 
192
  messages_payload.append({
193
  "role": "system",
194
- "content": SALES_SYSTEM_PROMPT
195
  })
196
 
197
  if context:
198
  messages_payload.append({
199
  "role": "system",
200
  "content": f"""
201
- КОНТЕКСТ ИЗ БАЗЫ ЗНАНИЙ.
202
- Используй только эти данные как факты.
203
-
204
  {context}
 
 
205
  """
206
  })
207
 
208
  # memory trimming
209
- history = history[-HISTORY_SEND_LAST:]
210
-
211
- messages_payload.extend(history)
212
 
213
  messages_payload.append({
214
  "role": "user",
@@ -227,9 +335,9 @@ async def main(message: cl.Message):
227
  try:
228
  stream = hf_client.chat_completion(
229
  messages=messages_payload,
230
- max_tokens=450,
231
- temperature=0.5,
232
- top_p=0.9,
233
  stream=True,
234
  )
235
 
@@ -254,4 +362,5 @@ async def main(message: cl.Message):
254
  cl.user_session.set("message_history", history)
255
 
256
  except Exception as e:
257
- await cl.Message(content=f"Ошибка LLM: {str(e)}").send()
 
 
1
  import os
2
  from typing import List, Dict, Optional
3
+ from enum import Enum
4
 
5
  import chainlit as cl
6
  from huggingface_hub import InferenceClient
 
16
  QDRANT_URL = os.getenv("QDRANT_URL")
17
  QDRANT_API_KEY = os.getenv("QDRANT_API_KEY")
18
 
19
+ # Улучшенная модель - Llama 3.1 70B лучше следует инструкциям
20
+ MODEL_ID = "Qwen/Qwen2.5-72B-Instruct"
21
+ # Альтернатива если нужно быстрее: "mistralai/Mixtral-8x7B-Instruct-v0.1"
22
+
23
  QDRANT_COLLECTION = "sales_knowledge"
24
 
25
  HISTORY_KEEP = 20
 
31
 
32
 
33
  # ================================
34
+ # СТАДИИ ВОРОНКИ
35
  # ================================
36
 
37
+ class Stage(str, Enum):
38
+ GREETING = "greeting" # Приветствие, первый контакт
39
+ DISCOVERY = "discovery" # Выявление боли и потребности
40
+ QUALIFICATION = "qualification" # Квалификация (бюджет, сроки, ЛПР)
41
+ SOLUTION = "solution" # Презентация решения и цены
42
+ CLOSING = "closing" # Закрытие на встречу
43
 
 
 
 
44
 
45
+ # ================================
46
+ # SYSTEM PROMPTS ПО СТАДИЯМ
47
+ # ================================
 
48
 
49
+ BASE_CONTEXT = """
50
+ Ты AI-консультант компании Alex.Dev. Специализация: чат-боты, 3D-аватары, AI-автоматизация для малого бизнеса.
51
+
52
+ ТВОИ УСЛУГИ И ЦЕНЫ:
53
+ - Telegram/WhatsApp бот с AI: 50,000 - 120,000
54
+ - Бот + интеграция с CRM: 80,000 - 180,000 ₽
55
+ - 3D-аватар для сайта/презентаций: 70,000 - 150,000 ₽
56
+ - Комплексная AI-автоматизация: 150,000 - 300,000 ₽
57
+ - Сроки: 2-6 недель в зависимости от сложности
58
+
59
+ СТИЛЬ ОБЩЕНИЯ:
60
+ - Уверенный эксперт, не продавец
61
+ - Короткие ответы: 2-4 предложения
62
+ - Без восклицательных знаков и эмодзи (кроме 👋 в приветствии)
63
+ - Не задавай больше одного вопроса за раз
64
+ - Если клиент задал вопрос — сначала ответь, потом можешь спросить
65
+ """
66
+
67
+ STAGE_PROMPTS = {
68
+ Stage.GREETING: BASE_CONTEXT + """
69
+ ТЕКУЩАЯ ЗАДАЧА: Установить контакт и понять, с чем пришел клиент.
70
+ Если клиент уже описал задачу — переходи к уточнению деталей.
71
+ Если просто поздоровался — спроси одним вопросом, какую задачу хочет решить.
72
+ """,
73
+
74
+ Stage.DISCOVERY: BASE_CONTEXT + """
75
+ ТЕКУЩАЯ ЗАДАЧА: Выявить боль клиента и понять контекст.
76
+
77
+ ВЫЯСНИ (не всё сразу, по одному):
78
+ - Какую проблему хочет решить
79
+ - Что сейчас не работает / что теряет
80
+ - Пробовал ли другие решения
81
+
82
+ ПРИЁМ: Отражай боль клиента: "Понимаю, ручная обработка заявок съедает время..."
83
+ После 2-3 обменов репликами — переходи к квалификации.
84
+ """,
85
+
86
+ Stage.QUALIFICATION: BASE_CONTEXT + """
87
+ ТЕКУЩАЯ ЗАДАЧА: Мягко квалифицировать клиента.
88
+
89
+ ВЫЯСНИ (элегантно, не как анкету):
90
+ - Размер бизнеса: "Решение оптимально для команд от 5 человек. Это ваш случай?"
91
+ - Бюджет: "Обычно такие проекты стоят от X до Y. Это вписывается в ожидания?"
92
+ - Срочность: "Когда хотели бы запустить?"
93
+ - ЛПР: "Вы принимаете решение или нужно согласовать?"
94
+
95
+ Если клиент квалифицирован (есть бюджет, потребность, срочность) — переходи к решению.
96
+ Если не квалифицирован — вежливо предложи бесплатные материалы и завершай.
97
+ """,
98
+
99
+ Stage.SOLUTION: BASE_CONTEXT + """
100
+ ТЕКУЩАЯ ЗАДАЧА: Дать конкретное предложение с ценой.
101
+
102
+ ФОРМУЛА ОТВЕТА:
103
+ 1. "Для вашей задачи подойдет [решение]"
104
+ 2. "Это стоит примерно [диапазон цен]"
105
+ 3. "Включает: [2-3 ключевых пункта]"
106
+ 4. "Точную стоимость и сроки обсудим на коротком созвоне"
107
+
108
+ После презентации цены — переходи к закрытию.
109
+ """,
110
+
111
+ Stage.CLOSING: BASE_CONTEXT + """
112
+ ТЕКУЩАЯ ЗАДАЧА: Закрыть на встречу с менеджером.
113
+
114
+ СКРИПТ ЗАКРЫТИЯ:
115
+ "Предлагаю созвониться на 15-20 минут: покажу похожие кейсы, обсудим детали, дам точную оценку. Когда удобно — завтра или в четверг?"
116
+
117
+ ЕСЛИ ВОЗРАЖАЕТ:
118
+ - "Дорого" → "Понимаю. Давайте на созвоне разберем, что можно оптимизировать под ваш бюджет"
119
+ - "Надо подумать" → "Конечно. Что именно хотите обдумать? Возможно, отвечу сейчас"
120
+ - "Пришлите КП" → "КП готовлю после короткого брифа, чтобы цифры были точными. 15 минут созвона — и будет детальное предложение"
121
+
122
+ ЦЕЛЬ: Получить согласие на созвон или контакт (телефон/email) для менеджера.
123
+ """
124
+ }
125
 
 
 
 
126
 
127
+ # ================================
128
+ # ОПРЕДЕЛЕНИЕ СТАДИИ
129
+ # ================================
130
 
131
+ def detect_stage(history: List[Dict[str, str]], user_text: str) -> Stage:
132
+ """Определяет текущую стадию воронки на основе истории диалога"""
133
+
134
+ msg_count = len(history)
135
+
136
+ # Первое сообщение
137
+ if msg_count == 0:
138
+ return Stage.GREETING
139
+
140
+ # Анализируем контент
141
+ full_text = " ".join([m["content"].lower() for m in history]) + " " + user_text.lower()
142
+
143
+ # Сигналы закрытия
144
+ closing_signals = ["созвон", "встреч", "позвон", "когда удобно", "давайте обсудим",
145
+ "телефон", "почта", "email", "контакт"]
146
+ if any(s in full_text for s in closing_signals) and msg_count > 4:
147
+ return Stage.CLOSING
148
+
149
+ # Сигналы обсуждения цены/решения
150
+ price_signals = ["сколько стоит", "цена", "стоимость", "бюджет", "во сколько обойдется"]
151
+ if any(s in full_text for s in price_signals) and msg_count > 2:
152
+ return Stage.SOLUTION
153
+
154
+ # Квалификация после discovery
155
+ if msg_count > 4:
156
+ return Stage.QUALIFICATION
157
+
158
+ # Discovery на ранних стадиях
159
+ if msg_count > 0:
160
+ return Stage.DISCOVERY
161
+
162
+ return Stage.GREETING
163
 
164
 
165
  # ================================
 
199
  for hit in hits:
200
  score = getattr(hit, "score", 0.0)
201
 
 
202
  if score < RAG_SCORE_THRESHOLD:
203
  continue
204
 
 
233
  check_env()
234
 
235
  await cl.Message(
236
+ content="👋 Привет! Я AI-консультант Alex.Dev. Помогаю бизнесу внедрять чат-боты и автоматизацию. Какую задачу хотите решить?"
237
  ).send()
238
 
239
  hf_client = InferenceClient(MODEL_ID, token=HF_TOKEN)
 
274
  user_text = (message.content or "").strip()
275
 
276
  if not user_text:
277
+ await cl.Message(content="Напишите ваш вопрос").send()
278
  return
279
 
280
+ # =========================
281
+ # ОПРЕДЕЛЯЕМ СТАДИЮ
282
+ # =========================
283
+
284
+ stage = detect_stage(history, user_text)
285
+ system_prompt = STAGE_PROMPTS[stage]
286
+
287
+ print(f"📊 Stage: {stage.value}, Messages: {len(history)}")
288
+
289
  # =========================
290
  # RAG
291
  # =========================
 
300
 
301
  messages_payload.append({
302
  "role": "system",
303
+ "content": system_prompt
304
  })
305
 
306
  if context:
307
  messages_payload.append({
308
  "role": "system",
309
  "content": f"""
310
+ РЕЛЕВАНТНАЯ ИНФОРМАЦИЯ ИЗ БАЗЫ ЗНАНИЙ:
 
 
311
  {context}
312
+
313
+ Используй эти данные, если они отвечают на вопрос клиента.
314
  """
315
  })
316
 
317
  # memory trimming
318
+ history_to_send = history[-HISTORY_SEND_LAST:]
319
+ messages_payload.extend(history_to_send)
 
320
 
321
  messages_payload.append({
322
  "role": "user",
 
335
  try:
336
  stream = hf_client.chat_completion(
337
  messages=messages_payload,
338
+ max_tokens=350, # Короче = лаконичнее
339
+ temperature=0.4, # Меньше = стабильнее
340
+ top_p=0.85,
341
  stream=True,
342
  )
343
 
 
362
  cl.user_session.set("message_history", history)
363
 
364
  except Exception as e:
365
+ await cl.Message(content=f"Произошла ошибка. Попробуйте еще раз или напишите нам напрямую: @alexdev").send()
366
+ print(f"LLM Error: {e}")