# πŸ” БистСма поиска Discours ## ΠžΠ±Π·ΠΎΡ€ БистСма поиска Discours ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠ΅Ρ‚ **сСмантичСскиС эмбСдинги** для Ρ‚ΠΎΡ‡Π½ΠΎΠ³ΠΎ поиска ΠΏΠΎ публикациям. Π Π΅Π°Π»ΠΈΠ·ΠΎΠ²Π°Π½Π° Π½Π° Π±Π°Π·Π΅ `SentenceTransformers` с ΠΏΠΎΠ΄Π΄Π΅Ρ€ΠΆΠΊΠΎΠΉ русского языка ΠΈ FDE (Fast Document Encoding) для ΠΎΠΏΡ‚ΠΈΠΌΠΈΠ·Π°Ρ†ΠΈΠΈ. ## πŸš€ ΠžΡΠ½ΠΎΠ²Π½Ρ‹Π΅ возмоТности ### **1. БСмантичСский поиск** - ПониманиС смысла запросов, Π° Π½Π΅ Ρ‚ΠΎΠ»ΡŒΠΊΠΎ ΠΊΠ»ΡŽΡ‡Π΅Π²Ρ‹Ρ… слов - ΠŸΠΎΠ΄Π΄Π΅Ρ€ΠΆΠΊΠ° русского ΠΈ английского языков - Π’Π΅ΠΊΡ‚ΠΎΡ€Π½ΠΎΠ΅ прСдставлСниС Π΄ΠΎΠΊΡƒΠΌΠ΅Π½Ρ‚ΠΎΠ² Ρ‡Π΅Ρ€Π΅Π· SentenceTransformers ### **2. ΠžΠΏΡ‚ΠΈΠΌΠΈΠ·ΠΈΡ€ΠΎΠ²Π°Π½Π½Π°Ρ индСксация** - Batch-ΠΎΠ±Ρ€Π°Π±ΠΎΡ‚ΠΊΠ° для Π±ΠΎΠ»ΡŒΡˆΠΈΡ… ΠΎΠ±ΡŠΡ‘ΠΌΠΎΠ² Π΄Π°Π½Π½Ρ‹Ρ… - Π’ΠΈΡ…ΠΈΠΉ Ρ€Π΅ΠΆΠΈΠΌ для массовых ΠΎΠΏΠ΅Ρ€Π°Ρ†ΠΈΠΉ - FDE ΠΊΠΎΠ΄ΠΈΡ€ΠΎΠ²Π°Π½ΠΈΠ΅ для сТатия Π²Π΅ΠΊΡ‚ΠΎΡ€ΠΎΠ² ### **3. Высокая ΠΏΡ€ΠΎΠΈΠ·Π²ΠΎΠ΄ΠΈΡ‚Π΅Π»ΡŒΠ½ΠΎΡΡ‚ΡŒ** - ΠšΠΎΡΠΈΠ½ΡƒΡΠ½ΠΎΠ΅ сходство для ранТирования - ΠšΠ΅ΡˆΠΈΡ€ΠΎΠ²Π°Π½ΠΈΠ΅ Ρ€Π΅Π·ΡƒΠ»ΡŒΡ‚Π°Ρ‚ΠΎΠ² - Асинхронная ΠΎΠ±Ρ€Π°Π±ΠΎΡ‚ΠΊΠ° ## πŸ“‹ API ### GraphQL запросы ```graphql # Поиск ΠΏΠΎ публикациям query SearchShouts($text: String!, $options: ShoutsOptions) { load_shouts_search(text: $text, options: $options) { id title body topics { title } } } # Поиск ΠΏΠΎ Π°Π²Ρ‚ΠΎΡ€Π°ΠΌ query SearchAuthors($text: String!, $limit: Int, $offset: Int) { load_authors_search(text: $text, limit: $limit, offset: $offset) { id name email } } ``` ### ΠŸΠ°Ρ€Π°ΠΌΠ΅Ρ‚Ρ€Ρ‹ поиска ```python options = { "limit": 10, # ΠšΠΎΠ»ΠΈΡ‡Π΅ΡΡ‚Π²ΠΎ Ρ€Π΅Π·ΡƒΠ»ΡŒΡ‚Π°Ρ‚ΠΎΠ² "offset": 0, # Π‘ΠΌΠ΅Ρ‰Π΅Π½ΠΈΠ΅ для ΠΏΠ°Π³ΠΈΠ½Π°Ρ†ΠΈΠΈ "filters": { # Π”ΠΎΠΏΠΎΠ»Π½ΠΈΡ‚Π΅Π»ΡŒΠ½Ρ‹Π΅ Ρ„ΠΈΠ»ΡŒΡ‚Ρ€Ρ‹ "community": 1, "status": "published" } } ``` ## πŸ› οΈ ВСхничСская Π°Ρ€Ρ…ΠΈΡ‚Π΅ΠΊΡ‚ΡƒΡ€Π° ### ΠšΠΎΠΌΠΏΠΎΠ½Π΅Π½Ρ‚Ρ‹ систСмы ``` πŸ“¦ Search System β”œβ”€β”€ 🧠 SentenceTransformer # ГСнСрация эмбСдингов β”œβ”€β”€ πŸ—œοΈ Muvera FDE # Π‘ΠΆΠ°Ρ‚ΠΈΠ΅ Π²Π΅ΠΊΡ‚ΠΎΡ€ΠΎΠ² β”œβ”€β”€ πŸ—ƒοΈ MuveraWrapper # Π₯Ρ€Π°Π½Π΅Π½ΠΈΠ΅ ΠΈ поиск β”œβ”€β”€ πŸ’Ύ File Persistence # Π‘ΠΎΡ…Ρ€Π°Π½Π΅Π½ΠΈΠ΅ Π² /dump ΠΏΠ°ΠΏΠΊΡƒ └── πŸ” SearchService # API интСрфСйс ``` ### МодСль эмбСдингов **Основная модСль**: `paraphrase-multilingual-MiniLM-L12-v2` - ΠŸΠΎΠ΄Π΄Π΅Ρ€ΠΆΠΊΠ° 50+ языков Π²ΠΊΠ»ΡŽΡ‡Π°Ρ русский - Π Π°Π·ΠΌΠ΅Ρ€Π½ΠΎΡΡ‚ΡŒ: 384D - Fallback: `all-MiniLM-L6-v2` ### ΠŸΡ€ΠΎΡ†Π΅ΡΡ индСксации ```python # 1. Π˜Π·Π²Π»Π΅Ρ‡Π΅Π½ΠΈΠ΅ тСкста doc_content = f"{title} {subtitle} {lead} {body}".strip() # 2. ГСнСрация эмбСдинга embedding = encoder.encode(doc_content) # 3. FDE ΠΊΠΎΠ΄ΠΈΡ€ΠΎΠ²Π°Π½ΠΈΠ΅ compressed = muvera.encode_fde(embedding, buckets=128, method="avg") # 4. Π‘ΠΎΡ…Ρ€Π°Π½Π΅Π½ΠΈΠ΅ Π² индСкс embeddings[doc_id] = compressed # 5. АвтосохранСниС Π² Ρ„Π°ΠΉΠ» await self.save_index_to_file("/dump") ``` ### Алгоритм поиска ```python # 1. Π­ΠΌΠ±Π΅Π΄ΠΈΠ½Π³ запроса query_embedding = encoder.encode(query_text) query_fde = muvera.encode_fde(query_embedding, buckets=128, method="avg") # 2. ΠšΠΎΡΠΈΠ½ΡƒΡΠ½ΠΎΠ΅ сходство for doc_id, doc_embedding in embeddings.items(): similarity = cosine_similarity(query_fde, doc_embedding) results.append({"id": doc_id, "score": similarity}) # 3. Π Π°Π½ΠΆΠΈΡ€ΠΎΠ²Π°Π½ΠΈΠ΅ results.sort(key=lambda x: x["score"], reverse=True) ``` ## βš™οΈ ΠšΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΡ ### ΠŸΠ΅Ρ€Π΅ΠΌΠ΅Π½Π½Ρ‹Π΅ окруТСния ```bash # Поиск MUVERA_INDEX_NAME=discours_search SEARCH_MAX_BATCH_SIZE=100 SEARCH_PREFETCH_SIZE=200 SEARCH_CACHE_ENABLED=true SEARCH_CACHE_TTL_SECONDS=600 ``` ### Настройки ΠΏΡ€ΠΎΠΈΠ·Π²ΠΎΠ΄ΠΈΡ‚Π΅Π»ΡŒΠ½ΠΎΡΡ‚ΠΈ ```python # Batch Ρ€Π°Π·ΠΌΠ΅Ρ€Ρ‹ SINGLE_DOC_THRESHOLD = 10 # МСньшС = одиночная ΠΎΠ±Ρ€Π°Π±ΠΎΡ‚ΠΊΠ° BATCH_SIZE = 32 # Π Π°Π·ΠΌΠ΅Ρ€ batch для SentenceTransformers FDE_BUCKETS = 128 # ΠšΠΎΠ»ΠΈΡ‡Π΅ΡΡ‚Π²ΠΎ bucket для сТатия # Logging SILENT_BATCH_MODE = True # Π’ΠΈΡ…ΠΈΠΉ Ρ€Π΅ΠΆΠΈΠΌ для batch ΠΎΠΏΠ΅Ρ€Π°Ρ†ΠΈΠΉ DEBUG_SINGLE_DOCS = True # ΠŸΠΎΠ΄Ρ€ΠΎΠ±Π½Ρ‹Π΅ Π»ΠΎΠ³ΠΈ для ΠΎΠ΄ΠΈΠ½ΠΎΡ‡Π½Ρ‹Ρ… Π΄ΠΎΠΊΡƒΠΌΠ΅Π½Ρ‚ΠΎΠ² ``` ## πŸ”§ ИспользованиС ### Π˜Π½Π΄Π΅ΠΊΡΠ°Ρ†ΠΈΡ Π½ΠΎΠ²Ρ‹Ρ… Π΄ΠΎΠΊΡƒΠΌΠ΅Π½Ρ‚ΠΎΠ² ```python from services.search import search_service # ΠžΠ΄ΠΈΠ½ΠΎΡ‡Π½Ρ‹ΠΉ Π΄ΠΎΠΊΡƒΠΌΠ΅Π½Ρ‚ search_service.index(shout) # Batch индСксация (Ρ‚ΠΈΡ…ΠΈΠΉ Ρ€Π΅ΠΆΠΈΠΌ) await search_service.bulk_index(shouts_list) ``` ### Поиск ```python # Поиск ΠΏΡƒΠ±Π»ΠΈΠΊΠ°Ρ†ΠΈΠΉ results = await search_service.search("машинноС ΠΎΠ±ΡƒΡ‡Π΅Π½ΠΈΠ΅", limit=10, offset=0) # Поиск Π°Π²Ρ‚ΠΎΡ€ΠΎΠ² authors = await search_service.search_authors("Иван ΠŸΠ΅Ρ‚Ρ€ΠΎΠ²", limit=5) ``` ### ΠŸΡ€ΠΎΠ²Π΅Ρ€ΠΊΠ° статуса ```python # Π˜Π½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΡ ΠΎ сСрвисС info = await search_service.info() # Бтатус индСкса status = await search_service.check_index_status() # ΠŸΡ€ΠΎΠ²Π΅Ρ€ΠΊΠ° Π΄ΠΎΠΊΡƒΠΌΠ΅Π½Ρ‚ΠΎΠ² verification = await search_service.verify_docs(["1", "2", "3"]) ``` ## πŸ› ΠžΡ‚Π»Π°Π΄ΠΊΠ° ### Π›ΠΎΠ³ΠΈΡ€ΠΎΠ²Π°Π½ΠΈΠ΅ ```python # Π’ΠΊΠ»ΡŽΡ‡ΠΈΡ‚ΡŒ debug Π»ΠΎΠ³ΠΈ import logging logging.getLogger("services.search").setLevel(logging.DEBUG) # ΠŸΡ€ΠΎΠ²Π΅Ρ€ΠΈΡ‚ΡŒ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΡƒ ΠΌΠΎΠ΄Π΅Π»ΠΈ logger.info("πŸ” SentenceTransformer model loaded successfully") ``` ### Диагностика ```python # ΠŸΡ€ΠΎΠ²Π΅Ρ€ΠΈΡ‚ΡŒ количСство проиндСксированных Π΄ΠΎΠΊΡƒΠΌΠ΅Π½Ρ‚ΠΎΠ² info = await search_service.info() print(f"Documents: {info['muvera_info']['documents_count']}") # Найти ΠΎΡ‚ΡΡƒΡ‚ΡΡ‚Π²ΡƒΡŽΡ‰ΠΈΠ΅ Π΄ΠΎΠΊΡƒΠΌΠ΅Π½Ρ‚Ρ‹ missing = await search_service.verify_docs(expected_doc_ids) print(f"Missing: {missing['missing']}") ``` ## πŸ“ˆ ΠœΠ΅Ρ‚Ρ€ΠΈΠΊΠΈ ΠΏΡ€ΠΎΠΈΠ·Π²ΠΎΠ΄ΠΈΡ‚Π΅Π»ΡŒΠ½ΠΎΡΡ‚ΠΈ ### Π’ΠΈΠΏΠΈΡ‡Π½Ρ‹Π΅ ΠΏΠΎΠΊΠ°Π·Π°Ρ‚Π΅Π»ΠΈ ``` πŸ“Š ΠŸΡ€ΠΎΠΈΠ·Π²ΠΎΠ΄ΠΈΡ‚Π΅Π»ΡŒΠ½ΠΎΡΡ‚ΡŒ поиска: β”œβ”€β”€ Поиск ΠΏΠΎ 1000 Π΄ΠΎΠΊΡƒΠΌΠ΅Π½Ρ‚ΠΎΠ²: ~50ms β”œβ”€β”€ Π˜Π½Π΄Π΅ΠΊΡΠ°Ρ†ΠΈΡ 1 Π΄ΠΎΠΊΡƒΠΌΠ΅Π½Ρ‚Π°: ~100ms β”œβ”€β”€ Batch индСксация 100 Π΄ΠΎΠΊΡƒΠΌΠ΅Π½Ρ‚ΠΎΠ²: ~2s └── ΠŸΠ°ΠΌΡΡ‚ΡŒ Π½Π° 1000 Π΄ΠΎΠΊΡƒΠΌΠ΅Π½Ρ‚ΠΎΠ²: ~50MB ``` ### ΠžΠΏΡ‚ΠΈΠΌΠΈΠ·Π°Ρ†ΠΈΡ 1. **Batch ΠΎΠ±Ρ€Π°Π±ΠΎΡ‚ΠΊΠ°** - для массовых ΠΎΠΏΠ΅Ρ€Π°Ρ†ΠΈΠΉ ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠΉΡ‚Π΅ `bulk_index()` 2. **Π’ΠΈΡ…ΠΈΠΉ Ρ€Π΅ΠΆΠΈΠΌ** - ΠΎΡ‚ΠΊΠ»ΡŽΡ‡Π°Π΅Ρ‚ Π΄Π΅Ρ‚Π°Π»ΡŒΠ½ΠΎΠ΅ Π»ΠΎΠ³ΠΈΡ€ΠΎΠ²Π°Π½ΠΈΠ΅ 3. **ΠšΠ΅ΡˆΠΈΡ€ΠΎΠ²Π°Π½ΠΈΠ΅** - Ρ€Π΅Π·ΡƒΠ»ΡŒΡ‚Π°Ρ‚Ρ‹ поиска ΠΊΠ΅ΡˆΠΈΡ€ΡƒΡŽΡ‚ΡΡ Π² Redis 4. **FDE сТатиС** - ΡƒΠΌΠ΅Π½ΡŒΡˆΠ°Π΅Ρ‚ Ρ€Π°Π·ΠΌΠ΅Ρ€ Π²Π΅ΠΊΡ‚ΠΎΡ€ΠΎΠ² Π² 2-3 Ρ€Π°Π·Π° ## πŸ’Ύ ΠŸΠ΅Ρ€ΡΠΈΡΡ‚Π΅Π½Ρ‚Π½ΠΎΡΡ‚ΡŒ ΠΈ восстановлСниС ### АвтоматичСскоС сохранСниС Π² Redis БистСма автоматичСски сохраняСт индСкс Π² Redis послС ΠΊΠ°ΠΆΠ΄ΠΎΠΉ ΡƒΡΠΏΠ΅ΡˆΠ½ΠΎΠΉ индСксации: ```python # АвтосохранСниС послС индСксации if indexed_count > 0: await self.save_index_to_redis() logger.debug("πŸ’Ύ ИндСкс автоматичСски сохранСн Π² Redis") ``` ### Π‘Ρ‚Ρ€ΡƒΠΊΡ‚ΡƒΡ€Π° Redis ΠΊΠ»ΡŽΡ‡Π΅ΠΉ ``` Redis: β”œβ”€β”€ search_index:discours_search:data # Основной индСкс (pickle) └── search_index:discours_search:metadata # ΠœΠ΅Ρ‚Π°Π΄Π°Π½Π½Ρ‹Π΅ (JSON) ``` ### ВосстановлСниС ΠΏΡ€ΠΈ запускС ΠŸΡ€ΠΈ запускС сСрвиса систСма автоматичСски восстанавливаСт индСкс ΠΈΠ· Redis: ```python # Π’ initialize_search_index() await search_service.async_init() # ВосстанавливаСт ΠΈΠ· Redis ``` ## πŸ†• ΠŸΡ€Π΅ΠΈΠΌΡƒΡ‰Π΅ΡΡ‚Π²Π° Redis хранСния ### По ΡΡ€Π°Π²Π½Π΅Π½ΠΈΡŽ с Ρ„Π°ΠΉΠ»Π°ΠΌΠΈ/Π‘Π” - **⚑ Π‘ΠΊΠΎΡ€ΠΎΡΡ‚ΡŒ**: ΠœΠ³Π½ΠΎΠ²Π΅Π½Π½Ρ‹ΠΉ доступ ΠΊ Π²Π΅ΠΊΡ‚ΠΎΡ€Π½ΠΎΠΌΡƒ индСксу - **πŸ”„ ΠΠ°Π΄Π΅ΠΆΠ½ΠΎΡΡ‚ΡŒ**: НСт ΠΏΡ€ΠΎΠ±Π»Π΅ΠΌ с ΠΏΡ€Π°Π²Π°ΠΌΠΈ доступа ΠΊ Ρ„Π°ΠΉΠ»ΠΎΠ²ΠΎΠΉ систСмС - **πŸ’Ύ Π­Ρ„Ρ„Π΅ΠΊΡ‚ΠΈΠ²Π½ΠΎΡΡ‚ΡŒ**: Pickle сСриализация для быстрого сохранСния/Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ - **πŸ”’ Π¦Π΅Π»ΠΎΡΡ‚Π½ΠΎΡΡ‚ΡŒ**: АтомарныС ΠΎΠΏΠ΅Ρ€Π°Ρ†ΠΈΠΈ записи Π² Redis - **πŸ“Š ΠœΠ΅Ρ‚Π°Π΄Π°Π½Π½Ρ‹Π΅**: ΠžΡ‚Π΄Π΅Π»ΡŒΠ½Ρ‹ΠΉ JSON ΠΊΠ»ΡŽΡ‡ для быстрого доступа ΠΊ статистикС ### ΠŸΡ€ΠΎΠΈΠ·Π²ΠΎΠ΄ΠΈΡ‚Π΅Π»ΡŒΠ½ΠΎΡΡ‚ΡŒ ``` πŸ“Š Π‘Ρ€Π°Π²Π½Π΅Π½ΠΈΠ΅ ΠΌΠ΅Ρ‚ΠΎΠ΄ΠΎΠ² хранСния: β”œβ”€β”€ Redis: ~50MB RAM, ΠΌΠ³Π½ΠΎΠ²Π΅Π½Π½ΠΎΠ΅ восстановлСниС βœ… β”œβ”€β”€ Π‘Π”: ~75MB RAM, ΠΌΠ΅Π΄Π»Π΅Π½Π½ΠΎΠ΅ восстановлСниС └── Π€Π°ΠΉΠ»: ~25MB RAM, ΠΏΡ€ΠΎΠ±Π»Π΅ΠΌΡ‹ с ΠΏΡ€Π°Π²Π°ΠΌΠΈ ❌ ``` ## πŸ”„ ΠœΠΈΠ³Ρ€Π°Ρ†ΠΈΡ ΠΈ обновлСния ### ΠŸΠ΅Ρ€Π΅ΠΈΠ½Π΄Π΅ΠΊΡΠ°Ρ†ΠΈΡ ```python # Полная пСрСиндСксация from main import initialize_search_index_with_data await initialize_search_index_with_data() ``` ### ОбновлСниС ΠΌΠΎΠ΄Π΅Π»ΠΈ 1. ΠžΡΡ‚Π°Π½ΠΎΠ²ΠΈΡ‚ΡŒ сСрвис 2. ΠžΠ±Π½ΠΎΠ²ΠΈΡ‚ΡŒ `sentence-transformers` 3. Π˜Π·ΠΌΠ΅Π½ΠΈΡ‚ΡŒ модСль Π² `MuveraWrapper.__init__()` 4. Π—Π°ΠΏΡƒΡΡ‚ΠΈΡ‚ΡŒ ΠΏΠ΅Ρ€Π΅ΠΈΠ½Π΄Π΅ΠΊΡΠ°Ρ†ΠΈΡŽ ### Π Π΅Π·Π΅Ρ€Π²Π½ΠΎΠ΅ ΠΊΠΎΠΏΠΈΡ€ΠΎΠ²Π°Π½ΠΈΠ΅ ```bash # Π‘ΠΎΠ·Π΄Π°Π½ΠΈΠ΅ бэкапа Redis ΠΊΠ»ΡŽΡ‡Π΅ΠΉ redis-cli --rdb backup.rdb # Или экспорт ΠΊΠΎΠ½ΠΊΡ€Π΅Ρ‚Π½Ρ‹Ρ… ΠΊΠ»ΡŽΡ‡Π΅ΠΉ redis-cli GET "search_index:discours_search:data" > backup_data.pkl redis-cli GET "search_index:discours_search:metadata" > backup_metadata.json # ВосстановлСниС ΠΈΠ· бэкапа redis-cli SET "search_index:discours_search:data" < backup_data.pkl redis-cli SET "search_index:discours_search:metadata" < backup_metadata.json ``` ## πŸ”— БвязанныС Π΄ΠΎΠΊΡƒΠΌΠ΅Π½Ρ‚Ρ‹ - [API Documentation](api.md) - GraphQL эндпоинты - [Testing](testing.md) - ВСстированиС поиска - [Performance](performance.md) - ΠžΠΏΡ‚ΠΈΠΌΠΈΠ·Π°Ρ†ΠΈΡ ΠΏΡ€ΠΎΠΈΠ·Π²ΠΎΠ΄ΠΈΡ‚Π΅Π»ΡŒΠ½ΠΎΡΡ‚ΠΈ