채팅·응답
POST /v1/chat/completions — OpenAI·Anthropic·Gemini 를 같은 형식으로 부릅니다.
요청 보내기
OpenAI Chat Completions 와 같은 요청·응답 형식입니다. model 만 바꾸면 OpenAI·Anthropic·Gemini 를 같은 코드로 호출합니다. baseURL 은 https://ai.cubion.kr/v1, 인증은 Authorization: Bearer sk-proj-… 헤더 하나입니다(키 발급과 첫 호출 참고).
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":"안녕"}]}'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)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 -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}'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)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 ?? "");
}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 계열
쓸 수 있는 모델은 바뀔 수 있으니 목록을 앱에 박아 두지 말고 모델 목록 (GET /v1/models)으로 채우세요. 그 목록에 없는 모델을 부르면 403 model_not_allowed 가 옵니다.
응답 형태
스트리밍이 아닐 때는 모델의 응답이 그대로 옵니다. 답 글자는 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 }
}