smart-bot-factory 0.2.1__py3-none-any.whl → 0.2.2__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.

@@ -48,6 +48,13 @@ class BotBuilder:
48
48
  self._telegram_routers: List = [] # Список Telegram роутеров
49
49
  self._start_handlers: List = [] # Список обработчиков on_start
50
50
 
51
+ # Хуки для кастомизации process_user_message
52
+ self._message_validators: List = [] # Валидация ДО обработки
53
+ self._prompt_enrichers: List = [] # Обогащение системного промпта
54
+ self._context_enrichers: List = [] # Обогащение контекста для AI
55
+ self._response_processors: List = [] # Обработка ответа AI
56
+ self._send_filters: List = [] # Фильтры перед отправкой пользователю
57
+
51
58
  # Флаги инициализации
52
59
  self._initialized = False
53
60
 
@@ -358,6 +365,134 @@ class BotBuilder:
358
365
  """Получает список обработчиков on_start"""
359
366
  return self._start_handlers.copy()
360
367
 
368
+ # ========== ХУКИ ДЛЯ КАСТОМИЗАЦИИ ОБРАБОТКИ СООБЩЕНИЙ ==========
369
+
370
+ def validate_message(self, handler):
371
+ """
372
+ Регистрирует валидатор сообщений (вызывается ДО обработки AI)
373
+
374
+ Если валидатор возвращает False, обработка прерывается
375
+
376
+ Args:
377
+ handler: async def(message: Message, supabase_client) -> bool
378
+
379
+ Example:
380
+ @bot_builder.validate_message
381
+ async def check_service_names(message, supabase_client):
382
+ if "неправильное название" in message.text:
383
+ await message.answer("Пожалуйста, уточните название услуги")
384
+ return False # Прерываем обработку
385
+ return True # Продолжаем
386
+ """
387
+ if not callable(handler):
388
+ raise TypeError(f"Обработчик должен быть callable, получен {type(handler)}")
389
+
390
+ self._message_validators.append(handler)
391
+ logger.info(f"✅ Зарегистрирован валидатор сообщений: {handler.__name__}")
392
+ return handler
393
+
394
+ def enrich_prompt(self, handler):
395
+ """
396
+ Регистрирует обогатитель системного промпта
397
+
398
+ Args:
399
+ handler: async def(system_prompt: str, user_id: int, session_id: str, supabase_client) -> str
400
+
401
+ Example:
402
+ @bot_builder.enrich_prompt
403
+ async def add_client_info(system_prompt, user_id, session_id, supabase_client):
404
+ session = await supabase_client.get_active_session(user_id)
405
+ phone = session.get('metadata', {}).get('phone')
406
+ if phone:
407
+ return f"{system_prompt}\\n\\nТелефон клиента: {phone}"
408
+ return system_prompt
409
+ """
410
+ if not callable(handler):
411
+ raise TypeError(f"Обработчик должен быть callable, получен {type(handler)}")
412
+
413
+ self._prompt_enrichers.append(handler)
414
+ logger.info(f"✅ Зарегистрирован обогатитель промпта: {handler.__name__}")
415
+ return handler
416
+
417
+ def enrich_context(self, handler):
418
+ """
419
+ Регистрирует обогатитель контекста для AI (messages array)
420
+
421
+ Args:
422
+ handler: async def(messages: List[dict], user_id: int, session_id: str) -> List[dict]
423
+
424
+ Example:
425
+ @bot_builder.enrich_context
426
+ async def add_external_data(messages, user_id, session_id):
427
+ # Добавляем данные из внешнего API
428
+ messages.append({
429
+ "role": "system",
430
+ "content": "Дополнительная информация..."
431
+ })
432
+ return messages
433
+ """
434
+ if not callable(handler):
435
+ raise TypeError(f"Обработчик должен быть callable, получен {type(handler)}")
436
+
437
+ self._context_enrichers.append(handler)
438
+ logger.info(f"✅ Зарегистрирован обогатитель контекста: {handler.__name__}")
439
+ return handler
440
+
441
+ def process_response(self, handler):
442
+ """
443
+ Регистрирует обработчик ответа AI (ПОСЛЕ получения ответа)
444
+
445
+ Args:
446
+ handler: async def(response_text: str, ai_metadata: dict, user_id: int) -> tuple[str, dict]
447
+
448
+ Example:
449
+ @bot_builder.process_response
450
+ async def modify_response(response_text, ai_metadata, user_id):
451
+ # Модифицируем ответ
452
+ if "цена" in response_text.lower():
453
+ response_text += "\\n\\n💰 Актуальные цены на сайте"
454
+ return response_text, ai_metadata
455
+ """
456
+ if not callable(handler):
457
+ raise TypeError(f"Обработчик должен быть callable, получен {type(handler)}")
458
+
459
+ self._response_processors.append(handler)
460
+ logger.info(f"✅ Зарегистрирован обработчик ответа: {handler.__name__}")
461
+ return handler
462
+
463
+ def filter_send(self, handler):
464
+ """
465
+ Регистрирует фильтр отправки (может блокировать отправку пользователю)
466
+
467
+ Если фильтр возвращает False, сообщение НЕ отправляется
468
+
469
+ Args:
470
+ handler: async def(user_id: int, response_text: str) -> bool
471
+
472
+ Example:
473
+ @bot_builder.filter_send
474
+ async def block_during_process(user_id, response_text):
475
+ if is_processing(user_id):
476
+ return False # Не отправляем
477
+ return True # Отправляем
478
+ """
479
+ if not callable(handler):
480
+ raise TypeError(f"Обработчик должен быть callable, получен {type(handler)}")
481
+
482
+ self._send_filters.append(handler)
483
+ logger.info(f"✅ Зарегистрирован фильтр отправки: {handler.__name__}")
484
+ return handler
485
+
486
+ def get_message_hooks(self) -> Dict[str, List]:
487
+ """Получает все хуки для обработки сообщений"""
488
+ return {
489
+ 'validators': self._message_validators.copy(),
490
+ 'prompt_enrichers': self._prompt_enrichers.copy(),
491
+ 'context_enrichers': self._context_enrichers.copy(),
492
+ 'response_processors': self._response_processors.copy(),
493
+ 'send_filters': self._send_filters.copy()
494
+ }
495
+
361
496
  def get_router_manager(self) -> RouterManager:
