ТЕХНОЛОГИИ

Завершение SSE: blank line, DONE и неожиданный EOF

В этом материале
ГЛАВНОЕ ЗА МИНУТУ

Закрытие соединения не доказывает прикладного успеха. Проверяйте framing, terminal marker и наличие обязательных итоговых данных раздельно.

Последнее прочитанное сообщение не обязательно является последним сообщением генерации. Клиент мог получить часть content и встретить EOF из-за сбоя сети. Для протокольного теста нужно отделить завершение SSE-события, прикладной маркер окончания и закрытие транспортного чтения. Тогда неполный результат не станет успешным только потому, что его последняя строка выглядит как корректный JSON.

Назовите три разных границы

Стандарт SSE определяет выделение событий из текстового потока. Blank line участвует в dispatch события, но не является универсальным сигналом завершения всей генерации. Прикладной протокол может дополнительно использовать собственный terminal marker, который parser формата сам по себе не интерпретирует.

В тестовом отчёте храните три признака: завершено ли последнее событие, получен ли ожидаемый прикладной итог и закончено ли чтение body. Их сочетания дают более точную диагностику, чем одно поле success. Отсутствие одного признака нельзя автоматически восстанавливать по наличию другого.

Проверьте нормальную трассу

Streaming OpenRouter описывает последовательность с финальным usage перед DONE для Chat Completions. Зафиксируйте выбранный endpoint и его текущую схему. Другие интерфейсы могут использовать собственные terminal events, поэтому строка DONE не должна становиться общим требованием к любому inference API.

Минимальный fixture содержит несколько content-events, корректный итог и закрытие. Сохраните ожидаемое число событий и итоговую строку. Это контрольный вариант, с которым сравниваются намеренно неполные трассы. Он не требует настоящей модели: достаточно небольшого fake upstream с известными bytes.

Обрежьте поток в разных местах

Первый вариант заканчивается внутри строки data, второй — после полного JSON, но до разделителя события, третий — после текста, но до accounting или terminal marker. Ожидаемые исходы различаются. Нельзя дописывать разделитель и объявлять всё успешным только ради удобного парсинга.

Сохраняйте место обрыва и последний полностью обработанный event. Если parser оставил незавершённый буфер, его объём полезен для диагностики, но содержимое настоящих запросов в production logs по умолчанию не требуется. На синтетическом fixture исходные данные можно хранить целиком без этой неопределённости.

Не смешивайте HTTP framing и SSE

RFC 9112 описывает полноту HTTP/1.1 сообщения и его framing. Корректное окончание HTTP body ещё не устанавливает, что прикладная генерация завершилась по ожидаемому контракту. Обратное также важно: ранний terminal marker не отменяет необходимость корректно обработать оставшиеся предусмотренные данные.

Ошибка может находиться на любом слое: транспорт сообщил неполное сообщение, SSE parser не получил законченного события либо приложение не увидело обещанного итога. Запишите слой первой наблюдаемой проблемы, сохраняя остальные признаки. Так один сетевой сбой не превратится в несколько независимых ошибок модели.

Классифицируйте результат осторожно

Полезны завершённый, неполный и явно неуспешный исходы. Частично полученный текст можно сохранить для диагностики, но его нельзя без отдельного правила включать в счётчик полных ответов. Автоматический повтор запроса тоже требует оценки, потому что первоначальная работа могла уже выполниться upstream.

Смежные темы — ошибки в stream и инкрементальный клиент. Эта проверка устанавливает полноту доставки выбранного протокола; она не подтверждает качество текста, оплату операции или любые гарантии внешнего провайдера.

← Все материалы