Команда: Артём
Тема: Ассистент
Задача: Сделайте интеграцию между вашим сервисом поиска фильмов и голосовым помощником. Сценарий следующий: вы говорите помощнику, что найти, он делает запрос в ваш сервис для ES, получает ответ и зачитывает его. Если вариантов несколько, то зачитывается первый вариант.
Пользовательский запрос (голосовой ввод) → Speech Service (получение транскрипции) → Intent Classifier Service (классификация намерения) → Translate Transcript (перевод транскрипции) -> Extract Entity (извлечение сущностей) -> Search Service (обращение к сервису контента).
- Intent Classifier - классифицирует намерения пользователя по заранее заданным меткам. Далее эти метки определяют тип запроса к сервису поиска.
- Translate Transcript - перевод транскрипции. Необходим для поиска фильмов и жанров, т.к. в базе данных они записаны на английском языке.
- Extract Entity - извлекает сущности, если такие имеются (персоны, фильм, жанр).
Таким образом:
Система способна реагировать согласно тем данным, которые есть в Elasticsearch.
Намерение: пользователь хочет получить авторов фильма
Намерение: пользователь хочет узнать продолжительность фильма
Файл model.ipynb подробно описывает процесс разработки ML части проекта:
- Формирование зависимостей и технологий
- Примеры данных, которые хранятся в ES и служат ориентиром для получения сущностей (заголовки жанров и фильмов)
- Формирование датасета для распознования намерения
- Реализация перевода на основе модели 'Helsinki-NLP/opus-mt-ru-en'
- Реализация Classify User Intention с использованием логистической регрессии и векторизатора, выгрузка моделей
- Реализация Entity Extracting, получения сущностей из транскрипции (людей, фильмов и жанров)
Датасет для обучения Classify User Intention
Для обучения модели был сформирован типовой датасет по соответствующим меткам. Подобный подход позволяет легко добавлять новые типы запросов, масштабировать систему, а также обрабатывать синонимы и различные варианты фраз.
intent_dataset = {
"get_film_duration": [
...,
"Сколько идет фильм",
"Длительность фильма",
"Продолжительность фильма",
...,
],
"get_film_genre": [
...,
"Какой жанр у фильма",
"Что за жанр у фильма",
"Жанр фильма",
...,
],
"get_film_rating": [
...,
"Рейтинг",
"Рейтинг фильма",
"Какой рейтинг у фильма",
...,
],
...
При запуске экземпляра сервиса происходит инициализация Redis, Scheduler и LifespanManager.
Redis
- cлужит для кэширования получения списков заголовков фильмов и жанров с сервиса контента. С ними работает Entity Extracting. Помимо этого, Redis также кэширует все запросы, исходящие от внутреннего сервиса поиска
Scheduler
- в заданный интервал времени получает список заголовков фильмов и жанров у сервиса контента с принудительным обновлением кэша. Таким образом достигается актуализация данных.
LifespanManager
- реализует функцию обновления данных о заголовках фильмов жанров, а также содержит метод загрузки NER модели для Entity Extracting
API сервиса представляет собой одну единственную ручку /assistant/api/v1/search
.
- принимает: аудиофайл
- возвращает: словарь с данными/аудиофайл для воспроизведения ответа
Пример ответа:
{
"response": "Сценарист(ы) фильма 'Star': Guy Ritchie, Joe Sweet.",
"transcript": "Авторы фильма звёздные войны",
"transcript_en": "The film's authors are Star Wars.",
"intent": "get_film_writers",
"persons": [],
"genres": [],
"films": [
"Star",
"Star Wars"
]
}
Формиривание запроса
Формирование запроса происходит по маппингу намерения - intent. В директории mappers есть "мапа", которая по ключу возвращает сформированный запрос в сервис поиска, а также заготовленный ответ.
Пример записей маппинга:
{
Intents.GET_FILM_GENRE.value: {
"method": lambda search_service, films: search_service.search(index=Indecies.MOVIES.value, query={"match": {"title": films[0]}} if films else {}),
"response": lambda result, films: (
f"Жанр фильма '{result['hits']['hits'][0]['_source']['title']}': {', '.join([genre['name'] for genre in result['hits']['hits'][0]['_source'].get('genres', [])])}."
if result.get('hits', {}).get('hits') else f"К сожалению, жанр фильма '{films[0]}' найти не удалось."
),
},
Intents.GET_FILM_RATING.value: {
"method": lambda search_service, films: search_service.search(index=Indecies.MOVIES.value, query={"match": {"title": films[0]}} if films else {}),
"response": lambda result, films: (
f"Рейтинг фильма '{result['hits']['hits'][0]['_source']['title']}' составляет {result['hits']['hits'][0]['_source'].get('imdb_rating', 'неизвестно')}."
if result.get('hits', {}).get('hits') else f"К сожалению, рейтинг фильма '{films[0]}' найти не удалось."
),
},
...
}
Отправка запроса и получения данных согласно маппингу где intent
- определенное моделью намерение пользователя
if intent in INTENT_TO_ACTION_MAPPING:
action = INTENT_TO_ACTION_MAPPING[intent]
search_result = await action["method"](search_service, films)
response_message = action["response"](search_result, films)
else:
raise Exception("Намерение не поддерживается.")
- Реализован сервис умного голосового помощника, который взаимодействует с пользователем и возвращает результаты полученные с сервиса контента
- Логика обработки запроса задействует NLP и базовый Machine Learning на основе логистической регрессии
- Технологии backend: FastAPI, Redis, Httpx