AI가 JSON처럼 보이는 답을 냈다고 해서 자동화에 바로 넘겨도 되는 것은 아닙니다.
필수 항목·값의 범위·실행 권한을 먼저 고정해야, 한 번의 잘못된 출력이 다음 작업을 망가뜨리는 일을 막을 수 있습니다.
AI 에이전트가 고객 문의를 분류하고, 티켓을 만들고, 캘린더를 수정하고, 파일을 옮기는 흐름이 늘고 있습니다. 이때 가장 위험한 순간은 모델이 틀린 말을 할 때만이 아닙니다. 형식은 그럴듯하지만 자동화가 기대하지 않은 값을 반환할 때입니다.
예를 들어 우선순위가 high·medium·low 중 하나여야 하는데 urgent가 들어오거나, 담당자 ID가 비어 있는데도 후속 API가 실행되면 어떨까요? 이 글에서는 AI 출력과 실제 작업 사이에 스키마 검증을 두는 최소 설계를 정리합니다.

핵심 요약
- JSON 형식과 업무에 안전한 데이터는 다릅니다. 파싱 성공만으로 실행을 허용하면 안 됩니다.
- 계약에는 필수 필드, 자료형, 허용값, 비어 있으면 안 되는 조건, 실패 시 처리 경로가 들어가야 합니다.
- 모델의 구조화 출력 기능은 출발점일 뿐입니다. 실제 시스템에서는 서버 측 검증과 실행 전 권한 확인이 한 번 더 필요합니다.
- 검증 실패는 재시도·보류·사람 검토로 분기하고, 원문과 실패 이유를 남겨야 다음 개선이 가능합니다.
JSON이면 충분하지 않은 이유
JSON은 데이터를 주고받기 좋은 형식입니다. 하지만 중괄호와 따옴표가 맞는다는 사실은 업무 규칙까지 맞는다는 뜻이 아닙니다. JSON Schema는 데이터의 구조와 제약을 선언하고, 검증기가 문서가 그 규칙을 따르는지 확인하도록 돕습니다.
실무에서는 아래 세 층을 나눠 보시면 좋습니다.
| 층 | 확인할 것 | 막는 문제 |
|---|---|---|
| 형식 | JSON 파싱 가능 여부 | 깨진 응답, 설명 문장 혼입 |
| 스키마 | 필수 필드·자료형·허용값 | 없는 키, 잘못된 enum, 문자열/숫자 혼동 |
| 업무 규칙 | 권한·중복·상태 전이·금액 한도 | 잘못된 실행, 중복 처리, 승인 없는 변경 |
핵심은 모델에게 “정확한 JSON으로 답해 주세요”라고 요청하는 데서 멈추지 않는 것입니다. 그 응답을 어떤 조건에서 실행할지를 시스템이 독립적으로 판단해야 합니다.
최소 데이터 계약: 다섯 가지면 시작할 수 있습니다
큰 데이터 거버넌스 프로젝트부터 시작할 필요는 없습니다. 자동화 하나에 아래 다섯 항목만 문서로 고정해도 충분히 효과가 있습니다.
- 필수 필드: 예를 들어 티켓 생성에는
title,priority,requester_id가 반드시 필요합니다. - 허용값: 우선순위는 정해진 세 값만 받고, 도구 이름도 허용 목록 안에서만 선택합니다.
- 값의 범위: 마감일은 과거가 아니어야 하고, 금액이나 건수에는 상한을 둡니다.
- 실행 조건: 외부 시스템을 변경하는 작업은 신뢰 점수나 사람 승인을 추가로 요구합니다.
- 실패 처리: 검증 실패 시 자동 실행 대신 재질문·큐 보류·사람 검토 중 어디로 갈지 정합니다.

