OpenAI 호환 API - Completions

OpenAI 호환 API - Completions

OpenAI 서비스에서 자체 호스팅 로컬 모델로 옮길 때, API 형태가 같으면 전환이 훨씬 매끄러워요. SGLang은 OpenAI 호환 API를 제공해서 그 전환을 쉽게 만들어 줍니다. 이 튜토리얼은 그중에서도 가장 많이 쓰는 chat/completionscompletions 두 API를 다룹니다.

출처: 공식 문서 - OpenAI APIs - Completions

비전 언어 모델용 vision APIs와 임베딩 모델용 embedding APIs도 별도 튜토리얼로 확인할 수 있어요.

서버 실행 (Launch A Server)

from sglang.test.doc_patch import launch_server_cmd
from sglang.utils import wait_for_server, print_highlight, terminate_process

server_process, port = launch_server_cmd(
    "python3 -m sglang.launch_server --model-path qwen/qwen2.5-0.5b-instruct --host 0.0.0.0 --log-level warning"
)

wait_for_server(f"http://localhost:{port}")
print(f"Server started on http://localhost:{port}")

채팅 컴플리션 (Chat Completions)

사용법 (Usage)

서버는 OpenAI API를 완전히 구현합니다. Hugging Face 토크나이저에 채팅 템플릿이 있으면 이를 자동으로 적용해요. 커스텀 채팅 템플릿은 서버 실행 시 --chat-template로 지정할 수 있습니다.

import openai

client = openai.Client(base_url=f"http://127.0.0.1:{port}/v1", api_key="None")

response = client.chat.completions.create(
    model="qwen/qwen2.5-0.5b-instruct",
    messages=[
        {"role": "user", "content": "List 3 countries and their capitals."},
    ],
    temperature=0,
    max_tokens=64,
)

print_highlight(f"Response: {response}")

모델 추론/사고 지원 (Model Thinking/Reasoning Support)

일부 모델은 내부 추론·사고 과정을 지원하고, 이를 API 응답에 노출할 수 있어요. SGLang은 chat_template_kwargs 파라미터와 호환되는 리즈닝 파서를 통해 다양한 리즈닝 모델을 통일된 방식으로 지원합니다.

지원 모델과 설정 (Supported Models and Configuration)

Model Family Chat Template Parameter Reasoning Parser Notes
DeepSeek-R1 (R1, R1-0528, R1-Distill) enable_thinking --reasoning-parser deepseek-r1 표준 리즈닝 모델
DeepSeek-V3.1 thinking --reasoning-parser deepseek-v3 하이브리드 모델 (thinking/non-thinking 모드)
Qwen3 (표준) enable_thinking --reasoning-parser qwen3 하이브리드 모델 (thinking/non-thinking 모드)
Qwen3-Thinking N/A (항상 활성) --reasoning-parser qwen3-thinking 항상 리즈닝을 생성
Kimi N/A (항상 활성) --reasoning-parser kimi Kimi 생각 모델
Gpt-Oss N/A (항상 활성) --reasoning-parser gpt-oss Gpt-Oss 생각 모델

기본 사용법 (Basic Usage)

리즈닝 출력을 쓰려면:

  1. 적절한 리즈닝 파서와 함께 서버를 띄웁니다.
  2. 모델의 채팅 템플릿 기본값을 덮어써야 할 때 chat_template_kwargs에 모델별 파라미터를 넣습니다.
  3. 필요하면 separate_reasoning: False로 리즈닝을 응답 콘텐츠 안에 유지할 수도 있어요 (기본값은 True로 따로 분리).

Qwen3-Thinking 모델 참고: 이 모델들은 항상 생각 콘텐츠를 생성하며 enable_thinking 파라미터를 지원하지 않습니다. --reasoning-parser qwen3-thinking 또는 --reasoning-parser qwen3을 써서 생각 콘텐츠를 파싱하세요.

예시: Qwen3 모델

# Launch server:
# python3 -m sglang.launch_server --model Qwen/Qwen3-4B --reasoning-parser qwen3

