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,449 @@
1
+ import asyncio
2
+ import logging
3
+ import os
4
+ from pathlib import Path
5
+ from typing import Optional, List, Tuple, Any
6
+
7
+ from dotenv import load_dotenv
8
+ from langchain_community.vectorstores import SupabaseVectorStore
9
+ from langchain_openai import OpenAIEmbeddings
10
+ from langchain_core.documents import Document
11
+
12
+ from project_root_finder import root
13
+ from supabase import Client, create_client
14
+
15
+ PROJECT_ROOT = root
16
+ logger = logging.getLogger(__name__)
17
+
18
+
19
+ class VectorStore(SupabaseVectorStore):
20
+ """Векторное хранилище с автоматической загрузкой настроек бота."""
21
+
22
+ def __init__(self, bot_id: str, table_name: str, query_name: str = None) -> None:
23
+ self.bot_id = bot_id
24
+ self.table_name = table_name
25
+ self.query_name = query_name
26
+ self.supabase_client: Optional[Client] = None
27
+ self.url: Optional[str] = None
28
+ self.key: Optional[str] = None
29
+ self.openai_api_key: Optional[str] = None
30
+
31
+ self._load_env_config()
32
+ self.supabase_client = create_client(self.url, self.key)
33
+
34
+ embedding_model = "text-embedding-3-small"
35
+ embedding = OpenAIEmbeddings(api_key=self.openai_api_key, model=embedding_model)
36
+ self.embedding = embedding
37
+
38
+ # Если query_name не указан, используем стандартное имя функции
39
+ if not query_name:
40
+ query_name = f"match_{table_name}"
41
+
42
+ self.query_name = query_name
43
+
44
+ super().__init__(client=self.supabase_client, embedding=embedding, table_name=table_name, query_name=query_name)
45
+
46
+ # Проверяем наличие таблицы и функции при инициализации
47
+ self._check_table_and_function()
48
+
49
+ # ======================================================================
50
+ # Методы загрузки ENV (оставлены для доступа к настройкам из .env)
51
+ # ======================================================================
52
+ def _load_env_config(self) -> None:
53
+ """Загружает конфигурацию из .env файла."""
54
+ try:
55
+ env_path = self._find_env_file()
56
+
57
+ if not env_path or not env_path.exists():
58
+ raise FileNotFoundError(f".env файл не найден: {env_path}")
59
+
60
+ load_dotenv(env_path)
61
+
62
+ self.url = os.getenv("SUPABASE_URL")
63
+ self.key = os.getenv("SUPABASE_KEY")
64
+ self.openai_api_key = os.getenv("OPENAI_API_KEY")
65
+
66
+ missing_vars = []
67
+ if not self.url:
68
+ missing_vars.append("SUPABASE_URL")
69
+ if not self.key:
70
+ missing_vars.append("SUPABASE_KEY")
71
+ if not self.openai_api_key:
72
+ missing_vars.append("OPENAI_API_KEY")
73
+
74
+ if missing_vars:
75
+ raise ValueError(
76
+ f"Отсутствуют обязательные переменные в .env: {', '.join(missing_vars)}"
77
+ )
78
+
79
+ logger.info("✅ Настройки Supabase загружены из %s", env_path)
80
+
81
+ except Exception as exc:
82
+ logger.error("❌ Ошибка загрузки конфигурации Supabase: %s", exc)
83
+ raise
84
+
85
+ def _find_env_file(self) -> Optional[Path]:
86
+ """Автоматически находит .env файл для указанного бота."""
87
+ bot_env_path = PROJECT_ROOT / "bots" / self.bot_id / ".env"
88
+
89
+ if bot_env_path.exists():
90
+ logger.info("🔍 Найден .env файл для бота %s: %s", self.bot_id, bot_env_path)
91
+ return bot_env_path
92
+
93
+ logger.error("❌ .env файл не найден для бота %s", self.bot_id)
94
+ logger.error(" Искали в: %s", bot_env_path)
95
+ return None
96
+
97
+ def _check_table_and_function(self) -> None:
98
+ """
99
+ Проверяет наличие таблицы и функции при инициализации класса.
100
+ Если таблицы нет - генерирует SQL файл и выбрасывает исключение, останавливающее код.
101
+ Если функции нет - генерирует SQL файл и выводит инструкции.
102
+ """
103
+ # Проверяем наличие таблицы
104
+ try:
105
+ # Пробуем выполнить простой запрос к таблице
106
+ self.supabase_client.table(self.table_name).select("id").limit(1).execute()
107
+ logger.info("✅ Таблица '%s' найдена в Supabase", self.table_name)
108
+ except Exception as table_error:
109
+ # Если есть ошибка при запросе к таблице - значит таблицы нет или она неправильная
110
+ # Генерируем SQL с таблицей и функцией и выбрасываем исключение
111
+ self._handle_table_not_found_error(table_error, self.table_name)
112
+ # Исключение уже выброшено в _handle_table_not_found_error
113
+
114
+ # Если таблица есть, проверяем функцию через попытку простого поиска
115
+ # Используем минимальный запрос, который вызовет функцию
116
+ try:
117
+ # Пробуем выполнить простой поиск с минимальными параметрами
118
+ # Это вызовет функцию, если она существует
119
+ test_query = "test"
120
+ # Пробуем вызвать через родительский класс - это вызовет функцию
121
+ super().similarity_search(query=test_query, k=1)
122
+ logger.info("✅ Функция '%s' найдена в Supabase", self.query_name)
123
+ except Exception as function_error:
124
+ # Если есть ошибка при запросе к функции - значит функции нет или она неправильная
125
+ # Генерируем SQL файл для создания функции
126
+ self._handle_function_not_found_error(function_error, self.query_name)
127
+
128
+ def _is_table_not_found_error(self, error: Exception) -> bool:
129
+ """Проверяет, является ли ошибка ошибкой отсутствия таблицы в Supabase."""
130
+ error_str = str(error).lower()
131
+ # Проверяем, что это ошибка таблицы (relation), а не функции
132
+ # Если в ошибке есть "relation" и "does not exist", но нет "function" - это таблица
133
+ has_relation_error = "relation" in error_str and "does not exist" in error_str
134
+ has_function_error = "function" in error_str
135
+
136
+ # Если ошибка содержит название таблицы - точно таблица
137
+ if has_relation_error and self.table_name.lower() in error_str and not has_function_error:
138
+ return True
139
+
140
+ # Если это ошибка relation, но не функция, и мы пытаемся работать с таблицей - вероятно таблица
141
+ if has_relation_error and not has_function_error:
142
+ return True
143
+
144
+ return False
145
+
146
+ def _is_function_not_found_error(self, error: Exception) -> bool:
147
+ """Проверяет, является ли ошибка ошибкой отсутствия функции в Supabase."""
148
+ error_str = str(error).lower()
149
+ return (
150
+ "function" in error_str and "does not exist" in error_str
151
+ ) or (
152
+ "function" in error_str and "not found" in error_str
153
+ ) or (
154
+ "relation" in error_str and "does not exist" in error_str and "function" in error_str
155
+ )
156
+
157
+ def _generate_sql_file_for_table_and_function(self, table_name: str) -> Path:
158
+ """
159
+ Генерирует SQL файл для создания таблицы и функции в Supabase.
160
+
161
+ Args:
162
+ table_name: Название таблицы
163
+
164
+ Returns:
165
+ Path к созданному SQL файлу
166
+ """
167
+ from .templates.create_table_and_function_template import generate_table_and_function_sql
168
+
169
+ # Получаем размерность embedding из модели
170
+ # text-embedding-3-small имеет размерность 1536
171
+ embedding_dim = 1536
172
+
173
+ # Генерируем SQL
174
+ sql_content = generate_table_and_function_sql(
175
+ table_name=table_name,
176
+ embedding_dim=embedding_dim,
177
+ )
178
+
179
+ # Создаем директорию для SQL файлов, если её нет
180
+ sql_dir = PROJECT_ROOT / "sql_functions"
181
+ sql_dir.mkdir(exist_ok=True)
182
+
183
+ # Создаем файл с именем таблицы
184
+ sql_file = sql_dir / f"create_{table_name}_table_and_function.sql"
185
+
186
+ # Записываем SQL в файл
187
+ sql_file.write_text(sql_content, encoding="utf-8")
188
+
189
+ logger.info("📝 SQL файл создан: %s", sql_file)
190
+
191
+ return sql_file
192
+
193
+ def _generate_sql_file_for_function(self, function_name: str) -> Path:
194
+ """
195
+ Генерирует SQL файл для создания функции в Supabase.
196
+
197
+ Args:
198
+ function_name: Название функции (например, "match_services")
199
+
200
+ Returns:
201
+ Path к созданному SQL файлу
202
+ """
203
+ from .templates.match_services_template import generate_match_services_sql
204
+
205
+ # Определяем название таблицы из имени функции
206
+ # Если функция match_services, то таблица services
207
+ if function_name.startswith("match_"):
208
+ table_name = function_name.replace("match_", "")
209
+ else:
210
+ table_name = self.table_name
211
+
212
+ # Генерируем SQL
213
+ sql_content = generate_match_services_sql(
214
+ table_name=table_name,
215
+ function_name=function_name,
216
+ )
217
+
218
+ # Создаем директорию для SQL файлов, если её нет
219
+ sql_dir = PROJECT_ROOT / "sql_functions"
220
+ sql_dir.mkdir(exist_ok=True)
221
+
222
+ # Создаем файл с именем функции
223
+ sql_file = sql_dir / f"{function_name}.sql"
224
+
225
+ # Записываем SQL в файл
226
+ sql_file.write_text(sql_content, encoding="utf-8")
227
+
228
+ logger.info("📝 SQL файл создан: %s", sql_file)
229
+
230
+ return sql_file
231
+
232
+ def _handle_table_not_found_error(self, error: Exception, table_name: str) -> None:
233
+ """
234
+ Обрабатывает ошибку отсутствия таблицы: генерирует SQL файл с таблицей и функцией.
235
+ Выбрасывает исключение, которое останавливает выполнение кода.
236
+
237
+ Args:
238
+ error: Исключение с ошибкой
239
+ table_name: Название отсутствующей таблицы
240
+
241
+ Raises:
242
+ RuntimeError: Всегда выбрасывается после генерации SQL файла
243
+ """
244
+ logger.error("❌ Таблица %s не найдена в Supabase: %s", table_name, error)
245
+
246
+ # Генерируем SQL файл с таблицей и функцией
247
+ sql_file = self._generate_sql_file_for_table_and_function(table_name)
248
+
249
+ function_name = f"match_{table_name}"
250
+
251
+ # Выводим понятное сообщение пользователю
252
+ print("\n" + "=" * 80)
253
+ print("⚠️ ОШИБКА: Таблица не найдена в Supabase")
254
+ print("=" * 80)
255
+ print(f"\nТаблица '{table_name}' отсутствует в вашей базе данных Supabase.")
256
+ print(f"Скорее всего, также отсутствует функция '{function_name}'.")
257
+ print(f"\n✅ SQL файл для создания таблицы и функции был автоматически сгенерирован:")
258
+ print(f" 📁 {sql_file}")
259
+ print(f"\n📋 ЧТО НУЖНО СДЕЛАТЬ:")
260
+ print(f" 1. Откройте файл: {sql_file}")
261
+ print(f" 2. Скопируйте содержимое SQL файла")
262
+ print(f" 3. Выполните SQL команду в Supabase:")
263
+ print(f" - Откройте Supabase Dashboard")
264
+ print(f" - Перейдите в SQL Editor")
265
+ print(f" - Вставьте SQL код из файла")
266
+ print(f" - Нажмите 'Run' для выполнения")
267
+ print(f"\nSQL файл создаст таблицу '{table_name}' и функцию '{function_name}'.")
268
+ print(f"После создания таблицы и функции в Supabase, повторите операцию.")
269
+ print("=" * 80 + "\n")
270
+
271
+ # Выбрасываем исключение, которое останавливает выполнение кода
272
+ raise RuntimeError(
273
+ f"Таблица '{table_name}' не найдена в Supabase. "
274
+ f"SQL файл для создания таблицы и функции сохранен в: {sql_file}"
275
+ )
276
+
277
+ def _handle_function_not_found_error(self, error: Exception, function_name: str) -> None:
278
+ """
279
+ Обрабатывает ошибку отсутствия функции: генерирует SQL файл и выводит инструкции.
280
+
281
+ Args:
282
+ error: Исключение с ошибкой
283
+ function_name: Название отсутствующей функции
284
+ """
285
+ logger.error("❌ Функция %s не найдена в Supabase: %s", function_name, error)
286
+
287
+ # Генерируем SQL файл
288
+ sql_file = self._generate_sql_file_for_function(function_name)
289
+
290
+ # Выводим понятное сообщение пользователю
291
+ print("\n" + "=" * 80)
292
+ print("⚠️ ОШИБКА: Функция не найдена в Supabase")
293
+ print("=" * 80)
294
+ print(f"\nФункция '{function_name}' отсутствует в вашей базе данных Supabase.")
295
+ print(f"\n✅ SQL файл для создания функции был автоматически сгенерирован:")
296
+ print(f" 📁 {sql_file}")
297
+ print(f"\n📋 ЧТО НУЖНО СДЕЛАТЬ:")
298
+ print(f" 1. Откройте файл: {sql_file}")
299
+ print(f" 2. Скопируйте содержимое SQL файла")
300
+ print(f" 3. Выполните SQL команду в Supabase:")
301
+ print(f" - Откройте Supabase Dashboard")
302
+ print(f" - Перейдите в SQL Editor")
303
+ print(f" - Вставьте SQL код из файла")
304
+ print(f" - Нажмите 'Run' для выполнения")
305
+ print(f"\nПосле создания функции в Supabase, повторите операцию.")
306
+ print("=" * 80 + "\n")
307
+
308
+
309
+ def as_retriever(self, **kwargs):
310
+ """
311
+ Returns a retriever for the vector store.
312
+ """
313
+ kwargs.setdefault("search_kwargs", {})
314
+ search_kwargs = kwargs["search_kwargs"]
315
+ filter_payload = search_kwargs.get("filter", {})
316
+
317
+ # Обеспечиваем, что filter_payload - это словарь
318
+ if not isinstance(filter_payload, dict):
319
+ filter_payload = {}
320
+
321
+ # Добавляем bot_id в фильтр
322
+ filter_payload = filter_payload.copy() # Копируем, чтобы не модифицировать оригинал
323
+ filter_payload["bot_id"] = self.bot_id
324
+
325
+ search_kwargs["filter"] = filter_payload
326
+ kwargs["search_kwargs"] = search_kwargs
327
+
328
+ logger.info("JSONB фильтр для поиска: %s", filter_payload)
329
+
330
+ return super().as_retriever(**kwargs)
331
+
332
+ def similarity_search(
333
+ self,
334
+ query: str,
335
+ k: int = 4,
336
+ filter: Optional[dict] = None,
337
+ score: float = 0.6,
338
+ **kwargs: Any,
339
+ ) -> List[Document]:
340
+ """
341
+ Поиск похожих документов с фильтрацией по минимальному score.
342
+
343
+ Args:
344
+ query: Поисковый запрос
345
+ k: Количество результатов для получения
346
+ filter: Фильтр по metadata
347
+ score: Минимальный score релевантности (по умолчанию 0.6)
348
+ **kwargs: Дополнительные аргументы
349
+
350
+ Returns:
351
+ Список документов с score >= указанного значения
352
+ """
353
+ # Подготавливаем фильтр
354
+ if filter is None:
355
+ filter = {}
356
+ filter = filter.copy()
357
+ filter["bot_id"] = self.bot_id
358
+
359
+ logger.info("Запрос: %s, score: %.2f", query, score)
360
+
361
+ try:
362
+ # Вызываем метод родительского класса для получения результатов с scores
363
+ results_with_scores: List[Tuple[Document, float]] = (
364
+ super().similarity_search_with_relevance_scores(
365
+ query, k=k, filter=filter, **kwargs
366
+ )
367
+ )
368
+
369
+ # Фильтруем по score
370
+ filtered_results = [
371
+ (doc, doc_score)
372
+ for doc, doc_score in results_with_scores
373
+ if doc_score >= score
374
+ ]
375
+
376
+ # Возвращаем только документы (без scores)
377
+ return [doc for doc, _ in filtered_results]
378
+
379
+ except NotImplementedError:
380
+ # Fallback на обычный поиск, если метод не поддерживается
381
+ return super().similarity_search(query, k=k, filter=filter, **kwargs)
382
+ except Exception as exc:
383
+ # Проверяем, является ли это ошибкой отсутствия таблицы
384
+ if self._is_table_not_found_error(exc):
385
+ self._handle_table_not_found_error(exc, self.table_name)
386
+ # Проверяем, является ли это ошибкой отсутствия функции
387
+ elif self._is_function_not_found_error(exc):
388
+ self._handle_function_not_found_error(exc, self.query_name)
389
+ logger.error("❌ Ошибка при поиске: %s", exc)
390
+ raise
391
+
392
+ async def asimilarity_search(
393
+ self,
394
+ query: str,
395
+ k: int = 4,
396
+ filter: Optional[dict] = None,
397
+ score: float = 0.6,
398
+ **kwargs: Any,
399
+ ) -> List[Document]:
400
+ """
401
+ Асинхронный поиск похожих документов с фильтрацией по минимальному score.
402
+
403
+ Args:
404
+ query: Поисковый запрос
405
+ k: Количество результатов для получения
406
+ filter: Фильтр по metadata
407
+ score: Минимальный score релевантности (по умолчанию 0.6)
408
+ **kwargs: Дополнительные аргументы
409
+
410
+ Returns:
411
+ Список документов с score >= указанного значения
412
+ """
413
+ # Подготавливаем фильтр
414
+ if filter is None:
415
+ filter = {}
416
+ filter = filter.copy()
417
+ filter["bot_id"] = self.bot_id
418
+
419
+ logger.info("Запрос: %s, score: %.2f", query, score)
420
+
421
+ try:
422
+ # Вызываем синхронный метод в отдельном потоке, так как async версии нет
423
+ results_with_scores: List[Tuple[Document, float]] = await asyncio.to_thread(
424
+ super().similarity_search_with_relevance_scores,
425
+ query, k=k, filter=filter, **kwargs
426
+ )
427
+
428
+ # Фильтруем по score
429
+ filtered_results = [
430
+ (doc, doc_score)
431
+ for doc, doc_score in results_with_scores
432
+ if doc_score >= score
433
+ ]
434
+
435
+ # Возвращаем только документы (без scores)
436
+ return [doc for doc, _ in filtered_results]
437
+
438
+ except NotImplementedError:
439
+ # Fallback на обычный async поиск, если метод не поддерживается
440
+ return await super().asimilarity_search(query, k=k, filter=filter, **kwargs)
441
+ except Exception as exc:
442
+ # Проверяем, является ли это ошибкой отсутствия таблицы
443
+ if self._is_table_not_found_error(exc):
444
+ self._handle_table_not_found_error(exc, self.table_name)
445
+ # Проверяем, является ли это ошибкой отсутствия функции
446
+ elif self._is_function_not_found_error(exc):
447
+ self._handle_function_not_found_error(exc, self.query_name)
448
+ logger.error("❌ Ошибка при асинхронном поиске: %s", exc)
449
+ raise
@@ -0,0 +1,10 @@
1
+ """
2
+ Router модули smart_bot_factory
3
+ """
4
+
5
+ from ..core.router import EventRouter
6
+
7
+ __all__ = [
8
+ "EventRouter", # Роутер для событий (бизнес-логика)
9
+ # Для Telegram используйте aiogram.Router напрямую
10
+ ]