smart-bot-factory 0.2.1__tar.gz → 0.2.2__tar.gz

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.

Files changed (102) hide show
  1. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.2}/PKG-INFO +1 -1
  2. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.2}/best-valera.py +77 -0
  3. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.2}/pyproject.toml +1 -1
  4. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.2}/smart_bot_factory/creation/bot_builder.py +136 -0
  5. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.2}/smart_bot_factory/handlers/handlers.py +134 -71
  6. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.2}/uv.lock +1 -1
  7. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.2}/.claude/settings.local.json +0 -0
  8. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.2}/.env.example +0 -0
  9. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.2}/.github/ISSUE_TEMPLATE//342/234/250-/320/267/320/260/320/277/321/200/320/276/321/201-/321/204/321/203/320/275/320/272/321/206/320/270/320/270.md" +0 -0
  10. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.2}/.github/ISSUE_TEMPLATE//360/237/220/233-/320/261/320/260/320/263-/321/200/320/265/320/277/320/276/321/200/321/202.md" +0 -0
  11. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.2}/.github/workflows/ci.yml +0 -0
  12. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.2}/.github/workflows/publish-private.yml +0 -0
  13. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.2}/.github/workflows/publish.yml +0 -0
  14. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.2}/.gitignore +0 -0
  15. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.2}/.python-version +0 -0
  16. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.2}/CLIENTS_USAGE.md +0 -0
  17. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.2}/Dockerfile +0 -0
  18. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.2}/LICENSE +0 -0
  19. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.2}/README.md +0 -0
  20. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.2}/bots/best-valera/prompts/1sales_context.txt +0 -0
  21. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.2}/bots/best-valera/prompts/2product_info.txt +0 -0
  22. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.2}/bots/best-valera/prompts/3objection_handling.txt +0 -0
  23. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.2}/bots/best-valera/prompts/final_instructions.txt +0 -0
  24. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.2}/bots/best-valera/prompts/help_message.txt +0 -0
  25. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.2}/bots/best-valera/prompts/welcome_message.txt +0 -0
  26. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.2}/bots/best-valera/tests/quick_scenarios.yaml +0 -0
  27. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.2}/bots/best-valera/tests/realistic_scenarios.yaml +0 -0
  28. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.2}/bots/best-valera/tests/scenario_examples.yaml +0 -0
  29. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.2}/bots/best-valera/welcome_files/welcome_file_msg.txt +0 -0
  30. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.2}/bots/best-valera/welcome_files//320/247/320/265/320/272 /320/273/320/270/321/201/321/202 /320/277/320/276 152/320/244/320/227 /320/270 323/320/244/320/227 /320/264/320/273/321/217 /320/274/320/265/320/264/320/270/321/206/320/270/320/275/321/213.pdf" +0 -0
  31. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.2}/bots/valera/prompts/2product_info.txt +0 -0
  32. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.2}/bots/valera/prompts/3objection_handling.txt +0 -0
  33. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.2}/bots/valera/prompts/final_instructions.txt +0 -0
  34. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.2}/bots/valera/prompts/help_message.txt +0 -0
  35. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.2}/bots/valera/prompts/welcome_message.txt +0 -0
  36. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.2}/bots/valera/tests/quick_scenarios.yaml +0 -0
  37. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.2}/bots/valera/tests/realistic_scenarios.yaml +0 -0
  38. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.2}/bots/valera/tests/scenario_examples.yaml +0 -0
  39. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.2}/bots/valera/welcome_files/welcome_file_msg.txt +0 -0
  40. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.2}/bots/valera/welcome_files//320/247/320/265/320/272 /320/273/320/270/321/201/321/202 /320/277/320/276 152/320/244/320/227 /320/270 323/320/244/320/227 /320/264/320/273/321/217 /320/274/320/265/320/264/320/270/321/206/320/270/320/275/321/213.pdf" +0 -0
  41. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.2}/configs/valera/cats//320/224/320/276/320/263/320/276/320/262/320/276/321/200.pdf" +0 -0
  42. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.2}/configs/valera/cats//320/272/320/276/320/275/320/270.jpg" +0 -0
  43. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.2}/configs/valera/cats//320/272/320/276/321/202.jpg" +0 -0
  44. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.2}/configs/valera/cats//320/273/320/265/321/201.jpg" +0 -0
  45. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.2}/configs/valera/tests/fixes_elina.yaml +0 -0
  46. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.2}/configs/valera/welcome_file//320/247/320/265/320/272 /320/273/320/270/321/201/321/202 /320/277/320/276 152/320/244/320/227 /320/270 323/320/244/320/227 /320/264/320/273/321/217 /320/274/320/265/320/264/320/270/321/206/320/270/320/275/321/213.pdf" +0 -0
  47. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.2}/create_tag.sh +0 -0
  48. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.2}/docker-compose.yml +0 -0
  49. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.2}/env.example +0 -0
  50. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.2}/requirements.txt +0 -0
  51. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.2}/save_backup.sh +0 -0
  52. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.2}/save_fixes.sh +0 -0
  53. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.2}/smart_bot_factory/__init__.py +0 -0
  54. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.2}/smart_bot_factory/admin/__init__.py +0 -0
  55. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.2}/smart_bot_factory/admin/admin_logic.py +0 -0
  56. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.2}/smart_bot_factory/admin/admin_manager.py +0 -0
  57. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.2}/smart_bot_factory/admin/admin_migration.sql +0 -0
  58. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.2}/smart_bot_factory/admin/admin_tester.py +0 -0
  59. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.2}/smart_bot_factory/admin/timeout_checker.py +0 -0
  60. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.2}/smart_bot_factory/analytics/analytics_manager.py +0 -0
  61. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.2}/smart_bot_factory/cli.py +0 -0
  62. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.2}/smart_bot_factory/config.py +0 -0
  63. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.2}/smart_bot_factory/configs/growthmed-october-24/prompts/1sales_context.txt +0 -0
  64. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.2}/smart_bot_factory/configs/growthmed-october-24/prompts/2product_info.txt +0 -0
  65. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.2}/smart_bot_factory/configs/growthmed-october-24/prompts/3objection_handling.txt +0 -0
  66. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.2}/smart_bot_factory/configs/growthmed-october-24/prompts/final_instructions.txt +0 -0
  67. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.2}/smart_bot_factory/configs/growthmed-october-24/prompts/help_message.txt +0 -0
  68. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.2}/smart_bot_factory/configs/growthmed-october-24/prompts/welcome_message.txt +0 -0
  69. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.2}/smart_bot_factory/configs/growthmed-october-24/reports/test_20250924_064229.txt +0 -0
  70. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.2}/smart_bot_factory/configs/growthmed-october-24/reports/test_20250924_064335.txt +0 -0
  71. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.2}/smart_bot_factory/configs/growthmed-october-24/reports/test_20250924_064638.txt +0 -0
  72. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.2}/smart_bot_factory/configs/growthmed-october-24/tests/quick_scenarios.yaml +0 -0
  73. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.2}/smart_bot_factory/configs/growthmed-october-24/tests/realistic_scenarios.yaml +0 -0
  74. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.2}/smart_bot_factory/configs/growthmed-october-24/tests/scenario_examples.yaml +0 -0
  75. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.2}/smart_bot_factory/configs/growthmed-october-24/welcome_file/welcome_file_msg.txt +0 -0
  76. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.2}/smart_bot_factory/configs/growthmed-october-24/welcome_file//320/247/320/265/320/272 /320/273/320/270/321/201/321/202 /320/277/320/276 152/320/244/320/227 /320/270 323/320/244/320/227 /320/264/320/273/321/217 /320/274/320/265/320/264/320/270/321/206/320/270/320/275/321/213.pdf" +0 -0
  77. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.2}/smart_bot_factory/core/bot_utils.py +0 -0
  78. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.2}/smart_bot_factory/core/conversation_manager.py +0 -0
  79. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.2}/smart_bot_factory/core/decorators.py +0 -0
  80. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.2}/smart_bot_factory/core/message_sender.py +0 -0
  81. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.2}/smart_bot_factory/core/router.py +0 -0
  82. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.2}/smart_bot_factory/core/router_manager.py +0 -0
  83. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.2}/smart_bot_factory/core/states.py +0 -0
  84. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.2}/smart_bot_factory/core/telegram_router.py +0 -0
  85. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.2}/smart_bot_factory/creation/__init__.py +0 -0
  86. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.2}/smart_bot_factory/creation/bot_testing.py +0 -0
  87. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.2}/smart_bot_factory/database/database_structure.sql +0 -0
  88. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.2}/smart_bot_factory/database/schema.sql +0 -0
  89. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.2}/smart_bot_factory/event/__init__.py +0 -0
  90. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.2}/smart_bot_factory/integrations/openai_client.py +0 -0
  91. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.2}/smart_bot_factory/integrations/supabase_client.py +0 -0
  92. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.2}/smart_bot_factory/message/__init__.py +0 -0
  93. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.2}/smart_bot_factory/router/__init__.py +0 -0
  94. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.2}/smart_bot_factory/setup_checker.py +0 -0
  95. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.2}/smart_bot_factory/supabase/__init__.py +0 -0
  96. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.2}/smart_bot_factory/supabase/client.py +0 -0
  97. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.2}/smart_bot_factory/utils/__init__.py +0 -0
  98. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.2}/smart_bot_factory/utils/debug_routing.py +0 -0
  99. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.2}/smart_bot_factory/utils/prompt_loader.py +0 -0
  100. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.2}/smart_bot_factory/utm_link_generator.py +0 -0
  101. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.2}/system_prompt_example.txt +0 -0
  102. {smart_bot_factory-0.2.1 → smart_bot_factory-0.2.2}/valera.py +0 -0