from openai import OpenAI

client = OpenAI(
    api_key="EMPTY",
    base_url=f"http://127.0.0.1:30000/v1",
)

model = "Qwen/Qwen3-4B"
messages = [{"role": "user", "content": "How many r's are in 'strawberry'?"}]

response = client.chat.completions.create(
    model=model,
    messages=messages,
    extra_body={
        "chat_template_kwargs": {"enable_thinking": True},
        "separate_reasoning": True
    }
)

print("Reasoning:", response.choices[0].message.reasoning_content)
print("-"*100)
print("Answer:", response.choices[0].message.content)

Example Output:

Reasoning: Okay, so the user is asking how many 'r's are in the word 'strawberry'. Let me think. First, I need to make sure I have the word spelled correctly. Strawberry... S-T-R-A-W-B-E-R-R-Y. Wait, is that right? Let me break it down.

Starting with 'strawberry', let's write out the letters one by one. S, T, R, A, W, B, E, R, R, Y. Hmm, wait, that's 10 letters. Let me check again. S (1), T (2), R (3), A (4), W (5), B (6), E (7), R (8), R (9), Y (10). So the letters are S-T-R-A-W-B-E-R-R-Y.
...
Therefore, the answer should be three R's in 'strawberry'. But I need to make sure I'm not counting any other letters as R. Let me check again. S, T, R, A, W, B, E, R, R, Y. No other R's. So three in total. Yeah, that seems right.

----------------------------------------------------------------------------------------------------
Answer: The word "strawberry" contains **three** letters 'r'. Here's the breakdown:

1. **S-T-R-A-W-B-E-R-R-Y**
   - The **third letter** is 'R'.
   - The **eighth and ninth letters** are also 'R's.

Thus, the total count is **3**.

**Answer:** 3.

"enable_thinking": False로 설정하면 Qwen3의 리즈닝을 끌 수 있어요. 파라미터를 생략하면 모델의 채팅 템플릿 기본값을 따르는데, Qwen3는 기본적으로 thinking이 활성화돼 있습니다. Qwen3-Thinking 모델은 항상 리즈닝 콘텐츠를 생성하며 enable_thinking 파라미터를 지원하지 않아요.

OpenAI 채팅 컴플리션 기본값 설정 (Set a Default for OpenAI Chat Completions)

대부분의 요청이 같은 채팅 템플릿 모드를 쓰게 하려면, 서버 실행 시 기본값을 지정하면 됩니다.

python -m sglang.launch_server \
  --model-path Qwen/Qwen3-4B \
  --reasoning-parser qwen3 \
  --default-chat-template-kwargs '{"enable_thinking": false}'

--default-chat-template-kwargs는 JSON 객체를 받아 각 OpenAI 채팅 컴플리션 요청의 chat_template_kwargs에 병합합니다. enable_thinking 같은 일반 템플릿 키의 우선순위는 다음과 같아요.

  1. 요청의 chat_template_kwargs에 명시된 값
  2. --default-chat-template-kwargs의 값
  3. 모델의 채팅 템플릿 기본값

reasoning_effort는 별도의 정규화 경로를 따르므로 위 우선순위가 적용되지 않습니다. reasoning_effort는 서버 기본값이나 개별 요청 중 한쪽에서만 설정하세요.

허용되는 키는 모델마다 달라요. 예를 들어 Qwen3는 enable_thinking을 쓰고, 다른 모델 패밀리는 thinking을 쓰거나 리즈닝 토글을 노출하지 않을 수 있습니다. 리즈닝 파서는 리즈닝 토큰을 응답에서 분리하는 방법을 제어할 뿐, 그 자체로 채팅 템플릿에서 리즈닝을 활성화하지는 않습니다.

로짓 바이어스 지원 (Logit Bias Support)

