# 채팅·응답

> POST /v1/chat/completions — OpenAI·Anthropic·Gemini 를 같은 형식으로 부릅니다.

원문: https://ai.cubion.kr/docs/chat

## 요청 보내기

OpenAI Chat Completions 와 같은 요청·응답 형식입니다. `model` 만 바꾸면 OpenAI·Anthropic·Gemini 를 같은 코드로 호출합니다. baseURL 은 `https://ai.cubion.kr/v1`, 인증은 `Authorization: Bearer sk-proj-…` 헤더 하나입니다([키 발급과 첫 호출](https://ai.cubion.kr/docs/quickstart) 참고).

cURL:

```bash
curl https://ai.cubion.kr/v1/chat/completions \
  -H "Authorization: Bearer $GATEWAY_KEY" \
  -H "Content-Type: application/json" \
  -d '{"model":"gpt-4o","messages":[{"role":"user","content":"안녕"}]}'
```

Python:

```python
import os
from openai import OpenAI

client = OpenAI(base_url="https://ai.cubion.kr/v1", api_key=os.environ["GATEWAY_KEY"])

r = client.chat.completions.create(
    model="gpt-4o",                     # "claude-sonnet-4-5", "gemini/gemini-2.5-flash" 등
    messages=[{"role": "user", "content": "안녕"}],
)
print(r.choices[0].message.content)
```

Node.js:

```javascript
import OpenAI from "openai";

const client = new OpenAI({ baseURL: "https://ai.cubion.kr/v1", apiKey: process.env.GATEWAY_KEY });

const r = await client.chat.completions.create({
  model: "gpt-4o",                      // "claude-sonnet-4-5", "gemini/gemini-2.5-flash" 등
  messages: [{ role: "user", content: "안녕" }],
});
console.log(r.choices[0].message.content);
```

## 요청 본문 필드

자주 쓰는 것만 추리면 다음과 같습니다. 나머지 필드는 손대지 않고 모델 쪽으로 그대로 전달됩니다.

| 필드 | 필수 | 설명 |
| --- | --- | --- |
| `model` | 필수 | 부를 모델 이름. `gpt-4o` · `claude-sonnet-4-5` · `gemini/gemini-2.5-flash` 처럼 적습니다. 빠지면 `400 missing model` 입니다. |
| `messages` | 필수 | 대화 내용. `role`(`system` · `user` · `assistant`)과 `content` 를 가진 객체의 배열입니다. 지난 대화를 이어 가려면 앞선 주고받음을 그대로 다시 실어 보냅니다. |
| `stream` | 선택 | `true` 면 답이 완성되기를 기다리지 않고 오는 대로 흘려보냅니다. 기본값은 `false`. |
| `temperature` | 선택 | 답의 들쭉날쭉함. 낮을수록 일정하고 높을수록 다양해집니다. `temperature` 를 받지 않는 추론형 모델에는 게이트웨이가 이 값을 빼고 다시 보냅니다. |
| `max_tokens` | 선택 | 답 길이의 상한. 안 보내면 모델 기본값을 씁니다. 짧게 끊어 두면 그만큼 요금도 줄어듭니다. |

요금은 보낸 토큰과 받은 토큰을 합쳐 계산됩니다. 대화가 길어질수록 매 요청에 실리는 지난 대화도 같이 계산되니, 필요 없는 앞부분은 잘라서 보내세요.

## 스트리밍

`stream: true` 를 붙이면 긴 답변을 기다리지 않고 글자가 오는 대로 보여 줄 수 있습니다. 응답은 JSON 한 덩어리가 아니라 SSE(`text/event-stream`)로 옵니다.

cURL:

```bash
curl -N https://ai.cubion.kr/v1/chat/completions \
  -H "Authorization: Bearer $GATEWAY_KEY" \
  -H "Content-Type: application/json" \
  -d '{"model":"gpt-4o","messages":[{"role":"user","content":"안녕"}],"stream":true}'
```

Python:

```python
stream = client.chat.completions.create(
    model="claude-sonnet-4-5",
    messages=[{"role": "user", "content": "긴 글을 써 줘"}],
    stream=True,
)
for chunk in stream:
    print(chunk.choices[0].delta.content or "", end="", flush=True)
```

Node.js:

```javascript
const stream = await client.chat.completions.create({
  model: "claude-sonnet-4-5",
  messages: [{ role: "user", content: "긴 글을 써 줘" }],
  stream: true,
});
for await (const chunk of stream) {
  process.stdout.write(chunk.choices[0]?.delta?.content ?? "");
}
```

**응답 (SSE)**

```
data: {"object":"chat.completion.chunk","choices":[{"delta":{"content":"안"}}]}

data: {"object":"chat.completion.chunk","choices":[{"delta":{"content":"녕"}}]}

data: [DONE]
```

`data:` 로 시작하는 줄이 이어지다 `data: [DONE]` 으로 끝납니다. SDK 를 쓰면 이 파싱은 SDK 가 합니다.

> **스트리밍이어도 요금은 똑같이 집계됩니다**
> 중간에 연결을 끊어도 그때까지 쓴 만큼은 차감됩니다. 사용자가 화면을 닫았다고 해서 요금이 사라지지는 않으니, 필요 없어진 요청은 아예 보내지 않는 편이 낫습니다.

## 모델 고르기

모델 이름은 `model` 문자열 하나로 결정됩니다. 코드는 그대로 두고 이름만 바꾸면 다른 회사 모델로 넘어갑니다.

- `gpt-4o` — OpenAI 계열
- `claude-sonnet-4-5` — Anthropic 계열
- `gemini/gemini-2.5-flash` — Gemini 계열

쓸 수 있는 모델은 바뀔 수 있으니 목록을 앱에 박아 두지 말고 [모델 목록](https://ai.cubion.kr/docs/models) (`GET /v1/models`)으로 채우세요. 그 목록에 없는 모델을 부르면 [403 model_not_allowed](https://ai.cubion.kr/docs/errors) 가 옵니다.

## 응답 형태

스트리밍이 아닐 때는 모델의 응답이 그대로 옵니다. 답 글자는 `choices[0].message.content` 에 있습니다.

**응답**

```
{
  "id": "chatcmpl-...",
  "object": "chat.completion",
  "model": "gpt-4o",
  "choices": [
    {
      "index": 0,
      "message": { "role": "assistant", "content": "안녕하세요! 무엇을 도와드릴까요?" },
      "finish_reason": "stop"
    }
  ],
  "usage": { "prompt_tokens": 9, "completion_tokens": 12, "total_tokens": 21 }
}
```

- `finish_reason` 이 `length` 면 `max_tokens` 에 걸려 답이 잘린 것입니다.
- `usage` 는 이번 요청에 쓴 토큰 수입니다. 차감된 금액은 응답에 실리지 않고 [사용량](https://ai.cubion.kr/app/usage) 에서 확인합니다.
- 요청이 막혔을 때의 본문 모양(`error` · `message` · `topup_url`)은 [오류 처리](https://ai.cubion.kr/docs/errors) 에 정리해 두었습니다.
