smart-bot-factory 1.1.1__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.
Files changed (73) hide show
  1. smart_bot_factory/__init__.py +3 -0
  2. smart_bot_factory/admin/__init__.py +18 -0
  3. smart_bot_factory/admin/admin_events.py +1223 -0
  4. smart_bot_factory/admin/admin_logic.py +553 -0
  5. smart_bot_factory/admin/admin_manager.py +156 -0
  6. smart_bot_factory/admin/admin_tester.py +157 -0
  7. smart_bot_factory/admin/timeout_checker.py +547 -0
  8. smart_bot_factory/aiogram_calendar/__init__.py +14 -0
  9. smart_bot_factory/aiogram_calendar/common.py +64 -0
  10. smart_bot_factory/aiogram_calendar/dialog_calendar.py +259 -0
  11. smart_bot_factory/aiogram_calendar/schemas.py +99 -0
  12. smart_bot_factory/aiogram_calendar/simple_calendar.py +224 -0
  13. smart_bot_factory/analytics/analytics_manager.py +414 -0
  14. smart_bot_factory/cli.py +806 -0
  15. smart_bot_factory/config.py +258 -0
  16. smart_bot_factory/configs/growthmed-october-24/prompts/1sales_context.txt +16 -0
  17. smart_bot_factory/configs/growthmed-october-24/prompts/2product_info.txt +582 -0
  18. smart_bot_factory/configs/growthmed-october-24/prompts/3objection_handling.txt +66 -0
  19. smart_bot_factory/configs/growthmed-october-24/prompts/final_instructions.txt +212 -0
  20. smart_bot_factory/configs/growthmed-october-24/prompts/help_message.txt +28 -0
  21. smart_bot_factory/configs/growthmed-october-24/prompts/welcome_message.txt +8 -0
  22. smart_bot_factory/configs/growthmed-october-24/reports/test_20250924_064229.txt +818 -0
  23. smart_bot_factory/configs/growthmed-october-24/reports/test_20250924_064335.txt +32 -0
  24. smart_bot_factory/configs/growthmed-october-24/reports/test_20250924_064638.txt +35 -0
  25. smart_bot_factory/configs/growthmed-october-24/tests/quick_scenarios.yaml +133 -0
  26. smart_bot_factory/configs/growthmed-october-24/tests/realistic_scenarios.yaml +108 -0
  27. smart_bot_factory/configs/growthmed-october-24/tests/scenario_examples.yaml +46 -0
  28. smart_bot_factory/configs/growthmed-october-24/welcome_file/welcome_file_msg.txt +16 -0
  29. smart_bot_factory/configs/growthmed-october-24/welcome_file//342/225/250/320/267/342/225/250/342/225/241/342/225/250/342/225/221 /342/225/250/342/225/227/342/225/250/342/225/225/342/225/244/320/221/342/225/244/320/222 /342/225/250/342/224/220/342/225/250/342/225/233 152/342/225/250/320/264/342/225/250/320/247 /342/225/250/342/225/225 323/342/225/250/320/264/342/225/250/320/247 /342/225/250/342/224/244/342/225/250/342/225/227/342/225/244/320/237 /342/225/250/342/225/235/342/225/250/342/225/241/342/225/250/342/224/244/342/225/250/342/225/225/342/225/244/320/226/342/225/250/342/225/225/342/225/250/342/225/234/342/225/244/320/233.pdf +0 -0
  30. smart_bot_factory/core/bot_utils.py +1108 -0
  31. smart_bot_factory/core/conversation_manager.py +653 -0
  32. smart_bot_factory/core/decorators.py +2464 -0
  33. smart_bot_factory/core/message_sender.py +729 -0
  34. smart_bot_factory/core/router.py +347 -0
  35. smart_bot_factory/core/router_manager.py +218 -0
  36. smart_bot_factory/core/states.py +27 -0
  37. smart_bot_factory/creation/__init__.py +7 -0
  38. smart_bot_factory/creation/bot_builder.py +1093 -0
  39. smart_bot_factory/creation/bot_testing.py +1122 -0
  40. smart_bot_factory/dashboard/__init__.py +3 -0
  41. smart_bot_factory/event/__init__.py +7 -0
  42. smart_bot_factory/handlers/handlers.py +2013 -0
  43. smart_bot_factory/integrations/langchain_openai.py +542 -0
  44. smart_bot_factory/integrations/openai_client.py +513 -0
  45. smart_bot_factory/integrations/supabase_client.py +1678 -0
  46. smart_bot_factory/memory/__init__.py +8 -0
  47. smart_bot_factory/memory/memory_manager.py +299 -0
  48. smart_bot_factory/memory/static_memory.py +214 -0
  49. smart_bot_factory/message/__init__.py +56 -0
  50. smart_bot_factory/rag/__init__.py +5 -0
  51. smart_bot_factory/rag/decorators.py +29 -0
  52. smart_bot_factory/rag/router.py +54 -0
  53. smart_bot_factory/rag/templates/__init__.py +3 -0
  54. smart_bot_factory/rag/templates/create_table.sql +7 -0
  55. smart_bot_factory/rag/templates/create_table_and_function_template.py +94 -0
  56. smart_bot_factory/rag/templates/match_function.sql +61 -0
  57. smart_bot_factory/rag/templates/match_services_template.py +82 -0
  58. smart_bot_factory/rag/vectorstore.py +449 -0
  59. smart_bot_factory/router/__init__.py +10 -0
  60. smart_bot_factory/setup_checker.py +512 -0
  61. smart_bot_factory/supabase/__init__.py +7 -0
  62. smart_bot_factory/supabase/client.py +631 -0
  63. smart_bot_factory/utils/__init__.py +11 -0
  64. smart_bot_factory/utils/debug_routing.py +114 -0
  65. smart_bot_factory/utils/prompt_loader.py +529 -0
  66. smart_bot_factory/utils/tool_router.py +68 -0
  67. smart_bot_factory/utils/user_prompt_loader.py +55 -0
  68. smart_bot_factory/utm_link_generator.py +123 -0
  69. smart_bot_factory-1.1.1.dist-info/METADATA +1135 -0
  70. smart_bot_factory-1.1.1.dist-info/RECORD +73 -0
  71. smart_bot_factory-1.1.1.dist-info/WHEEL +4 -0
  72. smart_bot_factory-1.1.1.dist-info/entry_points.txt +2 -0
  73. smart_bot_factory-1.1.1.dist-info/licenses/LICENSE +24 -0
