← AI 모델과 시스템
실전 가이드DEEPER INTO TECHNOLOGY

LLM의 JSON 출력이 맞는데 자동화는 왜 실패할까? 스키마와 업무 검증

LLM이 문법과 스키마에 맞는 JSON을 반환했는데도 업무 자동화가 실패하는 이유는 무엇이며, 어디서 무엇을 검사해야 할까?

먼저 읽는 핵심

구조화 출력은 모델의 답을 프로그램이 읽을 수 있는 계약으로 좁힌다. 그러나 계약에 맞는 값이 원래 요청과 같은지, 현재 사용자가 실행해도 되는지, 지금도 유효한지는 따로 확인해야 한다. 생성 형식·서버 스키마·업무 상태를 나누면 실패할 때 프롬프트를 고칠지, 데이터를 다시 읽을지, 실행을 거절할지 결정할 수 있다.

중괄호가 맞아도 8,000원은 틀릴 수 있다

고객지원 담당자가 “주문 O-1042에서 5,000원만 환불해 주세요”라는 요청을 처리한다고 하자. 모델은 다음 변경안을 만든다. 주문과 금액은 모두 설명을 위한 가상 데이터다.

{
  "schema_version": "refund-v1",
  "order_id": "O-1042",
  "amount_minor": 8000,
  "currency": "KRW",
  "expected_version": 7
}

중괄호와 따옴표가 맞고, 필수 필드가 있으며, 금액은 양의 정수다. 결제액 10,000원에서 이미 환불한 2,000원을 빼면 잔액도 8,000원이다. 형식과 잔액 검사를 모두 통과해도 요청한 5,000원과는 다르다. 이 사례의 실패는 JSON 생성 실패가 아니라 요청 의도와 변경안의 불일치다.

이 차이를 놓치면 스키마를 더 엄격하게 만들거나 모델에 같은 질문을 다시 보내는 데만 시간을 쓰게 된다. 먼저 어떤 증거를 어느 단계에서 확인해야 하는지 나눠 보자. 이 글은 공식 문서에 기초한 설계 해설과 로컬 검사기 실행을 결합했다. 실제 모델의 오류율이나 결제 시스템의 안전성을 측정한 글은 아니다.

세 단계의 통과가 의미하는 것

단계 통과하면 알 수 있는 것 여전히 모르는 것
JSON 파싱 문자열을 데이터 구조로 읽을 수 있음 필요한 필드·자료형·업무 의미
서버 스키마 검사 정해 놓은 필드·형식·범위·조건에 맞음 원문과 일치하는지, 실행자의 권한, 최신 잔액
업무 검사 확인한 근거와 현재 상태에서 제안이 조건을 만족함 검사 뒤 상태 변화, 실제 실행 완료

이 표는 구현을 나누기 위한 분석 틀이다. 모든 업무 조건이 스키마 밖에 있어야 한다는 뜻은 아니다. 예를 들어 허용 통화, 금액의 하한, 특정 필드가 있을 때 다른 필드도 요구하는 관계는 스키마로 표현할 수 있다. JSON Schema의 조건부 검증 문서는 if·then·else와 의존 관계로 이런 규칙을 구성하는 방법을 설명한다.

경계는 “단순한 검사는 스키마, 복잡한 검사는 코드”보다 검사에 필요한 증거가 어디 있는가로 정하는 편이 정확하다. 문서 자체와 고정 계약만으로 알 수 있는 것은 스키마에 두기 쉽다. 인증된 사용자, 방금 갱신된 주문, 원문에서 확인한 금액이 필요하면 그 자료를 신뢰할 수 있는 경로로 가져와야 한다.

주문별 잔액을 스키마의 maximum에 매번 주입할 수도 있다. 그래도 그 잔액이 최신인지, 검사 직후 다른 환불이 일어났는지는 별도 문제로 남는다. 스키마의 표현력을 높이는 것과 증거의 신뢰·유효 기간을 관리하는 것은 함께 해야 한다.

Structured Outputs가 줄여 주는 실패

자유로운 답변을 받은 뒤 코드 블록을 떼고 JSON을 찾아내는 방식은 출력 형식에 관한 실패가 생길 여지가 크다. 구조화 출력은 생성할 수 있는 형태를 좁힌다. 예를 들어 객체에서 다음에 나올 수 있는 키나 값의 형태를 스키마에 맞춰 제한하면, 읽을 수 없는 결과를 뒤에서 수선할 부담을 줄일 수 있다.

구체적인 지원 범위는 공급자와 기능에 따라 확인해야 한다. 2026-10-11 확인한 Claude Structured Outputs 문서는 문법 제약을 사용하며, JSON 응답 형식과 엄격한 도구 인자 생성을 구별한다. 또한 일반 JSON Schema 전체를 지원하지 않는다. 숫자의 minimum·maximum·multipleOf 등은 해당 문서에서 미지원으로 분류한다. 아래 서버 검증용 스키마를 API에 그대로 제출하는 호출 예제로 읽으면 안 된다.

