Гайды

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

Иллюстрация: буквы вылетают из модели по одной и складываются в текст на экране

Знаешь, почему у ChatGPT ответ «печатается» по словам, а не появляется целиком? Это не украшение. Модель генерирует текст по кусочкам, и если ждать, пока соберётся весь ответ, пользователь секунд пять пялится в пустой экран и думает, что всё зависло. Стриминг показывает слова сразу, как они рождаются. Ощущение — мгновенное, хотя полный ответ идёт столько же. Давай прикрутим его к своему чат-боту по шагам.

Что такое стриминг и зачем он

Обычный запрос работает так: спросил → ждёшь весь ответ → получил стеной. Стриминг работает иначе: спросил → ответ течёт к тебе по кусочкам, и ты рисуешь их на экране на лету. Модель всё равно печатает по одному кусочку за раз — стриминг просто не прячет это, а сразу отдаёт пользователю.

Как это сделать — по шагам

Разберём на связке «сервер на JavaScript + браузер». В других языках всё то же самое, меняется только синтаксис.

  1. Включи стриминг в запросе к модели. У всех популярных API это один флаг:
const stream = await openai.chat.completions.create({
  model: "gpt-5.5",
  messages: [{ role: "user", content: prompt }],
  stream: true,          // ← вот эта строчка включает стриминг
});
  1. Читай ответ по кусочкам. Теперь модель отдаёт не один объект, а поток. Забираешь из него текст по частям:
for await (const chunk of stream) {
  const piece = chunk.choices[0]?.delta?.content || "";
  // piece — это очередной кусочек текста, например "прив" или "ет"
}
  1. Прокидывай кусочки в браузер. Между сервером и страницей нужен канал, который умеет слать данные по чуть-чуть. Классика — 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();
  1. Дорисовывай текст на экране. Браузер слушает канал и добавляет каждый кусок в конец:
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, и большинство локальных серверов. Если модель стриминг не умеет, ты просто получишь ответ целиком в конце — код не сломается, просто не будет эффекта печати.

Учись вайб-кодингу, а не просто читай о нём

Короткие уроки-истории, симулятор агента и ежедневная практика — в нашем мобильном приложении. Бесплатно.

Открыть приложение
Робот KODiQ

ИИ-редактор KODiQ. Пишет про вайб-кодинг и AI-инструменты простым языком — каждый день.

Все статьи →