Chat Completions
OpenAI 호환 채팅 완성 엔드포인트입니다. Gateway에서 텍스트 모델을 호출할 때 쓰는 기본 경로입니다.
POST https://gw.letsur.ai/v1/chat/completions인증
| 방식 | 헤더 |
|---|---|
| Gateway 키 | Authorization: Bearer <API_KEY> |
요청
Body
| 필드 | 타입 | 필수 | 기본값 | 설명 |
|---|---|---|---|---|
model | string | ○ | — | 모델 코드 (카탈로그) |
messages | array | ○ | — | {role, content} 객체의 배열 |
temperature | number | × | 1 | 0 ~ 2 |
top_p | number | × | 1 | nucleus sampling |
max_tokens | integer | × | 모델별 | 응답 최대 토큰 |
stream | boolean | × | false | Server-Sent Events 스트리밍 |
stop | string | array | × | — | 응답 중단 시퀀스 |
n | integer | × | 1 | 응답 개수 (대부분 1) |
tools | array | × | — | Tool calling 정의 |
tool_choice | string | object | × | auto | Tool 선택 정책 |
response_format | object | × | — | JSON mode 등 |
모델별 지원 파라미터
| 기능 | 지원 모델 |
|---|---|
| Vision (이미지 입력) | OpenAI gpt-5.1·gpt-4o, Anthropic claude-sonnet-4-6·claude-haiku-4-5-20251001, Google gemini-2.5-pro·gemini-2.5-flash |
| Tool Calling | OpenAI gpt-5.1·gpt-4o, Anthropic claude-sonnet-4-6, Google gemini-2.5-pro |
JSON mode (response_format) | OpenAI gpt-4o·gpt-4o-mini |
| streaming | 거의 모든 모델 (모델별 일부 차이) |
정확한 지원 여부는 카탈로그의 모델 상세에서 확인합니다. 모델이 지원하지 않는 파라미터를 보내면 Gateway가 400 invalid_parameter를 반환합니다.
messages 배열
role | 의미 |
|---|---|
system | 시스템 프롬프트 (모델 행동 지정) |
user | 사용자 입력 |
assistant | 모델의 이전 응답 (대화 이어가기) |
tool | tool 호출 결과 (tool calling 시) |
content에는 텍스트 문자열이나 배열을 넣습니다. 이미지를 함께 보낼 때는 배열을 씁니다. 자세한 내용은 이미지 입력에서 확인합니다.
응답 (200)
{
"id": "chatcmpl-...",
"object": "chat.completion",
"created": 1730000000,
"model": "claude-sonnet-4-6",
"choices": [
{
"index": 0,
"message": { "role": "assistant", "content": "..." },
"finish_reason": "stop"
}
],
"usage": {
"prompt_tokens": 12,
"completion_tokens": 48,
"total_tokens": 60,
"estimated_cost": 0.000324
}
}| 필드 | 의미 |
|---|---|
id | 호출 식별자 (Langfuse 추적 ID와 매칭) |
model | 실제 응답한 모델 (요청한 모델과 일치, 우회 시 다를 수 있음) |
choices[0].message.content | 모델 응답 본문 |
choices[0].finish_reason | stop / length / tool_calls / content_filter (finish_reason 별 의미) |
usage.estimated_cost | Letsur가 추가한 필드로, 호출 시점 단가 기준 비용(USD)입니다 (요금) |
finish_reason
응답 종료 이유를 나타냅니다. 같은 200 OK라도 값에 따라 의미가 다릅니다.
| 값 | 의미 | 대응 |
|---|---|---|
stop | 모델이 자연스럽게 응답을 끝냈습니다 | 정상입니다. 그대로 사용합니다 |
length | max_tokens 또는 모델 컨텍스트 한도에 걸려 잘렸습니다 | max_tokens을 키우거나, 같은 프롬프트로 이어 받습니다 (대화 컨텍스트에 부분 응답을 포함해 재요청) |
tool_calls | 모델이 tool 호출을 요청했습니다. 응답 본문 대신 tool_calls 필드를 봅니다 | Tool Calling 흐름으로 진입합니다 |
content_filter | 공급사 안전 필터가 응답을 차단했습니다 | 사용자 입력의 어느 부분이 걸렸는지 검토합니다. 같은 프롬프트로 다시 보내면 결과도 같으니, 프롬프트나 시스템 메시지를 고친 뒤 재요청합니다 |
content_filter는 OpenAI·Anthropic·Google 모두 4xx 에러가 아니라 200 OK로 응답합니다. 이때 choices[0].message.content는 비거나 짧고, finish_reason은 content_filter로 내려옵니다. 클라이언트에서 이 경우를 따로 처리해야 빈 응답이 사용자에게 그대로 노출되지 않습니다.
예시
cURL
curl https://gw.letsur.ai/v1/chat/completions \
-H "Authorization: Bearer <API_KEY>" \
-H "Content-Type: application/json" \
-d '{
"model": "claude-sonnet-4-6",
"messages": [{"role": "user", "content": "안녕"}]
}'스트리밍
stream: true로 요청하면 Gateway가 응답을 Server-Sent Events(SSE)로 나눠서 보냅니다.
curl https://gw.letsur.ai/v1/chat/completions \
-H "Authorization: Bearer <API_KEY>" \
-H "Content-Type: application/json" \
-d '{
"model": "claude-sonnet-4-6",
"messages": [{"role": "user", "content": "안녕"}],
"stream": true
}'각 chunk는 다음 형태로 들어옵니다.
data: {"id":"chatcmpl-...","object":"chat.completion.chunk","choices":[{"index":0,"delta":{"content":"안"},"finish_reason":null}]}
data: {"id":"chatcmpl-...","object":"chat.completion.chunk","choices":[{"index":0,"delta":{"content":"녕"},"finish_reason":null}]}
...
data: {"id":"chatcmpl-...","object":"chat.completion.chunk","choices":[{"index":0,"delta":{},"finish_reason":"stop"}],"usage":{"prompt_tokens":12,"completion_tokens":48,"total_tokens":60,"estimated_cost":0.000324}}
data: [DONE]| 시점 | chunk |
|---|---|
| 첫 chunk | delta.role: "assistant" |
| 중간 chunks | delta.content에 부분 텍스트 |
| 마지막 chunk | finish_reason + 누적 usage (estimated_cost 포함) |
| 종료 신호 | data: [DONE] |
누적 usage는 마지막 chunk에만 들어갑니다. 중간 chunk만 보고 사용량을 집계하면 0이 나옵니다. 비용을 추적하려면 마지막 chunk까지 받습니다.
스트리밍 끊김 처리
스트리밍 응답은 네트워크나 공급사 문제로 마지막 chunk와 [DONE]을 받기 전에 끊기기도 합니다. 이때 클라이언트가 챙길 부분은 다음과 같습니다.
| 상황 | 알아내는 방법 | 대응 |
|---|---|---|
[DONE] 없이 연결 종료 | iterator가 StopIteration을 내거나 SSE 연결이 닫힙니다 | 받은 텍스트를 부분 응답으로 다룹니다. 끝까지 받았는지는 마지막 chunk의 finish_reason이 들어왔는지로 확인합니다 |
usage chunk를 못 받음 | 마지막으로 받은 chunk에 usage가 비어 있습니다 | 클라이언트에서는 그 호출의 사용량이 0으로 보입니다. 하지만 Gateway가 서버에서 사용량과 비용을 집계하므로 사용량 분석에는 정상으로 잡힙니다 |
| 중간에 4xx / 5xx 에러 chunk | data: 본문이 OpenAI 에러 객체 형태입니다 | 일반 에러 응답과 똑같이 처리합니다. 에러 코드를 참고합니다 |
스트리밍 클라이언트는 finish_reason이 한 번이라도 채워졌는지를 완료 신호로 봅니다. 부분 응답을 사용자에게 보여주기 전에 이 값을 확인하면 잘린 출력이 노출되는 일을 줄입니다.
이미지 입력
지원 모델에서는 이미지를 입력으로 보낼 수 있습니다.
client.chat.completions.create(
model="gpt-4o",
messages=[{
"role": "user",
"content": [
{"type": "text", "text": "이 이미지를 설명해 줘"},
{"type": "image_url", "image_url": {"url": "https://.../image.png"}},
],
}],
)| 입력 형식 | 값 |
|---|---|
| URL | {"type": "image_url", "image_url": {"url": "https://..."}} |
| Base64 | {"type": "image_url", "image_url": {"url": "data:image/png;base64,..."}} |
이미지 크기와 해상도 제한은 모델마다 다릅니다. 카탈로그의 모델 상세에서 확인합니다.
Tool Calling
모델이 외부 함수를 호출하게 하려면 tools에 함수를 정의해 보냅니다.
tools = [{
"type": "function",
"function": {
"name": "get_weather",
"description": "도시의 현재 날씨를 가져옵니다.",
"parameters": {
"type": "object",
"properties": {"city": {"type": "string"}},
"required": ["city"],
},
},
}]
response = client.chat.completions.create(
model="gpt-4o",
messages=[{"role": "user", "content": "서울 날씨 알려 줘"}],
tools=tools,
tool_choice="auto",
)tool 호출 정보는 응답의 choices[0].message.tool_calls에 들어 있습니다. 직접 tool을 실행한 뒤 그 결과를 role: "tool" 메시지로 다시 보냅니다.
tool_choice | 의미 |
|---|---|
"auto" (기본) | 모델이 판단합니다 |
"none" | tool을 호출하지 않습니다 |
"required" | 반드시 tool을 호출합니다 |
{"type": "function", "function": {"name": "..."}} | 특정 tool을 강제합니다 |
tool calling 지원 여부는 모델마다 다릅니다. Anthropic·Google 모델은 Gateway가 OpenAI 형식으로 변환해 처리합니다.
에러 (이 엔드포인트 고유)
이 엔드포인트에만 있는 에러를 표시합니다. 인증·한도 같은 공통 에러는 에러 코드에서 확인합니다.
| 코드 | HTTP | 의미 |
|---|---|---|
model_not_found | 404 | 카탈로그에 없는 모델 |
context_length_exceeded | 400 | 프롬프트 + 응답이 모델 컨텍스트 초과 |
tool_format_invalid | 400 | tools 스키마 오류 |
vision_not_supported | 400 | 이미지 입력을 지원하지 않는 모델 |
response_format_not_supported | 400 | JSON mode 등을 지원하지 않는 모델 |
한도 / 제약
- 모든 호출에 4단계 한도 검사를 적용합니다. 한도 개요를 참고합니다
- streaming 응답 도중 한도를 넘으면 연결이 끊기고 마지막 chunk에 에러가 들어갑니다
- 동시 호출 수는 따로 제한하지 않고, Service / Member Limit으로 제어합니다