Путь от фанатского вопроса до полноценного AI-поиска в МДС Коллекции

 Публичный пост
5 декабря 2025  51


В фан-сообществах Модели для сборки регулярно всплывают похожие сообщения:

«Помогите вспомнить рассказ… Там был мальчик… робот… и какой-то поворот в конце…»

Десятки комментариев, версии, догадки - почти коллективная реконструкция сюжета. Я каждый раз ловил себя на мысли, что и сам бывал в этой ситуации. Ты помнишь эмоцию, отдельный эпизод, звук голоса диктора - а название потерялось. Сегодня у слушателей есть один рабочий инструмент: спросить других.

В какой-то момент я понял, что больше не хочу наблюдать это со стороны. Так появилась идея создать в приложении МДС Коллекция поиск по описанию - не по ключевым словам, а по смыслу. Чтобы человек мог написать несколько фраз, как сумел сформулировать, и получить те самые рассказы.

Но идея упёрлась в фундаментальную проблему.

У меня не было текстов.
У меня были только аудиозаписи. С фоновой музыкой и проблемами с громкостью.

Whisper, ffmpeg и 1600 рассказов, которые нужно превратить в текст

МДС Коллекция - это примерно 1600 рассказов, более месяца непрерывного прослушивания. Записаны они в разное время, разными людьми, на разном оборудовании. Whisper, как и большинство моделей ASR, любит чистые, стабильные WAV-файлы. У меня были MP3, некоторые - повреждённые.

Пайплайн казался очевидным:
ffmpeg → Whisper → текст
Но масштаб означал, что работать «в лоб» нельзя - это заняло бы недели.

Варианты ускорения были простыми:

  • Одолжить у друга GPU (RTX 3060 Ti) и распараллелить скрипт.
  • Арендовать GPU-сервер у Hetzner.
  • Запустить всё локально на Apple M4 через Metal.

Экономика и удобство победили - я отнёс задачу другу.

И это сработало: три дня, три потока whisper.cpp, модель ggml-large-v3 - и все 1600 аудиофайлов превратились в текст.
Я выборочно проверил качество - оно казалось приемлемым. Но только до тех пор, пока я не начал строить поиск.

Когда поисковая модель говорит: «Я не знаю этот рассказ», хотя он должен быть найден

На этапе создания векторного поиска я заметил странность: некоторые рассказы не находились даже по точному описанию, хотя содержали соответствующие фразы.

Открываю текст - вижу искажения, пропуски, неправильно распознанные слова. Whisper сделал своё дело, но далеко не идеально.
Переписывать всю коллекцию заново я не хотел. Нужно было:

  • найти «кривые» тексты,
  • обработать только их,
  • повысить качество транскрибации.

Первую задачу решил простой shell-скрипт: эвристики, подсчёт плотности текста, анализ резких провалов по длине фрагментов.
Так нашлось около 200 проблемных файлов.

Теперь требовалось улучшить качество модели.

Как звук влияет на смысл: ffmpeg, VAD и вторая волна транскрибаций

При углублённом изучении Whisper стало ясно, что качество можно существенно улучшить двумя вещами:

1. Предобработка аудио
ffmpeg отлично справляется с задачами:

  • усиление речи относительно фона,
  • подавление шумов,
  • нормализация громкости.

Whisper после такой очистки слышит уже не «аудиофайл с музыкой», а голос, который он и обучен распознавать.

2. Voice Activity Detection (VAD)
VAD размечает участки, где есть человеческая речь. Whisper может пропускать тишину, искажения и бессмысленные фрагменты. Это:

  • ускоряет процесс,
  • уменьшает контекстные ошибки,
  • повышает точность.
  • Эти два шага в сумме дают гораздо более чистую транскрипцию.

GGML, GGUF и почему не CoreML

Техническое отступление.
GGML - лёгкий формат, оптимизированный под CPU.
GGUF - его преемник: более структурированный, с квантованием, удобными метаданными и лучшей совместимостью. Сегодня практически все Whisper-модели распространяются в GGUF.

Почему не CoreML?
Потому что под CoreML доступна только версия Whisper v2.
Она хуже по качеству, чем v3-модели, особенно на шумных и старых записях.

Поэтому вторую волну транскрибаций я делал локально, на Apple M4 с Metal backend, используя ggml-модель.
Результат: качество выросло настолько, что поисковая система наконец начала работать так, как задумано.

