Разработка потокового вывода LLM (Streaming Responses) под ключ

Вы запустили чат-бота на LLM, и пользователи жалуются на долгое ожидание ответа. Задержка в 5–10 секунд убивает конверсию — каждый токен отображается на клиенте практически мгновенно, создавая эффект живого набора текста. Это критически важно для чат-ботов, ассистентов и любого интерфейса, где польз

Направления AI-разработки

Часто задаваемые вопросы

Последние работы

  • image_website-b2b-advance_0.webp
    Разработка сайта компании B2B ADVANCE
    1441
  • image_web-applications_feedme_466_0.webp
    Разработка веб-приложения для компании FEEDME
    1301
  • image_websites_belfingroup_462_0.webp
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    998
  • image_ecommerce_furnoro_435_0.webp
    Разработка интернет магазина для компании FURNORO
    1267
  • image_logo-advance_0.webp
    Разработка логотипа компании B2B Advance
    713
  • image_crm_enviok_479_0.webp
    Разработка веб-приложения для компании Enviok
    1006

Вы запустили чат-бота на LLM, и пользователи жалуются на долгое ожидание ответа. Задержка в 5–10 секунд убивает конверсию — каждый токен отображается на клиенте практически мгновенно, создавая эффект живого набора текста. Это критически важно для чат-ботов, ассистентов и любого интерфейса, где пользователь ждёт ответ. Однажды мы внедряли стриминг для fintech-сервиса с 10 000 одновременных пользователей: после перехода на потоковый вывод latency p99 снизилась с 12 с до 400 мс, а конверсия выросла на 30%. Потоковый вывод ускоряет время первого ответа в 10–20 раз по сравнению с пакетной выдачей. Решение — потоковый вывод (streaming responses). Мы внедряем стриминг ответов LLM с помощью Server-Sent Events (SSE) и WebSocket, снижая видимую задержку до 200–500 мс. Наш опыт — 50+ интеграций LLM для финтеха, e-commerce и SaaS. Гарантируем качество: latency p99 < 500 мс.

Согласно документации MDN, SSE использует стандартное HTTP-соединение и автоматически переподключается при обрыве. Это делает его оптимальным для большинства сценариев чатов.

Пример замеров производительности
  • latency p99 до внедрения: 12 с
  • latency p99 после внедрения: 400 мс
  • Конверсия выросла на 30%

Как работает потоковый вывод LLM?

Вместо того чтобы ждать полный ответ, сервер отправляет клиенту токены по мере генерации. Используется протокол SSE (Server-Sent Events) — текстовый поток по HTTP для потоковой передачи данных. Клиент получает data: чанки с JSON-полем text. Когда модель завершает, отправляется done: true. SSE в 2–3 раза проще в реализации, чем WebSocket, и не требует дополнительных библиотек на бэкенде. Без стриминга пользователь ждёт 5–10 секунд — за это время он уходит. Со стримингом первая буква появляется через 200 мс. Таким образом, пользователь видит потоковый вывод ответов в реальном времени.

Почему SSE, а не WebSocket?

SSE проще: нет рукопожатия, автоматическое переподключение, работает через HTTP/2. WebSocket даёт двунаправленность, но для простого чата избыточен. Сравним:

Критерий SSE WebSocket
Протокол HTTP/1.1, HTTP/2 собственный (ws/wss)
Поддержка бэкенда FastAPI, Django, любой ASGI FastAPI, Django Channels
Авто-переподключение встроенное (EventSource) нужно реализовать вручную
Двунаправленность нет (только сервер->клиент) да
Пропускная способность ~ 1-2 KB/s на соединение выше, зависит от реализации

Для 90% случаев SSE — оптимальный выбор. WebSocket оправдан при двусторонней передаче (например, голосовые команды).

Поддерживаемые модели

Практически все современные LLM поддерживают стриминг: Claude (Anthropic), GPT-4o (OpenAI), LLaMA 3 (через vLLM), Gemini (Google), Mistral, Qwen. Для каждой модели нужно использовать соответствующий SDK с streaming-режимом. Например, AsyncAnthropic и AsyncOpenAI предоставляют асинхронные генераторы токенов. Мы выбираем модель под ваш сценарий: для чат-ботов — Claude Sonnet или GPT-4o, для real-time ассистентов — LLaMA 3 через vLLM с минимальной задержкой.

Обработка прерываний и backpressure

Клиент может отменить запрос в любой момент. Если не обработать, модель продолжит генерацию — лишние токены и деньги. Мы реализуем cancel_event:

