구조화된 출력
구조화된 출력 (Structured Outputs)
vLLM은 xgrammar 또는 guidance를 백엔드로 해서 구조화된 출력(structure outputs) 생성을 지원해요. 이 문서는 구조화된 출력을 만들 때 쓸 수 있는 여러 옵션들을 예제와 함께 보여드릴게요.
더 이상 사용하지 않는 API 필드 (Deprecated API Fields)
만약 v0.12.0에서 제거된 아래의 구식 API 필드를 아직 쓰고 있다면, 이 문서의 나머지에서 보여주는 것처럼 structured_outputs를 사용하도록 코드를 업데이트해야 해요.
guided_json→{"structured_outputs": {"json": ...}}또는StructuredOutputsParams(json=...)guided_regex→{"structured_outputs": {"regex": ...}}또는StructuredOutputsParams(regex=...)guided_choice→{"structured_outputs": {"choice": ...}}또는StructuredOutputsParams(choice=...)guided_grammar→{"structured_outputs": {"grammar": ...}}또는StructuredOutputsParams(grammar=...)guided_whitespace_pattern→{"structured_outputs": {"whitespace_pattern": ...}}또는StructuredOutputsParams(whitespace_pattern=...)structural_tag→{"structured_outputs": {"structural_tag": ...}}또는StructuredOutputsParams(structural_tag=...)guided_decoding_backend→ 요청에서 이 필드를 제거하세요.
온라인 서빙 (OpenAI API)
OpenAI의 Completions 및 Chat API를 사용해 구조화된 출력을 생성할 수 있어요.
추가 파라미터로 넣어야 하는 지원 옵션은 다음과 같아요.
choice: 출력이 선택지 중 정확히 하나가 되도록 해요.regex: 출력이 정규식 패턴을 따르도록 해요.json: 출력이 JSON 스키마를 따르도록 해요.grammar: 출력이 context free grammar를 따르도록 해요.structural_tag: 생성 텍스트 내 특정 태그 집합 안에서 JSON 스키마를 따르도록 해요.
OpenAI 호환 서버에서는 구조화된 출력이 기본적으로 지원돼요. --structured-outputs-config.backend 플래그로 vllm serve의 백엔드를 지정할 수 있는데, 기본값인 auto는 요청의 세부 사항에 따라 적절한 백엔드를 자동으로 고릅니다. 특정 백엔드와 옵션을 직접 고를 수도 있고, 전체 옵션은 vllm serve --help에서 확인할 수 있어요.
가장 쉬운 choice부터 예제를 볼게요.
from openai import OpenAI
client = OpenAI(base_url="http://localhost:8000/v1", api_key="-")
model = client.models.list().data[0].id
completion = client.chat.completions.create(
model=model,
messages=[{"role": "user", "content": "Classify this sentiment: vLLM is wonderful!"}],
extra_body={"structured_outputs": {"choice": ["positive", "negative"]}},
)
print(completion.choices[0].message.content)
다음은 regex 사용 예제예요. 지원되는 정규식 문법은 구조화된 출력 백엔드에 따라 달라져요. 예를 들어 xgrammar, guidance, outlines는 Rust 스타일 정규식을, lm-format-enforcer는 Python의 re 모듈을 사용해요.
completion = client.chat.completions.create(
model=model,
messages=[{"role": "user", "content": "Generate an example email address for Alan Turing, who works in Enigma. End in .com and new line. Example result: [email protected] \n"}],
extra_body={"structured_outputs": {"regex": "\w+@\w+\.com\n"}, "stop": ["\n"]},
)
print(completion.choices[0].message.content)
구조화된 텍스트 생성에서 가장 유용한 기능 중 하나는 미리 정의된 필드와 형식을 가진 유효한 JSON을 생성하는 거예요. 여기엔 json 파라미터를 두 가지 방법으로 쓸 수 있어요.
- JSON Schema를 직접 사용하기
- Pydantic model을 정의하고 그로부터 JSON Schema를 추출하기 (보통 더 쉬운 방법)
다음은 Pydantic 모델로 response_format을 쓰는 예제입니다.
from pydantic import BaseModel
from enum import Enum
class CarType(str, Enum):
sedan = "sedan"
suv = "SUV"
truck = "Truck"
coupe = "Coupe"
class CarDescription(BaseModel):
brand: str
model: str
car_type: CarType
json_schema = CarDescription.model_json_schema()
completion = client.chat.completions.create(
model=model,
messages=[{"role": "user", "content": "Generate a JSON with the brand, model and car_type of the most iconic car from the 90's"}],
response_format={"type": "json_schema", "json_schema": {"name": "car-description", "schema": CarDescription.model_json_schema()}},
)
print(completion.choices[0].message.content)
팁: 꼭 필요한 건 아니지만, 프롬프트에 JSON 스키마와 필드를 어떻게 채워야 하는지 명시해주는 게 대부분의 경우 결과를 눈에 띄게 개선해요.
마지막으로 grammar 옵션은 쓰기가 가장 어렵지만 정말 강력해요. SQL 쿼리 같은 완전한 언어를 정의할 수 있게 해주죠. context free EBNF grammar를 사용해서 동작해요. 예를 들어 간단한 SQL 쿼리 형식을 정의해볼게요.
simplified_sql_grammar = """
root ::= select_statement
select_statement ::= "SELECT " column " from " table " where " condition
column ::= "col_1 " | "col_2 "
table ::= "table_1 " | "table_2 "
condition ::= column "= " number
number ::= "1 " | "2 "
"""
completion = client.chat.completions.create(
model=model,
messages=[{"role": "user", "content": "Generate an SQL query to show the 'username' and 'email' from the 'users' table."}],
extra_body={"structured_outputs": {"grammar": simplified_sql_grammar}},
)
print(completion.choices[0].message.content)
전체 예제는 examples/features/structured_outputs/README.md에서 찾을 수 있어요.
이유추론 출력 (Reasoning Outputs)
구조화된 출력은 reason 모델과도 함께 사용할 수 있어요.
vllm serve deepseek-ai/DeepSeek-R1-Distill-Qwen-7B --reasoning-parser deepseek_r1
어떤 구조화된 출력 기능이든 reason과 함께 쓸 수 있어요. 다음은 JSON 스키마를 쓰는 예제입니다.
from pydantic import BaseModel
class People(BaseModel):
name: str
age: int
completion = client.chat.completions.create(
model=model,
messages=[{"role": "user", "content": "Generate a JSON with the name and age of one random person."}],
response_format={"type": "json_schema", "json_schema": {"name": "people", "schema": People.model_json_schema()}},
)
print("reasoning: ", completion.choices[0].message.reasoning)
print("content: ", completion.choices[0].message.content)
주의: Qwen3 Coder 모델을 reason과 함께 쓸 때, reason 콘텐츠가 reasoning 필드로 따로 파싱되지 않으면 구조화된 출력이 비활성화될 수 있어요(v0.11.2+). 두 기능을 함께 쓰려면 vLLM 서버를 시작할 때 --structured-outputs-config.enable_in_reasoning=True 플래그를 추가해 구조화된 출력을 reasoning 모드에서 명시적으로 활성화해야 해요.
실험적 자동 파싱 (OpenAI API)
이 섹션은 client.chat.completions.create() 메서드를 감싸는 OpenAI beta 래퍼를 다뤄요. Python 특정 타입과 더 풍부하게 통합됩니다.
작성 시점(openai==1.54.4)에서 이건 OpenAI 클라이언트 라이브러리의 "beta" 기능이에요.
Pydantic 모델로 구조화된 출력을 얻는 간단한 예제부터 볼게요.
from pydantic import BaseModel
from openai import OpenAI
class Info(BaseModel):
name: str
age: int
client = OpenAI(base_url="http://0.0.0.0:8000/v1", api_key="dummy")
model = client.models.list().data[0].id
completion = client.beta.chat.completions.parse(
model=model,
messages=[
{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": "My name is Cameron, I'm 28. What's my name and age?"},
],
response_format=Info,
)
message = completion.choices[0].message
print(message)
assert message.parsed
print("Name:", message.parsed.name)
print("Age:", message.parsed.age)
오프라인 추론 (Offline Inference)
오프라인 추론에서도 같은 종류의 구조화된 출력을 지원해요. 사용하려면 SamplingParams 안의 StructuredOutputsParams 클래스로 구조화된 출력을 설정해야 해요. 주요 옵션은 다음과 같아요.
jsonregexchoicegrammarstructural_tag
이 파라미터들은 위 온라인 서빙 예제와 같은 방식으로 사용할 수 있어요. choice 파라미터 사용 예시를 보여드릴게요.
from vllm import LLM, SamplingParams
from vllm.sampling_params import StructuredOutputsParams
llm = LLM(model="HuggingFaceTB/SmolLM2-1.7B-Instruct")
structured_outputs_params = StructuredOutputsParams(choice=["Positive", "Negative"])
sampling_params = SamplingParams(structured_outputs=structured_outputs_params)
outputs = llm.generate(prompts="Classify this sentiment: vLLM is wonderful!", sampling_params=sampling_params)
print(outputs[0].outputs[0].text)
전체 예제는 examples/features/structured_outputs/structured_outputs_offline.py에서 볼 수 있어요.