AI가 자유로운 문장 대신 정확한 JSON을 반환하면 운영 시스템에 바로 연결해도 될까요? 문법적으로 완벽한 JSON 안에 존재하지 않는 고객 ID, 잘못된 통화, 미래보다 늦은 시작일, 승인 한도를 넘는 금액이 들어갈 수 있습니다. 스키마에 맞는 도구 호출도 현재 사용자가 그 주문을 수정할 권한이 있다는 뜻은 아닙니다. 형식이 맞는 것과 업무적으로 실행 가능한 것은 전혀 다른 문제입니다.
구조화 출력(Structured Outputs)은 AI 애플리케이션의 중요한 발전입니다. 문자열에서 JSON을 억지로 잘라내거나 깨진 괄호를 복구하던 코드를 크게 줄이고, 모델과 애플리케이션 사이의 계약을 명확히 만들 수 있습니다. 하지만 이 기능의 경계를 오해하면 “파싱 성공”을 “업무 검증 성공”으로 착각하게 됩니다.
이 글에서는 구조화 출력과 함수 호출을 언제 구분해 쓰는지, 좋은 스키마를 어떻게 만들지, 모델 결과가 실제 ERP·CRM·결제·문서 시스템에 영향을 주기 전에 어떤 검증과 상태 머신을 거쳐야 하는지 정리합니다. 결론부터 말하면 모델 출력은 신뢰 경계 밖의 입력이며, 스키마 검증을 통과한 뒤에도 일반 API 요청과 같은 서버 측 업무·권한 검증을 받아야 합니다.
JSON 모드와 구조화 출력은 다르다
JSON 모드는 결과가 유효한 JSON 문법을 따르게 만드는 데 초점을 둡니다. 객체의 필드 이름, 필수값, 타입, 열거형이 우리가 기대한 계약과 일치하는지는 별개의 문제입니다. 구조화 출력은 지원되는 JSON Schema를 제공해 결과가 그 형태를 따르도록 제한합니다.
OpenAI 공식 문서는 JSON 모드와 달리 Structured Outputs가 지원되는 스키마 준수를 보장하며, 가능하면 구조화 출력을 사용하도록 안내합니다. 함수 호출의 엄격 모드에서는 객체마다 추가 속성을 허용하지 않고 모든 속성을 필수로 표시하는 등 호환 가능한 스키마 규칙을 요구합니다. Google Gemini와 Amazon Bedrock도 JSON Schema 기반 구조화 출력을 제공하지만 각 플랫폼이 지원하는 스키마 하위 집합은 다릅니다.
여기서 ‘스키마 준수’의 의미를 정확히 이해해야 합니다. 문자열 필드에 문자열이 들어가고 정해진 키가 존재한다는 뜻이지, 그 문자열이 실제 고객명인지 또는 금액이 계약 한도 안인지까지 증명하는 것은 아닙니다. Google의 공식 가이드도 문법적으로 올바른 JSON과 별개로 최종 값의 의미를 애플리케이션에서 검증해야 한다고 명시합니다.
구조화 출력과 함수 호출을 구분한다
두 기능 모두 JSON Schema를 사용하지만 목적이 다릅니다. 사용자의 문서를 추출해 화면에 표시할 구조화된 결과를 만들거나 분류 결과를 반환하려면 응답 형식의 구조화 출력이 알맞습니다. 모델이 데이터 조회, 이메일 발송, 주문 변경 같은 애플리케이션 기능을 요청해야 한다면 함수 또는 도구 호출을 사용합니다.
OpenAI 문서도 모델을 시스템의 도구·기능·데이터에 연결할 때는 함수 호출을, 사용자에게 보낼 최종 응답을 일정한 형태로 만들 때는 구조화된 응답 형식을 사용하도록 구분합니다. 실전에서는 둘을 함께 쓰기도 합니다. 에이전트가 도구 호출로 데이터를 모은 뒤 최종 답변을 UI 카드 스키마로 반환하는 식입니다.
가장 피해야 할 방식은 일반 텍스트 응답 안에서 “실행할 함수와 인자를 JSON으로 써 달라”고 요청한 뒤 문자열을 파싱해 바로 실행하는 것입니다. 도구 호출 식별자, 결과 연결, 거절과 중단 상태를 다루기 어렵고 실행 경계가 모호해집니다.
검증은 다섯 개의 서로 다른 질문이다
1. 전송과 생성 완료 상태
HTTP 요청이 성공했는지, 응답이 길이 제한 때문에 중단되지 않았는지, 안전 정책에 따른 거절인지, 콘텐츠 필터 또는 공급자 오류가 있었는지 먼저 확인합니다. 부분 응답을 완성된 JSON처럼 복구해 실행하면 안 됩니다. OpenAI Structured Outputs 문서도 길이 초과와 거절을 정상 구조화 결과와 별도로 처리하는 예시를 제공합니다.
2. 구문 검증
응답이 JSON으로 파싱되는지 확인합니다. 오래된 JSON 모드나 비엄격 출력에서는 코드 블록, 앞뒤 설명, 잘린 배열이 섞일 수 있습니다. 정규식으로 중괄호 구간만 추출하는 임시 처리는 중첩 구조와 문자열 안의 기호에서 쉽게 깨집니다. 공급자 SDK의 파싱 기능과 표준 JSON 파서를 사용하고 실패를 명시적 상태로 다룹니다.
3. 스키마와 타입 검증
필수 필드, 타입, 배열 구조, 열거형, 추가 속성, 형식과 길이를 검사합니다. 모델 API가 엄격 모드를 지원해도 애플리케이션 경계에서 같은 스키마로 다시 검증하면 공급자 변경, 저장된 응답 재처리, 테스트 입력에도 일관된 계약을 적용할 수 있습니다.
4. 의미와 업무 규칙 검증
시작일이 종료일보다 빠른지, 금액이 0보다 크고 승인 한도 안인지, 통화와 세율의 조합이 가능한지, 고객 ID가 실제 존재하는지 확인합니다. 필드 하나씩은 유효해도 조합이 잘못될 수 있습니다. 이 규칙은 모델 프롬프트가 아니라 도메인 코드와 정책 엔진에서 결정적으로 실행해야 합니다.
5. 권한과 최신 상태 검증
현재 사용자와 에이전트가 대상 자원에 해당 행동을 수행할 권한이 있는지, 레코드가 아직 같은 버전인지, 이미 취소 또는 처리되지 않았는지 실행 직전에 검사합니다. 모델이 과거 대화에서 본 상태는 권한 증거나 최신 데이터가 아닙니다.
스키마는 데이터 모양이 아니라 업무 계약이다
좋은 스키마는 객체의 키만 나열하지 않습니다. 각 필드가 무엇을 의미하고 어떤 단위와 출처를 갖는지 설명합니다. 이름은 짧게 줄이기보다 사람이 이해할 수 있게 쓰고, 설명에는 모델이 값을 선택할 기준을 넣습니다. 다만 실제 강제 규칙은 애플리케이션 검증기로 구현합니다.
예를 들어 청구서 추출 결과에 단순히 amount: number만 두면 공급가액, 세금 포함 총액, 문자열의 쉼표를 제거한 값 중 무엇인지 불명확합니다. net_amount, tax_amount, gross_amount, currency를 나누고 합계 관계를 업무 규칙으로 검증해야 합니다.
날짜에는 시간대와 기준일을, 비율에는 0~1인지 0~100인지, 무게에는 단위를 명시합니다. 돈은 부동소수점보다 통화와 최소 단위 정수 또는 정확한 십진수 표현을 고려합니다. 사용자에게 보여줄 이름과 시스템이 사용하는 안정적인 ID도 분리합니다.
필수, 선택, null, 빈 문자열을 구분한다
데이터 추출에서 값이 없다는 사실은 중요한 정보입니다. 필드 누락, null, 빈 문자열, 0, 빈 배열은 서로 다른 의미를 가집니다. 모델이 모르는 값을 추측하지 않게 하려면 필요한 필드를 항상 반환하되 값이 없을 때 null을 허용하는 방식이 유용합니다.
- 필드 누락: 계약 위반 또는 응답 불완전
- null: 문서나 상황에서 값을 확인할 수 없음
- 빈 문자열: 값은 문자열이지만 내용이 없음—대개 별도 검증 필요
- 0: 확인된 숫자 0이며 ‘모름’과 다름
- 빈 배열: 확인 결과 항목이 없으며 검색하지 않았다는 뜻과 다름
스키마 설명과 프롬프트에 “근거가 없으면 null, 추측 금지”를 명시하고, 중요한 추출값에는 근거 위치나 원문 조각의 참조를 함께 받습니다. 근거는 사용자에게 설명하거나 사람이 검토할 때 유용하지만, 원문과 일치하는지는 별도로 확인해야 합니다.
추가 속성을 닫고 열거형을 적극 활용한다
JSON Schema는 기본적으로 정의하지 않은 추가 속성을 허용할 수 있습니다. JSON Schema 공식 문서는 additionalProperties: false를 사용하면 명시하지 않은 필드를 거부할 수 있다고 설명합니다. 모델 도구 호출에서는 예상하지 않은 키가 실행 코드로 흘러가는 것을 막기 위해 닫힌 객체가 기본값에 가깝습니다.
상태, 통화, 작업 종류처럼 가능한 값이 제한된 필드는 자유 문자열보다 열거형을 사용합니다. “승인”, “approved”, “확정”처럼 의미가 같은 표현이 제각각 들어오면 후속 분기가 복잡해집니다. 열거형에 없는 새로운 상태가 필요하면 모델이 임의로 만들게 하지 말고 스키마 버전을 변경합니다.
반대로 주소, 고객 요청, 설명처럼 본질적으로 자유로운 텍스트를 무리한 열거형으로 만들면 정보가 손실됩니다. 구조화할 수 있는 결정값과 사람이 읽을 설명을 별도 필드로 나눕니다.
스키마에 맞는 거짓말을 막을 수는 없다
모델은 존재하지 않는 고객 ID를 올바른 문자열 형식으로 만들 수 있습니다. 합계 필드도 타입은 숫자지만 원문과 다를 수 있습니다. 구조화 출력은 생성 공간을 줄여도 사실 검증을 대신하지 않습니다.
추출 업무에서는 원문 근거와 페이지·문단 위치를 함께 반환받고 결정적 코드로 다시 계산할 수 있는 값은 재계산합니다. 분류 업무에서는 신뢰도 숫자를 그대로 믿기보다 평가 데이터셋에서 실제 정확도와 보정 정도를 확인합니다. 도구 호출에서는 이름 대신 서버가 조회한 안정적인 ID를 사용하고, 모델이 제안한 ID는 존재 여부와 접근 범위를 검사합니다.
Google의 구조화 출력 가이드가 구문적으로 유효한 JSON이어도 값이 의미적으로 잘못될 수 있으므로 최종 검증과 오류 처리가 필요하다고 강조하는 이유입니다.
‘계획 생성’과 ‘부작용 실행’을 분리한다
모델이 만든 구조체를 바로 데이터베이스 저장 함수에 넘기지 않습니다. 먼저 실행 계획 또는 명령 후보로 저장하고, 검증기가 이를 정규화하고 업무·권한 규칙을 적용합니다. 검증된 명령만 별도의 실행기가 처리합니다. 이 분리는 AI 공급자를 신뢰 경계 밖에 두고 기존 서비스 계층의 규칙을 재사용하게 합니다.
예를 들어 환불 에이전트는 order_id, requested_amount, reason_code를 제안할 수 있습니다. 실행기는 현재 주문, 실제 결제액, 이미 환불된 금액, 요청자의 권한, 승인 한도를 조회해 최종 허용 금액을 결정합니다. 모델이 “검증 완료” 필드를 true로 반환해도 아무 효력이 없어야 합니다.
OWASP 입력 검증 가이드는 신뢰할 수 없는 입력을 구문과 의미 수준에서 모두 서버 측 검증하고, 허용 목록을 우선 사용하도록 권고합니다. 모델 결과도 외부 파트너 피드나 사용자 요청과 마찬가지로 신뢰하지 않는 입력으로 취급하면 기존 보안 원칙을 그대로 적용할 수 있습니다.
도구 설명에는 실행 계약을 쓴다
도구 이름과 필드만 제공하면 모델은 언제 사용하고 어떤 부작용이 있는지 추측해야 합니다. 설명에는 도구의 목적, 호출해야 하는 조건, 호출하면 안 되는 조건, 필수 선행 조회, 각 인자의 의미, 성공과 실패 결과, 재시도 안전성을 간결하게 적습니다.
읽기 도구와 쓰기 도구를 분리하고, ‘고객 관리’처럼 여러 행동을 한 함수에 몰아넣지 않습니다. 초안 생성과 실제 발송도 별도 도구로 나누면 승인 경계를 명확히 만들 수 있습니다. 모델이 제공할 필요가 없는 사용자 ID, 권한 범위, 테넌트 ID는 서버 실행 문맥에서 주입해 조작 가능성을 줄입니다.
거절과 불완전 응답은 정상적인 상태다
구조화 출력이라고 해서 항상 스키마 객체가 오는 것은 아닙니다. 안전 정책에 따른 거절, 최대 출력 길이, 시간 초과, 콘텐츠 필터, 공급자 장애를 별도 상태로 모델링합니다. 빈 객체나 기본값으로 바꿔 정상 처리하면 원인을 숨기고 위험한 실행을 만들 수 있습니다.
- 거절: 사용자에게 안전한 안내를 제공하거나 허용된 대체 경로로 전환
- 길이 초과: 입력을 나누거나 출력 범위를 줄인 뒤 새 실행으로 재시도
- 스키마 오류: 모델·스키마 호환성과 프롬프트를 확인하고 제한 횟수만 재시도
- 업무 규칙 위반: 임의 수정하지 말고 필요한 정보를 재조회하거나 사용자에게 확인
- 권한 부족: 더 강한 자격 증명으로 자동 재시도하지 않고 승인 또는 접근 요청으로 전환
자동 복구와 재시도에는 한도가 필요하다
깨진 JSON을 모델에게 다시 고쳐 달라고 요청하는 복구는 읽기 전용 추출 작업에서 도움이 될 수 있습니다. 그러나 의미가 틀린 값을 형식만 고치면 더 위험한 정상 객체가 됩니다. 무엇이 잘못됐는지 구조화된 검증 오류를 전달하고, 모델이 수정할 수 있는 오류와 원본 조회가 필요한 오류를 구분합니다.
재시도는 횟수, 시간, 토큰 비용에 한도를 둡니다. 동일 입력과 동일 오류가 반복되면 사람 검토 또는 실패 큐로 보냅니다. 쓰기 작업은 재시도 전에 실제 시스템 상태를 조회하고 멱등성 키를 사용해 같은 주문이나 메시지가 두 번 처리되지 않게 합니다.
모델이 만든 ‘repair’ 결과가 원래 승인 범위를 벗어나면 다시 승인받아야 합니다. 금액이나 수신자처럼 중요한 값이 달라졌는데 기존 승인을 재사용하면 안 됩니다.
최신 상태와 동시성은 스키마 밖의 문제다
모델이 주문 상태를 조회한 뒤 변경 명령을 만들기까지 다른 사용자가 주문을 취소할 수 있습니다. 출력에는 올바른 주문 ID와 상태가 들어 있어도 이미 오래된 판단입니다. 실행 요청에 조회 당시 레코드 버전이나 ETag를 포함하고, 서버가 낙관적 동시성 검사를 수행해야 합니다.
버전이 달라지면 모델의 명령을 억지로 적용하지 말고 최신 데이터를 기준으로 다시 계획합니다. 특히 금액, 재고, 예약, 권한은 실행 직전 재검증합니다. 구조화된 출력의 안정성이 분산 시스템의 경쟁 조건을 해결해 주지는 않습니다.
스키마도 버전이 있는 API다
필드를 추가하거나 열거형을 바꾸면 저장된 응답, 평가 데이터, 재처리 작업과 소비 서비스가 영향을 받습니다. 스키마 이름과 버전을 응답 및 추적 정보에 남기고, 공급자 요청 스키마와 내부 도메인 명령을 분리하는 편이 좋습니다.
외부 모델 스키마는 각 공급자가 지원하는 JSON Schema 하위 집합에 맞춰 단순하게 유지합니다. 내부에서는 더 풍부한 검증 라이브러리로 업무 제약을 표현합니다. 공급자별 스키마 어댑터가 내부 명령으로 변환하면 모델을 교체할 때 업무 서비스 전체를 바꾸지 않아도 됩니다.
호환 변경은 새 선택 필드 추가처럼 구버전 소비자가 무시할 수 있는 범위에서 하고, 필드 의미 변경과 타입 변경은 새 버전으로 냅니다. 일정 기간 두 버전을 함께 읽는 마이그레이션 전략과 관측 지표를 둡니다.
평가는 파싱 성공률로 끝나지 않는다
구조화 출력의 품질 지표는 단계별로 나눠야 합니다.
- 완전 응답률과 거절·길이 초과·공급자 오류율
- JSON 파싱 성공률과 스키마 검증 성공률
- 필드별 추출 정확도, 누락률, 잘못된 추정률
- 업무 규칙 위반률과 권한·최신 상태 차단률
- 자동 복구 성공률, 평균 재시도 수, 사람 검토율
- 멱등성 충돌과 중복 부작용 건수
- 검증된 업무 성공 건당 비용과 지연시간
테스트 데이터에는 빈 문서, 모호한 날짜, 여러 통화, 잘못된 합계, 존재하지 않는 ID, 중복 항목, 매우 긴 입력, 악성 지시, 권한이 없는 자원을 포함합니다. 모델과 프롬프트뿐 아니라 스키마와 업무 검증기 버전을 함께 비교해야 합니다.
30일 도입 로드맵
- 1주차 — 계약 정의: 자유 텍스트 파싱 구간과 직접 실행 구간을 목록화하고, 한 업무의 구조화 출력과 도구 호출을 분리합니다. 누락·null·단위·ID·열거형 의미를 정합니다.
- 2주차 — 검증 계층: 전송 상태, JSON, 스키마, 업무 규칙, 권한과 최신 상태 검증을 별도 결과 코드로 구현합니다. 모델 결과를 기존 서버 측 검증기로 통과시킵니다.
- 3주차 — 안전 실행: 계획과 부작용 실행을 분리하고 승인, 멱등성 키, 낙관적 동시성, 결과 재조회와 실패 큐를 연결합니다.
- 4주차 — 평가와 버전: 실제 문서와 예외 사례로 필드·업무 성공률을 측정하고 스키마 버전, 공급자 어댑터, 제한 재시도와 카나리아 배포를 적용합니다.
처음에는 이메일 분류나 문서 메타데이터 추출처럼 되돌릴 수 있는 읽기 중심 업무가 좋습니다. 구조화 결과를 사람이 확인하며 오류 유형을 모은 뒤, 결정적인 검증과 승인이 갖춰진 필드부터 자동 실행 범위를 넓힙니다.
Nature Fractal이 보는 구조화 AI 연동의 핵심
기업 AI 프로젝트의 안정성은 모델이 얼마나 자연스럽게 답하는가보다 모델과 기존 시스템 사이의 계약이 얼마나 명확한가에 달려 있습니다. JSON Schema는 그 계약의 입구를 정리하지만 ERP, CRM, 결제와 승인 시스템의 실제 규칙은 기존 애플리케이션 계층이 소유해야 합니다.
Nature Fractal은 모델 출력을 바로 데이터베이스에 연결하지 않고, 공급자별 구조화 출력에서 내부 도메인 명령으로 변환한 뒤 검증·권한·승인·멱등성·상태 확인을 거쳐 실행하는 방식을 중요하게 봅니다. 이렇게 해야 모델이나 공급자를 교체해도 업무 규칙과 안전 경계가 흔들리지 않습니다.
도입 전 체크리스트
- JSON 모드와 스키마 기반 구조화 출력을 구분했는가?
- 최종 응답과 도구 호출에 맞는 기능을 각각 사용하는가?
- 거절·길이 초과·필터·공급자 오류를 정상 객체와 구분하는가?
- 필수·null·빈 값·추정 값의 의미가 정의되어 있는가?
- 추가 속성을 닫고 제한된 값에 열거형을 사용하는가?
- 날짜·시간대·금액·통화·단위·ID가 명확한가?
- 스키마 이후에 의미와 교차 필드 업무 규칙을 검증하는가?
- 사용자와 에이전트의 권한, 대상의 최신 상태를 다시 확인하는가?
- 모델의 계획과 실제 부작용 실행이 분리되어 있는가?
- 쓰기 작업에 승인·멱등성·동시성·결과 검증이 있는가?
- 오류 유형별 복구 경로와 재시도 한도가 있는가?
- 스키마와 검증기를 버전 관리하고 실제 업무 성공률을 평가하는가?
구조화 출력은 AI를 운영 시스템에 연결하는 훌륭한 출발점입니다. 그러나 JSON이 맞다는 이유로 곧바로 실행하면 모델의 의미 오류가 정확한 데이터 형식을 입고 더 깊숙이 들어옵니다. 형식은 모델과의 계약으로, 의미·권한·상태는 애플리케이션의 결정적 검증으로 지키는 것이 안전한 AI 통합의 핵심입니다.