이 문서를 통해 아래 질문들에 답할 수 있게 됩니다.
- Structured Output은 프롬프트로 JSON을 요청하는 것과 무엇이 다른가?
- 모델 응답을 DTO로 받을 때 어떤 검증이 필요한가?
- refusal과 schema validation 실패를 어떻게 구분해야 하는가?
개요
Structured Output은 모델 응답을 서버가 소비하기 쉬운 구조로 제한하는 방식이다. OpenAI 공식 문서의 Structured Outputs처럼 schema를 통해 모델이 따라야 할 구조를 지정할 수 있고, Spring AI도 Structured Output Converter를 통해 모델 출력을 Java 객체로 변환하는 흐름을 제공한다.
하지만 schema를 지정했다고 해서 실패가 사라지지는 않는다. 백엔드에서는 항상 validation과 실패 처리가 필요하다.
왜 백엔드 개발자에게 중요한가
자연어 응답은 사람에게 적합하지만 서버 후속 로직에는 불안정하다. 예를 들어 문의 분류 결과를 DB에 저장하거나, 위험도를 기준으로 workflow를 분기하려면 구조가 필요하다.
Structured Output이 필요한 경우:
- 문의 유형 분류
- 개인정보 탐지 결과
- 문서 요약 저장
- 티켓 우선순위 판단
- RAG 답변 source 목록
- tool 호출 인자 생성
핵심 원리
schema는 기대 형식을 명시한다. validation은 실제 결과를 확인한다. 둘은 모두 필요하다.
검증 항목:
- 필수 필드 존재
- enum 값
- 문자열 길이
- 숫자 범위
- 배열 길이
- source id 형식
- confidence 기준
코드로 이해하기
Python에서는 Pydantic으로 검증할 수 있다.
from typing import Literal
from pydantic import BaseModel, Field, ValidationError
class TicketClassification(BaseModel):
category: Literal["billing", "bug", "account", "other"]
priority: Literal["low", "normal", "high"]
confidence: float = Field(ge=0.0, le=1.0)
reason: str = Field(min_length=1, max_length=500)
def parse_classification(raw: dict) -> TicketClassification:
try:
return TicketClassification.model_validate(raw)
except ValidationError as exc:
raise ValueError("invalid_llm_output") from excJava/Spring에서는 DTO와 Bean Validation을 조합할 수 있다.
public record TicketClassificationResult(
@NotBlank String category,
@NotBlank String priority,
@DecimalMin("0.0") @DecimalMax("1.0") double confidence,
@Size(max = 500) String reason
) {
}백엔드 설계 판단
- 모델 출력이 후속 DB 변경에 쓰이는가?
- 실패 시 재시도할 것인가, human review로 보낼 것인가?
- confidence threshold는 어디에서 적용할 것인가?
- schema version 변경이 기존 저장 데이터와 호환되는가?
- fallback model도 같은 schema를 지킬 수 있는가?
장애 상황과 대응
validation 실패가 증가하면 다음을 본다.
- prompt version이 바뀌었는가?
- schema가 바뀌었는가?
- 모델이나 fallback이 바뀌었는가?
- output token limit이 낮아졌는가?
- refusal이 JSON 파싱 실패로 처리되고 있지 않은가?
인프라 협업 포인트
validation 실패율, refusal rate, retry count는 운영 지표다. 인프라팀과 알림 기준을 맞추면 provider 장애와 prompt 회귀를 더 빨리 잡을 수 있다.
개인 프로젝트 최소 기준
- JSON 파싱 실패를 처리한다.
- Pydantic, Zod, Bean Validation 중 하나로 schema를 검증한다.
- 실패 케이스를 사용자에게 재시도 가능하게 안내한다.
- prompt version과 실패 이유를 로그에 남긴다.
기업 운영 수준 기준
- schema version을 관리한다.
- validation failure rate를 dashboard로 본다.
- prompt/model 변경 시 regression eval을 돌린다.
- 고위험 결과는 human review로 보낸다.
실전 팁
- enum은 가능하면 좁게 둔다.
- 모델이 자유롭게 category를 만들게 하지 않는다.
- confidence는 모델이 만든 숫자이므로 절대적 진실로 보지 않는다.
- validation 실패는 500보다 도메인 실패로 분류하는 편이 좋다.
위험 신호!
JSON으로 답해라는 프롬프트만 믿는다.- 파싱 실패가 서버 500으로 노출된다.
- schema version 없이 저장한다.
- fallback model의 schema 준수율을 모른다.
확인 질문
확인 질문
- schema와 validation은 어떻게 다른가?
- schema는 기대 형식을 모델에게 알려주고, validation은 실제 응답이 그 형식을 만족하는지 서버가 확인하는 것이다.
- validation 실패를 운영 지표로 봐야 하는 이유는 무엇인가?
- prompt, model, schema 변경으로 발생한 품질 회귀를 나타낼 수 있기 때문이다.
Spring AI 적용 연결
- Spring AI에서는 structured output을 DTO 변환으로 끝내지 말고 Bean Validation, 재시도, fallback 응답까지 함께 설계한다. 적용 예시는 06. Structured Output과 Bean Validation에서 이어진다.
- DTO 결과가 tool 실행으로 연결될 때의 side effect 방어는 07. Tool Calling과 Side Effect 방어를 함께 본다.