같은 문서는 거절과 출력 토큰 한도 종료 때 스키마와 맞지 않거나 불완전한 응답이 올 수 있다고 설명한다. 따라서 처리 순서는 API 오류·종료 상태 확인, 완결된 출력 확보, 파싱·서버 검사 순으로 잡는다. 스트리밍 도중 중괄호 일부가 보였다는 이유로 변경을 실행하지 않는다.

이 글에서는 공급자 API와 SDK를 실행하지 않았다. 지원 모델·요금·호출 옵션을 포괄적으로 비교하지도 않는다. 도입 시에는 사용하는 API·모델·SDK 버전에서 생성에 전달한 스키마가 무엇인지 확인해야 한다. SDK가 일부 제약을 변환하거나 제거한다면 서버에서 검사할 원래 계약까지 함께 보관한다.

스키마를 강화할 때 먼저 잡을 세 가지 빈틈

다음은 다운로드 예제에 들어 있는 서버 검증용 JSON Schema Draft 2020-12다. amount_minor는 통화의 최소 단위이며, 이 예제에서는 KRW 1은 1원, USD 1은 1센트다. 환전과 수수료는 다루지 않는다.

{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "properties": {
    "schema_version": {"const": "refund-v1"},
    "order_id": {"type": "string", "minLength": 1},
    "amount_minor": {
      "anyOf": [{"type": "integer", "minimum": 1}, {"type": "null"}]
    },
    "currency": {"enum": ["KRW", "USD"]},
    "expected_version": {"type": "integer", "minimum": 0}
  },
  "required": [
    "schema_version", "order_id", "amount_minor", "currency", "expected_version"
  ],
  "additionalProperties": false
}

첫째, properties에 적었다고 필수가 되지는 않는다. 필요한 키는 required에 넣는다. 둘째, 키가 있는 것과 값이 알려진 것은 다르다. 위 계약에서는 amount_minor 키를 반드시 보내되, 금액이 확인되지 않았으면 null로 표현할 수 있다. 숫자를 추측해 채우는 대신 후속 업무 검사에서 재확인으로 보내는 경로다. 누락·null의 구별과 추가 키 허용 방식은 공식 object 설명에서 확인할 수 있다.

셋째, 추가 키의 처리다. additionalProperties: false는 모델이 임의로 만든 approved: true 같은 키를 거절한다. 이 예제는 정의하지 않은 정보를 조용히 버리지 않고 계약 불일치로 드러낸다. 확장 필드가 필요한 제품이라면 허용할 영역과 버전 변경 정책을 따로 설계할 수 있다.

날짜를 다룬다면 format도 확인한다. Draft 2020-12 검증 규격은 format의 주석 역할과 검증 역할을 구분한다. format: date를 적었다는 사실만으로 모든 검사기가 잘못된 날짜를 거절한다고 가정하면 안 된다. 사용하는 라이브러리의 설정과 지원 형식으로 실패 입력을 직접 시험해야 한다.

스키마가 바뀌는 시점도 오류의 원인이다. 새 필드나 통화 단위를 도입했다면 생성 쪽과 수신 쪽이 같은 계약을 쓰는지 확인한다. 예제의 schema_version은 오래된 계약을 구분하기 위한 표시이며, 버전 문자열이 일치한다고 값의 의미가 자동으로 맞아지는 것은 아니다.

환불 제안 검사기: 통과·거절·재확인을 나누기

오프라인 예제 ZIP에는 스키마, Python 검사기, 가상 입력 19개와 실행 안내가 들어 있다. API 키는 필요 없다. 설치할 때 패키지를 내려받으며, 검사기 자체는 모델·결제·DB·외부 네트워크를 호출하지 않는다.

압축을 푼 폴더에서 실행한다. Python 3.11.12와 jsonschema 4.26.0으로 확인했다. Windows에서는 아래 실행 경로의 .venv/bin/python을 .venv\Scripts\python.exe로 바꾼다. Windows에서의 실행은 이번 확인 범위 밖이다.

python3 -m venv .venv
.venv/bin/python -m pip install -r requirements.txt
.venv/bin/python validate.py

검사기는 Draft202012Validator로 스키마 자체를 먼저 확인한 뒤 입력을 검사한다. 이 라이브러리는 format_checker를 주지 않으면 format 검증을 하지 않으며, 형식에 따라 추가 의존성도 필요하다. 예제는 날짜·이메일 format을 사용하지 않는다.