От текста - к поиску: как построить семантическую систему для 30-летнего архива

Теперь у меня был корпус текстов, отражающий не только сюжеты, но и звучание эпохи.

Это важный момент:
МДС появилась более 30 лет назад, и ранние выпуски несут в себе культурный слой конца 90-х. Рекламные вставки клубов, объявления «для взрослых», шумы эфира - это тоже часть памяти слушателей.

Я хотел сперва их удалить, но после разговора с поклонниками передачи понял: люди ищут не только по сюжету, но и по этим случайным маркерам времени.
Очищать их - значит терять часть ключей для поиска.

Как устроен поиск: от человеческого описания до пяти самых подходящих рассказов

Структура моего поискового пайплайна выглядит так:

  • Пользователь вводит описание - иногда размытое, иногда в одно предложение.
  • Сервер преобразует описание в вектор - числовое представление смысла.
  • Этот вектор идёт в векторную базу данных, где ищутся 30 ближайших фрагментов текста.
  • Срабатывает реранкинг - уже не фрагментов, а историй целиком.
  • Пользователь получает топ-5 наиболее вероятных совпадений.

Снаружи всё выглядит просто, но внутри крутится несколько ключевых решений.

Почему именно Qdrant

Сегодня многие базы заявляют, что умеют векторный поиск: PostgreSQL, MongoDB, ClickHouse. Они действительно умеют, но как надстройку.

Мне нужен был инструмент, который:

  • создавался для векторного поиска,
  • быстрый,
  • лёгкий,
  • надёжен,
  • разворачивается в один docker run,
  • open source.

Таким инструментом оказался Qdrant.
Rust под капотом, минималистичная архитектура, удобный API - ровно то, что нужно мобильному сервису с быстрыми ответами.

Выбор эмбеддинговой модели: BGE-M3

Модель должна уметь понимать человеческий способ описания. Люди не пишут так: «пожалуйста, найди мне рассказ, содержащий признаки X, Y, Z». Они пишут:
«там был поезд… кажется, девушка… что-то таинственное…»

Модель BGE-M3 оказалась идеальным вариантом:

  • отлично работает на длинных текстах (целых рассказах),
  • понимает неформальные описания,
  • находит смысловые пересечения даже в туманных запросах,
  • не требует мощной видеокарты.

Она стала основой качества поиска.

Как работает реранкинг

Векторная база отдаёт не сами рассказы, а фрагменты. Один рассказ может дать один релевантный фрагмент, другой - пять.
Если брать только лучший фрагмент, можно потерять истории, где совпадений больше, но каждое чуть слабее.

Поэтому я ввёл формулу, которая:

  • усиливает вклад лучшего совпадения,
  • добавляет вес остальных релевантных фрагментов,
  • даёт итоговый story score для каждой истории.

Логика проста:
история, у которой несколько совпадающих фрагментов, скорее всего ближе к запросу, чем история с единственным случайным попаданием.

Первые результаты - и первое разочарование

Когда я собрал всё в FastAPI, завернул в Docker и развернул сервис, результаты были вдохновляющими.
Для подробных запросов поиск работал как надо: точный, быстрый, уверенный.

Но вскоре проявились два ограничения:
1. Большинство людей пишут очень коротко
Иногда всего одно слово.
Модель умеет работать с малым контекстом, но качество таких запросов естественно хуже.

2. Время запроса оказалось выше желаемого
На холодной системе: 600–1000 мс.
На p99 под нагрузкой - до 3 секунд.

Для мобильного приложения это многовато.
Система работала, но не была оптимизирована.

И стало ясно: впереди ещё одна важная глава - ускорение пайплайна, улучшение модели работы с короткими описаниями и дальнейшее развитие поиска.

Кстати, попробовать его можно в АппСторе и ГуглПлее

2 комментария 👇

Наивный вопрос - а почему по автору и названию не найти оригинальные тексты в условной Флибусте?

  Развернуть 1 комментарий

@SergeiTikhomirov, Почти всегда это рассказы из сборников, или циклов произведений. Насколько я знаю они не являются настолько большими чтобы выпускаться отдельно. Иногда рассказы вообще не печатаются, а выкупаются продюссерами под передачу.

  Развернуть 1 комментарий

😎

Автор поста открыл его для большого интернета, но комментирование и движухи доступны только участникам Клуба

Что вообще здесь происходит?


Войти  или  Вступить в Клуб