# API Chat Completions

> POST /v1/chat/completions в OpenCheese: примеры на curl, Python и JavaScript — простые запросы, потоковый ответ, инструменты, изображения и уровень рассуждений.
>
> Веб-страница: https://opencheese.dev/ru/docs/chat-completions/ · Все страницы документации: https://opencheese.dev/ru/docs.md

`POST https://api.opencheese.dev/v1/chat/completions` — это OpenAI Chat Completions API. Его
используют SDK OpenAI, OpenCode, Zed, Harness AI и большинство чат-приложений.

## Первый запрос

```bash
curl https://api.opencheese.dev/v1/chat/completions \
  -H "Authorization: Bearer $OPENCHEESE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"model": "MODEL_ID", "messages": [{"role": "system", "content": "Answer in one sentence."}, {"role": "user", "content": "What is a gateway?"}]}'
```

Поля `model` и `messages` обязательны. ID модели возьмите из
[`GET /v1/models`](https://opencheese.dev/ru/docs/models.md).

Ответ приходит в привычном формате:

```json
{
  "id": "chatcmpl-abc123",
  "object": "chat.completion",
  "model": "MODEL_ID",
  "choices": [
    {
      "index": 0,
      "message": { "role": "assistant", "content": "A gateway passes your requests on to the models behind it." },
      "finish_reason": "stop"
    }
  ],
  "usage": { "prompt_tokens": 24, "completion_tokens": 13, "total_tokens": 37 }
}
```

## Параметры

| Поле | Примечания |
| --- | --- |
| `model` | Обязательное. Значение `id` из `/v1/models`. |
| `messages` | Обязательное. Сообщения с ролями `system`, `user`, `assistant` и `tool`. |
| `stream` | `true`, чтобы получить ответ в виде server-sent events. |
| `tools`, `tool_choice` | Вызов функций, если модель поддерживает инструменты. |
| `reasoning_effort` | Один из уровней в `supported_reasoning_levels` модели. |
| `response_format` | Структурированный вывод, если модель его поддерживает. |
| `temperature`, `top_p`, `max_tokens` | Необязательные. Если их не задать, используются значения модели по умолчанию. |

## Потоковый ответ

```bash
curl -N https://api.opencheese.dev/v1/chat/completions \
  -H "Authorization: Bearer $OPENCHEESE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"model": "MODEL_ID", "stream": true, "messages": [{"role": "user", "content": "Count to three."}]}'
```

Каждое событие — это `chat.completion.chunk`; текст приходит в `choices[0].delta.content`.
Ближе к концу приходят `finish_reason` и `usage`, а завершается поток строкой `data: [DONE]`:

```text
data: {"object":"chat.completion.chunk","choices":[{"index":0,"delta":{"role":"assistant","content":"One"},"finish_reason":null}]}

data: {"object":"chat.completion.chunk","choices":[{"index":0,"delta":{"content":", two, three."},"finish_reason":null}]}

data: {"object":"chat.completion.chunk","choices":[{"index":0,"delta":{},"finish_reason":"stop"}]}

data: {"object":"chat.completion.chunk","choices":[],"usage":{"prompt_tokens":12,"completion_tokens":6,"total_tokens":18}}

data: [DONE]
```

## Вызовы инструментов

```bash
curl https://api.opencheese.dev/v1/chat/completions \
  -H "Authorization: Bearer $OPENCHEESE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"model": "MODEL_ID", "messages": [{"role": "user", "content": "What is the weather in Paris?"}], "tools": [{"type": "function", "function": {"name": "get_weather", "description": "Current weather for a city", "parameters": {"type": "object", "properties": {"city": {"type": "string"}}, "required": ["city"]}}}]}'
```

Когда модель вызывает инструмент, `finish_reason` в ответе равен `tool_calls`, а сам вызов находится
в `message.tool_calls`. Выполните инструмент и отправьте историю обратно, добавив сообщение
`tool` с результатом:

```json
{
  "model": "MODEL_ID",
  "messages": [
    { "role": "user", "content": "What is the weather in Paris?" },
    {
      "role": "assistant",
      "content": null,
      "tool_calls": [{ "id": "call_1", "type": "function", "function": { "name": "get_weather", "arguments": "{\"city\":\"Paris\"}" } }]
    },
    { "role": "tool", "tool_call_id": "call_1", "content": "18 degrees, sunny" }
  ]
}
```

Ответ, обрезанный лимитом вывода посреди вызова инструмента, завершается с
`finish_reason: "length"`: считайте такой вызов неполным и не выполняйте его.

## Изображения в промпте

Модели, у которых `modalities.input` включает `image`, понимают изображения в частях
`image_url` — с адресом `https://` или URL вида `data:`:

```json
{
  "model": "MODEL_ID",
  "messages": [
    {
      "role": "user",
      "content": [
        { "type": "text", "text": "What is in this picture?" },
        { "type": "image_url", "image_url": { "url": "data:image/png;base64,iVBORw0KGgo..." } }
      ]
    }
  ]
}
```

## Уровень рассуждений

Задайте в `reasoning_effort` один из уровней, которые модель перечисляет в
`supported_reasoning_levels` (также принимаются `reasoning.effort` и `output_config.effort` из
API Anthropic). Если уровень не задан, используется уровень модели по умолчанию.

```bash
curl https://api.opencheese.dev/v1/chat/completions \
  -H "Authorization: Bearer $OPENCHEESE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"model": "MODEL_ID", "reasoning_effort": "high", "messages": [{"role": "user", "content": "Plan a three-day trip to Lyon."}]}'
```

Уровень, которого у модели нет, отклоняется с ошибкой `unsupported_reasoning_effort`, и в ошибке
перечислены уровни, которые модель поддерживает. Текст рассуждений, если модель его возвращает,
приходит в `message.reasoning_content` (в потоковом ответе — в `delta.reasoning_content`).

## Модели изображений в чате

Выберите модель изображений (у неё `modalities.output` равно `image`), и последнее сообщение
пользователя станет промптом для изображения. Готовое изображение приходит в
`message.images` как URL вида `data:`, а изображение, отправленное в разговоре, модель редактирует,
если умеет. Модели изображений возвращают одно изображение на запрос; все параметры
изображений доступны в [API изображений](https://opencheese.dev/ru/docs/images.md).

## Ошибки, которые приходят как ответ

Проблемы, которые требуют ваших действий (например, недействительный ключ, исчерпанный тариф
или пустой баланс), возвращаются как сообщение ассистента в ответе `200`, переданном потоком
`text/event-stream`, даже если `stream` равен `false`: так чат-приложения покажут их вам. Все
остальные ошибки приходят как обычный HTTP-статус с телом в JSON. Прежде чем разбирать ответ как
JSON, проверьте его `Content-Type`; подробнее — на странице
[Ошибки и лимиты запросов](https://opencheese.dev/ru/docs/errors.md).

## SDK

Python:

```python
from openai import OpenAI

client = OpenAI(base_url="https://api.opencheese.dev/v1", api_key="YOUR_OPENCHEESE_API_KEY")
stream = client.chat.completions.create(
    model="MODEL_ID",
    messages=[{"role": "user", "content": "Write a haiku about cheese."}],
    stream=True,
)
for chunk in stream:
    if chunk.choices and chunk.choices[0].delta.content:
        print(chunk.choices[0].delta.content, end="")
```

JavaScript:

```javascript
import OpenAI from "openai";

const client = new OpenAI({ baseURL: "https://api.opencheese.dev/v1", apiKey: process.env.OPENCHEESE_API_KEY });
const reply = await client.chat.completions.create({
  model: "MODEL_ID",
  messages: [{ role: "user", content: "Write a haiku about cheese." }]
});
console.log(reply.choices[0].message.content);
```

Чтобы запросы одного разговора отображались в панели вместе, добавьте заголовок
[`opencheese-session-uuid`](https://opencheese.dev/ru/docs/sessions.md).
