# Чат

`POST /v1/chat/completions` — контракт OpenAI Chat Completions, в том числе с
`"stream": true`. Итог вызова (статус и цена, без текста) — `GET /v1/jobs/{id}` с `id`
ответа; карточка с токенами и временем — `GET /v1/usage/{id}` тем же ключом. Поля — схема
`ChatCompletionRequest`.

```bash tab="cURL"
curl https://api.souz.ai/v1/chat/completions \
  -H "Authorization: Bearer $SOUZ_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"model": "gpt-5-nano", "messages": [{"role": "user", "content": "Привет!"}], "routing": "fast"}'
```

```python tab="Python"
chat = client.chat.completions.create(
    model="gpt-5-nano",
    messages=[{"role": "user", "content": "Привет!"}],
    extra_body={"routing": "fast"},
)
print(chat.choices[0].message.content, chat.usage)
```

```ts tab="TypeScript"
const chat = await client.chat.completions.create({
  model: "gpt-5-nano",
  messages: [{ role: "user", content: "Привет!" }],
  // @ts-expect-error — поле Souz API сверх контракта OpenAI
  routing: "fast",
});
console.log(chat.choices[0].message.content, chat.usage);
```

## Поля запроса

- Поля, которых нет в схеме, уходят модели как есть; что модель читает — её карточка.
- Значения известных полей проверяются до списания, ошибка — `400 invalid_request`, в
  `detail[]` поле и что подойдёт:
  - `temperature` — от 0 до 2, `top_p` — от 0 до 1; если в `params` карточки у поля свой
    диапазон, действует он;
  - `reasoning_effort` и `reasoning.effort` — одна из ступеней `none`, `minimal`, `low`,
    `medium`, `high`, `xhigh`, `max`; ступени модели — в `params` карточки. Ступень, которой
    нет в карточке, канал получает ближайшей ступенью карточки не ниже запрошенной, а если
    такой нет — высшей; канал, который подбирает ступень под модель сам, получает её как
    есть и применяет своё правило соответствия;
  - `max_tokens`, `max_completion_tokens` и `n` — целые от 1; `max_tokens` и
    `max_completion_tokens` — не меньше `min` из `params` (у GPT-6 — 16);
  - имена полей — с учётом регистра: `Max_Tokens` или `N` — `400` с `{"path": "N",
    "reason": "unsupported_field"}`.

  ```json
  {"path": "temperature", "reason": "out_of_range", "allowed": ["at most 2"]}
  ```
- `tools`, картинка, файл, звук и видео в `messages[].content` и `response_format:
  json_schema` уходят только туда, где их понимают; если модель этого не умеет — `400
  capability_mismatch` до списания, в `detail[]` названо поле.
- `tool_choice: "required"` и `{"type": "function", "function": {"name": "…"}}` исполняют
  не все каналы: такой запрос идёт только по тем, что умеют. Не умеет ни один —
  `400 capability_mismatch` до списания, `{"path": "tool_choice", "reason":
  "unsupported_value"}`; с `"auto"` модель решает сама.
- Параметр, который канал не поддерживает, этим каналом игнорируется:
  `temperature`, `top_p`, `top_k`, `min_p`, `seed`, штрафы, `logprobs`, `logit_bias`,
  `stop` и подобные; что учитывает канал — `capabilities.supported_parameters`.
  `tools`, `tool_choice`, `response_format` и `verbosity` идут только к каналам, которые
  их учитывают; не учитывает ни один — `400 capability_mismatch` до списания с полем в
  `detail[]`.
- `response_format: {"type": "json_object"}` у моделей OpenAI требует слова «json» в
  сообщениях; без него — `400 invalid_request` с `{"path": "response_format", "reason":
  "incompatible_value"}`.
- Тип картинки в data URL определяется по её байтам: подпись `image/jpeg` у PNG не мешает.
- Веб-поиск ни одна модель не исполняет: `web_search_options` (и `{}`) или плагин
  `{"id": "web"}` в `plugins` — `400 capability_mismatch` до списания с полем в `detail[]`.
- `service_tier`, `provider`, `models`, `route`, `transforms` и остальные `plugins`
  (`file-parser` и другие) игнорируются: канал и запасные каналы выбирают Авто-роутинг и
  `routing_options`, PDF модель получает сама.
- Общие поля Souz API: `routing`, `max_price_multiplier`, `routing_options`, `store`.

## Claude Haiku 5.5: мышление и кэш

Модель `claude-haiku-5.5` принимает `reasoning_effort` со значениями `none`,
`low`, `medium`, `high`, `xhigh`, `max`; умолчание — `medium`. `none` отключает
мышление. `tools` поддерживает `tool_choice: "auto"` и `"none"`; принудительный
или именованный выбор функции у доступных каналов не поддерживается.
Изображения и PDF передаются стандартными частями сообщений.

Промпт можно закэшировать на 5 минут или 1 час. `cache_control` передаётся
внутри текстового блока без изменений, например:

```json
{
  "model": "claude-haiku-5.5",
  "reasoning_effort": "medium",
  "max_tokens": 4096,
  "messages": [{
    "role": "user",
    "content": [{
      "type": "text",
      "text": "Длинный документ и вопрос по нему",
      "cache_control": {"type": "ephemeral", "ttl": "1h"}
    }]
  }]
}
```

Повторный запрос с тем же префиксом может прочитать кэш. Запись и чтение
оплачиваются отдельно; для промпта свыше 100 тысяч токенов действует тариф
длинного контекста. Итоговое списание — по фактическому использованию.
`stream: true` включает потоковый ответ, `stream_options: {"include_usage": true}`
возвращает итоговый расход токенов в потоке.

## Файлы, звук и видео

Части `messages[].content` — как у OpenAI Chat Completions, SDK работает без правок.