@@ -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
@@ -1,10 +1,13 @@
1
1
  import asyncio
2
+ import logging
2
3
 
3
4
  from smart_bot_factory.router import EventRouter, TelegramRouter
4
5
  from smart_bot_factory.message import send_message_by_human, send_message_to_users_by_stage
5
6
  from smart_bot_factory.supabase import SupabaseClient
6
7
  from smart_bot_factory.creation import BotBuilder
7
8
 
9
+ logger = logging.getLogger(__name__)
10
+
8
11
  # =============================================================================
9
12
  # СОЗДАНИЕ РОУТЕРОВ
10
13
  # =============================================================================
@@ -55,6 +58,80 @@ async def custom_start_handler(user_id: int, session_id: str, message: Message,
55
58
  reply_markup=keyboard
56
59
  )
57
60
 
61
+ # =============================================================================
62
+ # ХУКИ ДЛЯ КАСТОМИЗАЦИИ ОБРАБОТКИ СООБЩЕНИЙ
63
+ # =============================================================================
64
+
65
+ # ХУК 1: Валидация сообщений
66
+ @bot_builder.validate_message
67
+ async def validate_service_names(message, supabase_client):
68
+ """Проверяем корректность названий услуг"""
69
+ # Пример: блокируем неправильные названия
70
+ incorrect_names = ["массаш", "педекюр", "макияш"]
71
+
72
+ if message.text:
73
+ for incorrect in incorrect_names:
74
+ if incorrect in message.text.lower():
75
+ await message.answer(
76
+ f"❗ Возможно, вы имели в виду другую услугу?\n"
77
+ f"Пожалуйста, уточните название"
78
+ )
79
+ return False # Прерываем обработку AI
80
+
81
+ return True # Продолжаем обработку
82
+
83
+ # ХУК 2: Обогащение промпта
84
+ @bot_builder.enrich_prompt
85
+ async def add_client_info_to_prompt(system_prompt, user_id, session_id, supabase_client):
86
+ """Добавляем информацию о клиенте в промпт"""
87
+ try:
88
+ session = await supabase_client.get_active_session(user_id)
89
+
90
+ if session and session.get('metadata'):
91
+ phone = session['metadata'].get('phone')
92
+ name = session['metadata'].get('confirmed_name') or session['metadata'].get('telegram_name')
93
+
94
+ if phone:
95
+ client_info = f"\n\nИНФОРМАЦИЯ О КЛИЕНТЕ:\n- Телефон: {phone}"
96
+ if name:
97
+ client_info += f"\n- Имя: {name}"
98
+ client_info += "\n\n⚠️ ВАЖНО: При создании записи используй ЭТОТ телефон и имя!"
99
+
100
+ return system_prompt + client_info
101
+ except Exception as e:
102
+ logger.error(f"Ошибка обогащения промпта: {e}")
103
+
104
+ return system_prompt
105
+
106
+ # ХУК 3: Обогащение контекста (например, данные из внешнего API)
107
+ @bot_builder.enrich_context
108
+ async def add_external_api_data(messages, user_id, session_id):
109
+ """Добавляем данные из внешних систем"""
110
+ # Пример: можно добавить расписание из YClients, данные из CRM и т.д.
111
+ # messages.append({
112
+ # "role": "system",
113
+ # "content": "Реальное доступное время: ..."
114
+ # })
115
+ return messages
116
+
117
+ # ХУК 4: Обработка ответа AI
118
+ @bot_builder.process_response
119
+ async def add_promo_to_price_response(response_text, ai_metadata, user_id):
120
+ """Добавляем промо-код к ответам о ценах"""
121
+ if "цен" in response_text.lower() or "стоимост" in response_text.lower():
122
+ response_text += "\n\n🎁 Промокод FIRST10 для скидки 10% на первый визит!"
123
+
124
+ return response_text, ai_metadata
125
+
126
+ # ХУК 5: Фильтр отправки
127
+ @bot_builder.filter_send
128
+ async def allow_all_messages(user_id, response_text):
129
+ """Разрешаем все сообщения (можно добавить блокировку по условию)"""
130
+ # Пример: блокировка во время обработки
131
+ # if is_processing(user_id):
132
+ # return False
133
+ return True
134
+
58
135
  @telegram_router_1.router.message(Command("price", "цена"))
59
136
  async def handle_price_command(message: Message, state: FSMContext):
60
137
  """Обработчик команды /price"""
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "smart-bot-factory"
3
- version = "0.2.1"
3
+ version = "0.2.2"
4
4
  description = "Библиотека для создания умных чат-ботов"
5
5
  authors = [
6
6
  {name = "Kopatych", email = "kopatych@example.com"}
@@ -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)
@@ -1259,7 +1259,7 @@ wheels = [
1259
1259
 
1260
1260
  [[package]]
1261
1261
  name = "smart-bot-factory"
1262
- version = "0.2.0"
1262
+ version = "0.2.1"
1263
1263
  source = { editable = "." }
1264
1264
  dependencies = [
1265
1265
  { name = "aiofiles" },