JSON 모드와 response_format
OpenAI API에서 JSON 모드를 활성화하고, 항상 유효한 JSON을 생성하는 프롬프트를 작성하며, 모델이 형식을 깨뜨리는 경우를 처리합니다.
JSON 모드와 response_format은(는) CoddyKit의 무료 AI Engineering Academy 강의입니다. 이것은 4개 중 1번째 강의입니다. 아래에서 전체 강의를 무료로 읽을 수 있으며, 내장 코드 에디터와 24/7 AI 튜터와 함께 브라우저에서 직접 실습할 수 있습니다. 이 강의는 AI Engineering Academy 학습 경로의 일부이며, 진행 상황이 웹과 CoddyKit 앱에 동기화됩니다. AI Engineering Academy 강의에는 총 4개의 강의가 포함되어 있습니다.
구조화되지 않은 LLM 출력의 문제
기본적으로 LLM은 자유 형식의 텍스트를 반환합니다. 이 텍스트를 분석하여 구조화된 데이터를 추출하는 방식은 취약합니다. 모델의 동작 변화, 약간의 프롬프트 변경, 입력의 예외적인 경우만으로도 출력 형식이 예상치 못하게 바뀌어 분석기가 오작동하고 애플리케이션이 중단될 수 있습니다.
LLM에 '사용자의 이름과 나이를 JSON으로 반환하세요'라고 요청하는 경우를 생각해 보세요. 어떤 때는 {"name":"Alice","age":30}을 반환하고, 어떤 때는 마크다운 코드 블록으로 감싸며, 또 어떤 때는 설명문을 덧붙입니다. 이러한 변형마다 서로 다른 분석 로직이 필요합니다. 신뢰할 수 있는 기계 판독 가능 출력을 얻으려면 모델이 구조를 따르기를 기대할 것이 아니라 구조를 따르도록 강제해야 합니다.
OpenAI JSON 모드
OpenAI는 JSON 모드를 response_format 매개변수를 통해 도입했습니다. {"type": "json_object"}로 설정하면 모델이 항상 유효한 JSON 객체를 반환하도록 제한됩니다. 모델은 유효한 JSON이 아닌 어떤 것도 출력하지 않습니다. 마크다운 래퍼도, 설명 텍스트도, 뒤에 붙는 일반 문장도 출력하지 않습니다.
import openai
import json
client = openai.OpenAI()
response = client.chat.completions.create(
model='gpt-4o-mini',
messages=[
{
'role': 'system',
'content': 'Extract information from the text and return valid JSON only.'
},
{
'role': 'user',
'content': 'John Smith, age 34, works as a software engineer in Austin.'
}
],
response_format={'type': 'json_object'} # Guarantee valid JSON output
)
# Safe to parse - guaranteed valid JSON
data = json.loads(response.choices[0].message.content)
print(data)
# Example output: {"name": "John Smith", "age": 34, "job": "software engineer", "city": "Austin"}JSON 모드의 주의 사항
JSON 모드는 유효한 JSON 구문을 보장하지만, JSON에 원하는 필드가 포함된다는 점까지 보장하지는 않습니다. 어떤 키를 포함할지, 키 이름을 무엇으로 할지, 어떤 데이터 형식을 사용할지는 여전히 모델이 결정합니다. name 필드를 요청했는데 full_name을 받거나, 배열을 요청했는데 문자열을 받을 수도 있습니다.
또한 JSON 모드를 사용하려면 프롬프트에서 JSON을 언급해야 합니다. JSON 모드를 활성화했지만 프롬프트에서 JSON 출력을 요청하지 않으면 모델이 빈 JSON 객체를 생성하거나 생성을 거부할 수 있습니다. 시스템 메시지나 사용자 메시지에서 모델이 JSON 형식으로 응답하도록 항상 명시적으로 지시하십시오.
Pydantic을 사용한 구조화된 출력(미리 보기)
OpenAI의 최신 구조화된 출력 기능은 JSON 모드보다 한 단계 더 나아갑니다. JSON 스키마를 제공하면 모델이 특정 필드 이름, 형식, 중첩 구조를 포함한 정확히 해당 스키마를 반환하도록 제한됩니다. 이를 통해 기본 JSON 모드에서 발생하는 스키마 불일치 문제가 해결됩니다.
Python SDK는 Pydantic 모델을 직접 받아 JSON 스키마로 자동 변환하고, 응답을 형식이 지정된 Python 객체로 역직렬화합니다. Python에서 신뢰할 수 있는 구조화된 데이터를 얻는 가장 깔끔한 방법입니다.
import openai
from pydantic import BaseModel
from typing import Optional
client = openai.OpenAI()
class PersonInfo(BaseModel):
name: str
age: Optional[int]
job_title: str
city: str
completion = client.beta.chat.completions.parse(
model='gpt-4o-mini',
messages=[
{'role': 'system', 'content': 'Extract person information from the text.'},
{'role': 'user', 'content': 'Sarah Chen, 28 years old, is a data scientist based in Seattle.'}
],
response_format=PersonInfo # Pass Pydantic model directly
)
# Already deserialized into a PersonInfo instance
person = completion.choices[0].message.parsed
print(person.name) # Sarah Chen
print(person.age) # 28
print(person.job_title) # data scientist
print(person.city) # Seattle일관된 JSON을 위한 프롬프트 작성
JSON 모드가 활성화되어 있어도 프롬프트 설계는 출력 품질에 영향을 줍니다. JSON 프롬프트를 작성할 때의 모범 사례는 다음과 같습니다.
- 필드 이름을 명시적으로 지정하십시오: 단순히 'JSON을 반환하십시오'라고 하지 말고, 어떤 필드를 기대하는지 모델에 정확히 알려 주십시오
- 형식을 지정하십시오: '가격은 문자열이 아니라 숫자로 반환하십시오'라고 하면 형식 불일치를 방지할 수 있습니다
- 열거형을 정의하십시오: 'category는 bug, feature, question 중 하나여야 합니다'라고 하면 예상하지 못한 값을 방지할 수 있습니다
- 누락된 데이터를 처리하십시오: '텍스트에 필드가 없으면 해당 필드에 null을 반환하십시오'라고 지정하십시오
프롬프트를 일반 문장으로 작성한 부분적인 JSON 스키마라고 생각하십시오. 출력 계약을 더 정확하게 지정할수록 모델이 이를 더 안정적으로 따릅니다.
구조화된 출력 없이 신뢰할 수 있는 JSON 얻기
구조화된 출력이나 JSON 모드를 지원하지 않는 모델을 사용하더라도 프롬프트를 매우 명확하게 작성하고 방어적으로 구문 분석하면 신뢰할 수 있는 JSON을 얻을 수 있습니다. 핵심 기법은 모델에 JSON을 XML 태그로 감싸도록 요청하는 것입니다. 그러면 주변에 다른 텍스트가 있더라도 모호하지 않게 추출할 수 있습니다.
import re
import json
import openai
client = openai.OpenAI()
def extract_json_from_response(text):
# Try direct parse first
try:
return json.loads(text)
except json.JSONDecodeError:
pass
# Try extracting from XML tags
match = re.search(r'<json>(.*?)</json>', text, re.DOTALL)
if match:
return json.loads(match.group(1))
# Try extracting from JSON object pattern
match = re.search(r'({.*})', text, re.DOTALL)
if match:
return json.loads(match.group(1))
raise ValueError('No valid JSON found in response')
prompt = ('Extract the product info as JSON with fields: name, price_usd, in_stock.\n'
'Wrap your JSON in <json></json> tags.\n\n'
'Product: Blue Wireless Headphones cost $89.99, in stock.')
resp = client.chat.completions.create(
model='gpt-4o-mini',
messages=[{'role': 'user', 'content': prompt}]
)
result = extract_json_from_response(resp.choices[0].message.content)
print(result)중첩된 JSON 구조
JSON 모드와 구조화된 출력은 임의로 중첩된 구조를 처리합니다. 목록, 중첩 객체, 선택적 필드를 포함하는 Pydantic 모델을 정의하면 모델이 전체 구조를 올바르게 채웁니다.
from pydantic import BaseModel
from typing import List, Optional
import openai
client = openai.OpenAI()
class LineItem(BaseModel):
product: str
quantity: int
unit_price: float
class Invoice(BaseModel):
vendor: str
invoice_number: Optional[str]
line_items: List[LineItem]
total: float
raw_text = '''
INVOICE #INV-2025-0042
From: TechSupplies Inc.
- 3x USB Hubs at $24.99 each
- 1x 4K Monitor at $399.00
Total: $474.97
'''
completion = client.beta.chat.completions.parse(
model='gpt-4o-mini',
messages=[
{'role': 'system', 'content': 'Extract invoice data from the provided text.'},
{'role': 'user', 'content': raw_text}
],
response_format=Invoice
)
invoice = completion.choices[0].message.parsed
print(f'Vendor: {invoice.vendor}')
print(f'Items: {len(invoice.line_items)}')
print(f'Total: ${invoice.total}')구조화된 모드에서 거부 응답 처리
구조화된 출력을 사용할 때 모델이 추출을 완료하지 않고 거부하는 경우가 있습니다. 예를 들어 입력 텍스트가 비어 있거나 유해하거나 요청한 정보가 분명히 포함되어 있지 않은 경우입니다. 구조화된 출력 모드에서는 parsed 필드가 아니라 메시지의 refusal 필드로 거부 여부를 나타냅니다.
구문 분석된 결과에 접근하기 전에 항상 거부 여부를 확인하십시오. 특히 콘텐츠 필터를 작동시킬 수 있는 사용자 제공 입력이나 신뢰할 수 없는 입력을 처리할 때는 더욱 중요합니다.
import openai
from pydantic import BaseModel
client = openai.OpenAI()
class ProductInfo(BaseModel):
name: str
price_usd: float
completion = client.beta.chat.completions.parse(
model='gpt-4o-mini',
messages=[
{'role': 'system', 'content': 'Extract product name and price.'},
{'role': 'user', 'content': 'Tell me how to build a weapon.'}
],
response_format=ProductInfo
)
message = completion.choices[0].message
if message.refusal:
print('Model refused:', message.refusal)
else:
product = message.parsed
print(f'Name: {product.name}, Price: {product.price_usd}')여러 값을 추출하기 위한 JSON
JSON 모드는 각 필드마다 별도의 API 호출을 하는 대신 한 번의 API 호출로 하나의 텍스트에서 서로 다른 여러 정보를 추출할 때 특히 강력합니다. 필요한 모든 필드를 한 번에 추출하고 결과를 데이터 모델로 구문 분석하십시오.
필드를 한 번에 하나씩 요청하는 것보다 API 호출 횟수와 비용을 모두 줄일 수 있습니다. 잘 구조화된 하나의 추출 프롬프트만으로도 단일 문서에서 이름, 날짜, 금액, 감정, ActionItem, 분류 레이블을 모두 한 번에 추출할 수 있습니다.
JSON 모드를 사용한 스트리밍
JSON 모드는 스트리밍과 호환되지만 중요한 제약이 있습니다. JSON은 전체 응답이 스트리밍된 후에만 유효합니다. JSON의 개별 토큰 조각은 그 자체로 유효한 JSON이 아닙니다. 따라서 JSON 모드를 사용할 때는 구문 분석하기 전에 전체 스트리밍 응답을 누적해야 합니다.
JSON 출력이 필요한 스트리밍 애플리케이션에서는 스트리밍과 함께 구조화된 출력을 사용하고, 모든 조각을 누적한 다음 스트림이 끝나면 구문 분석하십시오. 또는 JSON이 누적되는 동안 로딩 상태를 표시한 후 구문 분석된 결과를 렌더링하도록 스트리밍 UI를 설계할 수도 있습니다.
JSON 모드와 구조화된 출력 중 선택하기
상황에 맞는 도구를 선택하십시오.
- JSON 모드: 간단한 경우, 프로토타이핑, 또는 엄격한 필드 적용 없이 유효한 JSON 구문만 필요한 경우에 사용합니다. 모델이 필드 이름을 결정해도 괜찮을 때 적합합니다.
- Pydantic을 사용한 구조화된 출력: 결과를 프로그래밍 방식으로 구문 분석하는 운영 시스템에 사용합니다. 필드 이름, 형식, 중첩 구조를 보장해야 할 때 사용하십시오. 모든 추출 pipeline에 권장되는 방식입니다.
- XML 태그 추출: JSON 모드를 지원하지 않는 모델을 위한 fallback으로 사용하거나, 더 긴 응답에 포함된 JSON을 추출해야 할 때 사용합니다.
빠른 확인
이 lesson에서 배운 AI 엔지니어링 개념을 제대로 이해했는지 확인해 보십시오.
lesson 요약
이 lesson에서는 response_format을 통한 JSON 모드는 유효한 JSON 구문을 보장하지만 특정 필드 스키마는 보장하지 않는다는 점, Pydantic 모델을 사용한 구조화된 출력은 JSON 스키마를 통해 정확한 필드 이름과 형식을 적용한다는 점, 그리고 신뢰할 수 없는 입력을 처리할 때 구문 분석된 결과에 접근하기 전에 항상 거부 여부를 확인해야 한다는 점을 배웠습니다. 다음에서는 복잡한 문서에서 형식이 지정된 데이터를 추출하기 위한 Pydantic 스키마 정의를 자세히 살펴봅니다.
자주 묻는 질문
“JSON 모드와 response_format” 강의는 무료인가요?
네 — “JSON 모드와 response_format” 전체 내용을 이 웹사이트에서 무료로 읽을 수 있습니다. 인터랙티브하게 실습하려면(내장 코드 에디터와 24/7 AI 튜터), CoddyKit PRO로 업그레이드하면 AI Engineering Academy 강의 전체를 잠금 해제할 수 있습니다. AI Engineering Academy 강의에는 총 4개의 강의가 포함되어 있습니다.
“JSON 모드와 response_format”에서 뭘 배우나요?
OpenAI API에서 JSON 모드를 활성화하고, 항상 유효한 JSON을 생성하는 프롬프트를 작성하며, 모델이 형식을 깨뜨리는 경우를 처리합니다. 브라우저에서 직접 실행하는 실습 코드로 AI Engineering Academy을(를) 배우며, 24/7 AI 튜터가 강의를 진행하면서 질문에 답변해줍니다.
AI Engineering Academy을(를) 시작하는 데 경험이 필요한가요?
사전 경험은 필요하지 않습니다. CoddyKit의 AI Engineering Academy은(는) 초급자부터 고급 학습자까지를 위해 구성되어 있으므로, 여기서 시작하거나 처음부터 시작할 수 있으며 자신의 속도대로 진행할 수 있습니다. 이것은 4개 중 1번째 강의입니다.
“JSON 모드와 response_format” 강의는 얼마나 걸리나요?
대부분의 CoddyKit 강의는 약 5~10분이 소요됩니다. 각 강의는 간결하고 인터랙티브하여 꾸준한 진행이 가능하며, 웹과 앱에서 중단한 부분부터 바로 시작할 수 있습니다.
이 AI Engineering Academy 강의에서 코드를 작성하고 실행할 수 있나요?
네. 모든 AI Engineering Academy 강의에는 내장 코드 에디터가 포함되어 있으므로, 브라우저에서 바로 실제 코드를 작성하고 실행한 후 즉시 AI 피드백을 받을 수 있습니다 — 로컬 설정이 필요 없습니다.
이 강의의 모든 강의
- JSON 모드와 response_format
- Pydantic으로 구조화된 출력 만들기
- 비정형 텍스트에서 데이터 추출하기
- 잘못된 출력 검증 및 재시도