Developers can now guarantee that the JSON an LLM returns conforms to a predefined shape by wiring Zod schemas into the Vercel AI SDK or Anthropic’s tool-use API, eliminating the runtime crashes that happen when a model adds an unexpected field.
구체적인 가드(guard)의 필요성은 지난 1월, 프로덕션에 배포된 분류기(classifier)가 3주간 완벽하게 작동하다가 갑자기 두 번째 "explanation" 키를 반환하기 시작하면서 명확해졌습니다. 코드는 단일 필드만을 예상했기 때문에, 추가된 키로 인해 별도의 코드 배포 없이도 예외(exception)가 발생했습니다. 이 사건은 더 넓은 문제를 보여줍니다. 대부분의 튜토리얼은 모델이 프롬프트의 스키마를 준수할 것이라고 가정하고 JSON.parse(response) 단계에서 멈춥니다. 하지만 실제로 LLM은 대소문자를 변경하거나, 필드를 추가하거나, 출력을 마크다운 펜스(markdown fences)로 감싸는 등 빈번하게 경로를 벗어나며, 이는 조용한 데이터 오염이나 완전한 실패로 이어집니다.
원시 JSON 파싱이 안전하지 않은 이유
LLM은 순종하기 위해서가 아니라 도움이 되도록 훈련되었습니다. 다음과 같은 프롬프트는
{ "category": "string" }
모델을 해당 구조에 정확히 묶어두지 못합니다. 잘 작성된 프롬프트라 할지라도 모델의 내부 휴리스틱(heuristics)에 의해 무시될 수 있으며, 특히 temperature 설정이 창의성을 장려하거나 하위 명령이 상세한 설명을 유도할 때 더욱 그렇습니다. 그 결과, JSON처럼 보이지만 엄격한 형태를 기대하는 파서(parser)를 깨뜨릴 정도로 변형된 텍스트 스트림이 생성됩니다.
이러한 불일치가 프로덕션 코드에 도달하면 그 대가는 즉각적입니다. 예외 발생, 요청 실패, 그리고 잠재적으로 하위 시스템의 연쇄적인 오류로 이어집니다. 대규모 서비스에서 이러한 몇 분간의 다운타임은 매출 손실과 사용자 신뢰 저하로 직결됩니다.
Zod + Vercel AI SDK: 3단계 안전망
Zod는 모델이 출력해야 하는 정확한 데이터 형태를 기술할 수 있는 TypeScript 우선 스키마 검증기(validator)입니다. Vercel AI SDK의 Output.object 헬퍼와 결합하면, 모델이 응답을 생성한 직후 자동으로 검증이 이루어집니다.
- 스키마 정의 – 원하는 JSON을 반영하는 Zod 객체를 작성합니다. 간단한 분류기의 경우
z.object({ category: z.string() })가 될 수 있고, 복잡한 인보이스 추출기의 경우 스키마에 객체, 배열, discriminated unions를 중첩할 수 있습니다. - SDK에 전달 – 스키마를
Output.object(schema)로 감쌉니다. SDK는 모델에게 스키마와 일치하는 JSON 블록을 출력하도록 지시하는 프롬프트를 주입하고, Zod의safeParse를 사용하여 결과를 파싱합니다. - 실패 처리 –
safeParse는 예외를 던지는 대신 결과 객체를 반환합니다. 파싱에 실패하면 오류를 모델에 다시 전달하고 재시도합니다. 모델에 정확한 검증 메시지를 기반으로 출력을 수정하도록 지시할 수 있어, 대부분의 예외 케이스를 자가 치유(self-healing) 루프로 전환할 수 있습니다.
SDK가 프롬프트 작성, 파싱, 재시도 로직을 한 곳에서 처리하므로, 개발자는 몇 가지 임시적인 문자열 조작 코드를 단일한 타입 체크 호출로 대체할 수 있습니다.
Anthropic tool use: 구조화된 출력 강제하기
Anthropic API를 직접 사용하는 경우, "tool use"를 통해 동일한 보장을 달성할 수 있습니다. 도구(tool)는 입력 스키마가 JSON Schema로 표현된 함수로 정의됩니다. Anthropic 모델은 스키마를 충족할 수 있는 경우에만 도구를 호출합니다. tool_choice를 "any"(또는 특정 도구 이름)로 설정하면, 모델은 자유 형식의 텍스트 대신 구조화된 블록을 반환하도록 강제됩니다.
워크플로우는 Vercel의 방식과 유사합니다:
- Zod 스키마를 작성합니다.
- 이를 도구 정의를 위한 JSON Schema 페이로드로 변환합니다.
- 요청에 도구를 포함하고 모델이 이를 호출하도록 요구합니다.
zod.safeParse로 도구의 응답을 파싱합니다.
모델이 여전히 잘못된 형식의 데이터를 생성하는 경우에도 동일한 '피드백을 포함한 재시도' 패턴을 적용할 수 있습니다.
검증이 여전히 실패하는 경우
스키마 강제 적용을 하더라도 간혹 불일치가 발생할 수 있습니다. 원인은 다음과 같습니다:
- 모델 환각(Hallucination): 모델이 JSON처럼 보이지만 구문 오류가 포함된 문자열을 생성할 수 있습니다.
- 프롬프트 누출(Prompt leakage): 이전 대화 단계에서 스키마 요청을 무시하는 형식 지침이 누출될 수 있습니다.
- 버전 차이: 새로운 모델 버전이 출시되면서 도구 호출(tool calls)을 해석하는 방식이 변경될 수 있습니다.
권장되는 완화 방법은 가벼운 재시도 루프를 사용하는 것입니다. 파싱 실패 시, 코드는 "마지막 출력이 유효한 JSON이 아니었습니다. ...이 포함되어 있습니다. 스키마에 정의된 필드만 반환해 주세요."와 같은 후속 프롬프트를 보냅니다. 검증 오류가 명시적이기 때문에 모델은 사람의 개입 없이 스스로를 수정할 수 있습니다.
성능 및 비용 고려 사항
Zod 검증을 추가해도 CPU 오버헤드는 무시할 수 있는 수준입니다. 일반적인 페이로드에 대해 safeParse 작업은 마이크로초 단위로 실행됩니다. 네트워크 지연 시간은 변하지 않으며, 재시도를 위한 추가 왕복(round-trip)은 드문 실패 사례에서만 발생합니다. 실제로 예외(exception) 하나를 방지함으로써 얻는 이득이 요청 시간의 미미한 증가보다 훨씬 큽니다.
반론: 스키마 강제가 과도한 처사인가?
일부 개발자들은 엄격한 스키마가 모델의 유연성을 제한한다고 주장하며, 특히 새로운 필드가 유용한 컨텍스트를 제공할 수 있는 상황에서 더욱 그렇다고 말합니다. 이는 안전성과 개방성 사이의 트레이드오프입니다. 결제 처리, 신원 확인, 컴플라이언스 보고와 같은 미션 크리티컬(mission-critical) 서비스에서는 예측 가능성이 우선입니다. 탐색적인 프로토타입 단계에서는 더 느슨한 접근 방식이 허용될 수 있지만, 그 경우에도 최소한의 가드(예: z.object({}).passthrough())를 두면 유용한 확장성을 버리지 않으면서도 치명적인 파싱 오류를 잡아낼 수 있습니다.
향후 주목할 점
- SDK 진화: Vercel의 AI SDK 로드맵에는 내장된 재시도 정책과 더 풍부한 에러 보고 기능이 포함되어 있어, 복구 루프(repair loop)를 더욱 간소화할 것입니다.
- 도구 표준화: 더 많은 제공업체가 도구 사용(tool-use) 컨벤션을 채택함에 따라, 제공업체 간 교차 스키마 검증기(cross-provider schema validators)가 등장하여 특정 제공업체 전용 어댑터의 필요성을 줄일 수 있습니다.
- 커뮤니티 패턴: 오픈 소스 라이브러리들이 Zod 스키마를 프롬프트 템플릿과 함께 번들링하기 시작하면서, "스키마 우선(schema-first)" 워크플로우가 재사용 가능한 자산이 되고 있습니다.
핵심 요점
Zod 스키마를 모델이 어길 수 없는 계약(contract)으로 취급함으로써, 개발자는 취약한 JSON.parse 편법에서 벗어나 예상치 못한 필드가 발생하더라도 운영 환경의 크래시가 아닌 제어된 검증 실패를 일으키는 결정론적(deterministic) 파이프라인으로 전환할 수 있습니다. Vercel의 Output.object 헬퍼와 Anthropic의 도구 사용 메커니즘의 결합은 LLM을 예측 불가능한 텍스트 생성기에서 신뢰할 수 있는 데이터 제공자로 변화시켜, 팀이 끝없는 엣지 케이스 디버깅 대신 비즈니스 로직에 집중할 수 있게 해줍니다.
