smart-bot-factory 0.3.4__py3-none-any.whl → 0.3.5__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of smart-bot-factory might be problematic. Click here for more details.
- smart_bot_factory/core/bot_utils.py +34 -9
- smart_bot_factory/core/decorators.py +28 -16
- smart_bot_factory/core/message_sender.py +11 -2
- smart_bot_factory/core/router.py +59 -8
- smart_bot_factory/handlers/handlers.py +17 -4
- smart_bot_factory/utils/prompt_loader.py +12 -8
- {smart_bot_factory-0.3.4.dist-info → smart_bot_factory-0.3.5.dist-info}/METADATA +1 -3
- {smart_bot_factory-0.3.4.dist-info → smart_bot_factory-0.3.5.dist-info}/RECORD +11 -11
- {smart_bot_factory-0.3.4.dist-info → smart_bot_factory-0.3.5.dist-info}/WHEEL +0 -0
- {smart_bot_factory-0.3.4.dist-info → smart_bot_factory-0.3.5.dist-info}/entry_points.txt +0 -0
- {smart_bot_factory-0.3.4.dist-info → smart_bot_factory-0.3.5.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import asyncio
|
|
2
2
|
import json
|
|
3
3
|
import logging
|
|
4
|
-
import re
|
|
5
4
|
from datetime import datetime
|
|
6
5
|
from aiogram import Router
|
|
7
6
|
from aiogram.filters import Command
|
|
@@ -170,8 +169,13 @@ def parse_ai_response_method2(ai_response: str) -> tuple[str, dict]:
|
|
|
170
169
|
logger.warning(f"Ошибка резервного метода: {e}")
|
|
171
170
|
return ai_response, {}
|
|
172
171
|
|
|
173
|
-
async def process_events(session_id: str, events: list, user_id: int):
|
|
174
|
-
"""
|
|
172
|
+
async def process_events(session_id: str, events: list, user_id: int) -> bool:
|
|
173
|
+
"""
|
|
174
|
+
Обрабатывает события из ответа ИИ
|
|
175
|
+
|
|
176
|
+
Returns:
|
|
177
|
+
bool: True если нужно отправить сообщение от ИИ, False если не нужно
|
|
178
|
+
"""
|
|
175
179
|
|
|
176
180
|
# Проверяем кастомный процессор
|
|
177
181
|
custom_processor = get_global_var('custom_event_processor')
|
|
@@ -180,11 +184,14 @@ async def process_events(session_id: str, events: list, user_id: int):
|
|
|
180
184
|
# Используем кастомную функцию обработки событий
|
|
181
185
|
logger.info(f"🔄 Используется кастомная обработка событий: {custom_processor.__name__}")
|
|
182
186
|
await custom_processor(session_id, events, user_id)
|
|
183
|
-
return
|
|
187
|
+
return True # По умолчанию отправляем сообщение
|
|
184
188
|
|
|
185
189
|
# Стандартная обработка
|
|
186
190
|
supabase_client = get_global_var('supabase_client')
|
|
187
191
|
|
|
192
|
+
# Флаг для отслеживания, нужно ли отправлять сообщение от ИИ
|
|
193
|
+
should_send_ai_response = True
|
|
194
|
+
|
|
188
195
|
for event in events:
|
|
189
196
|
try:
|
|
190
197
|
event_type = event.get('тип', '')
|
|
@@ -200,7 +207,6 @@ async def process_events(session_id: str, events: list, user_id: int):
|
|
|
200
207
|
|
|
201
208
|
# Определяем категорию события и сохраняем в БД
|
|
202
209
|
event_id = None
|
|
203
|
-
should_execute_immediately = False
|
|
204
210
|
should_notify = False
|
|
205
211
|
|
|
206
212
|
try:
|
|
@@ -228,8 +234,14 @@ async def process_events(session_id: str, events: list, user_id: int):
|
|
|
228
234
|
|
|
229
235
|
event_handler_info = event_handlers.get(event_type, {})
|
|
230
236
|
once_only = event_handler_info.get('once_only', True)
|
|
237
|
+
send_ai_response_flag = event_handler_info.get('send_ai_response', True)
|
|
231
238
|
|
|
232
|
-
logger.info(f" 🔍 Обработчик '{event_type}': once_only={once_only}")
|
|
239
|
+
logger.info(f" 🔍 Обработчик '{event_type}': once_only={once_only}, send_ai_response={send_ai_response_flag}")
|
|
240
|
+
|
|
241
|
+
# Проверяем флаг send_ai_response ИЗ ДЕКОРАТОРА
|
|
242
|
+
if not send_ai_response_flag:
|
|
243
|
+
should_send_ai_response = False
|
|
244
|
+
logger.warning(f" 🔇🔇🔇 ОБРАБОТЧИК '{event_type}' ЗАПРЕТИЛ ОТПРАВКУ СООБЩЕНИЯ ОТ ИИ (send_ai_response=False) 🔇🔇🔇")
|
|
233
245
|
|
|
234
246
|
# Если once_only=True - проверяем в БД наличие выполненных событий
|
|
235
247
|
if once_only:
|
|
@@ -256,7 +268,7 @@ async def process_events(session_id: str, events: list, user_id: int):
|
|
|
256
268
|
logger.info(f" 🎯 Немедленно выполняем user_event: '{event_type}'")
|
|
257
269
|
|
|
258
270
|
try:
|
|
259
|
-
# Выполняем
|
|
271
|
+
# Выполняем событие
|
|
260
272
|
result = await execute_event_handler(event_type, user_id, event_info)
|
|
261
273
|
|
|
262
274
|
# Сохраняем в БД УЖЕ со статусом completed (избегаем дублирования)
|
|
@@ -275,7 +287,6 @@ async def process_events(session_id: str, events: list, user_id: int):
|
|
|
275
287
|
event_id = response.data[0]['id']
|
|
276
288
|
|
|
277
289
|
should_notify = event_handler_info.get('notify', False)
|
|
278
|
-
should_execute_immediately = True
|
|
279
290
|
|
|
280
291
|
logger.info(f" ✅ Событие {event_id} выполнено и сохранено как completed")
|
|
281
292
|
|
|
@@ -298,8 +309,18 @@ async def process_events(session_id: str, events: list, user_id: int):
|
|
|
298
309
|
# Если не user_event, пробуем как запланированную задачу
|
|
299
310
|
elif event_type in scheduled_tasks:
|
|
300
311
|
try:
|
|
312
|
+
# Достаем метаданные задачи
|
|
313
|
+
task_info = scheduled_tasks.get(event_type, {})
|
|
314
|
+
send_ai_response_flag = task_info.get('send_ai_response', True)
|
|
315
|
+
|
|
316
|
+
logger.info(f" ⏰ Планируем scheduled_task: '{event_type}', send_ai_response={send_ai_response_flag}")
|
|
317
|
+
|
|
318
|
+
# Проверяем флаг send_ai_response ИЗ ДЕКОРАТОРА
|
|
319
|
+
if not send_ai_response_flag:
|
|
320
|
+
should_send_ai_response = False
|
|
321
|
+
logger.warning(f" 🔇🔇🔇 ЗАДАЧА '{event_type}' ЗАПРЕТИЛА ОТПРАВКУ СООБЩЕНИЯ ОТ ИИ (send_ai_response=False) 🔇🔇🔇")
|
|
322
|
+
|
|
301
323
|
# Используем новую логику - время берется из декоратора
|
|
302
|
-
logger.info(f" ⏰ Планируем scheduled_task: '{event_type}' с данными: '{event_info}'")
|
|
303
324
|
result = await execute_scheduled_task_from_event(user_id, event_type, event_info, session_id)
|
|
304
325
|
event_id = result.get('event_id', 'unknown')
|
|
305
326
|
should_notify = result.get('notify', False)
|
|
@@ -354,6 +375,10 @@ async def process_events(session_id: str, events: list, user_id: int):
|
|
|
354
375
|
except Exception as e:
|
|
355
376
|
logger.error(f"❌ Ошибка обработки события {event}: {e}")
|
|
356
377
|
logger.exception("Стек ошибки:")
|
|
378
|
+
|
|
379
|
+
# Возвращаем флаг, нужно ли отправлять сообщение от ИИ
|
|
380
|
+
logger.warning(f"🔊🔊🔊 ИТОГОВЫЙ ФЛАГ send_ai_response: {should_send_ai_response} 🔊🔊🔊")
|
|
381
|
+
return should_send_ai_response
|
|
357
382
|
|
|
358
383
|
async def notify_admins_about_event(user_id: int, event: dict):
|
|
359
384
|
"""Отправляем уведомление админам о событии с явным указанием ID пользователя"""
|
|
@@ -377,7 +377,7 @@ _global_handlers: Dict[str, Dict[str, Any]] = {}
|
|
|
377
377
|
# Глобальный менеджер роутеров
|
|
378
378
|
_router_manager = None
|
|
379
379
|
|
|
380
|
-
def event_handler(event_type: str, notify: bool = False, once_only: bool = True):
|
|
380
|
+
def event_handler(event_type: str, notify: bool = False, once_only: bool = True, send_ai_response: bool = True):
|
|
381
381
|
"""
|
|
382
382
|
Декоратор для регистрации обработчика события
|
|
383
383
|
|
|
@@ -385,18 +385,19 @@ def event_handler(event_type: str, notify: bool = False, once_only: bool = True)
|
|
|
385
385
|
event_type: Тип события (например, 'appointment_booking', 'phone_collection')
|
|
386
386
|
notify: Уведомлять ли админов о выполнении события (по умолчанию False)
|
|
387
387
|
once_only: Обрабатывать ли событие только один раз (по умолчанию True)
|
|
388
|
+
send_ai_response: Отправлять ли сообщение от ИИ после обработки события (по умолчанию True)
|
|
388
389
|
|
|
389
390
|
Example:
|
|
390
|
-
# Обработчик
|
|
391
|
+
# Обработчик с отправкой сообщения от ИИ
|
|
391
392
|
@event_handler("appointment_booking", notify=True)
|
|
392
393
|
async def book_appointment(user_id: int, appointment_data: dict):
|
|
393
394
|
# Логика записи на прием
|
|
394
395
|
return {"status": "success", "appointment_id": "123"}
|
|
395
396
|
|
|
396
|
-
# Обработчик
|
|
397
|
-
@event_handler("phone_collection", once_only=False)
|
|
397
|
+
# Обработчик БЕЗ отправки сообщения от ИИ
|
|
398
|
+
@event_handler("phone_collection", once_only=False, send_ai_response=False)
|
|
398
399
|
async def collect_phone(user_id: int, phone_data: dict):
|
|
399
|
-
# Логика сбора телефона
|
|
400
|
+
# Логика сбора телефона - ИИ не отправит сообщение
|
|
400
401
|
return {"status": "phone_collected"}
|
|
401
402
|
"""
|
|
402
403
|
def decorator(func: Callable) -> Callable:
|
|
@@ -404,7 +405,8 @@ def event_handler(event_type: str, notify: bool = False, once_only: bool = True)
|
|
|
404
405
|
'handler': func,
|
|
405
406
|
'name': func.__name__,
|
|
406
407
|
'notify': notify,
|
|
407
|
-
'once_only': once_only
|
|
408
|
+
'once_only': once_only,
|
|
409
|
+
'send_ai_response': send_ai_response
|
|
408
410
|
}
|
|
409
411
|
|
|
410
412
|
logger.info(f"📝 Зарегистрирован обработчик события '{event_type}': {func.__name__}")
|
|
@@ -416,15 +418,17 @@ def event_handler(event_type: str, notify: bool = False, once_only: bool = True)
|
|
|
416
418
|
result = await func(*args, **kwargs)
|
|
417
419
|
logger.info(f"✅ Обработчик '{event_type}' выполнен успешно")
|
|
418
420
|
|
|
419
|
-
# Автоматически добавляем
|
|
421
|
+
# Автоматически добавляем флаги notify и send_ai_response к результату
|
|
420
422
|
if isinstance(result, dict):
|
|
421
423
|
result['notify'] = notify
|
|
424
|
+
result['send_ai_response'] = send_ai_response
|
|
422
425
|
else:
|
|
423
426
|
# Если результат не словарь, создаем словарь
|
|
424
427
|
result = {
|
|
425
428
|
'status': 'success',
|
|
426
429
|
'result': result,
|
|
427
|
-
'notify': notify
|
|
430
|
+
'notify': notify,
|
|
431
|
+
'send_ai_response': send_ai_response
|
|
428
432
|
}
|
|
429
433
|
|
|
430
434
|
return result
|
|
@@ -435,7 +439,7 @@ def event_handler(event_type: str, notify: bool = False, once_only: bool = True)
|
|
|
435
439
|
return wrapper
|
|
436
440
|
return decorator
|
|
437
441
|
|
|
438
|
-
def schedule_task(task_name: str, notify: bool = False, smart_check: bool = True, once_only: bool = True, delay: Union[str, int] = None, event_type: Union[str, Callable] = None):
|
|
442
|
+
def schedule_task(task_name: str, notify: bool = False, smart_check: bool = True, once_only: bool = True, delay: Union[str, int] = None, event_type: Union[str, Callable] = None, send_ai_response: bool = True):
|
|
439
443
|
"""
|
|
440
444
|
Декоратор для регистрации задачи, которую можно запланировать на время
|
|
441
445
|
|
|
@@ -448,6 +452,7 @@ def schedule_task(task_name: str, notify: bool = False, smart_check: bool = True
|
|
|
448
452
|
event_type: Источник времени события - ОПЦИОНАЛЬНО:
|
|
449
453
|
- str: Тип события для поиска в БД (например, 'appointment_booking')
|
|
450
454
|
- Callable: Функция для получения datetime (например, async def(user_id, user_data) -> datetime)
|
|
455
|
+
send_ai_response: Отправлять ли сообщение от ИИ после выполнения задачи (по умолчанию True)
|
|
451
456
|
|
|
452
457
|
Example:
|
|
453
458
|
# Обычная задача с фиксированным временем
|
|
@@ -509,7 +514,8 @@ def schedule_task(task_name: str, notify: bool = False, smart_check: bool = True
|
|
|
509
514
|
'smart_check': smart_check,
|
|
510
515
|
'once_only': once_only,
|
|
511
516
|
'default_delay': default_delay_seconds,
|
|
512
|
-
'event_type': event_type # Новое поле для типа события
|
|
517
|
+
'event_type': event_type, # Новое поле для типа события
|
|
518
|
+
'send_ai_response': send_ai_response
|
|
513
519
|
}
|
|
514
520
|
|
|
515
521
|
if event_type:
|
|
@@ -524,15 +530,17 @@ def schedule_task(task_name: str, notify: bool = False, smart_check: bool = True
|
|
|
524
530
|
result = await func(*args, **kwargs)
|
|
525
531
|
logger.info(f"✅ Задача '{task_name}' выполнена успешно")
|
|
526
532
|
|
|
527
|
-
# Автоматически добавляем
|
|
533
|
+
# Автоматически добавляем флаги notify и send_ai_response к результату
|
|
528
534
|
if isinstance(result, dict):
|
|
529
535
|
result['notify'] = notify
|
|
536
|
+
result['send_ai_response'] = send_ai_response
|
|
530
537
|
else:
|
|
531
538
|
# Если результат не словарь, создаем словарь
|
|
532
539
|
result = {
|
|
533
540
|
'status': 'success',
|
|
534
541
|
'result': result,
|
|
535
|
-
'notify': notify
|
|
542
|
+
'notify': notify,
|
|
543
|
+
'send_ai_response': send_ai_response
|
|
536
544
|
}
|
|
537
545
|
|
|
538
546
|
return result
|
|
@@ -543,7 +551,7 @@ def schedule_task(task_name: str, notify: bool = False, smart_check: bool = True
|
|
|
543
551
|
return wrapper
|
|
544
552
|
return decorator
|
|
545
553
|
|
|
546
|
-
def global_handler(handler_type: str, notify: bool = False, once_only: bool = True, delay: Union[str, int] = None, event_type: Union[str, Callable] = None):
|
|
554
|
+
def global_handler(handler_type: str, notify: bool = False, once_only: bool = True, delay: Union[str, int] = None, event_type: Union[str, Callable] = None, send_ai_response: bool = True):
|
|
547
555
|
"""
|
|
548
556
|
Декоратор для регистрации глобального обработчика (для всех пользователей)
|
|
549
557
|
|
|
@@ -555,6 +563,7 @@ def global_handler(handler_type: str, notify: bool = False, once_only: bool = Tr
|
|
|
555
563
|
event_type: Источник времени события - ОПЦИОНАЛЬНО:
|
|
556
564
|
- str: Тип события для поиска в БД
|
|
557
565
|
- Callable: Функция для получения datetime (например, async def(handler_data: str) -> datetime)
|
|
566
|
+
send_ai_response: Отправлять ли сообщение от ИИ после выполнения обработчика (по умолчанию True)
|
|
558
567
|
|
|
559
568
|
Example:
|
|
560
569
|
# Глобальный обработчик с задержкой
|
|
@@ -610,7 +619,8 @@ def global_handler(handler_type: str, notify: bool = False, once_only: bool = Tr
|
|
|
610
619
|
'notify': notify,
|
|
611
620
|
'once_only': once_only,
|
|
612
621
|
'default_delay': default_delay_seconds,
|
|
613
|
-
'event_type': event_type # Добавляем event_type для глобальных обработчиков
|
|
622
|
+
'event_type': event_type, # Добавляем event_type для глобальных обработчиков
|
|
623
|
+
'send_ai_response': send_ai_response
|
|
614
624
|
}
|
|
615
625
|
|
|
616
626
|
logger.info(f"🌍 Зарегистрирован глобальный обработчик '{handler_type}': {func.__name__}")
|
|
@@ -622,15 +632,17 @@ def global_handler(handler_type: str, notify: bool = False, once_only: bool = Tr
|
|
|
622
632
|
result = await func(*args, **kwargs)
|
|
623
633
|
logger.info(f"✅ Глобальный обработчик '{handler_type}' выполнен успешно")
|
|
624
634
|
|
|
625
|
-
# Автоматически добавляем
|
|
635
|
+
# Автоматически добавляем флаги notify и send_ai_response к результату
|
|
626
636
|
if isinstance(result, dict):
|
|
627
637
|
result['notify'] = notify
|
|
638
|
+
result['send_ai_response'] = send_ai_response
|
|
628
639
|
else:
|
|
629
640
|
# Если результат не словарь, создаем словарь
|
|
630
641
|
result = {
|
|
631
642
|
'status': 'success',
|
|
632
643
|
'result': result,
|
|
633
|
-
'notify': notify
|
|
644
|
+
'notify': notify,
|
|
645
|
+
'send_ai_response': send_ai_response
|
|
634
646
|
}
|
|
635
647
|
|
|
636
648
|
return result
|
|
@@ -143,10 +143,10 @@ async def send_message_by_ai(
|
|
|
143
143
|
logger.info(f"✅ Этап и качество обновлены в БД")
|
|
144
144
|
|
|
145
145
|
# Обрабатываем события
|
|
146
|
-
events = ai_metadata.get('
|
|
146
|
+
events = ai_metadata.get('събития', [])
|
|
147
147
|
if events:
|
|
148
148
|
logger.info(f"🔔 Обрабатываем {len(events)} событий")
|
|
149
|
-
await process_events(session_id, events, user_id)
|
|
149
|
+
should_send_response = await process_events(session_id, events, user_id)
|
|
150
150
|
|
|
151
151
|
# Сохраняем ответ ассистента
|
|
152
152
|
await supabase_client.add_message(
|
|
@@ -165,6 +165,15 @@ async def send_message_by_ai(
|
|
|
165
165
|
else:
|
|
166
166
|
final_response = response_text
|
|
167
167
|
|
|
168
|
+
# Проверяем, нужно ли отправлять сообщение от ИИ
|
|
169
|
+
if 'should_send_response' in locals() and not should_send_response:
|
|
170
|
+
logger.info("🔇 События запретили отправку сообщения от ИИ (message_sender), пропускаем отправку")
|
|
171
|
+
return {
|
|
172
|
+
"status": "skipped",
|
|
173
|
+
"reason": "send_ai_response=False",
|
|
174
|
+
"user_id": user_id
|
|
175
|
+
}
|
|
176
|
+
|
|
168
177
|
# Отправляем ответ пользователю напрямую через бота
|
|
169
178
|
await bot.send_message(
|
|
170
179
|
chat_id=user_id,
|
smart_bot_factory/core/router.py
CHANGED
|
@@ -26,7 +26,7 @@ class EventRouter:
|
|
|
26
26
|
|
|
27
27
|
logger.info(f"🔄 Создан роутер: {self.name}")
|
|
28
28
|
|
|
29
|
-
def event_handler(self, event_type: str, notify: bool = False, once_only: bool = True):
|
|
29
|
+
def event_handler(self, event_type: str, notify: bool = False, once_only: bool = True, send_ai_response: bool = True):
|
|
30
30
|
"""
|
|
31
31
|
Декоратор для регистрации обработчика события в роутере
|
|
32
32
|
|
|
@@ -34,6 +34,7 @@ class EventRouter:
|
|
|
34
34
|
event_type: Тип события
|
|
35
35
|
notify: Уведомлять ли админов
|
|
36
36
|
once_only: Выполнять ли только один раз
|
|
37
|
+
send_ai_response: Отправлять ли сообщение от ИИ после обработки события (по умолчанию True)
|
|
37
38
|
"""
|
|
38
39
|
def decorator(func: Callable) -> Callable:
|
|
39
40
|
self._event_handlers[event_type] = {
|
|
@@ -41,6 +42,7 @@ class EventRouter:
|
|
|
41
42
|
'name': func.__name__,
|
|
42
43
|
'notify': notify,
|
|
43
44
|
'once_only': once_only,
|
|
45
|
+
'send_ai_response': send_ai_response,
|
|
44
46
|
'router': self.name
|
|
45
47
|
}
|
|
46
48
|
|
|
@@ -50,14 +52,29 @@ class EventRouter:
|
|
|
50
52
|
@wraps(func)
|
|
51
53
|
async def wrapper(*args, **kwargs):
|
|
52
54
|
try:
|
|
53
|
-
|
|
55
|
+
result = await func(*args, **kwargs)
|
|
56
|
+
|
|
57
|
+
# Автоматически добавляем флаги notify и send_ai_response к результату
|
|
58
|
+
if isinstance(result, dict):
|
|
59
|
+
result['notify'] = notify
|
|
60
|
+
result['send_ai_response'] = send_ai_response
|
|
61
|
+
else:
|
|
62
|
+
# Если результат не словарь, создаем словарь
|
|
63
|
+
result = {
|
|
64
|
+
'status': 'success',
|
|
65
|
+
'result': result,
|
|
66
|
+
'notify': notify,
|
|
67
|
+
'send_ai_response': send_ai_response
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return result
|
|
54
71
|
except Exception as e:
|
|
55
72
|
logger.error(f"Ошибка выполнения обработчика '{event_type}' в роутере {self.name}: {e}")
|
|
56
73
|
raise
|
|
57
74
|
return wrapper
|
|
58
75
|
return decorator
|
|
59
76
|
|
|
60
|
-
def schedule_task(self, task_name: str, notify: bool = False, smart_check: bool = True, once_only: bool = True, delay: Union[str, int] = None, event_type: Union[str, Callable] = None):
|
|
77
|
+
def schedule_task(self, task_name: str, notify: bool = False, smart_check: bool = True, once_only: bool = True, delay: Union[str, int] = None, event_type: Union[str, Callable] = None, send_ai_response: bool = True):
|
|
61
78
|
"""
|
|
62
79
|
Декоратор для регистрации запланированной задачи в роутере
|
|
63
80
|
|
|
@@ -70,6 +87,7 @@ class EventRouter:
|
|
|
70
87
|
event_type: Источник времени события - ОПЦИОНАЛЬНО:
|
|
71
88
|
- str: Тип события для поиска в БД (например, 'appointment_booking')
|
|
72
89
|
- Callable: Функция async def(user_id, user_data) -> datetime
|
|
90
|
+
send_ai_response: Отправлять ли сообщение от ИИ после выполнения задачи (по умолчанию True)
|
|
73
91
|
"""
|
|
74
92
|
def decorator(func: Callable) -> Callable:
|
|
75
93
|
# Время ОБЯЗАТЕЛЬНО должно быть указано
|
|
@@ -98,7 +116,8 @@ class EventRouter:
|
|
|
98
116
|
'once_only': once_only,
|
|
99
117
|
'router': self.name,
|
|
100
118
|
'default_delay': default_delay_seconds,
|
|
101
|
-
'event_type': event_type # Новое поле для типа события
|
|
119
|
+
'event_type': event_type, # Новое поле для типа события
|
|
120
|
+
'send_ai_response': send_ai_response
|
|
102
121
|
}
|
|
103
122
|
|
|
104
123
|
if event_type:
|
|
@@ -110,14 +129,29 @@ class EventRouter:
|
|
|
110
129
|
@wraps(func)
|
|
111
130
|
async def wrapper(*args, **kwargs):
|
|
112
131
|
try:
|
|
113
|
-
|
|
132
|
+
result = await func(*args, **kwargs)
|
|
133
|
+
|
|
134
|
+
# Автоматически добавляем флаги notify и send_ai_response к результату
|
|
135
|
+
if isinstance(result, dict):
|
|
136
|
+
result['notify'] = notify
|
|
137
|
+
result['send_ai_response'] = send_ai_response
|
|
138
|
+
else:
|
|
139
|
+
# Если результат не словарь, создаем словарь
|
|
140
|
+
result = {
|
|
141
|
+
'status': 'success',
|
|
142
|
+
'result': result,
|
|
143
|
+
'notify': notify,
|
|
144
|
+
'send_ai_response': send_ai_response
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return result
|
|
114
148
|
except Exception as e:
|
|
115
149
|
logger.error(f"Ошибка выполнения задачи '{task_name}' в роутере {self.name}: {e}")
|
|
116
150
|
raise
|
|
117
151
|
return wrapper
|
|
118
152
|
return decorator
|
|
119
153
|
|
|
120
|
-
def global_handler(self, handler_type: str, notify: bool = False, once_only: bool = True, delay: Union[str, int] = None, event_type: Union[str, Callable] = None):
|
|
154
|
+
def global_handler(self, handler_type: str, notify: bool = False, once_only: bool = True, delay: Union[str, int] = None, event_type: Union[str, Callable] = None, send_ai_response: bool = True):
|
|
121
155
|
"""
|
|
122
156
|
Декоратор для регистрации глобального обработчика в роутере
|
|
123
157
|
|
|
@@ -126,6 +160,7 @@ class EventRouter:
|
|
|
126
160
|
notify: Уведомлять ли админов
|
|
127
161
|
once_only: Выполнять ли только один раз
|
|
128
162
|
delay: Время задержки в удобном формате (например, "1h 30m", "45m", 3600) - ОБЯЗАТЕЛЬНО
|
|
163
|
+
send_ai_response: Отправлять ли сообщение от ИИ после выполнения обработчика (по умолчанию True)
|
|
129
164
|
"""
|
|
130
165
|
def decorator(func: Callable) -> Callable:
|
|
131
166
|
# Время ОБЯЗАТЕЛЬНО должно быть указано
|
|
@@ -150,7 +185,8 @@ class EventRouter:
|
|
|
150
185
|
'once_only': once_only,
|
|
151
186
|
'router': self.name,
|
|
152
187
|
'default_delay': default_delay_seconds,
|
|
153
|
-
'event_type': event_type # Добавляем event_type для глобальных обработчиков
|
|
188
|
+
'event_type': event_type, # Добавляем event_type для глобальных обработчиков
|
|
189
|
+
'send_ai_response': send_ai_response
|
|
154
190
|
}
|
|
155
191
|
|
|
156
192
|
logger.info(f"🌍 Роутер {self.name}: зарегистрирован глобальный обработчик '{handler_type}': {func.__name__}")
|
|
@@ -159,7 +195,22 @@ class EventRouter:
|
|
|
159
195
|
@wraps(func)
|
|
160
196
|
async def wrapper(*args, **kwargs):
|
|
161
197
|
try:
|
|
162
|
-
|
|
198
|
+
result = await func(*args, **kwargs)
|
|
199
|
+
|
|
200
|
+
# Автоматически добавляем флаги notify и send_ai_response к результату
|
|
201
|
+
if isinstance(result, dict):
|
|
202
|
+
result['notify'] = notify
|
|
203
|
+
result['send_ai_response'] = send_ai_response
|
|
204
|
+
else:
|
|
205
|
+
# Если результат не словарь, создаем словарь
|
|
206
|
+
result = {
|
|
207
|
+
'status': 'success',
|
|
208
|
+
'result': result,
|
|
209
|
+
'notify': notify,
|
|
210
|
+
'send_ai_response': send_ai_response
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
return result
|
|
163
214
|
except Exception as e:
|
|
164
215
|
logger.error(f"Ошибка выполнения глобального обработчика '{handler_type}' в роутере {self.name}: {e}")
|
|
165
216
|
raise
|
|
@@ -942,8 +942,8 @@ async def process_user_message(message: Message, state: FSMContext, session_id:
|
|
|
942
942
|
logger.info(f" {idx}. {event_emoji} {event_type}: {event_info}")
|
|
943
943
|
|
|
944
944
|
# Обрабатываем события в системе
|
|
945
|
-
await process_events(session_id, events, message.from_user.id)
|
|
946
|
-
logger.
|
|
945
|
+
should_send_response = await process_events(session_id, events, message.from_user.id)
|
|
946
|
+
logger.warning(f" ✅ События обработаны, should_send_response = {should_send_response}")
|
|
947
947
|
|
|
948
948
|
# Обрабатываем файлы и каталоги
|
|
949
949
|
files_list = ai_metadata.get('файлы', [])
|
|
@@ -1007,6 +1007,14 @@ async def process_user_message(message: Message, state: FSMContext, session_id:
|
|
|
1007
1007
|
|
|
1008
1008
|
logger.info(f"📱 Отправляем пользователю: {len(final_response)} символов")
|
|
1009
1009
|
|
|
1010
|
+
# ============ ПРОВЕРКА: НУЖНО ЛИ ОТПРАВЛЯТЬ СООБЩЕНИЕ ОТ ИИ ============
|
|
1011
|
+
# Проверяем флаг из событий (если события запретили отправку)
|
|
1012
|
+
logger.warning(f"🔍 Проверка should_send_response: exists={('should_send_response' in locals())}, value={locals().get('should_send_response', 'NOT_SET')}")
|
|
1013
|
+
|
|
1014
|
+
if 'should_send_response' in locals() and not should_send_response:
|
|
1015
|
+
logger.warning("🔇🔇🔇 СОБЫТИЯ ЗАПРЕТИЛИ ОТПРАВКУ СООБЩЕНИЯ ОТ ИИ, ПРОПУСКАЕМ ОТПРАВКУ 🔇🔇🔇")
|
|
1016
|
+
return
|
|
1017
|
+
|
|
1010
1018
|
# ============ ХУК 5: ФИЛЬТРЫ ОТПРАВКИ ============
|
|
1011
1019
|
send_filters = message_hooks.get('send_filters', [])
|
|
1012
1020
|
for filter_func in send_filters:
|
|
@@ -1133,9 +1141,9 @@ async def process_voice_message(message: Message, state: FSMContext, session_id:
|
|
|
1133
1141
|
await supabase_client.update_session_stage(session_id, stage, quality)
|
|
1134
1142
|
|
|
1135
1143
|
# Обрабатываем события
|
|
1136
|
-
events = ai_metadata.get('
|
|
1144
|
+
events = ai_metadata.get('події', [])
|
|
1137
1145
|
if events:
|
|
1138
|
-
await process_events(session_id, events, message.from_user.id)
|
|
1146
|
+
should_send_response = await process_events(session_id, events, message.from_user.id)
|
|
1139
1147
|
|
|
1140
1148
|
# Сохраняем ответ ассистента
|
|
1141
1149
|
await supabase_client.add_message(
|
|
@@ -1158,6 +1166,11 @@ async def process_voice_message(message: Message, state: FSMContext, session_id:
|
|
|
1158
1166
|
files_list = ai_metadata.get('файлы', [])
|
|
1159
1167
|
directories_list = ai_metadata.get('каталоги', [])
|
|
1160
1168
|
|
|
1169
|
+
# Проверяем, нужно ли отправлять сообщение от ИИ
|
|
1170
|
+
if 'should_send_response' in locals() and not should_send_response:
|
|
1171
|
+
logger.info("🔇 События запретили отправку сообщения от ИИ (voice), пропускаем отправку")
|
|
1172
|
+
return
|
|
1173
|
+
|
|
1161
1174
|
# Отправляем ответ пользователю
|
|
1162
1175
|
await send_message(message, final_response, files_list=files_list, directories_list=directories_list)
|
|
1163
1176
|
logger.info(f"✅ Ответ отправлен пользователю {message.from_user.id}")
|
|
@@ -107,6 +107,7 @@ class PromptLoader:
|
|
|
107
107
|
{
|
|
108
108
|
"этап": "introduction|consult|offer|contacts",
|
|
109
109
|
"качество": 1-10,
|
|
110
|
+
"ссылка": 1,
|
|
110
111
|
"события": [
|
|
111
112
|
{
|
|
112
113
|
"тип": "телефон|консультация|покупка|отказ",
|
|
@@ -126,9 +127,10 @@ class PromptLoader:
|
|
|
126
127
|
СИСТЕМА ОЦЕНКИ КАЧЕСТВА (1-10):
|
|
127
128
|
1-3: низкий интерес, много возражений, скептически настроен
|
|
128
129
|
4-6: средний интерес, есть вопросы, обдумывает
|
|
129
|
-
7-8: высокий интерес, готов к покупке, активно интересуется
|
|
130
|
+
7-8: высокий интерес, готов к покупке, активно интересуется
|
|
130
131
|
9-10: горячий лид, предоставил контакты или готов к действию
|
|
131
132
|
|
|
133
|
+
|
|
132
134
|
СОБЫТИЯ - добавляй ТОЛЬКО когда происходит что-то из этого:
|
|
133
135
|
- "телефон": пользователь предоставил номер телефона
|
|
134
136
|
- "консультация": пользователь просит живую консультацию по телефону
|
|
@@ -140,7 +142,7 @@ class PromptLoader:
|
|
|
140
142
|
|
|
141
143
|
ПРИМЕРЫ ПРАВИЛЬНОГО ИСПОЛЬЗОВАНИЯ:
|
|
142
144
|
|
|
143
|
-
Пример 1 - обычный
|
|
145
|
+
Пример 1 - обычный диалог (без ссылки):
|
|
144
146
|
"Расскажу подробнее о конференции GrowthMED. Она пройдет 24-25 октября..."
|
|
145
147
|
|
|
146
148
|
{
|
|
@@ -151,11 +153,11 @@ class PromptLoader:
|
|
|
151
153
|
"каталоги": []
|
|
152
154
|
}
|
|
153
155
|
|
|
154
|
-
Пример 2 - получен
|
|
156
|
+
Пример 2 - получен телефон (без ссылки):
|
|
155
157
|
"Отлично! Записал ваш номер. Мы перезвоним в течение 10 минут!"
|
|
156
158
|
|
|
157
159
|
{
|
|
158
|
-
"этап": "contacts",
|
|
160
|
+
"этап": "contacts",
|
|
159
161
|
"качество": 9,
|
|
160
162
|
"события": [
|
|
161
163
|
{
|
|
@@ -167,7 +169,7 @@ class PromptLoader:
|
|
|
167
169
|
"каталоги": []
|
|
168
170
|
}
|
|
169
171
|
|
|
170
|
-
Пример 3 - отправка
|
|
172
|
+
Пример 3 - отправка презентации (без ссылки):
|
|
171
173
|
"Отправляю вам презентацию о нашей компании и прайс-лист с актуальными ценами."
|
|
172
174
|
|
|
173
175
|
{
|
|
@@ -175,7 +177,7 @@ class PromptLoader:
|
|
|
175
177
|
"качество": 7,
|
|
176
178
|
"события": [
|
|
177
179
|
{
|
|
178
|
-
"тип": "консультация",
|
|
180
|
+
"тип": "консультация",
|
|
179
181
|
"инфо": "Запросил материалы"
|
|
180
182
|
}
|
|
181
183
|
],
|
|
@@ -183,7 +185,7 @@ class PromptLoader:
|
|
|
183
185
|
"каталоги": []
|
|
184
186
|
}
|
|
185
187
|
|
|
186
|
-
Пример 4 - отправка файлов из
|
|
188
|
+
Пример 4 - отправка файлов из каталога (без ссылки):
|
|
187
189
|
"В каталоге 'примеры_работ' вы можете посмотреть наши последние проекты."
|
|
188
190
|
|
|
189
191
|
{
|
|
@@ -194,7 +196,7 @@ class PromptLoader:
|
|
|
194
196
|
"каталоги": ["примеры_работ"]
|
|
195
197
|
}
|
|
196
198
|
|
|
197
|
-
Пример 5 - комбинированная
|
|
199
|
+
Пример 5 - комбинированная отправка (без ссылки):
|
|
198
200
|
"Отправляю вам коммерческое предложение и примеры похожих проектов из нашего портфолио."
|
|
199
201
|
|
|
200
202
|
{
|
|
@@ -210,6 +212,7 @@ class PromptLoader:
|
|
|
210
212
|
"каталоги": ["портфолио_2023"]
|
|
211
213
|
}
|
|
212
214
|
|
|
215
|
+
|
|
213
216
|
ТРЕБОВАНИЯ К JSON:
|
|
214
217
|
- JSON должен быть валидным и находиться в самом конце ответа
|
|
215
218
|
- Всегда используй кавычки для строк
|
|
@@ -217,6 +220,7 @@ class PromptLoader:
|
|
|
217
220
|
- Если событий нет - не добавляй их в массив
|
|
218
221
|
- Качество должно быть числом от 1 до 10
|
|
219
222
|
|
|
223
|
+
|
|
220
224
|
ПОМНИ: Этот JSON критически важен для работы системы администрирования и аналитики!
|
|
221
225
|
"""
|
|
222
226
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: smart-bot-factory
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.5
|
|
4
4
|
Summary: Библиотека для создания умных чат-ботов
|
|
5
5
|
Author-email: Kopatych <eserov73@gmail.com>
|
|
6
6
|
License: MIT
|
|
@@ -21,12 +21,10 @@ Classifier: Topic :: Communications :: Chat
|
|
|
21
21
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
22
22
|
Requires-Python: >=3.9
|
|
23
23
|
Requires-Dist: aiofiles>=23.0.0
|
|
24
|
-
Requires-Dist: aiogram-media-group>=0.5.1
|
|
25
24
|
Requires-Dist: aiogram>=3.4.1
|
|
26
25
|
Requires-Dist: click>=8.0.0
|
|
27
26
|
Requires-Dist: openai>=1.12.0
|
|
28
27
|
Requires-Dist: project-root-finder>=1.9
|
|
29
|
-
Requires-Dist: python-dateutil>=2.9.0.post0
|
|
30
28
|
Requires-Dist: python-dotenv>=1.0.1
|
|
31
29
|
Requires-Dist: pytz>=2023.3
|
|
32
30
|
Requires-Dist: pyyaml>=6.0.2
|
|
@@ -30,18 +30,18 @@ smart_bot_factory/configs/growthmed-october-24/tests/realistic_scenarios.yaml,sh
|
|
|
30
30
|
smart_bot_factory/configs/growthmed-october-24/tests/scenario_examples.yaml,sha256=bzDulOU4a2LyWlcHzlQU8GYhOky2WTfyizGfjX4ioMY,2436
|
|
31
31
|
smart_bot_factory/configs/growthmed-october-24/welcome_file/welcome_file_msg.txt,sha256=Db21Mm0r8SBWFdX9EeIF2FZtLQ2cvuwVlSRJd2KEYCg,922
|
|
32
32
|
smart_bot_factory/configs/growthmed-october-24/welcome_file/Чек лист по 152ФЗ и 323ФЗ для медицины.pdf,sha256=BiAiQHNnQXJPMsks9AeL6s0beEjRFkRMJLMlAn4WorA,5284954
|
|
33
|
-
smart_bot_factory/core/bot_utils.py,sha256=
|
|
33
|
+
smart_bot_factory/core/bot_utils.py,sha256=4c98tVPThXW4ET6XJ0FYMePrx4gYrw_HNhx3YLep9hY,46976
|
|
34
34
|
smart_bot_factory/core/conversation_manager.py,sha256=eoHL7MCEz68DRvTVwRwZgf2PWwGv4T6J9D-I-thETi8,28289
|
|
35
|
-
smart_bot_factory/core/decorators.py,sha256=
|
|
36
|
-
smart_bot_factory/core/message_sender.py,sha256=
|
|
37
|
-
smart_bot_factory/core/router.py,sha256=
|
|
35
|
+
smart_bot_factory/core/decorators.py,sha256=0fDgYNaXU3ZLhFJBxAognLWfy_Fz1F63wdUNaVziDQU,95141
|
|
36
|
+
smart_bot_factory/core/message_sender.py,sha256=0-SQcK4W1x__VgvyaeVRuFlXcxV56TsR_nNK07Nr4b4,32763
|
|
37
|
+
smart_bot_factory/core/router.py,sha256=ji7rzpuKaO8yKaxFW58WhlgG5ExXlbCgqCTONxAyqL4,15022
|
|
38
38
|
smart_bot_factory/core/router_manager.py,sha256=dUwesog-oHk1U2EDdS8p0e4MTSkwtx5_qXn6nrJ9l9I,9700
|
|
39
39
|
smart_bot_factory/core/states.py,sha256=L8qp1UmYFuxTN5U9tY076rDuKgxtFbpSGqBpva2eWbo,895
|
|
40
40
|
smart_bot_factory/creation/__init__.py,sha256=IgDk8GDS3pg7Pw_Et41J33ZmeZIU5dRwQdTmYKXfJfE,128
|
|
41
41
|
smart_bot_factory/creation/bot_builder.py,sha256=yGRmOPD7qCMbhcBiltHWISoKxWx8eqjDSnZXpwhqnUs,43115
|
|
42
42
|
smart_bot_factory/creation/bot_testing.py,sha256=JDWXyJfZmbgo-DLdAPk8Sd9FiehtHHa4sLD17lBrTOc,55669
|
|
43
43
|
smart_bot_factory/event/__init__.py,sha256=hPL449RULIOB-OXv1ZbGNiHctAYaOMUqhSWGPrDHYBM,212
|
|
44
|
-
smart_bot_factory/handlers/handlers.py,sha256=
|
|
44
|
+
smart_bot_factory/handlers/handlers.py,sha256=v3ubszkN7ML-CXChveTdp68EdMjHl02NTJ3hMT2zXKA,61893
|
|
45
45
|
smart_bot_factory/integrations/openai_client.py,sha256=fwaJpwojFdLBWChcFWpFGOHK9upG-nCIwDochkCRRlY,24291
|
|
46
46
|
smart_bot_factory/integrations/supabase_client.py,sha256=Rv0sZHXGSfm3UWodmaR1N-X5-2JbmynPWJKY0a0k_Tk,63557
|
|
47
47
|
smart_bot_factory/message/__init__.py,sha256=-ehDZweUc3uKgmLLxFVsD-KWrDtnHpHms7pCrDelWo0,1950
|
|
@@ -50,10 +50,10 @@ smart_bot_factory/supabase/__init__.py,sha256=XmZP6yM9ffERM5ddAWyJnrNzEhCYtMu3Ac
|
|
|
50
50
|
smart_bot_factory/supabase/client.py,sha256=lWIzfOgoSvU7xPhYLoJtM5GnbWdoWsvHcRFC22sFBMU,25637
|
|
51
51
|
smart_bot_factory/utils/__init__.py,sha256=UhsJXEHfrIK8h1AHsroHSwAriijk-LvnqLyvgzi2VYs,273
|
|
52
52
|
smart_bot_factory/utils/debug_routing.py,sha256=BOoDhKBg7UXe5uHQxRk3TSfPfLPOFqt0N7lAo6kjCOo,4719
|
|
53
|
-
smart_bot_factory/utils/prompt_loader.py,sha256=
|
|
53
|
+
smart_bot_factory/utils/prompt_loader.py,sha256=HS_6Vf-qvRBkhvyzu-HNVS1swFgmqWOKNNv0F6We_AQ,20060
|
|
54
54
|
smart_bot_factory/utils/user_prompt_loader.py,sha256=dk6P0X_3UcNqxjRtuIvb0LcPrp03zIIsstZwdmeCPaE,2519
|
|
55
|
-
smart_bot_factory-0.3.
|
|
56
|
-
smart_bot_factory-0.3.
|
|
57
|
-
smart_bot_factory-0.3.
|
|
58
|
-
smart_bot_factory-0.3.
|
|
59
|
-
smart_bot_factory-0.3.
|
|
55
|
+
smart_bot_factory-0.3.5.dist-info/METADATA,sha256=xOG5tl-e-w8yfvxuZfiyVRnQGdEvUxYLMPbKVGTcbmY,31905
|
|
56
|
+
smart_bot_factory-0.3.5.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
57
|
+
smart_bot_factory-0.3.5.dist-info/entry_points.txt,sha256=ybKEAI0WSb7WoRiey7QE-HHfn88UGV7nxLDxXq7b7SU,50
|
|
58
|
+
smart_bot_factory-0.3.5.dist-info/licenses/LICENSE,sha256=OrK3cwdUTzNzIhJvSPtJaVMoYIyC_sSx5EFE_FDMvGs,1092
|
|
59
|
+
smart_bot_factory-0.3.5.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|