Skip to Content

Chat Completions

OpenAI 호환 채팅 완성 엔드포인트입니다. Gateway에서 텍스트 모델을 호출할 때 쓰는 기본 경로입니다.

POST https://gw.letsur.ai/v1/chat/completions

인증

방식헤더
Gateway 키Authorization: Bearer <API_KEY>

요청

Body

필드타입필수기본값설명
modelstring모델 코드 (카탈로그)
messagesarray{role, content} 객체의 배열
temperaturenumber×10 ~ 2
top_pnumber×1nucleus sampling
max_tokensinteger×모델별응답 최대 토큰
streamboolean×falseServer-Sent Events 스트리밍
stopstring | array×응답 중단 시퀀스
ninteger×1응답 개수 (대부분 1)
toolsarray×Tool calling 정의
tool_choicestring | object×autoTool 선택 정책
response_formatobject×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 CallingOpenAI 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모델의 이전 응답 (대화 이어가기)
tooltool 호출 결과 (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_reasonstop / length / tool_calls / content_filter (finish_reason 별 의미)
usage.estimated_costLetsur가 추가한 필드로, 호출 시점 단가 기준 비용(USD)입니다 (요금)

finish_reason

응답 종료 이유를 나타냅니다. 같은 200 OK라도 값에 따라 의미가 다릅니다.

의미대응
stop모델이 자연스럽게 응답을 끝냈습니다정상입니다. 그대로 사용합니다
lengthmax_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_reasoncontent_filter로 내려옵니다. 클라이언트에서 이 경우를 따로 처리해야 빈 응답이 사용자에게 그대로 노출되지 않습니다.

예시

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
첫 chunkdelta.role: "assistant"
중간 chunksdelta.content에 부분 텍스트
마지막 chunkfinish_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 에러 chunkdata: 본문이 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_found404카탈로그에 없는 모델
context_length_exceeded400프롬프트 + 응답이 모델 컨텍스트 초과
tool_format_invalid400tools 스키마 오류
vision_not_supported400이미지 입력을 지원하지 않는 모델
response_format_not_supported400JSON mode 등을 지원하지 않는 모델

한도 / 제약

  • 모든 호출에 4단계 한도 검사를 적용합니다. 한도 개요를 참고합니다
  • streaming 응답 도중 한도를 넘으면 연결이 끊기고 마지막 chunk에 에러가 들어갑니다
  • 동시 호출 수는 따로 제한하지 않고, Service / Member Limit으로 제어합니다

관련 문서

Last updated on