structured output(JSON 강제) 쓸 때 enum이나 minLength 같은 제약이 무시되는 것 같아요
사용자 입력에서 연락처 정보를 추출해서 정해진 스키마로 받으려고 structured output을 쓰고 있습니다. Pydantic 모델로 정의했는데, Literal로 막아둔 plan 필드는 잘 지켜지는 반면 min_length나 숫자 범위(ge, le) 제약은 가끔 무시된 값이 나옵니다.
class Contact(BaseModel):
name: str = Field(min_length=1)
plan: Literal["free", "pro", "enterprise"]
seats: int = Field(ge=1, le=100)
plan은 항상 셋 중 하나로 잘 오는데 seats에 0이나 200이 오는 경우가 있습니다. structured output이 이런 건 보장 안 해주나요? 아니면 제가 잘못 쓰고 있는 걸까요?
답변 2개
- 채택된 답변에디터 검증
버그 아니고 의도된 한계예요. structured output(strict 스키마)이 강제해주는 건 JSON Schema 중 일부 키워드만이고, 숫자/문자열 범위 제약은 거기 안 들어갑니다.
강제되는 것: 타입,
enum,const,anyOf/allOf,$ref, 포맷(date·email·uuid 등),additionalProperties: false.안 되는 것:
minimum/maximum/multipleOf(← seats의 ge/le),minLength/maxLength(← name), 복잡한 배열 제약, 재귀 스키마.plan이 잘 지켜진 건
Literal이enum으로 컴파일돼서 지원 대상이라 그렇고, seats 범위는 API로 보낼 때 스키마에서 그냥 떨어져 나간 채 모델한테 전달됩니다. 그래서 0이나 200이 통과하는 거예요. 모델은 그 제약을 본 적도 없는 셈.그래서 받는 쪽에서 한 번 더 검증하는 게 정석입니다.
client.messages.parse()로 받으면 Pydantic이 거기서ValidationError던져주니까, 그걸 잡아서 재시도하거나 클램프하세요. 저는 description에도 범위를 박아둡니다 — 강제는 안 돼도 모델이 참고는 하더라고요:seats: int = Field(ge=1, le=100, description="좌석 수, 1~100 사이 정수")요약하면 출력 자체를 믿지 말고 description으로 유도 + 받는 쪽 validate + 실패 시 재시도, 이 3단으로 가면 됩니다.
맞아요. 하나 더 보태자면 seats처럼 값 도메인이 작고 이산적이면 그냥 enum으로 박는 게 제일 확실합니다.
int+ ge/le 대신:seats: Literal[1,2,3,4,5,6,7,8]이러면 enum으로 컴파일돼서 그 8개 밖으론 절대 안 나와요. (anthropic strict tool use 예제에서 passengers를 enum으로 잡는 게 딱 이 이유.) 물론 1~100이면 enum이 비현실적이니 그땐 윗분 말대로 parse() 검증으로 가야죠.
그리고 사람들이 자주 까먹는데,
stop_reason이max_tokens로 끊기면 JSON이 중간에 잘려서 옵니다. 그건 스키마 위반이 아니라 애초에 파싱이 안 되는 케이스라 별도로 잡아야 해요. refusal 케이스랑 같이 방어해두세요.