이 문서를 통해 아래 질문들에 답할 수 있게 됩니다.

  • 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 exc

Java/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 적용 연결

참고 문서