예시: 문의 분류 자동화에 검증을 넣는 법
아래는 AI가 생성한 결과를 바로 티켓 API로 보내지 않고, 먼저 애플리케이션에서 확인하는 단순한 예시입니다. 라이브러리 선택보다 중요한 것은 검증과 실행을 분리하는 구조입니다.
ALLOWED_PRIORITIES = {"low", "medium", "high"}
def validate_ticket(data: dict) -> list[str]:
errors = []
if not isinstance(data.get("title"), str) or not data["title"].strip():
errors.append("title은 비어 있지 않은 문자열이어야 합니다.")
if data.get("priority") not in ALLOWED_PRIORITIES:
errors.append("priority는 low, medium, high 중 하나여야 합니다.")
if not isinstance(data.get("requester_id"), str) or not data["requester_id"].strip():
errors.append("requester_id가 필요합니다.")
if data.get("action") != "create_ticket":
errors.append("허용되지 않은 작업입니다.")
return errors
result = {
"action": "create_ticket",
"title": "로그인 오류 문의",
"priority": "high",
"requester_id": "user_128"
}
errors = validate_ticket(result)
if errors:
# 실행하지 말고 원문·실패 이유와 함께 검토 큐로 보냅니다.
queue_for_review(result, errors)
else:
create_ticket(result) # 검증을 통과한 경우에만 외부 시스템을 변경합니다.
OpenAI의 Structured Outputs처럼 모델 출력이 지정한 JSON Schema를 따르도록 하는 기능은 유용합니다. 다만 공식 문서도 불완전한 응답이나 거절 같은 예외를 처리해야 한다고 안내합니다. 따라서 모델 응답 제약과 서버 측 검증은 경쟁 관계가 아니라 서로 다른 실패를 막는 두 겹의 안전망입니다.
검증 실패를 재시도로만 처리하면 생기는 문제
형식 오류라면 한 번의 재시도가 도움이 될 수 있습니다. 하지만 고객 ID가 없거나, 요청이 모호하거나, 허용되지 않은 실행을 요구하는 상황은 재시도한다고 해결되지 않을 수 있습니다. 이 경우 모델에 같은 질문을 반복하기보다 실패를 분류해야 합니다.
Great Expectations가 말하는 Expectation도 본질은 같습니다. 데이터가 이상적인 상태라고 기대하는 조건을 검증 가능한 단위로 만들고, 여러 조건을 묶어 운영한다는 접근입니다. AI 에이전트에도 이 관점을 가져오면 “좋은 답변” 대신 “실행 가능한 결과”를 관리하게 됩니다.

운영에 바로 쓰는 체크리스트
- □ 자동화가 받는 AI 출력의 필수 필드를 한 줄씩 적었습니다.
- □ 선택지·도구·상태값에 허용 목록을 만들었습니다.
- □ 검증 실패 시 외부 시스템을 바꾸지 않도록 막았습니다.
- □ 재시도·보류·사람 검토의 분기 기준을 정했습니다.
- □ 실패한 원문, 검증 오류, 최종 처리 결과를 나중에 볼 수 있게 남깁니다.
마무리: 프롬프트보다 먼저 고정할 것
프롬프트를 더 길게 쓰면 출력 모양은 좋아질 수 있습니다. 하지만 자동화의 안전성은 프롬프트 문장보다 검증 가능한 계약, 실행 전 확인, 실패 시 되돌아갈 경로에서 나옵니다.
다음 자동화 하나만 골라 보세요. 그 작업이 받는 필수 입력 세 개, 허용할 실행 한 가지, 실패했을 때 멈출 조건 한 가지를 적는 것만으로도 훨씬 안전한 출발점이 됩니다.
자주 묻는 질문
구조화된 출력 기능을 쓰면 서버 검증은 필요 없나요?
필요합니다. 구조화된 출력은 모델 응답의 형식을 안정시키는 데 도움이 되지만, 사용자 권한·중복 요청·현재 시스템 상태처럼 모델 밖의 업무 규칙까지 대신 확인하지는 않습니다.
스키마는 언제부터 만들어야 하나요?
AI 결과가 다른 시스템의 입력이 되는 순간부터가 좋습니다. 특히 티켓 생성, 메시지 발송, 파일 이동, 결제·예약·권한 변경처럼 되돌리기 어려운 작업은 처음부터 검증 경계를 두는 편이 안전합니다.
검증에 실패한 데이터를 버려야 하나요?
바로 버리기보다 원문과 실패 이유를 남기고 분류하세요. 반복되는 실패는 프롬프트, 입력 수집, 스키마, 업무 규칙 중 어디를 개선해야 하는지 알려주는 운영 데이터가 됩니다.
참고 자료
이 글이 마음에 드세요?
FLOWIT on YouTube
영상으로도 FLOWIT을 이어서 보세요
AI 자동화, Claude Code, n8n, 데이터 분석 흐름을 블로그와 영상으로 함께 정리하고 있습니다.
Claude API 400 오류, adaptive thinking 전환 전 점검할 것
Gemini 3.6 Flash와 Flash-Lite, AI 에이전트에 어떻게 나눠 쓸까
AI가 이슈를 닫기 시작했다면: GitHub 자동화에 승인·근거·되돌리기를 넣는 법