- BrainTools - https://www.braintools.ru -

Шахматы голосом: как объяснить Алисе ход, если игрок не видит доску

Шахматы — почти идеальная игра для голосового интерфейса. Состояние компактное, ходов конечное число, правила формальны. Казалось бы, бери и делай.

На практике голосовой навык ломается не о шахматы, а о расшифровку речи. Игрок говорит «пешка е два е четыре», распознавание может вернуть «пешка е два и четыре». Для человека смысл очевиден; навык должен восстановить e2–e4 и при этом не угадывать, если позиция или качество разбора оставляют несколько вариантов.

Ниже — про то, как устроен навык «Шахматы с Юрой» для Яндекс Алисы и какие решения пришлось принять, чтобы игра оставалась играбельной на слух [1].

Ограничение, которое определило всё

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

Экранная карточка с доской есть, но она лишь дополняет ответ: ни одно обязательное действие не требует экрана, а позицию, историю и результат можно получить голосом. Это не про доступность как галочку в чек-листе — иначе продукт просто не работает для той аудитории, для которой он сделан.

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

Проблема 1: ASR не знает шахматной нотации

Распознавание речи обучено на обычном языке, а шахматная запись состоит из коротких похожих слогов. Названия вертикалей легко спутать; парсер учитывает, например, варианты «бэ», «пэ», «дэ» и склеенные координаты вроде «едва». Игроки также опускают предлог: «конь эф три» вместо «конь на эф три». Один и тот же ход можно правильно назвать несколькими способами.

Первая версия пыталась нормализовать фразу регулярками и надеяться на лучшее. Замеров я тогда не вёл, поэтому цифры не назову — но спор с ассистентом посреди партии случался достаточно часто, чтобы переписать этот слой целиком.

Рабочим оказался другой подход: текущая позиция служит словарём. Для каждого допустимого хода строятся возможные голосовые формы, а нормализованная фраза сопоставляется с ними. Один вариант с достаточной уверенностью — применяем ход. Несколько вариантов или низкая уверенность — переспрашиваем и не меняем доску. Если допустимого варианта нет, но фигуру и поля удалось восстановить, отдельно объясняем нарушенное правило; если намерение осталось неясным, просим уточнить.

Последний случай важнее, чем кажется. «Так нельзя» — бесполезный ответ, когда игрок не видит доску. Например, навык называет поле первой фигуры, закрывающей путь: «Путь закрыт: на поле e3 стоит пешка».

Проблема 2: движок не должен решать, что легально

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

Stockfish — оптимизирующий поиск, а не арбитр правил. Он работает со своим представлением позиции, его вывод зависит от параметров поиска, и он вправе молчать при нехватке времени. Правила проверяет python-chess, детерминированно и без сети. Движок выбирает ответ соперника и используется в аналитических функциях: по запросу игрока, при разборе партии и для предупреждений тренера.

Разделение дало неожиданный бонус: тесты на правила не требуют движка вообще. Полный прогон шахматной логики идёт без единого процесса Stockfish.

Проблема 3: у ответа есть дедлайн 5 секунд

Алиса обрывает запрос через пять секунд. Не «показывает спиннер» — обрывает. Значит, весь путь «распознали → разобрали фразу → проверили правила → нашли ход → собрали речь» должен укладываться в бюджет с запасом.

Как это устроено:

  • общий бюджет ответа — 4,5 с; ожидание свободного процесса Stockfish вместе с поиском получает не более 3 с;

  • Stockfish живёт постоянно запущенным пулом процессов (по умолчанию два), каждый со своей блокировкой: запуск процесса на каждый ход не влезал в бюджет;

  • пул ограничен, и при исчерпании навык отвечает осмысленной фразой, а не встаёт в очередь до обрыва;

  • поиск уходит с event loop, чтобы не блокировать обработку.

Отдельная история — идемпотентность. Алиса повторяет запрос при сетевых сбоях, и повтор не должен приводить ко второму ходу. Каждый запрос несёт идентификатор, по которому распознаётся повтор, и повторный запрос получает тот же ответ, а не новую партию.

Проблема 4: позицию нужно уместить в человеческую память

Первая версия команды «какая позиция» читала всю доску одним ответом. Технически корректно. На слух — бесполезно: к четвёртой горизонтали первая уже забыта.

