Без стрімінгу користувач дивиться на порожній екран 3–10 секунд, поки LLM генерує відповідь. З SSE-стрімінгом текст з'являється токен за токеном — сприймається як миттєва відповідь, хоча загальний час не змінився. SSE-стрімінг AI-відповідей у 20 разів швидше за традиційне POST-очікування за часом до першого токена: на одному з проєктів з GPT-4 час до першого токена скоротився з 3 секунд до 150 мс, хоча повна відповідь генерувалася ті ж 10 секунд. За 5+ років досвіду ми реалізували стрімінг на 30+ проєктах — від чат-ботів до комплексних AI-асистентів з історією діалогу. Досвід включає інтеграцію з OpenAI, Anthropic, локальними моделями. Впровадження SSE дозволяє знизити час очікування на 80% і зменшити витрати на підтримку на 30% (економія до $3000 на рік для проєктів із 1000+ запитів). Прискорення виведення першого токена підвищує конверсію на 15%.
Server-Sent Events specification
Server-Sent Events: ідеальний протокол для AI-стрімінгу
SSE — стандартний API браузера, не потребує додаткових бібліотек. На відміну від WebSocket, SSE однонаправлений (сервер→клієнт) і працює поверх HTTP, тому легко прокси через Nginx без додаткових налаштувань. Для стрімінгу токенів LLM це ідеально: кожен токен надсилається як окрема подія, а браузер автоматично обробляє reconnect.
| Критерій | SSE | WebSocket |
|---|---|---|
| Напрям | Однонаправлений (сервер → клієнт) | Двонаправлений |
| Автоматичний reconnect | Вбудований в EventSource | Потрібно реалізовувати вручну |
| Протокол | HTTP (прокси без проблем) | HTTP Upgrade (потребує налаштування) |
| Складність | Низька | Вища |
| Використання з AI | Ідеально для стрімінгу токенів | Надлишково |
Як влаштований стрімінг LLM?
LLM генерує токени послідовно. API провайдерів підтримує stream=True — у цьому режимі сервер надсилає кожен токен одразу після генерації, не чекаючи завершення. Протокол SSE — це HTTP-з'єднання, яке залишається відкритим. Сервер надсилає текстові події у форматі data: {\n}\n\n. Браузер читає їх через EventSource API. Наприклад, OpenAI повертає чанки з полем choices[0].delta.content. Ми вилучаємо контент і надсилаємо як SSE-подію. Час генерації варіюється від 2 до 30 секунд залежно від моделі та складності запиту.
Серверна реалізація: Python і Node.js
FastAPI (Python)
from fastapi import FastAPI from fastapi.responses import StreamingResponse from openai import AsyncOpenAI import json app = FastAPI() client = AsyncOpenAI() async def stream_openai_response(messages: list[dict], model: str): async with client.chat.completions.stream( model=model, messages=messages, temperature=0.7 ) as stream: async for chunk in stream: delta = chunk.choices[0].delta if delta.content: yield f"data: {json.dumps({'content': delta.content})}\n\n" yield "data: [DONE]\n\n" @app.post("/api/chat/stream") async def chat_stream(request: ChatRequest): messages = build_messages(request.history, request.message) return StreamingResponse( stream_openai_response(messages, "gpt-4o-mini"), media_type="text/event-stream", headers={ "Cache-Control": "no-cache", "X-Accel-Buffering": "no", "Connection": "keep-alive" } ) Express (Node.js)
import express from "express"; import OpenAI from "openai"; const app = express(); const openai = new OpenAI(); app.post("/api/chat/stream", async (req, res) => { res.setHeader("Content-Type", "text/event-stream"); res.setHeader("Cache-Control", "no-cache"); res.setHeader("Connection", "keep-alive"); res.setHeader("X-Accel-Buffering", "no"); const { messages } = req.body; try { const stream = await openai.chat.completions.create({ model: "gpt-4o-mini", messages, stream: true, }); for await (const chunk of stream) { const content = chunk.choices[0]?.delta?.content; if (content) { res.write(`data: ${JSON.stringify({ content })}\n\n`); } } res.write("data: [DONE]\n\n"); res.end(); } catch (error) { res.write(`data: ${JSON.stringify({ error: error.message })}\n\n`); res.end(); } }); Клієнтська частина на React
import { useState, useCallback, useRef } from "react"; function useChatStream() { const [content, setContent] = useState(""); const [isStreaming, setIsStreaming] = useState(false); const abortRef = useRef<AbortController | null>(null); const sendMessage = useCallback(async (messages: Message[]) => { abortRef.current = new AbortController(); setContent(""); setIsStreaming(true); try { const response = await fetch("/api/chat/stream", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ messages }), signal: abortRef.current.signal, }); const reader = response.body!.getReader(); const decoder = new TextDecoder(); while (true) { const { done, value } = await reader.read(); if (done) break; const chunk = decoder.decode(value); const lines = chunk.split("\n"); for (const line of lines) { if (line.startsWith("data: ")) { const data = line.slice(6); if (data === "[DONE]") { setIsStreaming(false); return; } try { const parsed = JSON.parse(data); if (parsed.content) { setContent(prev => prev + parsed.content); } } catch {} } } } } catch (error) { if (error.name !== "AbortError") { console.error("Stream error:", error); } } finally { setIsStreaming(false); } }, []); const stop = useCallback(() => { abortRef.current?.abort(); setIsStreaming(false); }, []); return { content, isStreaming, sendMessage, stop }; } Відображення markdown в реальному часі
Стрімлячий текст часто містить markdown. Рендерити через react-markdown кожен токен дорого — перемальовування всього дерева. Краще дебаунсити:
import ReactMarkdown from "react-markdown"; import { useDebounce } from "@/hooks/useDebounce"; function StreamingMessage({ content, isStreaming }: Props) { const debouncedContent = useDebounce(content, isStreaming ? 50 : 0); return ( <div className="prose prose-sm max-w-none"> <ReactMarkdown>{debouncedContent}</ReactMarkdown> {isStreaming && <span className="animate-pulse">▊</span>} </div> ); } Як налаштувати Nginx для коректного стрімінгу?
location /api/chat/stream { proxy_pass http://backend; proxy_buffering off; proxy_cache off; proxy_set_header X-Accel-Buffering no; proxy_read_timeout 120s; proxy_send_timeout 120s; chunked_transfer_encoding on; } proxy_buffering off вимикає буферизацію, proxy_read_timeout 120s продовжує таймаут для довгих відповідей.
Основні помилки при впровадженні SSE
| Помилка | Симптом | Рішення |
|---|---|---|
| Буферизація Nginx | Текст приходить пачками, а не потоком | Додати X-Accel-Buffering: no та proxy_buffering off |
| Таймаут за замовчуванням 60 с | Довгі відповіді обриваються | Збільшити proxy_read_timeout до 120–300 с |
| Рендер кожного токена | Високе навантаження на CPU | Використовувати debounce 50 мс |
| Відсутність reconnect | Втрата відповіді при збої мережі | Реалізувати повторні спроби (3 рази з затримкою) |
Якщо з'єднання перервалося, браузерний EventSource автоматично перепідключається. Для fetch-підходу потрібно реалізувати reconnect вручну — ми завжди включаємо таку логіку в рішення.
Що входить у роботу (deliverables)
- Робочий серверний ендпоінт SSE (FastAPI / Node.js / Python) з інтеграцією вашої LLM.
- React-хук для клієнта з можливістю зупинки стріму.
- Компонент для відображення стрімлінгового markdown-тексту.
- Конфігурація Nginx для коректної роботи SSE.
- Обробка помилок і механізм reconnect.
- Документація API та приклади використання.
- Доступ до репозиторію з кодом.
- Навчання команди (1 година онлайн).
- Підтримка протягом 1 місяця після впровадження.
Процес впровадження під ключ
- Аналіз архітектури — визначаємо точки інтеграції (чат-бот, AI-асистент, генерація контенту).
- Проєктування ендпоінту — обираємо стек (FastAPI, Node.js, Django), налаштовуємо SSE та вимикаємо буферизацію.
- Розробка клієнтської частини — пишемо React-хук з підтримкою паузи/зупинки, відображення markdown з дебаунсом.
- Інтеграція з LLM — підключаємо OpenAI, Anthropic або локальні моделі, обробляємо стрімінг.
- Тестування — перевіряємо reconnect, таймаути, рендеринг у slow network.
- Деплой — налаштовуємо Nginx, моніторинг, логування помилок.
- Документація — передаємо схему API та інструкцію з використання.
Що ви отримуєте в результаті
- Робочий серверний ендпоінт SSE з інтеграцією вашої LLM.
- React-хук для клієнта з можливістю зупинки стріму.
- Компонент для відображення стрімлінгового markdown-тексту.
- Конфігурація Nginx для коректної роботи SSE.
- Обробка помилок і механізм reconnect.
- Документація API та приклади використання.
- Підтримка після впровадження — гарантуємо стабільну роботу.
Терміни та вартість
Базова реалізація (ендпоінт + хук) — від $500 (2–3 дні). Повноцінне рішення з історією, markdown-рендерингом та кнопкою зупинки — від $1200 (4–5 днів). Ми оцінимо ваш проект безкоштовно — пишіть нам. Наша компанія має 5+ років досвіду в AI-інтеграціях та реалізувала 30+ проєктів зі стрімінгом. Отримайте консультацію — допоможемо впровадити стрімінг AI-відповідей на вашому сайті.