SGLang은 채팅 컴플리션과 컴플리션 API 모두에서 logit_bias 파라미터를 지원합니다. 이 파라미터로 특정 토큰의 로짓에 바이어스 값을 더해 생성될 가능성을 조절할 수 있어요. 바이어스 값은 -100에서 100 사이이고:

  • 양수 값 (0~100)은 토큰이 선택될 가능성을 높입니다.
  • 음수 값 (-100~0)은 토큰이 선택될 가능성을 낮춥니다.
  • -100은 해당 토큰이 사실상 생성되지 않게 막습니다.

logit_bias는 키가 토큰 ID(문자열), 값이 바이어스 양(플로트)인 딕셔너리를 받습니다.

토큰 ID 얻기 (Getting Token IDs)

logit_bias를 효과적으로 쓰려면 바이어스를 걸 단어의 토큰 ID를 알아야 합니다.

# Get tokenizer to find token IDs
import tiktoken

# For OpenAI models, use the appropriate encoding
tokenizer = tiktoken.encoding_for_model("gpt-3.5-turbo")  # or your model

# Get token IDs for specific words
word = "sunny"
token_ids = tokenizer.encode(word)
print(f"Token IDs for '{word}': {token_ids}")

# For SGLang models, you can access the tokenizer through the client
# and get token IDs for bias

중요: logit_bias 파라미터는 실제 단어가 아니라 토큰 ID를 문자열 키로 사용합니다.

예시: DeepSeek-V3 모델

DeepSeek-V3 모델은 thinking 파라미터로 사고 모드를 지원합니다.

# Launch server:
# python3 -m sglang.launch_server --model deepseek-ai/DeepSeek-V3.1 --tp 8  --reasoning-parser deepseek-v3

from openai import OpenAI

client = OpenAI(
    api_key="EMPTY",
    base_url=f"http://127.0.0.1:30000/v1",
)

model = "deepseek-ai/DeepSeek-V3.1"
messages = [{"role": "user", "content": "How many r's are in 'strawberry'?"}]

response = client.chat.completions.create(
    model=model,
    messages=messages,
    extra_body={
        "chat_template_kwargs": {"thinking": True},
        "separate_reasoning": True
    }
)

print("Reasoning:", response.choices[0].message.reasoning_content)
print("-"*100)
print("Answer:", response.choices[0].message.content)

Example Output:

Reasoning: First, the question is: "How many r's are in 'strawberry'?"

I need to count the number of times the letter 'r' appears in the word "strawberry".

Let me write out the word: S-T-R-A-W-B-E-R-R-Y.

Now, I'll go through each letter and count the 'r's.
...
So, I have three 'r's in "strawberry".

I should double-check. The word is spelled S-T-R-A-W-B-E-R-R-Y. The letters are at positions: 3, 8, and 9 are 'r's. Yes, that's correct.

Therefore, the answer should be 3.
----------------------------------------------------------------------------------------------------
Answer: The word "strawberry" contains **3** instances of the letter "r". Here's a breakdown for clarity:

- The word is spelled: S-T-R-A-W-B-E-R-R-Y
- The "r" appears at the 3rd, 8th, and 9th positions.

DeepSeek-V3 모델은 리즈닝 출력을 제어할 때 thinking 파라미터를 씁니다 (enable_thinking이 아니에요).

# Example with logit_bias parameter
# Note: You need to get the actual token IDs from your tokenizer
# For demonstration, we'll use some example token IDs
response = client.chat.completions.create(
    model="qwen/qwen2.5-0.5b-instruct",
    messages=[
        {"role": "user", "content": "Complete this sentence: The weather today is"}
    ],
    temperature=0.7,
    max_tokens=20,
    logit_bias={
        "12345": 50,  # Increase likelihood of token ID 12345
        "67890": -50,  # Decrease likelihood of token ID 67890
        "11111": 25,  # Slightly increase likelihood of token ID 11111
    },
)

print_highlight(f"Response with logit bias: {response.choices[0].message.content}")

파라미터 (Parameters)

채팅 컴플리션 API는 OpenAI Chat Completions API의 파라미터를 받습니다. 자세한 내용은 OpenAI Chat Completions API를 참고하세요.