Сейчас доска читается по две горизонтали за раз, «дальше» продолжает чтение. Но чаще работает не полное чтение, а точечные вопросы: «что на е четыре», «где белые слоны», «чей ход», «есть ли шах», «какие фигуры съедены», «какой был последний ход», «что делали чёрные четыре хода назад».

Та же логика [2] в справке: единый длинный список команд ухом не воспринимается, поэтому справка разбита на именованные разделы и читается страницами по три строки, с навигацией «дальше», «назад», «сначала».

Общий вывод, применимый к любому голосовому интерфейсу: ограничение — не время ответа, а объём, который удерживается в памяти [3] без возможности перечитать.

Проблема 5: партия длиннее сессии

Голосовая сессия обрывается легко: колонку выключили, ушли из комнаты, Алиса переключилась на другой навык. Партия при этом не должна теряться.

Полное состояние партии хранится на сервере: каноничен список ходов UCI, FEN — производный снимок. В состоянии Алисы остаются идентификатор партии, ревизия и ограниченные данные текущего диалога — например, незавершённое уточнение и позиция в справке. Самой доски и полной истории там нет. Команда «продолжить последнюю партию» восстанавливает позицию с сервера и напоминает последние ходы.

Приватность: идентификатор, который нельзя развернуть

Исходный Alice ID в базу не попадает: до сохранения из него вычисляется псевдоним через HMAC-SHA256 с серверным секретом. Сырые webhook-запросы и аудио не сохраняются. Для улучшения распознавания навык до 30 дней хранит нормализованный текст команды и результат маршрутизации, связанные только с псевдонимом. Ключи запросов и сессий в служебной статистике хешируются, а публичная страница показывает лишь агрегированные счётчики и динамику по дням — без идентификаторов и текстов команд.

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

Что в итоге получилось

  • Полная партия против Stockfish, уровень сложности от 0 до 20, сохранение и продолжение.

  • Объяснение недопустимого хода конкретной причиной, когда понятен его замысел.

  • Голосовое описание позиции, истории, фактов о партии, дебюта и стадии.

  • Режим тренера: оценка позиции словами и числом, ходы-кандидаты, угрозы, подсказки от общего намёка до конкретного хода, разбор варианта без его исполнения.

  • Разбор сыгранной партии: перелом, главная ошибка [4], PGN, переигрывание позиции.

  • Голосовые задачи: мат в один и два хода, вилка, связка, сквозной удар.

  • Настройки речи, которые запоминаются между партиями.

Стек: Python 3.12, FastAPI, SQLAlchemy, MariaDB, python-chess, Stockfish. Модульный монолит, исходники открыты.

Попробовать: «Алиса, запусти навык Шахматы с Юрой». Сайт проекта: yurachess.ru [5]. Про игру без экрана — отдельная страница [6], про тренировку вслепую — здесь [7]. Код: github.com/Blaryxoff/yura-chess [8].

Что я сознательно не привожу: цифр точности распознавания. Корпус реальных фраз собирается с июля, но исторический результат показывает поведение [9] той версии, которая работала в момент запроса. Чтобы честно назвать точность текущей версии, нужно повторно прогнать корпус через актуальный маршрутизатор и отдельно проверить выборку вручную. До такого замера процент был бы выдумкой.

Замечания и разборы кейсов, где навык говорит непонятно, — то, ради чего эта статья и написана.

Автор: blaryx

Источник [10]


Сайт-источник BrainTools: https://www.braintools.ru

Путь до страницы источника: https://www.braintools.ru/article/35977

URLs in this post:

[1] слух: http://www.braintools.ru/article/6251

[2] логика: http://www.braintools.ru/article/7640

[3] памяти: http://www.braintools.ru/article/4140

[4] ошибка: http://www.braintools.ru/article/4192

[5] yurachess.ru: https://yurachess.ru/?source=habr_1

[6] отдельная страница: https://yurachess.ru/accessibility?source=habr_1

[7] здесь: https://yurachess.ru/blindfold?source=habr_1

[8] github.com/Blaryxoff/yura-chess: https://github.com/Blaryxoff/yura-chess

[9] поведение: http://www.braintools.ru/article/9372

[10] Источник: https://habr.com/ru/articles/1086234/?utm_source=habrahabr&utm_medium=rss&utm_campaign=1086234

www.BrainTools.ru

Rambler's Top100