> ## Documentation Index
> Fetch the complete documentation index at: https://docs.ruapi.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Потоковый вывод (streaming)

> Как получать ответ нейросети по токенам через RuAPI: stream=True в Python, Node.js и curl

Когда вы запрашиваете ответ обычным способом, клиент ждёт, пока модель допишет последнее слово, и только потом показывает результат. На длинном ответе это секунды тишины. Потоковый режим (streaming) отдаёт текст по кусочкам, прямо во время генерации — пользователь видит, как ответ печатается, и воспринимаемая задержка падает почти до нуля.

Технически это не магия: вместо одного JSON-ответа сервер открывает поток Server-Sent Events (SSE) и шлёт по нему мелкие фрагменты (chunks). Включается это одним флагом — `stream=True`.

<Tip>
  Streaming работает на том же OpenAI-совместимом эндпоинте, что и обычные запросы. Меняется только флаг `stream` и способ чтения ответа. `base_url` остаётся прежним: `https://www.ruapi.ai/v1`.
</Tip>

## Как это выглядит в коде

Вместо одного объекта `response` вы получаете итератор. На каждой итерации приходит `chunk`, а полезный текст лежит в `chunk.choices[0].delta.content`. Его нужно дописывать к выводу по мере поступления.

<CodeGroup>
  ```python Python (OpenAI SDK) theme={null}
  from openai import OpenAI

  client = OpenAI(
      api_key="sk-ВАШ_КЛЮЧ",
      base_url="https://www.ruapi.ai/v1",
  )

  stream = client.chat.completions.create(
      model="claude-opus-4-8",  # точные имена — на www.ruapi.ai
      messages=[{"role": "user", "content": "Расскажи короткую историю про кота."}],
      stream=True,
  )

  for chunk in stream:
      delta = chunk.choices[0].delta.content
      if delta:  # в последнем chunk content бывает None — пропускаем
          print(delta, end="", flush=True)
  print()
  ```

  ```javascript Node.js (OpenAI SDK) theme={null}
  import OpenAI from "openai";

  const client = new OpenAI({
    apiKey: "sk-ВАШ_КЛЮЧ",
    baseURL: "https://www.ruapi.ai/v1",
  });

  const stream = await client.chat.completions.create({
    model: "claude-opus-4-8", // точные имена — на www.ruapi.ai
    messages: [{ role: "user", content: "Расскажи короткую историю про кота." }],
    stream: true,
  });

  for await (const chunk of stream) {
    const delta = chunk.choices[0]?.delta?.content;
    if (delta) process.stdout.write(delta);
  }
  process.stdout.write("\n");
  ```

  ```bash curl theme={null}
  curl https://www.ruapi.ai/v1/chat/completions \
    -H "Authorization: Bearer sk-ВАШ_КЛЮЧ" \
    -H "Content-Type: application/json" \
    -d '{
      "model": "claude-opus-4-8",
      "messages": [{"role": "user", "content": "Расскажи короткую историю про кота."}],
      "stream": true
    }'
  ```
</CodeGroup>

В curl ответ приходит как поток строк `data: {...}`, каждая со своим фрагментом. Признак конца — строка `data: [DONE]`.

## Смена модели

Как и в обычных запросах, для переключения модели меняется только поле `model` — код стриминга остаётся тем же. Например, `gpt-5.4`, `gemini-3.5-flash` или `deepseek-v4-pro`:

```python theme={null}
model="gpt-5.4"            # вместо claude-opus-4-8
```

Полный список доступных имён — на странице **«Цены»** на [www.ruapi.ai](https://www.ruapi.ai).

## Стриминг по протоколу Anthropic

Если вы работаете через нативный протокол Anthropic (Claude Code, Anthropic SDK), стриминг тоже поддерживается — через `client.messages.stream(...)`. Адрес здесь **без** `/v1`:

```python theme={null}
import anthropic

client = anthropic.Anthropic(
    api_key="sk-ВАШ_КЛЮЧ",
    base_url="https://www.ruapi.ai",  # для Anthropic — без /v1
)

with client.messages.stream(
    model="claude-opus-4-8",
    max_tokens=1024,
    messages=[{"role": "user", "content": "Расскажи короткую историю про кота."}],
) as stream:
    for text in stream.text_stream:
        print(text, end="", flush=True)
print()
```

Подробнее про два протокола и базовые адреса — в [справочнике API](/ru/api-reference).

## Подводные камни

<AccordionGroup>
  <Accordion title="В последнем chunk delta.content равен None">
    Финальный фрагмент потока обычно несёт служебную информацию (например, `finish_reason`), а не текст — поэтому `delta.content` там `None` (в Node.js — `undefined`). Всегда проверяйте значение перед тем, как его выводить или склеивать, иначе получите `TypeError` при попытке конкатенации.
  </Accordion>

  <Accordion title="Как узнать расход токенов при стриминге">
    По умолчанию итоговая статистика (`usage`) в потоке не приходит. Чтобы её получить, добавьте в запрос параметр `stream_options`:

    ```python theme={null}
    stream = client.chat.completions.create(
        model="claude-opus-4-8",
        messages=[{"role": "user", "content": "Привет!"}],
        stream=True,
        stream_options={"include_usage": True},
    )
    ```

    Тогда в самом конце потока придёт отдельный chunk с полем `usage`. У такого chunk список `choices` пустой — учитывайте это при разборе.
  </Accordion>

  <Accordion title="Ответ приходит не по частям, а целиком">
    Если текст появляется одним куском, а не печатается постепенно — скорее всего, между вами и API стоит прокси или балансировщик, который буферизует SSE. Проверьте, что отключена буферизация ответа (например, `proxy_buffering off;` в nginx) и что ваш HTTP-клиент не накапливает поток сам. В curl для надёжности добавьте флаг `-N` (`--no-buffer`).
  </Accordion>
</AccordionGroup>

## Что дальше

* [Быстрый старт](/ru/quickstart) — регистрация, ключ и первый запрос
* [Модели Claude](/ru/claude-api) — какую линейку взять для стриминга
* [Телеграм-бот на нейросети](/ru/recipes/telegram-bot) — стриминг ответов в живом боте
* [Ошибки и их разбор](/ru/errors) — что делать, если запрос упал