SGLang은 표준 API를 extra_body 파라미터로 확장해 추가 커스터마이징을 허용합니다. extra_body의 핵심 옵션 중 하나가 chat_template_kwargs인데, 채팅 템플릿 프로세서에 인자를 넘길 수 있어요.

response = client.chat.completions.create(
    model="qwen/qwen2.5-0.5b-instruct",
    messages=[
        {
            "role": "system",
            "content": "You are a knowledgeable historian who provides concise responses.",
        },
        {"role": "user", "content": "Tell me about ancient Rome"},
        {
            "role": "assistant",
            "content": "Ancient Rome was a civilization centered in Italy.",
        },
        {"role": "user", "content": "What were their major achievements?"},
    ],
    temperature=0.3,  # Lower temperature for more focused responses
    max_tokens=128,  # Reasonable length for a concise response
    top_p=0.95,  # Slightly higher for better fluency
    presence_penalty=0.2,  # Mild penalty to avoid repetition
    frequency_penalty=0.2,  # Mild penalty for more natural language
    n=1,  # Single response is usually more stable
    seed=42,  # Keep for reproducibility
)

print_highlight(response.choices[0].message.content)

스트리밍 모드도 지원됩니다.

로짓 바이어스 지원

컴플리션 API도 위의 채팅 컴플리션 섹션에서 설명한 것과 같은 기능으로 logit_bias 파라미터를 지원합니다.

stream = client.chat.completions.create(
    model="qwen/qwen2.5-0.5b-instruct",
    messages=[{"role": "user", "content": "Say this is a test"}],
    stream=True,
)
for chunk in stream:
    if chunk.choices[0].delta.content is not None:
        print(chunk.choices[0].delta.content, end="")

라우팅된 전문가 반환 (Returning Routed Experts, MoE 모델)

MoE 모델에서는 extra_bodyreturn_routed_experts: true를 설정하면 전문가 라우팅 데이터를 반환합니다. --enable-return-routed-experts 서버 플래그가 필요해요. 기본적으로 routed_experts는 응답 레벨의 sglext 객체에 base64 인코딩된 int32 전문가 ID로 반환되며, 논리 형상은 [num_tokens, num_layers, top_k]입니다. 논스트리밍 채팅 응답에서 return_meta_info: true를 쓰면 routed_experts는 각 choice의 meta_info 객체에만 반환되고 응답 레벨 sglext에서는 빠져서 라우팅 데이터가 중복되지 않습니다. cached_tokens_details, spec_tokens_details 같은 다른 확장 필드는 응답 레벨 sglext에 남아요. 기본적으로 라우팅 전문가는 전체 가용 시퀀스 [0, seqlen - 1)를 포함하는데, RL 워크플로우가 전체 시퀀스가 필요하기 때문입니다. extra_bodyrouted_experts_start_len을 절대 프리픽스 길이로 설정하면 [routed_experts_start_len, seqlen - 1)만 반환해요. 예를 들어 멀티턴 RL 롤아웃에서 이전 턴의 라우팅 결과는 이미 수집됐으니, 이 값을 설정하면 병목을 일으키는 불필요한 전송을 피할 수 있습니다.

# Example with logit_bias parameter for completions API
# Note: You need to get the actual token IDs from your tokenizer
# For demonstration, we'll use some example token IDs
response = client.completions.create(
    model="qwen/qwen2.5-0.5b-instruct",
    prompt="The best programming language for AI is",
    temperature=0.7,
    max_tokens=20,
    logit_bias={
        "12345": 75,  # Strongly favor token ID 12345
        "67890": -100,  # Completely avoid token ID 67890
        "11111": -25,  # Slightly discourage token ID 11111
    },
)

print_highlight(f"Response with logit bias: {response.choices[0].text}")

컴플리션 (Completions)

사용법 (Usage)

컴플리션 API는 채팅 컴플리션 API와 비슷하지만, messages 파라미터와 채팅 템플릿이 없습니다.

