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

Как различать завершение SSE-события, маркер DONE и закрытие HTTP: минимальные трассы, неожиданный EOF, usage и полнота потокового результата.

Автор: Команда Retrivium
Обновлено: 2026-11-01
Канонический URL: https://retrivium.ai/ru/journal/provider-sse-terminal-eof-contract/
Language: ru

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

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

[Стандарт SSE](https://html.spec.whatwg.org/multipage/server-sent-events.html) определяет выделение событий из текстового потока. Blank line участвует в dispatch события, но не является универсальным сигналом завершения всей генерации. Прикладной протокол может дополнительно использовать собственный terminal marker, который parser формата сам по себе не интерпретирует.

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

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

[Streaming OpenRouter](https://openrouter.ai/docs/api_reference/streaming) описывает последовательность с финальным 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](https://www.rfc-editor.org/rfc/rfc9112.html) описывает полноту HTTP/1.1 сообщения и его framing. Корректное окончание HTTP body ещё не устанавливает, что прикладная генерация завершилась по ожидаемому контракту. Обратное также важно: ранний terminal marker не отменяет необходимость корректно обработать оставшиеся предусмотренные данные.

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

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

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

Смежные темы — [ошибки в stream](/ru/journal/openrouter-stream-errors/) и [инкрементальный клиент](/ru/journal/openrouter-streaming-client/). Эта проверка устанавливает полноту доставки выбранного протокола; она не подтверждает качество текста, оплату операции или любые гарантии внешнего провайдера.
