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.
- smart_bot_factory/__init__.py +3 -0
- smart_bot_factory/admin/__init__.py +18 -0
- smart_bot_factory/admin/admin_events.py +1223 -0
- smart_bot_factory/admin/admin_logic.py +553 -0
- smart_bot_factory/admin/admin_manager.py +156 -0
- smart_bot_factory/admin/admin_tester.py +157 -0
- smart_bot_factory/admin/timeout_checker.py +547 -0
- smart_bot_factory/aiogram_calendar/__init__.py +14 -0
- smart_bot_factory/aiogram_calendar/common.py +64 -0
- smart_bot_factory/aiogram_calendar/dialog_calendar.py +259 -0
- smart_bot_factory/aiogram_calendar/schemas.py +99 -0
- smart_bot_factory/aiogram_calendar/simple_calendar.py +224 -0
- smart_bot_factory/analytics/analytics_manager.py +414 -0
- smart_bot_factory/cli.py +806 -0
- smart_bot_factory/config.py +258 -0
- smart_bot_factory/configs/growthmed-october-24/prompts/1sales_context.txt +16 -0
- smart_bot_factory/configs/growthmed-october-24/prompts/2product_info.txt +582 -0
- smart_bot_factory/configs/growthmed-october-24/prompts/3objection_handling.txt +66 -0
- smart_bot_factory/configs/growthmed-october-24/prompts/final_instructions.txt +212 -0
- smart_bot_factory/configs/growthmed-october-24/prompts/help_message.txt +28 -0
- smart_bot_factory/configs/growthmed-october-24/prompts/welcome_message.txt +8 -0
- smart_bot_factory/configs/growthmed-october-24/reports/test_20250924_064229.txt +818 -0
- smart_bot_factory/configs/growthmed-october-24/reports/test_20250924_064335.txt +32 -0
- smart_bot_factory/configs/growthmed-october-24/reports/test_20250924_064638.txt +35 -0
- smart_bot_factory/configs/growthmed-october-24/tests/quick_scenarios.yaml +133 -0
- smart_bot_factory/configs/growthmed-october-24/tests/realistic_scenarios.yaml +108 -0
- smart_bot_factory/configs/growthmed-october-24/tests/scenario_examples.yaml +46 -0
- smart_bot_factory/configs/growthmed-october-24/welcome_file/welcome_file_msg.txt +16 -0
- 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
- smart_bot_factory/core/bot_utils.py +1108 -0
- smart_bot_factory/core/conversation_manager.py +653 -0
- smart_bot_factory/core/decorators.py +2464 -0
- smart_bot_factory/core/message_sender.py +729 -0
- smart_bot_factory/core/router.py +347 -0
- smart_bot_factory/core/router_manager.py +218 -0
- smart_bot_factory/core/states.py +27 -0
- smart_bot_factory/creation/__init__.py +7 -0
- smart_bot_factory/creation/bot_builder.py +1093 -0
- smart_bot_factory/creation/bot_testing.py +1122 -0
- smart_bot_factory/dashboard/__init__.py +3 -0
- smart_bot_factory/event/__init__.py +7 -0
- smart_bot_factory/handlers/handlers.py +2013 -0
- smart_bot_factory/integrations/langchain_openai.py +542 -0
- smart_bot_factory/integrations/openai_client.py +513 -0
- smart_bot_factory/integrations/supabase_client.py +1678 -0
- smart_bot_factory/memory/__init__.py +8 -0
- smart_bot_factory/memory/memory_manager.py +299 -0
- smart_bot_factory/memory/static_memory.py +214 -0
- smart_bot_factory/message/__init__.py +56 -0
- smart_bot_factory/rag/__init__.py +5 -0
- smart_bot_factory/rag/decorators.py +29 -0
- smart_bot_factory/rag/router.py +54 -0
- smart_bot_factory/rag/templates/__init__.py +3 -0
- smart_bot_factory/rag/templates/create_table.sql +7 -0
- smart_bot_factory/rag/templates/create_table_and_function_template.py +94 -0
- smart_bot_factory/rag/templates/match_function.sql +61 -0
- smart_bot_factory/rag/templates/match_services_template.py +82 -0
- smart_bot_factory/rag/vectorstore.py +449 -0
- smart_bot_factory/router/__init__.py +10 -0
- smart_bot_factory/setup_checker.py +512 -0
- smart_bot_factory/supabase/__init__.py +7 -0
- smart_bot_factory/supabase/client.py +631 -0
- smart_bot_factory/utils/__init__.py +11 -0
- smart_bot_factory/utils/debug_routing.py +114 -0
- smart_bot_factory/utils/prompt_loader.py +529 -0
- smart_bot_factory/utils/tool_router.py +68 -0
- smart_bot_factory/utils/user_prompt_loader.py +55 -0
- smart_bot_factory/utm_link_generator.py +123 -0
- smart_bot_factory-1.1.1.dist-info/METADATA +1135 -0
- smart_bot_factory-1.1.1.dist-info/RECORD +73 -0
- smart_bot_factory-1.1.1.dist-info/WHEEL +4 -0
- smart_bot_factory-1.1.1.dist-info/entry_points.txt +2 -0
- 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()
|