입력은 두 부분이다. raw는 모델이 만들었다고 가정한 JSON 문자열이다. context는 인증된 실행자와 허용 동작, 주문 원장, 사용자가 별도로 확인한 의도라는 신뢰된 서버 자료를 흉내 낸 테스트 데이터다. 실제 구현에서 context까지 모델이나 클라이언트가 보내게 하고 그대로 믿으면 권한 검사가 무의미해진다.

기준 주문은 결제 10,000원, 기존 환불 2,000원, 버전 7이다. 서버 측 확인 의도는 이 주문의 5,000원 환불이다. 아래는 실행한 사례 중 핵심 경계를 골라 정리한 것이다.

입력 또는 상태 변화 첫 실패 지점과 결과 다음 행동
금액 5,000원, 근거·권한·상태 일치 ready-for-commit 실제 쓰기 단계로 넘길 수 있는 후보
금액이 문자열 "5000" schema: SCHEMA_MISMATCH 단위·계약 확인 후 생성 경로 수정
임의의 approved 키 추가 schema: SCHEMA_MISMATCH 승인 표시를 신뢰하지 않고 거절
금액 null business: MISSING_AMOUNT 원문 또는 사용자에게 금액 확인
금액 9,000원 business: EXCEEDS_REMAINING 자동 실행 거절, 잔액과 요청 확인
다른 소유자 또는 환불 권한 없음 business: NOT_AUTHORIZED 실행 거절, 정상 권한 경로로 안내
원장 버전이 8로 바뀜 business: STALE_STATE 상태 재조회 후 변경안 재평가
금액 8,000원, 확인 의도는 5,000원 business: INTENT_MISMATCH 원문·확인 의도와 제안 대조

코드는 주문 정보를 노출하기 전에 동작 권한과 대상 접근을 확인한다. 알 수 없는 주문도 같은 권한 거절 코드로 처리한다. 이는 예제를 위한 정책이며, 실제 서비스의 인증·권한 모델 전체를 구현한 것은 아니다. OWASP 인가 지침은 로그인 여부와 자원에 대한 권한을 구별하고 매 요청의 권한 검사를 권고한다.

실행 결과는 passed: 19와 각 사례의 stage·decision·reason이다. 정상 제안 외에 깨진 JSON, 중복 키, NaN, 누락·음수·추가 키, 다른 버전, 권한·통화·상태·의도 불일치를 확인했다. 중복 키와 비표준 숫자 상수의 거절은 이 검사기가 정한 파싱 정책이다. 이 결과는 작성한 입력에 대해 작성한 검사가 기대대로 분기했다는 증거다. 모델 출력의 정확도나 실제 환불 성공률이 아니다.

직접 바꿔 볼 때는 cases.json의 valid-proposal에서 raw의 금액만 5,000에서 8,000으로 바꿔 본다. 이제 스키마·잔액은 맞지만 확인 의도와 달라 INTENT_MISMATCH가 된다. 기존 기대값과 달라 테스트가 실패하는 것이 정상이다. 기대값부터 고치기 전에 어떤 경계가 달라졌는지 확인한다.

검사에 통과한 변경안에도 유효 기간이 있다

ready-for-commit은 환불 완료가 아니다. 검사에 사용한 상태에서는 실행 후보가 될 수 있다는 뜻이다. 예제는 파일에 들어 있는 고정 상태만 읽으므로, 검사와 실제 쓰기 사이에 다른 요청이 끼어드는 상황을 막지 못한다.

실제 서비스에서는 버전이 여전히 같은지, 잔액 조건이 여전히 유효한지 확인하면서 변경을 원자적으로 처리해야 한다. 외부 결제 시스템 호출에는 중복 요청 식별과 결과 확인도 필요하다. 이 글의 다운로드에는 쓰기·동시성 제어·결제·재시도 기능이 없다. 데이터베이스 안의 규칙과 동시 요청은 PostgreSQL 격리 수준과 쓰기 편향, 응답 유실 뒤 재시도는 에이전트의 완료·복구 기준으로 이어서 볼 수 있다.

또한 confirmed_intent를 모델에게 한 번 더 생성하게 했다고 독립적인 확인이 되지는 않는다. 예제는 별도 확인이 끝난 의도가 있다고 가정한다. 실제 제품에서는 사용자가 선택한 주문·금액, 검증된 시스템 필드, 필요한 경우 원문과 사람의 확인처럼 신뢰할 수 있는 근거를 정해야 한다. 추출 결과와 근거가 같은 오류를 공유한다면 두 값을 비교해도 오류가 남을 수 있다.

모든 AI 결과에 사람 승인을 붙이라는 뜻은 아니다. 읽기 전용 분류나 되돌리기 쉬운 초안은 용도에 맞는 자동 검사와 표본 검토로 운영할 수 있다. 돈·권한·외부 전송을 바꾸는 작업은 어떤 값과 근거를 확인해야 실행할 수 있는지 더 구체적으로 정할 필요가 있다.

재시도 버튼보다 실패 분류를 먼저 만들기

