ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Structured Outputs란? JSON 형식이 맞아도 값이 틀릴 수 있습니다
    AI Agent 2026. 9. 14. 11:57
    728x90
    반응형

    칸막이 나무 트레이의 네모 칸에 놓인 둥근 물체

    칸막이 나무 트레이의 네모 칸에 놓인 둥근 물체. 이해를 돕기 위한 AI 생성 개념 이미지입니다.

    AI가 JSON을 잘 출력하면 데이터 추출이 끝난 것처럼 보입니다. 하지만 날짜가 문자열이고 수량이 숫자라는 사실만으로 그 값이 원문과 같다고 할 수는 없습니다.

    JSON 스키마 검사 뒤에 원문 대조와 업무 규칙 검사를 별도로 두세요.

    스키마가 확인하는 모양과 내용의 사실성은 다릅니다

    Claude의 Structured Outputs 문서는 스키마에 맞는 JSON 출력과 엄격한 도구 입력을 다룹니다. 형식이 흔들려 파서가 실패하는 문제를 줄이는 데 목적이 있습니다.

    JSON Schema의 object 설명에서 properties는 각 속성의 규칙, required는 반드시 있어야 할 속성, additionalProperties는 추가 속성의 허용 여부를 정합니다. 이 규칙에 “문서에 실제로 쓰인 주문 수량인가”라는 사실 대조가 저절로 포함되지는 않습니다.

    Claude API에서 JSON 출력을 요청하는 최소 구조는 다음과 같습니다. MODEL_ID와 업무 스키마는 예시이며, API가 반환한 텍스트 블록을 애플리케이션에서 파싱하고 원문·업무 검사를 이어서 수행해야 합니다.

    response = client.messages.create(
        model="MODEL_ID",
        max_tokens=1024,
        messages=[{"role": "user", "content": "주문 메모에서 상품과 수량을 추출하세요."}],
        output_config={
            "format": {
                "type": "json_schema",
                "schema": {
                    "type": "object",
                    "properties": {
                        "item": {"type": "string"},
                        "quantity": {"type": "integer"}
                    },
                    "required": ["item", "quantity"],
                    "additionalProperties": False
                }
            }
        }
    )

    공식 문서에서 JSON outputs(output_config.format)와 strict tool use(strict: true)는 서로 다른 기능입니다. 전자는 모델의 최종 응답 모양을, 후자는 도구 호출의 이름과 입력 스키마를 제한합니다. 에이전트가 도구를 호출한 뒤 구조화된 최종 답까지 필요하면 둘을 함께 쓸 수 있지만, 어느 하나가 다른 하나의 검증을 대신하지는 않습니다.

    주문 메모 하나로 보면 빠르게 구분됩니다

    다음은 가상 원문입니다.

    파란 컵 두 개. 배송 날짜는 아직 정하지 않음.

    결과가 {"item":"파란 컵","quantity":2,"delivery_date":"2026-09-10"}이라면 JSON 파싱은 성공할 수 있습니다. 배송일을 문자열로 받는 스키마에도 맞을 수 있습니다. 그러나 원문이 정하지 않은 날짜를 채웠으므로 추출 결과로는 틀렸습니다.

    이런 입력에서는 미정 값을 표현할 방법부터 설계해야 합니다. 날짜에 null을 허용하거나 별도의 상태를 두는 방법이 있습니다. 어느 쪽이든 “필드를 채우라”는 요구가 추측을 유도하지 않게 해야 합니다. required는 해당 키의 존재를 요구하는 것이지, 모든 업무 값이 확정되었다는 뜻이 아닙니다.

    처리 단계마다 다른 질문을 합니다

    단계묻는 질문가상 예시의 판정
    파싱JSON으로 읽을 수 있는가읽을 수 있음
    스키마키와 자료형이 맞는가설계에 따라 통과 가능
    원문 대조수량·색상·날짜가 원문에 있는가배송일은 근거 없음
    업무 규칙지금 다음 단계로 넘겨도 되는가날짜 확인이 필요한 상태

    숫자도 마찬가지입니다. quantity가 정수여도 음수인지, 낱개와 상자 단위가 섞였는지, 다른 상품의 수량을 가져왔는지는 따로 봐야 합니다. 허용 가능한 범위는 스키마나 프로그램 검사로 줄일 수 있지만, 원문과의 대응까지 모두 대체하지는 못합니다.

    응답이 끝났는지도 먼저 확인합니다

    Claude 문서는 거절이나 최대 출력 토큰 도달 때 스키마를 만족하지 않는 응답이 생길 수 있다고 명시합니다. HTTP 응답을 받았다고 바로 결과 JSON만 파싱하는 흐름은 이 상태를 놓칠 수 있습니다.

    호출 결과의 종료 이유를 확인하고, 불완전한 응답과 정상 추출을 분리하세요. 공식 문서의 대표 예외는 안전 거절(stop_reason: "refusal")과 출력 토큰 제한(stop_reason: "max_tokens")입니다. 거절은 HTTP 200이어도 스키마와 다른 내용이 올 수 있고, 잘림은 JSON이 불완전할 수 있습니다. 누락된 필드를 자동으로 빈 문자열로 바꾸면, 모델이 미정이라고 답한 것인지 응답이 잘린 것인지 구분하기 더 어려워집니다.

    스키마에 미정 상태를 설계합니다

    가상 주문 메모에 맞는 스키마 일부는 다음처럼 만들 수 있습니다. 실제 API가 지원하는 JSON Schema 범위는 별도로 확인해야 합니다.

    {
      "type": "object",
      "properties": {
        "item": { "type": "string" },
        "quantity": { "type": "integer", "minimum": 1 },
        "delivery_date": {
          "type": ["string", "null"],
          "description": "원문에 날짜가 없으면 null"
        },
        "needs_review": { "type": "boolean" }
      },
      "required": ["item", "quantity", "delivery_date", "needs_review"],
      "additionalProperties": false
    }

    이 구조는 날짜 키가 빠지는 문제와 임의의 추가 필드가 생기는 문제를 줄일 수 있습니다. 그러나 모델이 근거 없이 날짜 문자열을 만들어도 형식 검사는 통과합니다. minimum: 1은 음수 수량을 막지만, 컵 두 개를 스무 개로 잘못 읽는 오류는 막지 못합니다.

    여기서 일반 JSON Schema의 모든 키워드가 특정 API에서 같은 방식으로 집행된다고 가정하면 안 됩니다. Claude 공식 문서는 SDK가 지원되지 않는 제약(예: minimum, maximum, 문자열 길이 제한)을 전송 스키마에서 제거하거나 설명으로 바꾸고, 원래 제약은 SDK의 응답 검증에서 다시 확인할 수 있다고 설명합니다. 직접 JSON Schema를 보내는 경우에는 해당 모델·SDK의 지원 범위와 실패 응답을 먼저 확인하세요.

    null과 빈 문자열도 같은 의미로 섞지 않는 편이 좋습니다. “원문에 없음”, “읽을 수 없음”, “업무상 적용 불가”가 후속 처리에서 다르다면 상태 필드를 더 명시적으로 설계해야 합니다. 모든 필드를 필수로 만들더라도 값의 불확실성을 표현할 통로는 남겨야 합니다.

    enum도 문자열 정확성을 따로 확인합니다

    열거형을 사용하면 허용된 선택지를 좁힐 수 있지만, Claude 공식 문서는 enum·const 문자열의 대소문자가 항상 스키마와 똑같이 보존된다고 보장하지 않습니다. 예를 들어 "Conversation Topic 3" 대신 첫 단어 뒤의 대문자가 달라진 값이 정상 종료와 함께 올 수 있습니다. 후속 코드가 문자열을 exact match해야 한다면 case-insensitive 비교 후 표준값으로 정규화하거나, 별도 허용 목록 검사에서 거부해야 합니다.

    애플리케이션 검증을 네 단계로 둡니다

    1. API 상태 검사: 거절·길이 제한·전송 오류인가?
    2. 구조 검사: JSON 파싱과 스키마 검증을 통과하는가?
    3. 근거 검사: 각 값이 원문의 어느 구절에서 왔는가?
    4. 업무 검사: 단위·날짜·허용 범위와 다음 행동 조건이 맞는가?

    예를 들어 배송일이 null이면 구조적으로 정상일 수 있지만 주문 확정 단계에는 넘기지 않습니다. 수량이 2이고 원문에도 “두 개”가 있으면 근거 검사를 통과할 수 있지만, 재고가 1개라면 업무 규칙은 실패합니다. 각 단계가 다른 오류 코드를 내야 무작정 모델을 재호출하는 일을 줄일 수 있습니다.

    근거를 남길 필요가 있다면 source_quote나 문자 위치 같은 필드를 별도 설계할 수 있습니다. 다만 모델이 만든 인용문도 원문과 다시 대조해야 합니다. 인용 필드가 존재한다는 사실이 근거의 진위를 보증하지 않습니다.

    실패를 자동으로 고칠 때 주의할 점

    스키마 오류가 나면 같은 요청을 다시 보낼 수 있지만, 재시도가 의미 있는 경우와 그렇지 않은 경우를 구분합니다.

    실패재시도 전에 할 일
    전송 오류멱등성과 중복 처리 확인
    출력 길이 제한입력·출력 범위를 줄이거나 분할
    거절거절 상태로 처리, 빈 JSON으로 대체 금지
    스키마 불일치오류 위치를 기록하고 제한된 재시도
    근거 없는 값원문 범위와 미정 규칙 수정
    업무 규칙 위반모델 재시도보다 담당자 확인 또는 명시적 분기

    결제·발송처럼 외부 상태를 바꾸는 작업에서 추출 결과가 스키마를 통과했다는 이유로 바로 실행하면 안 됩니다. 수신자, 금액, 대상 계정을 코드에서 다시 확인하고 영향이 큰 행동은 실제 내용을 보여주는 승인 단계를 둡니다.

    평가 데이터는 예쁜 정상 입력보다 모서리 사례가 중요합니다

    최소 평가 묶음에 다음을 포함합니다.

    • 필수 값이 모두 있는 정상 메모
    • 배송일처럼 일부 값이 미정인 메모
    • 상품과 수량이 둘 이상인 메모
    • “두 상자, 상자당 여섯 개”처럼 단위 계산이 필요한 메모
    • 앞뒤 문장이 서로 모순되는 메모
    • 글자가 잘리거나 표 순서가 뒤섞인 입력
    • 요청 범위를 벗어난 개인정보가 포함된 입력

    각 사례에서 파싱, 스키마, 원문 대조, 업무 판정을 따로 기록합니다. 전체 성공률 하나만 쓰면 JSON 모양은 좋아졌지만 값 정확도는 그대로인 변화를 놓칠 수 있습니다.

    처음부터 다양한 정상·실패 입력을 넣습니다

    기본 샘플 외에 날짜 미정, 상품 둘, 단위 변경, 서로 모순되는 메모를 함께 준비하면 무엇을 스키마에 넣고 무엇을 원문 대조로 남겨야 할지 드러납니다. 이 글의 샘플은 설명용이며 실제 API 검증 결과가 아닙니다.

    2026년 9월 7일 확인한 문서 기준입니다. 지원되는 JSON Schema 범위는 제공사와 모델별로 다르므로, 일반 JSON Schema에서 가능한 문법이 특정 API에도 전부 적용된다고 가정하지 마세요.

    728x90
    반응형
Designed by Tistory.