362
497
  """Получает менеджер роутеров событий"""
363
498
  return self.router_manager
@@ -407,6 +542,7 @@ class BotBuilder:
407
542
  handlers_module.analytics_manager = self.analytics_manager
408
543
  handlers_module.conversation_manager = self.conversation_manager
409
544
  handlers_module.start_handlers = self._start_handlers # Передаем обработчики on_start
545
+ handlers_module.message_hooks = self.get_message_hooks() # Передаем хуки для обработки сообщений
410
546
  logger.info("✅ Глобальные переменные установлены в handlers")
411
547
  except Exception as e:
412
548
  logger.warning(f"⚠️ Не удалось установить глобальные переменные в handlers: {e}")
@@ -492,10 +492,22 @@ async def process_user_message(message: Message, state: FSMContext, session_id:
492
492
  config = get_global_var('config')
493
493
  bot = get_global_var('bot')
494
494
  prompt_loader = get_global_var('prompt_loader')
495
+ message_hooks = get_global_var('message_hooks') or {}
495
496
  from datetime import datetime
496
497
  import pytz # Добавляем импорт для работы с временными зонами
497
498
 
498
499
  try:
500
+ # ============ ХУК 1: ВАЛИДАЦИЯ СООБЩЕНИЯ ============
501
+ validators = message_hooks.get('validators', [])
502
+ for validator in validators:
503
+ try:
504
+ should_continue = await validator(message, supabase_client)
505
+ if not should_continue:
506
+ logger.info(f"⛔ Валидатор '{validator.__name__}' прервал обработку")
507
+ return # Прерываем обработку
508
+ except Exception as e:
509
+ logger.error(f"❌ Ошибка в валидаторе '{validator.__name__}': {e}")
510
+
499
511
  # Сохраняем сообщение пользователя
500
512
  await supabase_client.add_message(
501
513
  session_id=session_id,
@@ -514,13 +526,27 @@ async def process_user_message(message: Message, state: FSMContext, session_id:
514
526
  current_time = datetime.now(moscow_tz)
515
527
  time_info = current_time.strftime('%H:%M, %d.%m.%Y, %A')
516
528
 
517
- # Модифицируем системный промпт, добавляя время
529
+ # Базовый системный промпт с временем
518
530
  system_prompt_with_time = f"""
519
531
  {system_prompt}
520
532
 
521
533
  ТЕКУЩЕЕ ВРЕМЯ: {time_info} (московское время)
522
534
  """
523
535
 
536
+ # ============ ХУК 2: ОБОГАЩЕНИЕ ПРОМПТА ============
537
+ prompt_enrichers = message_hooks.get('prompt_enrichers', [])
538
+ for enricher in prompt_enrichers:
539
+ try:
540
+ system_prompt_with_time = await enricher(
541
+ system_prompt_with_time,
542
+ message.from_user.id,
543
+ session_id,
544
+ supabase_client
545
+ )
546
+ logger.info(f"✅ Промпт обогащен '{enricher.__name__}'")
547
+ except Exception as e:
548
+ logger.error(f"❌ Ошибка в обогатителе промпта '{enricher.__name__}': {e}")
549
+
524
550
  # Формируем контекст для OpenAI с обновленным системным промптом
525
551
  messages = [{"role": "system", "content": system_prompt_with_time}]
526
552
 
@@ -536,6 +562,19 @@ async def process_user_message(message: Message, state: FSMContext, session_id:
536
562
  messages.append({"role": "system", "content": final_instructions})
537
563
  logger.info(f"🎯 Добавлены финальные инструкции ({len(final_instructions)} символов)")
538
564
 
565
+ # ============ ХУК 3: ОБОГАЩЕНИЕ КОНТЕКСТА ============
566
+ context_enrichers = message_hooks.get('context_enrichers', [])
567
+ for enricher in context_enrichers:
568
+ try:
569
+ messages = await enricher(
570
+ messages,
571
+ message.from_user.id,
572
+ session_id
573
+ )
574
+ logger.info(f"✅ Контекст обогащен '{enricher.__name__}'")
575
+ except Exception as e:
576
+ logger.error(f"❌ Ошибка в обогатителе контекста '{enricher.__name__}': {e}")
577
+
539
578
  logger.info(f"📝 Контекст сформирован: {len(messages)} сообщений (включая время: {time_info})")
540
579
 
541
580
  await bot.send_chat_action(message.chat.id, "typing")
@@ -588,82 +627,95 @@ async def process_user_message(message: Message, state: FSMContext, session_id:
588
627
  response_text = ai_response
589
628
 
590
629
  logger.info(f"✅ Финальный текст для отправки: {len(response_text)} символов")
591
-
592
- # Обновляем этап сессии и качество лида
593
- if ai_metadata:
594
- logger.info("🔍 Анализ метаданных от ИИ:")
595
-
596
- # Вывод информации об этапе
597
- stage = ai_metadata.get('этап')
598
- if stage:
599
- logger.info(f" 📈 Этап диалога: {stage}")
600
-
601
- # Вывод информации о качестве лида
602
- quality = ai_metadata.get('качество')
603
- if quality is not None:
604
- quality_emoji = "⭐" * min(quality, 5) # Максимум 5 звезд
605
- logger.info(f" {quality_emoji} Качество лида: {quality}/10")
606
-
607
- # Обновляем в базе данных
608
- if stage or quality is not None:
609
- await supabase_client.update_session_stage(session_id, stage, quality)
610
- logger.info(f" ✅ Этап и качество обновлены в БД")
611
-
612
- # Обрабатываем события
613
- events = ai_metadata.get('события', [])
614
- if events:
615
- logger.info(f"\n🔔 События в диалоге ({len(events)}):")
616
- for idx, event in enumerate(events, 1):
617
- event_type = event.get('тип', 'неизвестно')
618
- event_info = event.get('инфо', 'нет информации')
619
-
620
- # Подбираем эмодзи для разных типов событий
621
- event_emoji = {
622
- 'телефон': '📱',
623
- 'email': '📧',
624
- 'встреча': '📅',
625
- 'заказ': '🛍️',
626
- 'вопрос': '❓',
627
- 'консультация': '💬',
628
- 'жалоба': '⚠️',
629
- 'отзыв': '💭'
630
- }.get(event_type.lower(), '📌')
631
-
632
- logger.info(f" {idx}. {event_emoji} {event_type}: {event_info}")
633
-
634
- # Обрабатываем события в системе
635
- await process_events(session_id, events, message.from_user.id)
636
- logger.info(" ✅ События обработаны")
630
+
631
+ # ============ ХУК 4: ОБРАБОТКА ОТВЕТА ============
632
+ response_processors = message_hooks.get('response_processors', [])
633
+ for processor in response_processors:
634
+ try:
635
+ response_text, ai_metadata = await processor(
636
+ response_text,
637
+ ai_metadata,
638
+ message.from_user.id
639
+ )
640
+ logger.info(f"✅ Ответ обработан '{processor.__name__}'")
641
+ except Exception as e:
642
+ logger.error(f"❌ Ошибка в обработчике ответа '{processor.__name__}': {e}")
643
+
644
+ # Обновляем этап сессии и качество лида
645
+ if ai_metadata:
646
+ logger.info("🔍 Анализ метаданных от ИИ:")
637
647
 
638
- # Обрабатываем файлы и каталоги
639
- files_list = ai_metadata.get('файлы', [])
640
- directories_list = ai_metadata.get('каталоги', [])
648
+ # Вывод информации об этапе
649
+ stage = ai_metadata.get('этап')
650
+ if stage:
651
+ logger.info(f" 📈 Этап диалога: {stage}")
641
652
 
642
- # Форматируем информацию о файлах
653
+ # Вывод информации о качестве лида
654
+ quality = ai_metadata.get('качество')
655
+ if quality is not None:
656
+ quality_emoji = "⭐" * min(quality, 5) # Максимум 5 звезд
657
+ logger.info(f" {quality_emoji} Качество лида: {quality}/10")
658
+
659
+ # Обновляем в базе данных
660
+ if stage or quality is not None:
661
+ await supabase_client.update_session_stage(session_id, stage, quality)
662
+ logger.info(f" ✅ Этап и качество обновлены в БД")
663
+
664
+ # Обрабатываем события
665
+ events = ai_metadata.get('события', [])
666
+ if events:
667
+ logger.info(f"\n🔔 События в диалоге ({len(events)}):")
668
+ for idx, event in enumerate(events, 1):
669
+ event_type = event.get('тип', 'неизвестно')
670
+ event_info = event.get('инфо', 'нет информации')
671
+
672
+ # Подбираем эмодзи для разных типов событий
673
+ event_emoji = {
674
+ 'телефон': '📱',
675
+ 'email': '📧',
676
+ 'встреча': '📅',
677
+ 'заказ': '🛍️',
678
+ 'вопрос': '❓',
679
+ 'консультация': '💬',
680
+ 'жалоба': '⚠️',
681
+ 'отзыв': '💭'
682
+ }.get(event_type.lower(), '📌')
683
+
684
+ logger.info(f" {idx}. {event_emoji} {event_type}: {event_info}")
685
+
686
+ # Обрабатываем события в системе
687
+ await process_events(session_id, events, message.from_user.id)
688
+ logger.info(" ✅ События обработаны")
689
+
690
+ # Обрабатываем файлы и каталоги
691
+ files_list = ai_metadata.get('файлы', [])
692
+ directories_list = ai_metadata.get('каталоги', [])
693
+
694
+ # Форматируем информацию о файлах
695
+ if files_list:
696
+ logger.info("📎 Найденные файлы:")
697
+ for idx, file in enumerate(files_list, 1):
698
+ logger.info(f" {idx}. 📄 {file}")
699
+
700
+ # Форматируем информацию о каталогах
701
+ if directories_list:
702
+ logger.info("📂 Найденные каталоги:")
703
+ for idx, directory in enumerate(directories_list, 1):
704
+ logger.info(f" {idx}. 📁 {directory}")
705
+
706
+ # Добавляем информацию в текст ответа
707
+ if files_list or directories_list:
708
+ files_info = []
643
709
  if files_list:
644
- logger.info("📎 Найденные файлы:")
645
- for idx, file in enumerate(files_list, 1):
646
- logger.info(f" {idx}. 📄 {file}")
710
+ files_str = "\n".join(f" {file}" for file in files_list)
711
+ files_info.append(f"\n\n📎 Доступные файлы:\n{files_str}")
647
712
 
648
- # Форматируем информацию о каталогах
649
713
  if directories_list:
650
- logger.info("📂 Найденные каталоги:")
651
- for idx, directory in enumerate(directories_list, 1):
652
- logger.info(f" {idx}. 📁 {directory}")
714
+ dirs_str = "\n".join(f" {directory}" for directory in directories_list)
715
+ files_info.append(f"\n\n📂 Доступные каталоги:\n{dirs_str}")
653
716
 
654
- # Добавляем информацию в текст ответа
655
- if files_list or directories_list:
656
- files_info = []
657
- if files_list:
658
- files_str = "\n".join(f"• {file}" for file in files_list)
659
- files_info.append(f"\n\n📎 Доступные файлы:\n{files_str}")
660
-
661
- if directories_list:
662
- dirs_str = "\n".join(f"• {directory}" for directory in directories_list)
663
- files_info.append(f"\n\n📂 Доступные каталоги:\n{dirs_str}")
664
-
665
- else:
666
- logger.info("📎 Файлы и каталоги не указаны")
717
+ else:
718
+ logger.info("📎 Файлы и каталоги не указаны")
667
719
 
668
720
  # Сохраняем ответ ассистента с метаданными
669
721
  try:
@@ -697,6 +749,17 @@ async def process_user_message(message: Message, state: FSMContext, session_id:
697
749
 
698
750
  logger.info(f"📱 Отправляем пользователю: {len(final_response)} символов")
699
751
 
752
+ # ============ ХУК 5: ФИЛЬТРЫ ОТПРАВКИ ============
753
+ send_filters = message_hooks.get('send_filters', [])
754
+ for filter_func in send_filters:
755
+ try:
756
+ should_send = await filter_func(message.from_user.id, final_response)
757
+ if not should_send:
758
+ logger.info(f"⛔ Фильтр '{filter_func.__name__}' заблокировал отправку")
759
+ return # Не отправляем
760
+ except Exception as e:
761
+ logger.error(f"❌ Ошибка в фильтре отправки '{filter_func.__name__}': {e}")
762
+
700
763
  # Отправляем ответ пользователю
701
764
  try:
702
765
  await send_message(message, final_response, files_list=files_list, directories_list=directories_list)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: smart-bot-factory
3
- Version: 0.2.1
3
+ Version: 0.2.2
4
4
  Summary: Библиотека для создания умных чат-ботов
5
5
  Author-email: Kopatych <kopatych@example.com>
6
6
  License: MIT
@@ -33,12 +33,12 @@ smart_bot_factory/core/router_manager.py,sha256=dUwesog-oHk1U2EDdS8p0e4MTSkwtx5_
33
33
  smart_bot_factory/core/states.py,sha256=AOc19_yAsDW_8md-2oiowjBhuW3bwcsmMxszCXWZZTQ,355
34
34
  smart_bot_factory/core/telegram_router.py,sha256=LcPLOd87Y4Y4YN6TBXVAtmpSp8gAK2otgMI4YS5f5tk,2091
35
35
  smart_bot_factory/creation/__init__.py,sha256=IgDk8GDS3pg7Pw_Et41J33ZmeZIU5dRwQdTmYKXfJfE,128
36
- smart_bot_factory/creation/bot_builder.py,sha256=RhnnZZhNvJC-nKTly5VvoxpXBucKFoXUl4e5gfBk7TI,27057
36
+ smart_bot_factory/creation/bot_builder.py,sha256=VjRPlar6xgul7QpG57od--uS-juWrlW9-YYNnFN_Yx4,33828
37
37
  smart_bot_factory/creation/bot_testing.py,sha256=JDWXyJfZmbgo-DLdAPk8Sd9FiehtHHa4sLD17lBrTOc,55669
38
38
  smart_bot_factory/database/database_structure.sql,sha256=26gFtMC2jdQGQF7Zb_F4Br56rMd4hUDTk9FkNZYneLo,2789
39
39
  smart_bot_factory/database/schema.sql,sha256=-6kOmA9QnSkUtmGI2iQRbTvbdiqOhEOQcuz1lJn79mU,28059
40
40
  smart_bot_factory/event/__init__.py,sha256=hPL449RULIOB-OXv1ZbGNiHctAYaOMUqhSWGPrDHYBM,212
41
- smart_bot_factory/handlers/handlers.py,sha256=1_0CAbedbmdSBeWPF9KR97RM6zNWPv56wR9EdrfPtdo,38630
41
+ smart_bot_factory/handlers/handlers.py,sha256=kfzveBtye0KIrRVWhegNVQxfj3PyL9M3hvQBE-lseaY,41601
42
42
  smart_bot_factory/integrations/openai_client.py,sha256=aMcDrKO0GEx3ZSVEOGDeDtFCDWSXs6biUfgrbRK8yTU,23180
43
43
  smart_bot_factory/integrations/supabase_client.py,sha256=AfALLZdDYeMWHLJw6POTGiBd-sH3i03oT6tT7m9C28I,44644
44
44
  smart_bot_factory/message/__init__.py,sha256=6QvjdfF99venyDB9udZv9WDNjIHJLNuaVhYdTK3a44A,282
@@ -48,8 +48,8 @@ smart_bot_factory/supabase/client.py,sha256=8_-I3kxZQlKQElI4cTUjNGYcqlyIyEkSrUZa
48
48
  smart_bot_factory/utils/__init__.py,sha256=5zNjw491bj5VkOhoEAwh2hjmv8nldyDOTrG7pbGpz6A,285
49
49
  smart_bot_factory/utils/debug_routing.py,sha256=BOoDhKBg7UXe5uHQxRk3TSfPfLPOFqt0N7lAo6kjCOo,4719
50
50
  smart_bot_factory/utils/prompt_loader.py,sha256=JSn7CsWnToSbHYtURdeuZn7ectyDqQGrPGHN2ixIGkw,19930
51
- smart_bot_factory-0.2.1.dist-info/METADATA,sha256=wj1v5p2joJlA_u9LcfBFPFjPqBfxvFu1BxUE389uwDA,28224
52
- smart_bot_factory-0.2.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
53
- smart_bot_factory-0.2.1.dist-info/entry_points.txt,sha256=ybKEAI0WSb7WoRiey7QE-HHfn88UGV7nxLDxXq7b7SU,50
54
- smart_bot_factory-0.2.1.dist-info/licenses/LICENSE,sha256=OrK3cwdUTzNzIhJvSPtJaVMoYIyC_sSx5EFE_FDMvGs,1092
55
- smart_bot_factory-0.2.1.dist-info/RECORD,,
51
+ smart_bot_factory-0.2.2.dist-info/METADATA,sha256=XB5NPdlGpOpGPQtsbskUmJ0SGI3hp74IfAE5OHHAmq4,28224
52
+ smart_bot_factory-0.2.2.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
53
+ smart_bot_factory-0.2.2.dist-info/entry_points.txt,sha256=ybKEAI0WSb7WoRiey7QE-HHfn88UGV7nxLDxXq7b7SU,50
54
+ smart_bot_factory-0.2.2.dist-info/licenses/LICENSE,sha256=OrK3cwdUTzNzIhJvSPtJaVMoYIyC_sSx5EFE_FDMvGs,1092
55
+ smart_bot_factory-0.2.2.dist-info/RECORD,,