import asyncio async def stream_with_cancellation( messages: list[dict], cancel_event: asyncio.Event, ) -> AsyncGenerator[str, None]: """Стриминг с поддержкой отмены""" async with anthropic_client.messages.stream( model="claude-sonnet-4-5", max_tokens=2048, messages=messages, ) as stream: async for text in stream.text_stream: if cancel_event.is_set(): stream.close() yield f"data: {json.dumps({'cancelled': True})}\n\n" return yield f"data: {json.dumps({'text': text})}\n\n" 

Дополнительно используем backpressure: при переполнении буфера клиента приостанавливаем итерацию генератора. Это предотвращает отвал соединения.

Backend: FastAPI + SSE

from fastapi import FastAPI from fastapi.responses import StreamingResponse from anthropic import AsyncAnthropic from openai import AsyncOpenAI import asyncio import json app = FastAPI() anthropic_client = AsyncAnthropic() openai_client = AsyncOpenAI() async def stream_anthropic(messages: list[dict], system: str = "") -> AsyncGenerator[str, None]: """Генератор для Claude streaming""" async with anthropic_client.messages.stream( model="claude-sonnet-4-5", max_tokens=2048, system=system, messages=messages, ) as stream: async for text in stream.text_stream: yield f"data: {json.dumps({'text': text})}\n\n" yield f"data: {json.dumps({'done': True})}\n\n" async def stream_openai(messages: list[dict]) -> AsyncGenerator[str, None]: """Генератор для OpenAI streaming""" async with await openai_client.chat.completions.create( model="gpt-4o", messages=messages, stream=True, ) as stream: async for chunk in stream: delta = chunk.choices[0].delta if delta.content: yield f"data: {json.dumps({'text': delta.content})}\n\n" yield f"data: {json.dumps({'done': True})}\n\n" @app.post("/chat/stream") async def chat_stream(request: dict): messages = request.get("messages", []) provider = request.get("provider", "anthropic") generator = ( stream_anthropic(messages) if provider == "anthropic" else stream_openai(messages) ) return StreamingResponse( generator, media_type="text/event-stream", headers={ "Cache-Control": "no-cache", "X-Accel-Buffering": "no", } ) 

Мы выбираем AsyncAnthropic и AsyncOpenAI для асинхронного стриминга. Важно: выставляем X-Accel-Buffering: no для nginx — иначе nginx начнёт буферизировать SSE.

Frontend: React с потоковым чтением

import { useState, useCallback } from 'react'; function useStreamingChat() { const [response, setResponse] = useState(''); const [isStreaming, setIsStreaming] = useState(false); const sendMessage = useCallback(async (messages: Message[]) => { setIsStreaming(true); setResponse(''); const res = await fetch('/chat/stream', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ messages }), }); const reader = res.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\n'); for (const line of lines) { if (line.startsWith('data: ')) { const data = JSON.parse(line.slice(6)); if (data.done) { setIsStreaming(false); return; } setResponse(prev => prev + data.text); } } } setIsStreaming(false); }, []); return { response, isStreaming, sendMessage }; } 

Для POST-запросов используем ReadableStream — это гибче, чем EventSource, и позволяет отправлять тело запроса.

Процесс работы

  1. Аналитика: разбираем вашу архитектуру, выбираем модель и протокол (SSE/WebSocket).
  2. Проектирование: рисуем диаграмму потоков, определяем точки прерывания.
  3. Реализация: пишем backend endpoint, frontend компонент, обработку ошибок.
  4. Тестирование: нагрузочный тест с имитацией N пользователей, замер p99 latency.
  5. Деплой и мониторинг: настраиваем nginx, CDN, мониторинг (Grafana + Prometheus) для отслеживания стриминговых соединений.

Что входит в работу

  • Backend endpoint с SSE или WebSocket (FastAPI).
  • React-компонент (TypeScript) с обработкой прерываний, индикатором загрузки, авто-скроллом.
  • Документация: описание протокола, конфиги nginx, примеры запросов.
  • Поддержка после запуска: 1 месяц инцидент-менеджмента.

Сроки ориентировочно

Этап Время
Базовая интеграция SSE 1–2 дня
Frontend компонент 1–2 дня
Обработка прерываний 1 день
WebSocket альтернатива 2–3 дня
Тестирование и деплой 2 дня

Стоимость рассчитывается индивидуально. Свяжитесь с нами для оценки вашего проекта. Закажите внедрение потокового вывода для вашего LLM-сервиса — улучшите UX и конверсию.

Потоковый вывод — не просто фича, а требование современного UX для потоковых чат-ботов. Мы реализуем его под ключ за 5–10 дней. Наш опыт — 50+ проектов с LLM. Получите консультацию прямо сейчас.