← Все инструкции

РУКОВОДСТВО · ОБНОВЛЕНО 2026-10-31

Кириллица ломается в потоке LLM: как декодировать UTF‑8 по частям

Проверка транспортного декодирования, если вместо букв появляются символы замены.

Сетевая порция байтов не обязана заканчиваться на границе символа. Если каждую часть UTF‑8 декодировать независимо, многобайтовая буква может превратиться в символ замены. Используйте один потоковый декодер на соединение и отдельно собирайте события протокола. Корректный текст ещё не означает корректный SSE: разделитель события тоже может оказаться между порциями. Проверяйте оба слоя на синтетическом ответе, который воспроизводит нужное разбиение без обращения к платной модели. Проблема декодирования отличается от буферизации прокси и от обрезанного моделью ответа.

1. Найдите первый участок повреждения

Сравните исходные байты, текст после декодирования и строку в интерфейсе. Если повреждение появилось до браузера, исследуйте чтение upstream в приложении. Если текст правильный до отрисовки, проверьте собственные преобразования строки. Не пытайтесь исправлять проблему повторной генерацией. Сохраните минимальный пример с русскими буквами, эмодзи и обычным латинским текстом. Не включайте в диагностический лог полный закрытый документ только ради нескольких повреждённых символов.

2. Сохраняйте состояние декодера

Для одного потока используйте один TextDecoder и режим stream при промежуточном decode, либо подходящий TextDecoderStream. При завершении выполните предусмотренное API финальное декодирование, чтобы обработать оставшиеся байты. Не создавайте новый декодер для каждой порции. Выберите явную политику повреждённых последовательностей: ошибка или замена символом. Режим fatal помогает обнаружить недопустимое кодирование, но не устраняет причину повреждения. Сначала проверьте фактическую кодировку отправителя и заявленный контракт транспорта.

3. Отделите события от сетевых порций

После декодирования добавляйте текст в буфер парсера SSE или другого выбранного протокола. Поле data, разделитель строки и JSON внутри события могут приходить частями. Не вызывайте JSON.parse на каждой сетевой порции автоматически. Извлекайте только полные сообщения и сохраняйте остаток до следующего чтения. Финальный маркер и нормальное закрытие соединения проверяйте по документации сервиса. Не переносите особенности одного API на другой только из-за одинакового параметра stream.

4. Проверьте все точки разбиения

Возьмите короткую строку и разделите её байтовое представление в разных местах, включая середину русской буквы и эмодзи. Ожидаемый итог должен совпасть с оригиналом независимо от разбиения. Отдельно проверьте пустую порцию, повреждённую последовательность и незавершённый последний символ. Затем добавьте события и разделители протокола. Такой тест можно выполнить локально. Он проверяет декодер и парсер, но не доказывает работоспособность внешнего API или скорость реальной генерации.

Частые вопросы

Почему обычный английский текст выглядит правильно?

Большинство базовых латинских символов занимают один байт. Ошибка границ становится заметнее на многобайтовых буквах и эмодзи.

Достаточно ли указать UTF‑8 в заголовке?

Нет. Заголовок описывает кодировку, но программа всё равно должна правильно обрабатывать последовательность байтов между сетевыми порциями.

Поможет ли отключение буферизации nginx?

Это другая проблема. Буферизация меняет момент доставки частей, а неправильное декодирование портит их текст. Проверяйте причины раздельно.

Нужен ли платный вызов модели для проверки?

Нет. Синтетический поток с заранее известными байтами позволяет проверить все границы символов и событий без генерации.

Источники

Калькулятор стоимости →