Как сделать стриминг ответа ИИ — чтобы текст печатался по буквам, а не висел

Знаешь, почему у ChatGPT ответ «печатается» по словам, а не появляется целиком? Это не украшение. Модель генерирует текст по кусочкам, и если ждать, пока соберётся весь ответ, пользователь секунд пять пялится в пустой экран и думает, что всё зависло. Стриминг показывает слова сразу, как они рождаются. Ощущение — мгновенное, хотя полный ответ идёт столько же. Давай прикрутим его к своему чат-боту по шагам.
Что такое стриминг и зачем он
Обычный запрос работает так: спросил → ждёшь весь ответ → получил стеной. Стриминг работает иначе: спросил → ответ течёт к тебе по кусочкам, и ты рисуешь их на экране на лету. Модель всё равно печатает по одному кусочку за раз — стриминг просто не прячет это, а сразу отдаёт пользователю.
Как это сделать — по шагам
Разберём на связке «сервер на JavaScript + браузер». В других языках всё то же самое, меняется только синтаксис.
- Включи стриминг в запросе к модели. У всех популярных API это один флаг:
const stream = await openai.chat.completions.create({
model: "gpt-5.5",
messages: [{ role: "user", content: prompt }],
stream: true, // ← вот эта строчка включает стриминг
});
- Читай ответ по кусочкам. Теперь модель отдаёт не один объект, а поток. Забираешь из него текст по частям:
for await (const chunk of stream) {
const piece = chunk.choices[0]?.delta?.content || "";
// piece — это очередной кусочек текста, например "прив" или "ет"
}
- Прокидывай кусочки в браузер. Между сервером и страницей нужен канал, который умеет слать данные по чуть-чуть. Классика — Server-Sent Events (SSE): ставишь особый заголовок и пишешь строки в формате
data: ...:
res.setHeader("Content-Type", "text/event-stream");
for await (const chunk of stream) {
const piece = chunk.choices[0]?.delta?.content || "";
res.write(`data: ${JSON.stringify(piece)}\n\n`);
}
res.end();
- Дорисовывай текст на экране. Браузер слушает канал и добавляет каждый кусок в конец:
const es = new EventSource("/api/chat");
es.onmessage = (e) => {
box.textContent += JSON.parse(e.data); // дописываем по буквам
};
Что получится
Ответ начинает появляться почти сразу — первые слова видны за доли секунды, а не через пять. Полный текст идёт ровно столько же, но пользователь этого не чувствует: он уже читает. Именно этот трюк делает чат-бота «живым», а не «висящим».
Частые грабли
EventSourceумеет только GET. А промпт часто хочется слать через POST. Тогда бери неEventSource, а обычныйfetchи читай его тело как поток (response.body.getReader()) — принцип тот же, канал другой.- Прокси и буферизация. Некоторые серверы и CDN копят ответ и отдают его целиком — стриминг «пропадает». Проверь, что буферизация ответа выключена на всём пути.
- Обрыв соединения. Пользователь закрыл вкладку — поток надо закрыть на сервере, иначе будешь платить за генерацию в пустоту.
Вопрос: стриминг ускоряет саму модель?
Нет. Полный ответ приходит за то же время — скорость модели стриминг не меняет. Он меняет ощущение: первое слово на экране за долю секунды вместо ожидания перед пустотой. Это про восприятие, а не про реальную скорость — но для пользователя разница огромная.
Вопрос: это работает с любой моделью?
Почти со всеми современными API — флаг stream: true (или его аналог) поддерживают и OpenAI, и Anthropic, и Google, и большинство локальных серверов. Если модель стриминг не умеет, ты просто получишь ответ целиком в конце — код не сломается, просто не будет эффекта печати.
Короткие уроки-истории, симулятор агента и ежедневная практика — в нашем мобильном приложении. Бесплатно.





