LLM이 유효한 JSON을 반환하기를 기도하는 것을 멈추세요: Next.js 15에서 게이트 레벨 스키마를 강제하는 방법
요약
LLM의 비결정론적인 JSON 응답으로 인한 런타임 오류를 방지하기 위해 Next.js 15에서 게이트웨이 레벨의 스키마 검증을 구현하는 방법을 다룹니다. Zod와 정제 로직을 사용하여 마크다운 아티팩트를 제거하고 데이터의 무결성을 보장하는 프로덕션 패턴을 소개합니다.
핵심 포인트
- LLM 응답의 마크다운 코드 블록은 JSON.parse() 오류의 주요 원인임
- Zod를 활용하여 API 경계에서 엄격한 스키마 검증이 필요함
- 마크다운 아티팩트를 제거하는 정제(Sanitization) 단계가 필수적임
- 검증 로직을 애플리케이션 로직과 분리하여 게이트웨이 레벨에서 관리 권장
API 핸들러 내의 JSON.parse()가 왜 운영 환경의 장애를 초래할 수 있는 잠재적 요인인지, 그리고 SpaceAI360에서 어떻게 결정론적인 게이트웨이 검증 (Gateway Validation)을 설계했는지에 대해 설명합니다.
AI 기반 기능을 구축하는 모든 개발자는 정확히 똑같은 '허니문 단계'를 거칩니다.
Gemini나 Claude에게 구조화된 JSON 응답을 반환하도록 요청하는 깔끔한 시스템 프롬프트 (System Prompt)를 작성합니다:
다음 구조와 일치하는 유효한 JSON 객체만 반환하세요:
{ "summary": string, "sentiment": "positive" | "negative", "score": number }
마크다운 코드 블록이나 대화형 텍스트를 포함하지 마세요.
테스트의 90%에서는 완벽하게 작동합니다. 프로덕션에 배포하고, 커피 한 잔을 마시며 스스로 천재라고 느낍니다.
그러다 새벽 2시, 클라이언트가 엣지 케이스 (Edge Case) 페이로드를 입력하거나 (또는 제공업체가 모델 가중치를 업데이트하면), LLM이 갑자기 응답을 백틱 세 개(triple backticks)로 감싸버립니다:
{
"summary": "User reported a bug.",
"sentiment": "negative",
...
백엔드에서 JSON.parse()를 실행하면, 이스케이프 처리되지 않은 마크다운이나 타입 불일치로 인해 크래시가 발생하고, 프론트엔드에는 처리되지 않은 500 Internal Server Error를 반환하며 UI 상태를 망가뜨립니다.
**SpaceAI360**에서 우리는 이 규칙을 일찍 배웠습니다: 검증되지 않은 LLM 출력값이 API 경계를 넘어가게 두지 마세요.
다음은 데이터가 데이터베이스나 클라이언트 애플리케이션에 닿기 전에 100% 결정론적인 JSON 스키마 (JSON Schemas)를 보장하기 위해 우리가 Next.js 15 (Route Handlers)에서 사용하는 실제 프로덕션용 패턴입니다.
결함이 있는 접근 방식: JSON.parse()를 신뢰하기
대부분의 팀은 LLM 응답을 다음과 같이 처리합니다:
// ❌ 나쁜 예: 처리되지 않은 예외를 발생시키는 취약한 파싱
const rawText = await callLLM(prompt);
const data = JSON.parse(rawText); // 모델이 백틱을 추가하거나 잘못된 타입을 넣으면 크래시 발생
...
해결책: Zod 및 폴백 파싱 (Fallback Parsing)을 통한 게이트웨이 스키마 강제
우리는 검증 로직을 애플리케이션 로직에서 분리하여 API 게이트웨이 (API Gateway) 레벨에서 엄격하게 강제합니다.
다음은 TypeScript, Zod, 그리고 구조적 제거 (Structured Stripping)를 사용하여 Next.js 15에서 구현한 우리의 프로덕션 설정입니다.
1단계: 엄격한 타겟 스키마 (Strict Target Schema) 정의
// lib/schemas/analysis.ts
import { z } from 'zod';
...
2단계: 정제 및 검증 (Sanitization & Validation) 헬퍼
원문 텍스트를 Zod에 전달하기 전에, 모델에서 흔히 발생하는 마크다운 아티팩트 (markdown artifacts)를 안전하게 제거합니다.
// lib/utils/sanitize-json.ts
export function extractCleanJson(rawString: string): string {
// 마크다운 코드 펜스 (code fences)가 존재하는 경우 제거합니다 (예: ```json ... ```)
const cleaned = rawString
.replace(/^```(?:json)?\s*/i, '')
.replace(/\s*```$/, '')
.trim();
return cleaned;
...
3단계: 자동 재시도 (Automatic Retries) 기능이 포함된 프로덕션 라우트 핸들러 (Route Handler)
LLM이 유효하지 않은 JSON을 반환하더라도 HTTP 요청이 중단되지 않도록 하세요. 우아하게 실패 (fail gracefully)하기 전에 단일 재시도 복구 루프 (single-retry repair loop)를 사용합니다.
// app/api/analyze/route.ts
import { NextResponse } from 'next/server';
...
아키텍처적 이점 (Architectural Benefits)
런타임 UI 크래시 제로 (Zero Runtime UI Crashes): 프론트엔드 컴포넌트는 특정 속성이 undefined이거나 잘못된 타입일지 걱정할 필요 없이 정확한 TypeScript 인터페이스를 신뢰할 수 있습니다.
결정론적 DB 쓰기 (Deterministic DB Writes): 데이터베이스 삽입 (inserts)은 잘못된 타입이나 누락된 필드로 인해 절대 실패하지 않습니다.
우아한 성능 저하 (Graceful Degradation): 모델이 두 번 실패하면, API는 요청이 중단되거나 일반적인 500 에러를 반환하는 대신 깔끔한 422 Unprocessable Entity 에러를 반환합니다.
2026년 AI 엔지니어링을 위한 시사점
LLM은 근본적으로 비결정론적 (non-deterministic) 엔진입니다. 외부 가드레일 (guardrails) 없이 LLM이 경직된 REST 엔드포인트처럼 동작하기를 기대하는 것은 백엔드 설계의 결함입니다.
모델 응답을 신뢰할 수 없는 사용자 입력 (untrusted user inputs)처럼 취급하세요: 정제(Sanitize)하고, Zod로 검증(Validate)하며, 강력한 폴백(Hard Fallbacks)을 강제하세요.
외부 AI 제공업체와 작업할 때 귀하의 백엔드 팀은 스키마 검증 (schema validation)을 어떻게 처리하고 있나요? 댓글에서 논의해 봅시다!
Founder, SpaceAI360
우리는 고성능 웹 애플리케이션, 디커플링된 백엔드 아키텍처, 그리고 프로덕션급 자동화 시스템을 설계합니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기