@@ -0,0 +1,1093 @@
1
+ """
2
+ Строитель ботов для Smart Bot Factory
3
+ """
4
+
5
+ import logging
6
+ import os
7
+ from pathlib import Path
8
+ from typing import Any, Dict, List, Optional, TYPE_CHECKING
9
+
10
+ from ..admin.admin_manager import AdminManager
11
+ from ..analytics.analytics_manager import AnalyticsManager
12
+ from ..config import Config
13
+ from ..core.conversation_manager import ConversationManager
14
+ from ..core.decorators import get_handlers_for_prompt
15
+ from ..core.router_manager import RouterManager
16
+ from ..integrations.langchain_openai import LangChainOpenAIClient
17
+ from ..integrations.supabase_client import SupabaseClient
18
+ from ..utils.prompt_loader import PromptLoader
19
+ from ..memory.memory_manager import MemoryManager
20
+
21
+ if TYPE_CHECKING:
22
+ from ..rag.router import RagRouter
23
+ from ..utils.tool_router import ToolRouter
24
+
25
+ logger = logging.getLogger(__name__)
26
+
27
+
28
+ class BotBuilder:
29
+ """
30
+ Строитель ботов, который использует существующие файлы проекта
31
+ и добавляет новые возможности через декораторы
32
+ """
33
+
34
+ def __init__(self, bot_id: str, config_dir: Optional[Path] = None):
35
+ """
36
+ Инициализация строителя бота
37
+
38
+ Args:
39
+ bot_id: Идентификатор бота
40
+ config_dir: Путь к директории конфигурации (по умолчанию configs/bot_id)
41
+ """
42
+ self.bot_id = bot_id
43
+ self.config_dir = config_dir or Path("bots") / bot_id
44
+
45
+ # Компоненты бота
46
+ self.config: Optional[Config] = None
47
+ self.openai_client: Optional[LangChainOpenAIClient] = None
48
+ self.supabase_client: Optional[SupabaseClient] = None
49
+ self.conversation_manager: Optional[ConversationManager] = None
50
+ self.admin_manager: Optional[AdminManager] = None
51
+ self.analytics_manager: Optional[AnalyticsManager] = None
52
+ self.prompt_loader: Optional[PromptLoader] = None
53
+ self.router_manager: Optional[RouterManager] = None
54
+ self.memory_manager: Optional[MemoryManager] = None
55
+ self._telegram_routers: List = [] # Список Telegram роутеров
56
+ self._start_handlers: List = [] # Список обработчиков on_start
57
+ self._tools: List = [] # Список инструментов для ChatOpenAI
58
+ self._tool_routers: List = [] # Зарегистрированные роутеры инструментов
59
+ self._rag_routers: List = [] # Зарегистрированные RAG-роутеры
60
+
61
+ # Хуки для кастомизации process_user_message
62
+ self._message_validators: List = [] # Валидация ДО обработки
63
+ self._prompt_enrichers: List = [] # Обогащение системного промпта
64
+ self._context_enrichers: List = [] # Обогащение контекста для AI
65
+ self._response_processors: List = [] # Обработка ответа AI
66
+ self._send_filters: List = [] # Фильтры перед отправкой пользователю
67
+
68
+ # Кастомный PromptLoader
69
+ self._custom_prompt_loader = None
70
+
71
+ # Кастомный процессор событий
72
+ self._custom_event_processor = None
73
+
74
+ # Флаги инициализации
75
+ self._initialized = False
76
+
77
+ logger.info(f"🏗️ Создан BotBuilder для бота: {bot_id}")
78
+
79
+ async def build(self) -> "BotBuilder":
80
+ """
81
+ Строит и инициализирует все компоненты бота
82
+
83
+ Returns:
84
+ BotBuilder: Возвращает self для цепочки вызовов
85
+ """
86
+ if self._initialized:
87
+ logger.warning(f"⚠️ Бот {self.bot_id} уже инициализирован")
88
+ return self
89
+
90
+ try:
91
+ logger.info(f"🚀 Начинаем сборку бота {self.bot_id}")
92
+
93
+ # 1. Инициализируем конфигурацию
94
+ await self._init_config()
95
+
96
+ # 2. Инициализируем клиенты
97
+ await self._init_clients()
98
+
99
+ # 3. Инициализируем менеджеры
100
+ await self._init_managers()
101
+
102
+ # 4. Регистрируем инструменты в OpenAI клиенте
103
+ await self._register_tools_in_client()
104
+
105
+ # 5. Обновляем промпты с информацией о доступных инструментах
106
+ await self._update_prompts_with_tools()
107
+
108
+ # 6. Обновляем описание инструментов в промпт-лоадере
109
+ await self._update_tools_description_in_prompt_loader()
110
+
111
+ self._initialized = True
112
+ logger.info(f"✅ Бот {self.bot_id} успешно собран и готов к работе")
113
+
114
+ return self
115
+
116
+ except Exception as e:
117
+ logger.error(f"❌ Ошибка при сборке бота {self.bot_id}: {e}")
118
+ raise
119
+
120
+ async def _init_config(self):
121
+ """Инициализация конфигурации"""
122
+ logger.info(f"⚙️ Инициализация конфигурации для {self.bot_id}")
123
+
124
+ # Устанавливаем BOT_ID в переменные окружения
125
+ os.environ["BOT_ID"] = self.bot_id
126
+
127
+ # Загружаем .env файл если существует
128
+ env_file = self.config_dir / ".env"
129
+ if env_file.exists():
130
+ from dotenv import load_dotenv
131
+
132
+ load_dotenv(env_file)
133
+ logger.info(f"📄 Загружен .env файл: {env_file}")
134
+
135
+ # Устанавливаем путь к промптам относительно папки бота
136
+ prompts_subdir = os.environ.get("PROMT_FILES_DIR", "prompts")
137
+ logger.info(f"🔍 PROMT_FILES_DIR из .env: {prompts_subdir}")
138
+
139
+ prompts_dir = self.config_dir / prompts_subdir
140
+ logger.info(f"🔍 Путь к промптам: {prompts_dir}")
141
+ logger.info(f"🔍 Существует ли папка: {prompts_dir.exists()}")
142
+
143
+ # ВАЖНО: Устанавливаем правильный путь ДО создания Config
144
+ os.environ["PROMT_FILES_DIR"] = str(prompts_dir)
145
+ logger.info(f"📁 Установлен путь к промптам: {prompts_dir}")
146
+
147
+ # Создаем конфигурацию
148
+ logger.info(
149
+ f"🔍 PROMT_FILES_DIR перед созданием Config: {os.environ.get('PROMT_FILES_DIR')}"
150
+ )
151
+ self.config = Config()
152
+ logger.info("✅ Конфигурация инициализирована")
153
+
154
+ async def _init_clients(self):
155
+ """Инициализация клиентов"""
156
+ logger.info(f"🔌 Инициализация клиентов для {self.bot_id}")
157
+
158
+ # OpenAI клиент
159
+ self.openai_client = LangChainOpenAIClient(
160
+ api_key=self.config.OPENAI_API_KEY,
161
+ model=self.config.OPENAI_MODEL,
162
+ max_tokens=self.config.OPENAI_MAX_TOKENS,
163
+ temperature=self.config.OPENAI_TEMPERATURE,
164
+ )
165
+ logger.info("✅ OpenAI клиент инициализирован")
166
+
167
+ # Supabase клиент
168
+ self.supabase_client = SupabaseClient(
169
+ url=self.config.SUPABASE_URL,
170
+ key=self.config.SUPABASE_KEY,
171
+ bot_id=self.bot_id,
172
+ )
173
+ await self.supabase_client.initialize()
174
+ logger.info("✅ Supabase клиент инициализирован")
175
+
176
+ async def _register_tools_in_client(self):
177
+ """Регистрирует зарегистрированные инструменты в OpenAI клиенте"""
178
+ if self._tools and self.openai_client:
179
+ logger.info(f"🔧 Регистрация {len(self._tools)} инструментов в ChatOpenAI")
180
+ self.openai_client.add_tools(self._tools)
181
+ logger.info("✅ Инструменты зарегистрированы в ChatOpenAI")
182
+
183
+ async def _update_tools_description_in_prompt_loader(self):
184
+ """Обновляет описание инструментов в PromptLoader"""
185
+ if self.openai_client and self.prompt_loader:
186
+ tools_description = self.openai_client.get_tools_description_for_prompt()
187
+ if tools_description:
188
+ self.prompt_loader.set_tools_description(tools_description)
189
+ logger.info("✅ Описание инструментов обновлено в PromptLoader")
190
+ else:
191
+ logger.debug("Нет инструментов для добавления в промпт")
192
+
193
+ async def _init_managers(self):
194
+ """Инициализация менеджеров"""
195
+ logger.info(f"👥 Инициализация менеджеров для {self.bot_id}")
196
+
197
+ # Admin Manager
198
+ self.admin_manager = AdminManager(self.config, self.supabase_client)
199
+ await self.admin_manager.sync_admins_from_config()
200
+ logger.info("✅ Admin Manager инициализирован")
201
+
202
+ # Analytics Manager
203
+ self.analytics_manager = AnalyticsManager(self.supabase_client)
204
+ logger.info("✅ Analytics Manager инициализирован")
205
+
206
+ # Conversation Manager
207
+ parse_mode = os.environ.get("MESSAGE_PARSE_MODE", "Markdown")
208
+ admin_session_timeout_minutes = int(
209
+ os.environ.get("ADMIN_SESSION_TIMEOUT_MINUTES", "30")
210
+ )
211
+
212
+ self.conversation_manager = ConversationManager(
213
+ self.supabase_client,
214
+ self.admin_manager,
215
+ parse_mode,
216
+ admin_session_timeout_minutes,
217
+ )
218
+ logger.info("✅ Conversation Manager инициализирован")
219
+
220
+ # Router Manager (создаем только если еще не создан)
221
+ if not self.router_manager:
222
+ self.router_manager = RouterManager()
223
+ logger.info("✅ Router Manager инициализирован")
224
+ else:
225
+ logger.info("✅ Router Manager уже был создан ранее")
226
+
227
+ # Prompt Loader (используем кастомный если установлен)
228
+ if self._custom_prompt_loader:
229
+ self.prompt_loader = self._custom_prompt_loader
230
+ logger.info(
231
+ f"✅ Используется кастомный Prompt Loader: {type(self.prompt_loader).__name__}"
232
+ )
233
+ else:
234
+ self.prompt_loader = PromptLoader(prompts_dir=self.config.PROMT_FILES_DIR)
235
+ logger.info("✅ Используется стандартный Prompt Loader")
236
+
237
+ await self.prompt_loader.validate_prompts()
238
+ logger.info("✅ Prompt Loader инициализирован")
239
+
240
+ async def _update_prompts_with_tools(self):
241
+ """
242
+ Обновляет промпты информацией о доступных обработчиках событий
243
+ """
244
+ logger.info("🔧 Обновление промптов с информацией об обработчиках")
245
+
246
+ # Получаем информацию о доступных обработчиках
247
+ # Сначала пробуем получить из роутеров, если нет - из старых декораторов
248
+ if self.router_manager:
249
+ event_handlers_info = self.router_manager.get_handlers_for_prompt()
250
+ else:
251
+ event_handlers_info = get_handlers_for_prompt()
252
+
253
+ # Если есть обработчики, добавляем их в системный промпт
254
+ if event_handlers_info:
255
+ # Сохраняем информацию о обработчиках для использования в handlers.py
256
+ self._tools_prompt = event_handlers_info
257
+
258
+ logger.info("✅ Промпты обновлены с информацией об обработчиках")
259
+ else:
260
+ self._tools_prompt = ""
261
+ logger.info("ℹ️ Нет зарегистрированных обработчиков")
262
+
263
+ def get_tools_prompt(self) -> str:
264
+ """Возвращает промпт с информацией об инструментах"""
265
+ return getattr(self, "_tools_prompt", "")
266
+
267
+ def get_status(self) -> Dict[str, Any]:
268
+ """Возвращает статус бота"""
269
+ return {
270
+ "bot_id": self.bot_id,
271
+ "initialized": self._initialized,
272
+ "config_dir": str(self.config_dir),
273
+ "components": {
274
+ "config": self.config is not None,
275
+ "openai_client": self.openai_client is not None,
276
+ "supabase_client": self.supabase_client is not None,
277
+ "conversation_manager": self.conversation_manager is not None,
278
+ "admin_manager": self.admin_manager is not None,
279
+ "analytics_manager": self.analytics_manager is not None,
280
+ "prompt_loader": self.prompt_loader is not None,
281
+ },
282
+ "tools": {
283
+ "event_handlers": (
284
+ len(get_handlers_for_prompt().split("\n"))
285
+ if get_handlers_for_prompt()
286
+ else 0
287
+ ),
288
+ "chatopenai_tools": len(self._tools),
289
+ },
290
+ }
291
+
292
+ def set_global_vars_in_module(self, module_name: str):
293
+ """
294
+ Устанавливает глобальные переменные в указанном модуле для удобного доступа
295
+
296
+ Args:
297
+ module_name: Имя модуля (например, 'valera', 'my_bot')
298
+ """
299
+ try:
300
+ import importlib
301
+ import sys
302
+
303
+ # Получаем модуль бота
304
+ bot_module = sys.modules.get(module_name)
305
+ if not bot_module:
306
+ # Пытаемся импортировать модуль, если он не загружен
307
+ try:
308
+ bot_module = importlib.import_module(module_name)
309
+ logger.info(
310
+ f"📦 Модуль '{module_name}' импортирован для установки глобальных переменных"
311
+ )
312
+ except ImportError as ie:
313
+ logger.warning(
314
+ f"⚠️ Не удалось импортировать модуль '{module_name}': {ie}"
315
+ )
316
+ return
317
+
318
+ # Устанавливаем глобальные переменные
319
+ bot_module.supabase_client = self.supabase_client
320
+ bot_module.openai_client = self.openai_client
321
+ bot_module.config = self.config
322
+ bot_module.admin_manager = self.admin_manager
323
+ bot_module.analytics_manager = self.analytics_manager
324
+ bot_module.conversation_manager = self.conversation_manager
325
+ bot_module.prompt_loader = self.prompt_loader
326
+ bot_module.memory_manager = self.memory_manager
327
+
328
+ logger.info(
329
+ f"✅ Глобальные переменные установлены в модуле '{module_name}'"
330
+ )
331
+
332
+ except Exception as e:
333
+ logger.warning(
334
+ f"⚠️ Не удалось установить глобальные переменные в модуле '{module_name}': {e}"
335
+ )
336
+
337
+ def register_router(self, router):
338
+ """
339
+ Регистрирует роутер событий в менеджере роутеров
340
+
341
+ Args:
342
+ router: EventRouter для регистрации
343
+ """
344
+ # Если RouterManager еще не инициализирован, создаем его
345
+ if not self.router_manager:
346
+ from ..core.router_manager import RouterManager
347
+
348
+ self.router_manager = RouterManager()
349
+ logger.info(
350
+ f"✅ Router Manager создан для регистрации роутера '{router.name}'"
351
+ )
352
+
353
+ self.router_manager.register_router(router)
354
+ logger.info(
355
+ f"✅ Роутер событий '{router.name}' зарегистрирован в боте {self.bot_id}"
356
+ )
357
+
358
+ def register_routers(self, *event_routers):
359
+ """
360
+ Регистрирует несколько роутеров событий одновременно
361
+
362
+ Args:
363
+ *event_routers: Произвольное количество EventRouter
364
+
365
+ Example:
366
+ bot_builder.register_routers(event_router1, event_router2, event_router3)
367
+ """
368
+ if not event_routers:
369
+ logger.warning("⚠️ register_routers вызван без аргументов")
370
+ return
371
+
372
+ for router in event_routers:
373
+ self.register_router(router)
374
+
375
+ logger.info(f"✅ Зарегистрировано {len(event_routers)} роутеров событий")
376
+
377
+ def register_telegram_router(self, telegram_router):
378
+ """
379
+ Регистрирует Telegram роутер для обработки команд и сообщений
380
+
381
+ Args:
382
+ telegram_router: aiogram.Router для регистрации
383
+
384
+ Example:
385
+ from aiogram import Router
386
+ from aiogram.filters import Command
387
+
388
+ # Создаем обычный aiogram Router
389
+ my_router = Router(name="my_commands")
390
+
391
+ @my_router.message(Command("price"))
392
+ async def price_handler(message: Message):
393
+ await message.answer("Наши цены...")
394
+
395
+ # Регистрируем в боте
396
+ bot_builder.register_telegram_router(my_router)
397
+ """
398
+ from aiogram import Router as AiogramRouter
399
+
400
+ if not isinstance(telegram_router, AiogramRouter):
401
+ raise TypeError(
402
+ f"Ожидается aiogram.Router, получен {type(telegram_router)}"
403
+ )
404
+
405
+ self._telegram_routers.append(telegram_router)
406
+ router_name = getattr(telegram_router, "name", "unnamed")
407
+ logger.info(
408
+ f"✅ Telegram роутер '{router_name}' зарегистрирован в боте {self.bot_id}"
409
+ )
410
+
411
+ def register_telegram_routers(self, *telegram_routers):
412
+ """
413
+ Регистрирует несколько Telegram роутеров одновременно
414
+
415
+ Args:
416
+ *telegram_routers: Произвольное количество aiogram.Router
417
+
418
+ Example:
419
+ from aiogram import Router
420
+
421
+ router1 = Router(name="commands")
422
+ router2 = Router(name="callbacks")
423
+
424
+ bot_builder.register_telegram_routers(router1, router2)
425
+ """
426
+ if not telegram_routers:
427
+ logger.warning("⚠️ register_telegram_routers вызван без аргументов")
428
+ return
429
+
430
+ for router in telegram_routers:
431
+ self.register_telegram_router(router)
432
+
433
+ logger.info(f"✅ Зарегистрировано {len(telegram_routers)} Telegram роутеров")
434
+
435
+ def register_tool(self, tool):
436
+ """
437
+ Регистрирует инструмент для ChatOpenAI
438
+
439
+ Args:
440
+ tool: Инструмент LangChain (например, StructuredTool, FunctionTool и т.д.)
441
+
442
+ Example:
443
+ from langchain_core.tools import StructuredTool
444
+ from pydantic import BaseModel, Field
445
+
446
+ class CalculatorInput(BaseModel):
447
+ a: float = Field(description="Первое число")
448
+ b: float = Field(description="Второе число")
449
+
450
+ def add(a: float, b: float) -> float:
451
+ return a + b
452
+
453
+ calculator_tool = StructuredTool.from_function(
454
+ func=add,
455
+ name="calculator",
456
+ description="Складывает два числа",
457
+ args_schema=CalculatorInput
458
+ )
459
+
460
+ bot_builder.register_tool(calculator_tool)
461
+ """
462
+ if tool not in self._tools:
463
+ self._tools.append(tool)
464
+ # Если клиент уже инициализирован, сразу добавляем инструмент
465
+ if self.openai_client:
466
+ self.openai_client.add_tool(tool)
467
+ tool_name = getattr(tool, "name", str(tool))
468
+ logger.info(
469
+ f"✅ Инструмент '{tool_name}' зарегистрирован в боте {self.bot_id}"
470
+ )
471
+
472
+ # Обновляем описание инструментов в промпт-лоадере (если доступен)
473
+ if self.prompt_loader and self.openai_client:
474
+ tools_description = self.openai_client.get_tools_description_for_prompt()
475
+ if tools_description:
476
+ self.prompt_loader.set_tools_description(tools_description)
477
+ else:
478
+ tool_name = getattr(tool, "name", str(tool))
479
+ logger.warning(f"⚠️ Инструмент '{tool_name}' уже зарегистрирован")
480
+
481
+ def register_tools(self, *tools):
482
+ """
483
+ Регистрирует несколько инструментов для ChatOpenAI одновременно
484
+
485
+ Args:
486
+ *tools: Произвольное количество инструментов LangChain или список(ы) инструментов
487
+
488
+ Example:
489
+ from langchain_core.tools import StructuredTool
490
+
491
+ tool1 = StructuredTool.from_function(...)
492
+ tool2 = StructuredTool.from_function(...)
493
+ tool3 = StructuredTool.from_function(...)
494
+
495
+ # Отдельные инструменты
496
+ bot_builder.register_tools(tool1, tool2, tool3)
497
+
498
+ # Список инструментов
499
+ bot_builder.register_tools([tool1, tool2, tool3])
500
+ """
501
+ if not tools:
502
+ logger.warning("⚠️ register_tools вызван без аргументов")
503
+ return
504
+
505
+ # Распаковываем списки инструментов
506
+ unpacked_tools = []
507
+ for tool in tools:
508
+ if isinstance(tool, (list, tuple)):
509
+ unpacked_tools.extend(tool)
510
+ else:
511
+ unpacked_tools.append(tool)
512
+
513
+ for tool in unpacked_tools:
514
+ self.register_tool(tool)
515
+
516
+ # Обновляем описание инструментов в промпт-лоадере после добавления всех
517
+ if self.openai_client and self.prompt_loader:
518
+ tools_description = self.openai_client.get_tools_description_for_prompt()
519
+ if tools_description:
520
+ self.prompt_loader.set_tools_description(tools_description)
521
+
522
+ logger.info(f"✅ Зарегистрировано {len(unpacked_tools)} инструментов для ChatOpenAI")
523
+
524
+ def register_tool_set(self, tool_router: "ToolRouter"):
525
+ """
526
+ Регистрирует роутер обычных инструментов LangChain.
527
+ """
528
+ if tool_router in self._tool_routers:
529
+ logger.warning(
530
+ "⚠️ ToolRouter %s уже зарегистрирован",
531
+ getattr(tool_router, "name", tool_router),
532
+ )
533
+ return
534
+
535
+ tools = getattr(tool_router, "get_tools", lambda: [])()
536
+ if not tools:
537
+ logger.warning(
538
+ "⚠️ ToolRouter %s не содержит инструментов для регистрации",
539
+ getattr(tool_router, "name", tool_router),
540
+ )
541
+ else:
542
+ self.register_tools(tools)
543
+
544
+ self._tool_routers.append(tool_router)
545
+ logger.info(
546
+ "✅ Зарегистрирован ToolRouter: %s",
547
+ getattr(tool_router, "name", tool_router),
548
+ )
549
+ return tool_router
550
+
551
+ def register_tool_sets(self, *tool_routers: "ToolRouter"):
552
+ """
553
+ Регистрирует несколько роутеров обычных инструментов.
554
+ """
555
+ if not tool_routers:
556
+ logger.warning("⚠️ register_tool_sets вызван без аргументов")
557
+ return
558
+ for router in tool_routers:
559
+ self.register_tool_set(router)
560
+
561
+ def register_rag(self, rag_router: "RagRouter"):
562
+ """
563
+ Регистрирует RAG-роутер и все его инструменты.
564
+
565
+ Args:
566
+ rag_router: Экземпляр RagRouter с описанными инструментами.
567
+ """
568
+ if rag_router in self._rag_routers:
569
+ logger.warning("⚠️ RAG-роутер %s уже зарегистрирован", getattr(rag_router, "name", rag_router))
570
+ return
571
+
572
+ tools = getattr(rag_router, "get_tools", lambda: [])()
573
+ if not tools:
574
+ logger.warning("⚠️ RAG-роутер %s не содержит инструментов для регистрации", getattr(rag_router, "name", rag_router))
575
+ else:
576
+ self.register_tools(tools)
577
+ self._rag_routers.append(rag_router)
578
+ logger.info("✅ Зарегистрирован RAG-роутер: %s", getattr(rag_router, "name", rag_router))
579
+ return rag_router
580
+
581
+ def register_rag_routers(self, *rag_routers: "RagRouter"):
582
+ """
583
+ Регистрирует несколько RAG-роутеров.
584
+ """
585
+ if not rag_routers:
586
+ logger.warning("⚠️ register_rag_routers вызван без аргументов")
587
+ return
588
+ for router in rag_routers:
589
+ self.register_rag(router)
590
+
591
+ def on_start(self, handler):
592
+ """
593
+ Регистрирует обработчик, который вызывается после стандартной логики /start
594
+
595
+ Обработчик получает доступ к:
596
+ - user_id: int - ID пользователя Telegram
597
+ - session_id: str - ID созданной сессии
598
+ - message: Message - Объект сообщения от aiogram
599
+ - state: FSMContext - Контекст состояния
600
+
601
+ Args:
602
+ handler: Async функция с сигнатурой:
603
+ async def handler(user_id: int, session_id: str, message: Message, state: FSMContext)
604
+
605
+ Example:
606
+ @bot_builder.on_start
607
+ async def my_start_handler(user_id, session_id, message, state):
608
+ keyboard = InlineKeyboardMarkup(...)
609
+ await message.answer("Выберите действие:", reply_markup=keyboard)
610
+ """
611
+ if not callable(handler):
612
+ raise TypeError(f"Обработчик должен быть callable, получен {type(handler)}")
613
+
614
+ self._start_handlers.append(handler)
615
+ logger.info(f"✅ Зарегистрирован обработчик on_start: {handler.__name__}")
616
+ return handler # Возвращаем handler для использования как декоратор
617
+
618
+ def get_start_handlers(self) -> List:
619
+ """Получает список обработчиков on_start"""
620
+ return self._start_handlers.copy()
621
+
622
+ def set_prompt_loader(self, prompt_loader):
623
+ """
624
+ Устанавливает кастомный PromptLoader
625
+
626
+ Должен быть вызван ДО build()
627
+
628
+ Args:
629
+ prompt_loader: Экземпляр PromptLoader или его наследника (например UserPromptLoader)
630
+
631
+ Example:
632
+ from smart_bot_factory.utils import UserPromptLoader
633
+
634
+ # Использовать UserPromptLoader с автопоиском prompts_dir
635
+ custom_loader = UserPromptLoader("my-bot")
636
+ bot_builder.set_prompt_loader(custom_loader)
637
+
638
+ # Или кастомный наследник
639
+ class MyPromptLoader(UserPromptLoader):
640
+ def __init__(self, bot_id):
641
+ super().__init__(bot_id)
642
+ self.extra_file = self.prompts_dir / 'extra.txt'
643
+
644
+ my_loader = MyPromptLoader("my-bot")
645
+ bot_builder.set_prompt_loader(my_loader)
646
+ """
647
+ self._custom_prompt_loader = prompt_loader
648
+ logger.info(
649
+ f"✅ Установлен кастомный PromptLoader: {type(prompt_loader).__name__}"
650
+ )
651
+
652
+ def set_event_processor(self, custom_processor):
653
+ """
654
+ Устанавливает кастомную функцию для обработки событий
655
+
656
+ Полностью заменяет стандартную process_events из bot_utils
657
+
658
+ Args:
659
+ custom_processor: async def(session_id: str, events: list, user_id: int)
660
+
661
+ Example:
662
+ from smart_bot_factory.message import get_bot
663
+ from smart_bot_factory.core.decorators import execute_event_handler
664
+
665
+ async def my_process_events(session_id, events, user_id):
666
+ '''Моя кастомная обработка событий'''
667
+ bot = get_bot()
668
+
669
+ for event in events:
670
+ event_type = event.get('тип')
671
+ event_info = event.get('инфо')
672
+
673
+ if event_type == 'запись':
674
+ # Кастомная логика для бронирования
675
+ telegram_user = await bot.get_chat(user_id)
676
+ name = telegram_user.first_name or 'Клиент'
677
+ # ... ваша обработка
678
+ else:
679
+ # Для остальных - стандартная обработка
680
+ await execute_event_handler(event_type, user_id, event_info)
681
+
682
+ bot_builder.set_event_processor(my_process_events)
683
+ """
684
+ if not callable(custom_processor):
685
+ raise TypeError(
686
+ f"Процессор должен быть callable, получен {type(custom_processor)}"
687
+ )
688
+
689
+ self._custom_event_processor = custom_processor
690
+ logger.info(
691
+ f"✅ Установлена кастомная функция обработки событий: {custom_processor.__name__}"
692
+ )
693
+
694
+ # ========== ХУКИ ДЛЯ КАСТОМИЗАЦИИ ОБРАБОТКИ СООБЩЕНИЙ ==========
695
+
696
+ def validate_message(self, handler):
697
+ """
698
+ Регистрирует валидатор сообщений (вызывается ДО обработки AI)
699
+
700
+ Если валидатор возвращает False, обработка прерывается
701
+
702
+ Args:
703
+ handler: async def(message: Message, supabase_client) -> bool
704
+
705
+ Example:
706
+ @bot_builder.validate_message
707
+ async def check_service_names(message, supabase_client):
708
+ if "неправильное название" in message.text:
709
+ await message.answer("Пожалуйста, уточните название услуги")
710
+ return False # Прерываем обработку
711
+ return True # Продолжаем
712
+ """
713
+ if not callable(handler):
714
+ raise TypeError(f"Обработчик должен быть callable, получен {type(handler)}")
715
+
716
+ self._message_validators.append(handler)
717
+ logger.info(f"✅ Зарегистрирован валидатор сообщений: {handler.__name__}")
718
+ return handler
719
+
720
+ def enrich_prompt(self, handler):
721
+ """
722
+ Регистрирует обогатитель системного промпта
723
+
724
+ Args:
725
+ handler: async def(system_prompt: str, user_id: int, session_id: str, supabase_client) -> str
726
+
727
+ Example:
728
+ @bot_builder.enrich_prompt
729
+ async def add_client_info(system_prompt, user_id, session_id, supabase_client):
730
+ session = await supabase_client.get_active_session(user_id)
731
+ phone = session.get('metadata', {}).get('phone')
732
+ if phone:
733
+ return f"{system_prompt}\\n\\nТелефон клиента: {phone}"
734
+ return system_prompt
735
+ """
736
+ if not callable(handler):
737
+ raise TypeError(f"Обработчик должен быть callable, получен {type(handler)}")
738
+
739
+ self._prompt_enrichers.append(handler)
740
+ logger.info(f"✅ Зарегистрирован обогатитель промпта: {handler.__name__}")
741
+ return handler
742
+
743
+ def enrich_context(self, handler):
744
+ """
745
+ Регистрирует обогатитель контекста для AI (messages array)
746
+
747
+ Args:
748
+ handler: async def(messages: List[dict], user_id: int, session_id: str) -> List[dict]
749
+
750
+ Example:
751
+ @bot_builder.enrich_context
752
+ async def add_external_data(messages, user_id, session_id):
753
+ # Добавляем данные из внешнего API
754
+ messages.append({
755
+ "role": "system",
756
+ "content": "Дополнительная информация..."
757
+ })
758
+ return messages
759
+ """
760
+ if not callable(handler):
761
+ raise TypeError(f"Обработчик должен быть callable, получен {type(handler)}")
762
+
763
+ self._context_enrichers.append(handler)
764
+ logger.info(f"✅ Зарегистрирован обогатитель контекста: {handler.__name__}")
765
+ return handler
766
+
767
+ def process_response(self, handler):
768
+ """
769
+ Регистрирует обработчик ответа AI (ПОСЛЕ получения ответа)
770
+
771
+ Args:
772
+ handler: async def(response_text: str, ai_metadata: dict, user_id: int) -> tuple[str, dict]
773
+
774
+ Example:
775
+ @bot_builder.process_response
776
+ async def modify_response(response_text, ai_metadata, user_id):
777
+ # Модифицируем ответ
778
+ if "цена" in response_text.lower():
779
+ response_text += "\\n\\n💰 Актуальные цены на сайте"
780
+ return response_text, ai_metadata
781
+ """
782
+ if not callable(handler):
783
+ raise TypeError(f"Обработчик должен быть callable, получен {type(handler)}")
784
+
785
+ self._response_processors.append(handler)
786
+ logger.info(f"✅ Зарегистрирован обработчик ответа: {handler.__name__}")
787
+ return handler
788
+
789
+ def filter_send(self, handler):
790
+ """
791
+ Регистрирует фильтр отправки (может блокировать отправку пользователю)
792
+
793
+ Если фильтр возвращает True, сообщение НЕ отправляется
794
+
795
+ Args:
796
+ handler: async def(user_id: int) -> bool
797
+
798
+ Example:
799
+ @bot_builder.filter_send
800
+ async def block_during_process(user_id):
801
+ if is_processing(user_id):
802
+ return True # Блокируем отправку
803
+ return False # Разрешаем отправку
804
+
805
+ # Или совместимый с should_block_ai_response
806
+ @bot_builder.filter_send
807
+ async def should_block_ai_response(user_id):
808
+ # Ваша логика проверки
809
+ return user_is_blocked(user_id) # True = блокировать
810
+ """
811
+ if not callable(handler):
812
+ raise TypeError(f"Обработчик должен быть callable, получен {type(handler)}")
813
+
814
+ self._send_filters.append(handler)
815
+ logger.info(f"✅ Зарегистрирован фильтр отправки: {handler.__name__}")
816
+ return handler
817
+
818
+ def get_message_hooks(self) -> Dict[str, List]:
819
+ """Получает все хуки для обработки сообщений"""
820
+ return {
821
+ "validators": self._message_validators.copy(),
822
+ "prompt_enrichers": self._prompt_enrichers.copy(),
823
+ "context_enrichers": self._context_enrichers.copy(),
824
+ "response_processors": self._response_processors.copy(),
825
+ "send_filters": self._send_filters.copy(),
826
+ }
827
+
828
+ def get_router_manager(self) -> RouterManager:
829
+ """Получает менеджер роутеров событий"""
830
+ return self.router_manager
831
+
832
+ async def _setup_bot_commands(self, bot):
833
+ """Устанавливает меню команд для бота (разные для админов и пользователей)"""
834
+ from aiogram.types import (BotCommand, BotCommandScopeChat,
835
+ BotCommandScopeDefault)
836
+
837
+ try:
838
+ # Команды для обычных пользователей
839
+ user_commands = [
840
+ BotCommand(command="start", description="🚀 Начать/перезапустить бота"),
841
+ BotCommand(command="help", description="❓ Помощь"),
842
+ ]
843
+
844
+ # Устанавливаем для всех пользователей по умолчанию
845
+ await bot.set_my_commands(user_commands, scope=BotCommandScopeDefault())
846
+ logger.info("✅ Установлены команды для обычных пользователей")
847
+
848
+ # Команды для админов (включая команды пользователей + админские)
849
+ admin_commands = [
850
+ BotCommand(command="start", description="🚀 Начать/перезапустить бота"),
851
+ BotCommand(command="help", description="❓ Помощь"),
852
+ BotCommand(
853
+ command="cancel", description="❌ Отменить текущее действие"
854
+ ),
855
+ BotCommand(command="admin", description="👑 Админ панель"),
856
+ BotCommand(command="stats", description="📊 Статистика"),
857
+ BotCommand(command="chat", description="💬 Начать чат с пользователем"),
858
+ BotCommand(command="chats", description="👥 Активные чаты"),
859
+ BotCommand(command="stop", description="⛔ Остановить текущий чат"),
860
+ BotCommand(command="history", description="📜 История сообщений"),
861
+ BotCommand(command="create_event", description="📝 Создать событие"),
862
+ BotCommand(command="list_events", description="📋 Список событий"),
863
+ BotCommand(command="delete_event", description="🗑️ Удалить событие"),
864
+ ]
865
+
866
+ # Устанавливаем для каждого админа персональные команды
867
+ for admin_id in self.config.ADMIN_TELEGRAM_IDS:
868
+ try:
869
+ await bot.set_my_commands(
870
+ admin_commands, scope=BotCommandScopeChat(chat_id=admin_id)
871
+ )
872
+ logger.info(f"✅ Установлены админские команды для {admin_id}")
873
+ except Exception as e:
874
+ logger.warning(
875
+ f"⚠️ Не удалось установить команды для админа {admin_id}: {e}"
876
+ )
877
+
878
+ logger.info(
879
+ f"✅ Меню команд настроено ({len(self.config.ADMIN_TELEGRAM_IDS)} админов)"
880
+ )
881
+
882
+ except Exception as e:
883
+ logger.error(f"❌ Ошибка установки команд бота: {e}")
884
+
885
+ async def start(self):
886
+ """
887
+ Запускает бота (аналог main.py)
888
+ """
889
+ if not self._initialized:
890
+ raise RuntimeError(
891
+ f"Бот {self.bot_id} не инициализирован. Вызовите build() сначала"
892
+ )
893
+
894
+ logger.info(f"🚀 Запускаем бота {self.bot_id}")
895
+
896
+ try:
897
+ # Импортируем необходимые компоненты
898
+ from aiogram import Bot, Dispatcher
899
+ from aiogram.fsm.storage.memory import MemoryStorage
900
+
901
+ # Создаем бота и диспетчер
902
+ bot = Bot(token=self.config.TELEGRAM_BOT_TOKEN)
903
+ storage = MemoryStorage()
904
+ dp = Dispatcher(storage=storage)
905
+
906
+ # Устанавливаем меню команд для бота
907
+ await self._setup_bot_commands(bot)
908
+
909
+ # Инициализируем базу данных
910
+ await self.supabase_client.initialize()
911
+
912
+ # Синхронизируем админов из конфигурации
913
+ await self.admin_manager.sync_admins_from_config()
914
+
915
+ # Проверяем доступность промптов
916
+ prompts_status = await self.prompt_loader.validate_prompts()
917
+ logger.info(f"Статус промптов: {prompts_status}")
918
+
919
+ import importlib
920
+
921
+ # Устанавливаем глобальные переменные в модулях handlers и admin_logic
922
+ try:
923
+ handlers_module = importlib.import_module(
924
+ "smart_bot_factory.handlers.handlers"
925
+ )
926
+ handlers_module.config = self.config
927
+ handlers_module.bot = bot
928
+ handlers_module.dp = dp
929
+ handlers_module.supabase_client = self.supabase_client
930
+ handlers_module.openai_client = self.openai_client
931
+ handlers_module.prompt_loader = self.prompt_loader
932
+ handlers_module.admin_manager = self.admin_manager
933
+ handlers_module.analytics_manager = self.analytics_manager
934
+ handlers_module.conversation_manager = self.conversation_manager
935
+ handlers_module.start_handlers = (
936
+ self._start_handlers
937
+ ) # Передаем обработчики on_start
938
+ handlers_module.message_hooks = (
939
+ self.get_message_hooks()
940
+ ) # Передаем хуки для обработки сообщений
941
+ handlers_module.custom_event_processor = (
942
+ self._custom_event_processor
943
+ ) # Передаем кастомный процессор событий
944
+ if not self.memory_manager:
945
+ self.memory_manager = MemoryManager()
946
+ handlers_module.memory_manager = self.memory_manager
947
+ logger.info("✅ Глобальные переменные установлены в handlers")
948
+ except Exception as e:
949
+ logger.warning(
950
+ f"⚠️ Не удалось установить глобальные переменные в handlers: {e}"
951
+ )
952
+
953
+ try:
954
+ admin_logic_module = importlib.import_module(
955
+ "smart_bot_factory.admin.admin_logic"
956
+ )
957
+ admin_logic_module.config = self.config
958
+ admin_logic_module.bot = bot
959
+ admin_logic_module.dp = dp
960
+ admin_logic_module.supabase_client = self.supabase_client
961
+ admin_logic_module.openai_client = self.openai_client
962
+ admin_logic_module.prompt_loader = self.prompt_loader
963
+ admin_logic_module.admin_manager = self.admin_manager
964
+ admin_logic_module.analytics_manager = self.analytics_manager
965
+ admin_logic_module.conversation_manager = self.conversation_manager
966
+ admin_logic_module.memory_manager = self.memory_manager
967
+ logger.info("✅ Глобальные переменные установлены в admin_logic")
968
+ except Exception as e:
969
+ logger.warning(
970
+ f"⚠️ Не удалось установить глобальные переменные в admin_logic: {e}"
971
+ )
972
+
973
+ # Также устанавливаем в bot_utils
974
+ try:
975
+ bot_utils_module = importlib.import_module(
976
+ "smart_bot_factory.core.bot_utils"
977
+ )
978
+ bot_utils_module.config = self.config
979
+ bot_utils_module.bot = bot
980
+ bot_utils_module.dp = dp
981
+ bot_utils_module.supabase_client = self.supabase_client
982
+ bot_utils_module.openai_client = self.openai_client
983
+ bot_utils_module.prompt_loader = self.prompt_loader
984
+ bot_utils_module.admin_manager = self.admin_manager
985
+ bot_utils_module.analytics_manager = self.analytics_manager
986
+ bot_utils_module.conversation_manager = self.conversation_manager
987
+ bot_utils_module.custom_event_processor = (
988
+ self._custom_event_processor
989
+ ) # Передаем кастомный процессор событий
990
+ bot_utils_module.memory_manager = self.memory_manager
991
+ logger.info("✅ Глобальные переменные установлены в bot_utils")
992
+ except Exception as e:
993
+ logger.warning(
994
+ f"⚠️ Не удалось установить глобальные переменные в bot_utils: {e}"
995
+ )
996
+
997
+ # Также устанавливаем в debug_routing
998
+ try:
999
+ from ..utils import debug_routing
1000
+
1001
+ debug_routing.config = self.config
1002
+ debug_routing.bot = bot
1003
+ debug_routing.dp = dp
1004
+ debug_routing.supabase_client = self.supabase_client
1005
+ debug_routing.openai_client = self.openai_client
1006
+ debug_routing.prompt_loader = self.prompt_loader
1007
+ debug_routing.admin_manager = self.admin_manager
1008
+ debug_routing.conversation_manager = self.conversation_manager
1009
+ debug_routing.memory_manager = self.memory_manager
1010
+ logger.info("✅ Глобальные переменные установлены в debug_routing")
1011
+ except Exception as e:
1012
+ logger.warning(
1013
+ f"⚠️ Не удалось установить глобальные переменные в debug_routing: {e}"
1014
+ )
1015
+
1016
+ # Теперь импортируем и настраиваем обработчики
1017
+ from ..admin.admin_events import setup_admin_events_handlers
1018
+ from ..admin.admin_logic import setup_admin_handlers
1019
+ from ..core.bot_utils import setup_utils_handlers
1020
+ from ..handlers.handlers import setup_handlers
1021
+
1022
+ # Подключаем пользовательские Telegram роутеры ПЕРВЫМИ (высший приоритет)
1023
+ if self._telegram_routers:
1024
+ logger.info(
1025
+ f"🔗 Подключаем {len(self._telegram_routers)} пользовательских Telegram роутеров"
1026
+ )
1027
+ for telegram_router in self._telegram_routers:
1028
+ dp.include_router(telegram_router)
1029
+ router_name = getattr(telegram_router, "name", "unnamed")
1030
+ logger.info(f"✅ Подключен Telegram роутер: {router_name}")
1031
+
1032
+ # Настраиваем стандартные обработчики (меньший приоритет)
1033
+ setup_utils_handlers(dp) # Утилитарные команды (/status, /help)
1034
+ setup_admin_handlers(dp) # Админские команды (/админ, /стат, /чат)
1035
+ setup_admin_events_handlers(dp) # Админские события (/создать_событие)
1036
+ setup_handlers(dp) # Основные пользовательские обработчики
1037
+
1038
+ # Устанавливаем глобальные переменные в модуле бота для удобного доступа
1039
+ self.set_global_vars_in_module(self.bot_id)
1040
+
1041
+ # Устанавливаем роутер-менеджер в декораторы ПЕРЕД настройкой обработчиков
1042
+ if self.router_manager:
1043
+ from ..core.decorators import set_router_manager
1044
+
1045
+ set_router_manager(self.router_manager)
1046
+ logger.info("✅ RouterManager установлен в decorators")
1047
+
1048
+ # Обновляем обработчики после установки RouterManager
1049
+ # (на случай если декораторы выполнялись после добавления роутера)
1050
+ self.router_manager._update_combined_handlers()
1051
+ logger.info("✅ RouterManager обработчики обновлены")
1052
+
1053
+ # Фоновые задачи выполняются через asyncio.create_task в decorators.py
1054
+
1055
+ # Логируем информацию о запуске
1056
+ logger.info(f"✅ Бот {self.bot_id} запущен и готов к работе!")
1057
+ logger.info(f" 📊 Изоляция данных: bot_id = {self.config.BOT_ID}")
1058
+ logger.info(
1059
+ f" 👑 Админов настроено: {len(self.config.ADMIN_TELEGRAM_IDS)}"
1060
+ )
1061
+ logger.info(f" 📝 Загружено промптов: {len(self.config.PROMPT_FILES)}")
1062
+
1063
+ # Запускаем единый фоновый процессор для всех событий
1064
+ import asyncio
1065
+
1066
+ from ..core.decorators import background_event_processor
1067
+
1068
+ asyncio.create_task(background_event_processor())
1069
+ logger.info(
1070
+ "✅ Фоновый процессор событий запущен (user_event, scheduled_task, global_handler, admin_event)"
1071
+ )
1072
+
1073
+ # Четкое сообщение о запуске
1074
+ print(f"\n🤖 БОТ {self.bot_id.upper()} УСПЕШНО ЗАПУЩЕН!")
1075
+ print(f"📱 Telegram Bot ID: {self.config.BOT_ID}")
1076
+ print(f"👑 Админов: {len(self.config.ADMIN_TELEGRAM_IDS)}")
1077
+ print(f"📝 Промптов: {len(self.config.PROMPT_FILES)}")
1078
+ print("⏳ Ожидание сообщений...")
1079
+ print("⏹️ Для остановки нажмите Ctrl+C\n")
1080
+
1081
+ # Запуск polling (бесконечная обработка сообщений)
1082
+ await dp.start_polling(bot)
1083
+
1084
+ except Exception as e:
1085
+ logger.error(f"❌ Ошибка при запуске бота {self.bot_id}: {e}")
1086
+ import traceback
1087
+
1088
+ logger.error(f"Стек ошибки: {traceback.format_exc()}")
1089
+ raise
1090
+ finally:
1091
+ # Очистка ресурсов при завершении
1092
+ if "bot" in locals():
1093
+ await bot.session.close()