| Часть | Форма | Карточка модели |
|---|---|---|
| файл (PDF) | `{"type": "file", "file": {"filename": "doc.pdf", "file_data": "data:application/pdf;base64,…"}}` | `capabilities.file_input` |
| звук | `{"type": "input_audio", "input_audio": {"data": "<base64>", "format": "wav"}}`, `wav` или `mp3` | `capabilities.audio_input` |
| видео | `{"type": "video_url", "video_url": {"url": "https://…/clip.mp4"}}` или `data:video/mp4;base64,…` | `capabilities.video_input` |

- Файл и звук — в теле, base64; видео — ссылкой или data URL. Ссылка должна открываться
  без авторизации; подойдёт `url` файла из `POST /v1/files`. Если модель не смогла скачать
  ссылку — `400 invalid_input` с `{"path": "messages", "reason": "unreachable", "allowed":
  ["data URL"]}`: передайте вложение data URL.
- Модель без нужного входа — `400 capability_mismatch` до списания, в `detail[]` —
  `{"path": "messages", "reason": "unsupported_value"}`. Какие каналы примут вход — превью
  с `file_input`, `audio_input`, `video_input`.
- Картинка data URL больше предела канала уходит только каналам, которые её примут. Не
  примет ни один — `400 capability_mismatch` до списания с `{"path": "messages", "reason":
  "too_large", "allowed": ["at most <N> base64 bytes per image"]}`, где N — наибольший
  предел каналов модели. Уменьшите картинку или передайте её ссылкой.
- Файл, звук и кадры видео модель считает во входных токенах; у части моделей токены звука
  дороже текстовых. Итог — по факту выполнения.
- Тело запроса — до 25 МиБ; больше — `413` с `{"path": "body", "reason": "too_large",
  "allowed": ["at most 26214400 bytes"]}`. Крупное вложение загрузите через
  [`POST /v1/files`](https://souz.ai/docs/files.md) и передайте его `url` или передайте ссылкой `https://…`.

```python tab="Python"
import base64

pdf = base64.b64encode(open("contract.pdf", "rb").read()).decode()
chat = client.chat.completions.create(
    model="gemini-3.8-flash",
    messages=[{"role": "user", "content": [
        {"type": "text", "text": "Перечисли сроки из договора."},
        {"type": "file", "file": {"filename": "contract.pdf",
                                  "file_data": f"data:application/pdf;base64,{pdf}"}},
    ]}],
)
```

## Поток

`"stream": true` — `text/event-stream` по контракту OpenAI. Заголовки приходят с первым
содержательным чанком; ошибка до него — обычный JSON со статусом. После 15 секунд тишины —
пока модель думает до первого токена или между чанками — поток пишет комментарий
`: ping`; SDK и разбор SSE его пропускают. Если поток уже открыт пингом, ошибка до первого
чанка приходит событием `data: {"error": {…}}` с тем же телом, без `data: [DONE]`.
`stream_options: {"include_usage": true}` — последний чанк с пустым `choices`, `usage` и
`usage.cost`. Без флага `usage` в потоке нет, у каждого чанка есть `choices[0]`; списание —
в `GET /v1/jobs/{id}`. Поток, оборвавшийся без `usage`, бесплатен.

```python tab="Python"
stream = client.chat.completions.create(
    model="gpt-5-nano",
    messages=[{"role": "user", "content": "Привет!"}],
    stream=True,
    stream_options={"include_usage": True},
)
for chunk in stream:
    if chunk.choices:
        print(chunk.choices[0].delta.content or "", end="")
```

```ts tab="TypeScript"
const stream = await client.chat.completions.create({
  model: "gpt-5-nano",
  messages: [{ role: "user", content: "Привет!" }],
  stream: true,
  stream_options: { include_usage: true },
});
for await (const chunk of stream) {
  process.stdout.write(chunk.choices[0]?.delta?.content ?? "");
}
```

## Ответ

`usage.cost` и `usage.currency` — списано, `channel` — канал запроса. Ожидание ответа — до 14
минут; дольше — `502 generation_failed` (в потоке — обрыв без `usage`), без списания.

SDK OpenAI и Anthropic ждут ответа 10 минут и после таймаута сами повторяют запрос. Обрыв
вызов не отменяет: без ключа каждый повтор — новый вызов модели и новое списание. Передавайте
свой `Idempotency-Key` на каждый вызов: повтор с тем же ключом и тем же телом дожидается первого
вызова и получает его ответ (и поток), списание одно. Тот же ключ с другим телом — `409
idempotency_key_conflict`. Ответ под ключом хранится сутки.

```python tab="Python"
chat = client.chat.completions.create(
    model="gpt-5-nano",
    messages=[{"role": "user", "content": "Привет!"}],
    extra_headers={"Idempotency-Key": "order-1842-summary"},
)
```

Вход длиннее, чем принимает модель, или слишком большой `max_tokens` — `400` с полем в
`detail[]` (`messages` с `reason: "too_long"` или `max_tokens`); тело больше, чем принимает
любой канал модели, — `messages` с `reason: "too_large"`; пустой `messages` — тоже с полем.

Ответ, остановленный фильтром содержания (`finish_reason: "content_filter"`), несёт в
`refusal` нашу фразу, а не текст фильтра; в потоке `refusal` приходит одним куском в чанке
с `finish_reason`.

> [!TIP]
> Постоянный `routing_options.session_id` закрепляет успешный канал за диалогом на 10 минут
> ([Каналы](https://souz.ai/docs/channels.md#диалог-чата-держится-канала)).

---

Оглавление документации: https://souz.ai/llms.txt. Всё руководство одним файлом: https://souz.ai/llms-full.txt. Справочник методов: https://souz.ai/docs/api-reference.md.
