[SGLang] OpenAI 호환 API: Chat, Completions, Embedding 엔드포인트 구현
들어가며
SGLang은 자체 /generate API 외에 OpenAI 호환 API를 제공한다. /v1/chat/completions, /v1/completions, /v1/embeddings 세 엔드포인트를 지원하므로, 기존 OpenAI SDK 코드를 그대로 사용해 SGLang 서버로 요청을 보낼 수 있다.
이 글에서는 python/sglang/srt/entrypoints/openai/ 디렉토리의 핵심 파일들을 분석한다. OpenAI 형식의 요청이 SGLang 내부의 GenerateReqInput으로 변환되는 과정, 스트리밍 응답 구현, 그리고 SGLang만의 확장 파라미터를 살펴본다.
OpenAI API 호환 구조도
요청이 들어오면 각 엔드포인트 핸들러가 OpenAI 형식을 SGLang 내부 형식으로 변환한 뒤 TokenizerManager에 전달한다.
Client (OpenAI SDK)
|
| POST /v1/chat/completions
| POST /v1/completions
| POST /v1/embeddings
v
+-------------------------------------------+
| FastAPI Router |
+-------------------------------------------+
| | |
v v v
+----------+ +-----------+ +----------+
| Serving | | Serving | | Serving |
| Chat | | Completion| | Embedding|
+----------+ +-----------+ +----------+
| | |
| _convert_to_internal_request()
v v v
+-------------------------------------------+
| protocol.py (Pydantic models) |
| ChatCompletionRequest -> GenerateReqInput|
| CompletionRequest -> GenerateReqInput|
| EmbeddingRequest -> EmbeddingReqInput|
+-------------------------------------------+
|
v
+-------------------------------------------+
| TokenizerManager.generate_request() |
| -> Scheduler -> Model Execution |
+-------------------------------------------+
|
v
Response (streaming SSE / JSON)
핵심 코드 분석
OpenAIServingBase: 공통 추상 클래스
python/sglang/srt/entrypoints/openai/serving_base.py에 정의된 OpenAIServingBase는 세 엔드포인트의 공통 로직을 담은 추상 클래스다. Template Method 패턴으로 요청 처리 흐름을 정의한다.
class OpenAIServingBase(ABC):
async def handle_request(
self, request: OpenAIServingRequest, raw_request: Request
) -> Union[Any, StreamingResponse, ErrorResponse]:
received_time = monotonic_time()
try:
error_msg = self._validate_request(request)
if error_msg:
return self.create_error_response(error_msg)
adapted_request, processed_request = self._convert_to_internal_request(
request, raw_request
)
if isinstance(adapted_request, (GenerateReqInput, EmbeddingReqInput)):
adapted_request.received_time = received_time
if hasattr(request, "stream") and request.stream:
return await self._handle_streaming_request(
adapted_request, processed_request, raw_request
)
else:
return await self._handle_non_streaming_request(
adapted_request, processed_request, raw_request
)
except HTTPException as e:
return self.create_error_response(
message=e.detail, err_type=str(e.status_code), status_code=e.status_code
)
핵심은 _validate_request -> _convert_to_internal_request -> streaming/non-streaming 분기의 3단계 파이프라인이다. 각 서브클래스는 이 추상 메서드를 구현한다.
LoRA adapter 지원도 base에서 처리한다. model 파라미터에 base-model:adapter-name 형식을 사용하면 자동으로 파싱된다.
def _parse_model_parameter(self, model: str) -> Tuple[str, Optional[str]]:
if ":" not in model:
return model, None
parts = model.split(":", 1)
base_model = parts[0].strip()
adapter_name = parts[1].strip() or None
return base_model, adapter_name
ServingChat: Chat Completions 엔드포인트
python/sglang/srt/entrypoints/openai/serving_chat.py의 OpenAIServingChat은 /v1/chat/completions를 담당한다. 가장 복잡한 핸들러로, chat template 적용, tool calling, reasoning mode, multimodal 입력을 모두 처리한다.
_convert_to_internal_request 메서드에서 OpenAI 형식 메시지를 내부 형식으로 변환한다.
def _convert_to_internal_request(
self, request: ChatCompletionRequest, raw_request: Request = None,
) -> tuple[GenerateReqInput, ChatCompletionRequest]:
is_multimodal = self.tokenizer_manager.model_config.is_multimodal
processed_messages = self._process_messages(request, is_multimodal)
sampling_params = request.to_sampling_params(
stop=processed_messages.stop,
model_generation_config=self.default_sampling_params,
tool_call_constraint=processed_messages.tool_call_constraint,
)
adapted_request = GenerateReqInput(
**prompt_kwargs,
image_data=processed_messages.image_data,
video_data=processed_messages.video_data,
audio_data=processed_messages.audio_data,
sampling_params=sampling_params,
return_logprob=request.logprobs,
stream=request.stream,
# ... 추가 파라미터
)
return adapted_request, request
Chat template 적용은 두 경로로 나뉜다. HuggingFace tokenizer의 Jinja template이 있으면 apply_chat_template()을 호출하고, 없으면 SGLang 내장 conversation template을 사용한다. DeepSeek V3의 경우 별도의 DSv32 encoding 경로를 탄다.
continue_final_message 기능은 SGLang 확장 기능이다. 마지막 메시지가 assistant이면 해당 내용을 prefix로 분리하여 이어서 생성하도록 한다.
ServingCompletions: Text Completions 엔드포인트
python/sglang/srt/entrypoints/openai/serving_completions.py의 OpenAIServingCompletion은 /v1/completions를 담당한다. Chat과 달리 chat template 적용 없이 raw prompt를 그대로 처리한다.
스트리밍 응답 구현에서 주목할 점은 첫 번째 chunk를 미리 소비(kick-start)하여 validation 에러를 HTTP 200 전에 감지하는 패턴이다.
async def _handle_streaming_request(self, adapted_request, request, raw_request):
generator = self._generate_completion_stream(adapted_request, request, raw_request)
try:
first_chunk = await generator.__anext__()
except ValueError as e:
return self.create_error_response(str(e))
async def prepend_first_chunk():
yield first_chunk
async for chunk in generator:
yield chunk
return StreamingResponse(
prepend_first_chunk(),
media_type="text/event-stream",
background=self.tokenizer_manager.create_abort_task(adapted_request),
)
이 패턴은 Chat 핸들러에서도 동일하게 사용된다. 스트리밍 응답이 시작된 뒤에는 HTTP 상태 코드를 변경할 수 없으므로, 첫 chunk를 먼저 생성해보고 에러가 발생하면 일반 에러 응답을 반환한다.
ServingEmbedding: Embeddings 엔드포인트
python/sglang/srt/entrypoints/openai/serving_embedding.py의 OpenAIServingEmbedding은 /v1/embeddings를 담당한다. 다른 핸들러와 달리 EmbeddingReqInput으로 변환하며 스트리밍을 지원하지 않는다.
class OpenAIServingEmbedding(OpenAIServingBase):
def _convert_to_internal_request(
self, request: EmbeddingRequest, raw_request: Request = None,
) -> tuple[EmbeddingReqInput, EmbeddingRequest]:
prompt = request.input
if isinstance(prompt, str):
prompt_kwargs = {"text": prompt}
elif isinstance(prompt, list):
if len(prompt) > 0 and isinstance(prompt[0], str):
prompt_kwargs = {"text": prompt}
elif len(prompt) > 0 and isinstance(prompt[0], MultimodalEmbeddingInput):
# Multimodal embedding 처리...
else:
prompt_kwargs = {"input_ids": prompt}
adapted_request = EmbeddingReqInput(
**prompt_kwargs,
dimensions=request.dimensions,
lora_path=lora_path,
embed_override_token_id=request.embed_override_token_id,
embed_overrides=embed_overrides,
)
return adapted_request, request
Multimodal embedding 입력도 지원한다. MultimodalEmbeddingInput으로 text, image, video를 함께 전달할 수 있으며, chat template이 설정되어 있으면 대화 형식으로 변환한 뒤 임베딩을 추출한다.
프로토콜 변환: protocol.py
python/sglang/srt/entrypoints/openai/protocol.py에는 모든 요청/응답의 Pydantic 모델이 정의되어 있다. ChatCompletionRequest의 to_sampling_params() 메서드가 핵심이다.
def to_sampling_params(self, stop, model_generation_config, tool_call_constraint=None):
def get_param(param_name: str):
value = getattr(self, param_name)
if value is None:
return model_generation_config.get(
param_name, self._DEFAULT_SAMPLING_PARAMS[param_name]
)
return value
sampling_params = {
"temperature": get_param("temperature"),
"max_new_tokens": self.max_completion_tokens or self.max_tokens,
"top_p": get_param("top_p"),
"top_k": get_param("top_k"),
"min_p": get_param("min_p"),
"repetition_penalty": get_param("repetition_penalty"),
# ...
}
파라미터 우선순위는 사용자 지정값 > 모델 generation_config > OpenAI 기본값 순이다. 모델마다 다른 기본 sampling 설정을 generation_config에서 읽어 적용할 수 있다.
OpenAIServingRequest 타입은 세 요청 타입의 Union으로 정의된다:
OpenAIServingRequest = Union[
ChatCompletionRequest,
CompletionRequest,
EmbeddingRequest,
# ...
]
기존 OpenAI API 대비 확장점
SGLang은 OpenAI API를 완전히 호환하면서 독자적인 파라미터를 추가했다. 아래 표는 주요 확장 기능을 정리한 것이다.
| 기능 | OpenAI 표준 | SGLang 확장 |
|---|---|---|
| Constrained decoding | response_format (json_schema, json_object) |
regex, ebnf, structural_tag 추가 지원 |
| Sampling | temperature, top_p |
top_k, min_p, repetition_penalty 추가 |
| Stop 조건 | stop (문자열/리스트) |
stop_token_ids, stop_regex, no_stop_trim, ignore_eos 추가 |
| LoRA | 미지원 | model 필드에 base:adapter 구문 또는 lora_path 파라미터 |
| Reasoning | reasoning_effort |
separate_reasoning, stream_reasoning, chat_template_kwargs |
| Data Parallel 라우팅 | 미지원 | routed_dp_rank, X-Data-Parallel-Rank 헤더 |
| Prefill-Decode 분리 | 미지원 | bootstrap_host/port/room, disagg_prefill_dp_rank |
| 캐시 제어 | 미지원 | cache_salt, extra_key |
| Hidden states | 미지원 | return_hidden_states |
| Expert 라우팅 정보 | 미지원 | return_routed_experts |
| 어시스턴트 이어쓰기 | 미지원 | continue_final_message |
| Embedding override | 미지원 | embed_override_token_id, embed_overrides |
| 요청 우선순위 | 미지원 | priority |
이 확장 파라미터들은 sglext 필드로 응답에도 반영된다. SglExt 모델은 routed_experts, cached_tokens_details 등 SGLang 전용 응답 데이터를 담으며, None인 필드는 직렬화에서 자동 제외되어 표준 OpenAI 응답과 호환성을 유지한다.
관련 포스트
- SGLang flush_cache API에 timeout 파라미터 추가
- SGLang CUDA IPC Multimodal Transfer Caching Pool Handles 최적화
참고
관련 포스트
SGLang 의 다른글
- 이전글 [SGLang] Engine: 멀티프로세스 오케스트레이터의 설계와 구현
- 현재글 : [SGLang] OpenAI 호환 API: Chat, Completions, Embedding 엔드포인트 구현
- 다음글 [SGLang] Anthropic/Ollama 호환 API: 멀티 프로토콜 LLM 서빙
댓글