모든 실패에 “JSON으로 다시 답하라”를 적용하면 해결되지 않는 입력도 계속 생성한다. 실패 기록은 다음 변경을 고를 수 있을 만큼 구체적이어야 한다.

실패 종류 먼저 바꿀 것 반복해도 해결되지 않는 것
API 거절·중단·불완전 출력 종료 상태별 처리와 해당 공급자의 복구 절차 부분 출력을 정상 데이터로 처리하기
스키마 불일치 생성/수신 계약의 버전·지원 범위 존재하지 않는 권한을 프롬프트로 요청하기
원문과 값 불일치·미확인 근거 조회·추출 범위·사용자 확인 모델의 높은 자신감 숫자 믿기
최신 상태 충돌 상태 재조회와 변경안 재평가 오래된 상태로 같은 변경 반복하기
실행 여부 불명 업무 처리 상태 조회·중복 제어 새 작업으로 무조건 다시 보내기

운영 기록에는 계약 버전, 실패 단계, 정해진 이유 코드, 재조회·확인·실행 결과를 연결한다. 원문 전체나 고객 정보를 기본 로그에 복사할 필요는 없다. 예제의 상세 코드는 개발 진단용이며, 사용자에게 보여 줄 오류 메시지는 권한과 정보 노출 범위를 따로 정한다.

평가할 때도 분모를 나눈다. 전체 요청 중 거절·중단 수, 완결 출력 중 스키마 통과 수, 그중 근거·권한·상태 검사 통과 수, 실행 후보 중 실제 완료 수를 각각 세면 어디가 좋아졌는지 알 수 있다. 스키마 통과율이 올라도 원문 불일치가 그대로라면 자동화의 정확도가 그만큼 오른 것은 아니다. 이번 예제에는 실제 모델 요청이 없으므로 그런 통과율을 계산하지 않았다.

다음 구현 검토에는 “어떤 JSON인가”와 함께 “어떤 증거에서 왔고, 언제까지 유효하며, 실패하면 어디로 돌아가는가”를 적어 두자. 도구 연결의 책임 범위는 MCP와 업무 API, 관련 글은 AI 시스템 모음에서 이어서 읽을 수 있다. 검사표를 다시 쓸 계획이라면 글 위의 저장 기능을, 후속 글 확인에는 RSS를 사용할 수 있다.

다시 읽을 때의 기준
  • JSON 파싱 성공, 스키마 통과, 업무 실행 가능은 서로 다른 판정이다.
  • 스키마는 값의 범위와 일부 조건 관계도 검사한다. 원문의 사실·외부 권한·현재 상태까지 자동으로 확인하지는 않는다.
  • 생성 API의 지원 스키마와 서버의 전체 검증 계약을 구분하고, null과 거절·중단 경로를 설계한다.
  • 실패 이유에 따라 거절·원문 재확인·상태 재조회로 나눈다. 같은 입력의 재생성만 반복하지 않는다.
SOURCES & CONTEXT

근거와 원자료

자료의 발행 시점과 이번 글에서 참고한 범위를 함께 기록합니다.

  1. Structured outputs ↗Anthropic · Claude API Docs · 2026

    2026-10-11 live docs: constrained sampling, JSON output/strict tool distinction, numeric constraints unsupported, refusal/max_tokens exceptions. No API/SDK/model execution or benchmark; support is provider-specific.

  2. Understanding JSON Schema: object ↗JSON Schema · 2020-12

    properties, required, null versus absent, additionalProperties. Server Draft 2020-12 example; not a vendor generation-subset guarantee.

  3. JSON Schema Validation — Draft 2020-12 ↗JSON Schema · 2020-12

    Section 7: Format-Annotation versus Format-Assertion; implementation options and format validation boundaries. Not live business-state validation.

  4. Understanding JSON Schema: Conditional schema validation ↗JSON Schema · 2020-12

    Conditional/dependent validation: schema can express intra-document business constraints; avoids claiming it only checks primitive types.

  5. jsonschema 4.26.0 — Schema Validation ↗python-jsonschema maintainers · 4.26.0

    Draft202012Validator.check_schema/is_valid and opt-in FormatChecker. Offline Python 3.11.12 fixture execution recorded separately; no API request.

  6. Authorization Cheat Sheet ↗OWASP Cheat Sheet Series · 2026

    Authentication versus authorization; validate permissions on every request, deny by default. The article example is a limited server-context simulation, not a production authorization implementation.

갱신 기록

첫 원고. 공식 스키마·구조화 출력 문서를 대조하고 Python 검사기의 가상 반례 19개를 실행했다. 실제 모델 호출과 환불은 수행하지 않았다.

오류를 발견했다면 →
S
Starhunter

테크독해 운영·편집. AI·IT의 개념과 기술을 연결해 읽습니다.