response = client.completions.create(
    model="qwen/qwen2.5-0.5b-instruct",
    prompt="List 3 countries and their capitals.",
    temperature=0,
    max_tokens=64,
    n=1,
    stop=None,
)

print_highlight(f"Response: {response}")

파라미터 (Parameters)

컴플리션 API는 OpenAI Completions API의 파라미터를 받습니다. 자세한 내용은 OpenAI Completions API를 참고하세요.

상세한 컴플리션 요청 예시입니다:

response = client.completions.create(
    model="qwen/qwen2.5-0.5b-instruct",
    prompt="Write a short story about a space explorer.",
    temperature=0.7,  # Moderate temperature for creative writing
    max_tokens=150,  # Longer response for a story
    top_p=0.9,  # Balanced diversity in word choice
    stop=["\n\n", "THE END"],  # Multiple stop sequences
    presence_penalty=0.3,  # Encourage novel elements
    frequency_penalty=0.3,  # Reduce repetitive phrases
    n=1,  # Generate one completion
    seed=123,  # For reproducible results
)

print_highlight(f"Response: {response}")

라우팅된 전문가 반환 (Returning Routed Experts, MoE 모델)

MoE 모델에서는 extra_bodyreturn_routed_experts: true를 설정하면 전문가 라우팅 데이터를 반환합니다. --enable-return-routed-experts 서버 플래그가 필요합니다. routed_experts 필드는 각 choice의 sgl_ext 객체에 반환되는데, base64 인코딩된 int32 전문가 ID를 논리 형상 [num_tokens, num_layers, top_k]의 평탄 배열로 담습니다. 기본적으로 전체 가용 시퀀스 [0, seqlen - 1)를 반환하는데, RL 워크플로우가 전체 시퀀스의 라우팅 전문가가 필요하기 때문입니다. extra_bodyrouted_experts_start_len을 절대 프리픽스 길이로 설정하면 [routed_experts_start_len, seqlen - 1)만 반환해요. 예를 들어 멀티턴 RL 롤아웃에서 이전 턴의 라우팅 전문가는 이미 수집됐으니, 이 값을 설정하면 병목을 일으키는 불필요한 전송을 피할 수 있습니다.

구조화된 출력 (JSON, Regex, EBNF)

OpenAI 호환 구조화된 출력 API는 Structured Outputs에서 자세히 다룹니다.

LoRA 어댑터 사용 (Using LoRA Adapters)

SGLang은 OpenAI 호환 API에서 LoRA(Low-Rank Adaptation) 어댑터를 지원합니다. model 파라미터에 base-model:adapter-name 문법으로 사용할 어댑터를 직접 지정할 수 있어요.

서버 설정:

python -m sglang.launch_server \
    --model-path qwen/qwen2.5-0.5b-instruct \
    --enable-lora \
    --lora-paths adapter_a=/path/to/adapter_a adapter_b=/path/to/adapter_b

LoRA 서빙 구성에 대한 자세한 내용은 LoRA documentation을 참고하세요.

API 호출:

(권장) model:adapter 문법으로 사용할 어댑터를 지정합니다.

response = client.chat.completions.create(
    model="qwen/qwen2.5-0.5b-instruct:adapter_a",  # ← base-model:adapter-name
    messages=[{"role": "user", "content": "Convert to SQL: show all users"}],
    max_tokens=50,
)

하위 호환: extra_body 사용

기존 extra_body 방법도 하위 호환을 위해 계속 지원됩니다.

# Backward compatible method
response = client.chat.completions.create(
    model="qwen/qwen2.5-0.5b-instruct",
    messages=[{"role": "user", "content": "Convert to SQL: show all users"}],
    extra_body={"lora_path": "adapter_a"},  # ← old method
    max_tokens=50,
)

model:adapterextra_body["lora_path"]가 모두 지정되면 model:adapter 문법이 우선합니다.

terminate_process(server_process)

더 알아보기 (Learn more)