Streaming
Получайте вывод модели постепенно и правильно обрабатывайте события, приращения, завершение и обрывы потока.
Streaming позволяет клиенту получать приращения содержимого до завершения полного ответа модели. Он подходит для чатов, генерации длинных текстов и быстрого показа первых результатов, но требует правильной обработки границ событий, объединения содержимого, завершения и разрыва соединения.
Streaming меняет способ передачи результата, а не саму задачу
Обычно для одной задачи можно выбрать обычный или потоковый ответ. Сначала выполните обычный запрос, затем включите Streaming: так проще отличить проблемы параметров от ошибок разбора потока.
Проверки перед использованием
- В каталоге моделей убедитесь, что целевая модель поддерживает выбранный протокол и Streaming.
- Используйте endpoint и структуру событий выбранного протокола; не заставляйте один парсер угадывать все протоколы.
- Убедитесь, что клиент, обратный прокси и шлюз не буферизуют весь ответ перед его передачей.
Полные поля описаны в Reference: Chat Completions, Responses, Anthropic Messages и Gemini Stream Generate Content.
Минимальный потоковый запрос
Следующий пример включает потоковый вывод через Chat Completions:
curl --no-buffer 'https://api.agtcloud.cn/v1/chat/completions' \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer your-api-key' \
--data '{
"model": "MODEL_ID",
"messages": [
{"role": "user", "content": "Объясните экспоненциальную задержку в трёх пунктах."}
],
"stream": true
}'--no-buffer позволяет cURL сразу показывать полученные данные, но не меняет структуру серверных событий.
Читайте события, а не сетевые пакеты
Количество байтов, переданных клиенту за один раз по HTTP, не равно размеру целого события. Один JSON-объект может быть разделён между блоками, а несколько событий — прийти в одном блоке.
Для SSE сначала выделяйте полные события по пустым строкам, затем читайте их содержимое data::
const response = await fetch('https://api.agtcloud.cn/v1/chat/completions', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: 'Bearer your-api-key',
},
body: JSON.stringify({
model: 'MODEL_ID',
messages: [{ role: 'user', content: 'Объясните, что такое экспоненциальная задержка.' }],
stream: true,
}),
});
if (!response.ok || !response.body) {
throw new Error(`Ошибка запроса: ${response.status}`);
}
const reader = response.body.getReader();
const decoder = new TextDecoder();
let buffer = '';
while (true) {
const { value, done } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
const events = buffer.split('\n\n');
buffer = events.pop() ?? '';
for (const event of events) {
const data = event
.split('\n')
.filter((line) => line.startsWith('data:'))
.map((line) => line.slice(5).trimStart())
.join('\n');
if (!data || data === '[DONE]') continue;
const chunk = JSON.parse(data);
const delta = chunk.choices?.[0]?.delta?.content;
if (delta) process.stdout.write(delta);
}
}В производственном коде также обрабатывайте отмену запроса, тайм-аут чтения, heartbeat, события не в формате JSON и оставшийся необработанный буфер в конце потока.
Потоковые структуры четырёх протоколов
Chat обычно возвращает Chat Completion Chunk. Текст находится в choices[].delta.content, параметры инструментов могут быть в choices[].delta.tool_calls, причина завершения — в finish_reason; поток может заканчиваться [DONE].
Наличие интерфейса протокола не означает, что все модели возвращают все перечисленные типы содержимого. Реализуйте только ветви, которые использует приложение и действительно возвращает целевая модель.
Объединение текста и параметров инструментов
- Объединяйте текстовые приращения отдельно по индексу кандидата, выходного элемента или Content Block.
- Имя инструмента, ID вызова и параметры могут приходить частями; ведите отдельный буфер для каждого вызова.
- Разбирайте параметры инструмента как JSON только после полного завершения; не выполняйте их по отдельным блокам.
- Для краткого изложения рассуждения, Thinking Block и окончательного ответа используйте разные буферы, не смешивайте их в один текст.
- Если Usage возвращается в конце, продолжайте читать соединение до получения события завершения.
Успех, ошибка и прерывание
| Ситуация | Действие клиента |
|---|---|
| Получено предусмотренное протоколом событие завершения | Сохраните полный результат и Usage, отметьте запрос успешным |
| Получено событие ошибки или сбоя | Сохраните уже показанное содержимое и отметьте запрос неуспешным |
| Соединение закрыто преждевременно | Отметьте результат неполным, не выдавайте полученный текст за полный ответ |
| Пользователь остановил генерацию | Отмените чтение и покажите состояние «Генерация остановлена» |
| Долго нет новых событий | Отмените по политике тайм-аутов приложения и сохраните нужные диагностические данные |
После показа части потокового ответа не следует автоматически повторять запрос бесконечно. Повтор может создать другой ответ и повторно вызвать инструменты или действия с побочными эффектами.
Проверка перед запуском
- Страница показывает текст постепенно, а не только после закрытия соединения.
- Китайские и многобайтовые символы не повреждаются при разделении блоков.
- Параметры инструментов правильно объединяются по ID вызова и проверяются перед выполнением.
- Отмена, разрыв сети, тайм-аут и серверные ошибки имеют явные состояния.
- Логи не содержат API Key, полных чувствительных Prompt или неочищенных параметров инструментов.
Далее изучите Tool Calling и ошибки и устранение неполадок.