LLM의 출력을 직접 신뢰하지 마세요. 제가 모든 에이전트에 적용하는 검증 레이어(Validation Layer)를 소개합니다.
요약
LLM 에이전트 개발 시 모델의 구조적 환각으로 인한 런타임 오류를 방지하기 위한 검증 레이어(Validation Layer) 구축 방법을 소개합니다. JSON 스키마 준수만으로는 부족한 의미론적 유효성을 확보하기 위해 Parse-Validate-Classify 패턴을 제안합니다.
핵심 포인트
- 모델의 구조적 환각(Structural Hallucination)은 런타임 오류의 주요 원인임
- 구조화된 출력(Structured Output)은 타입은 보장하지만 의미론적 유효성은 보장하지 않음
- Parse-Validate-Classify 3단계 패턴을 통한 견고한 에이전트 설계 필요
- Zod와 Discriminated Union을 활용한 타입 안전한 에러 처리 구현
제가 검토한 거의 모든 AI 에이전트 코드베이스에서 목격한 실패 모드가 있습니다. 에이전트가 모델의 응답을 받고, 그 안에 포함된 JSON을 신뢰하여 .result.items[0].id를 호출합니다. 그런데 모델이 엣지 케이스(edge case)에서 {"result": null}을 반환하면서, 새벽 2시에 Cannot read properties of undefined 오류를 발생시키며 터져버리는 상황입니다.
모델이 내용을 환각(hallucinate)한 것이 아닙니다. 모델은 _구조(structure)_를 환각한 것입니다.
이런 일은 놀라울 정도로 흔하게 발생하며, 해결책은 "더 나은 프롬프트를 사용하는 것"이 아닙니다. 해결책은 가공되지 않은 모델 출력(raw model output)과 그 출력에 따라 동작하는 코드 사이에 실행되는 검증 레이어(validation layer)를 두는 것입니다.
구조화된 출력(Structured Output)만으로는 충분하지 않은 이유
Claude와 GPT-4는 모두 주어진 스키마(schema)에 일치하는 유효한 JSON을 생성하도록 모델을 제한하는 구조화된 출력(structured output) 모드를 지원합니다. 이는 정말 유용하며 반드시 사용해야 합니다. 하지만 두 가지 이유 때문에 이 문제가 완전히 해결되지는 않습니다.
1. JSON 유효성이 의미론적(semantically) 유효성을 보장하지 않습니다.
모델은 귀하의 스키마를 준수하는 완벽하게 유효한 JSON을 생성할 수 있지만, 여전히 틀릴 수 있습니다. UUID여야 하는 문자열 필드에 데이터베이스 조회에 실패하는 가공의 식별자가 포함될 수 있습니다. confidence_score라고 표시된 정수(integer) 필드가 귀하의 코드가 0-1 사이의 부동 소수점(float)을 기대할 때 847이 될 수도 있습니다. 스키마는 타입(type)을 강제할 뿐, 의미론(semantics)을 강제하지 않습니다.
2. 모든 LLM 호출이 구조화된 출력을 사용하는 것은 아닙니다.
다단계 추론(multi-step reasoning), 사고의 사슬(chain-of-thought) 단계, 도구 호출 파싱(tool call parsing), 또는 네이티브 JSON 모드를 지원하지 않는 모델의 출력을 처리하는 경우, 자유 형식의 텍스트(free-text) 응답을 파싱하게 됩니다. 이를 견고하게 처리해야 합니다.
패턴: 파싱(parse), 검증(validate), 분류(classify)
제가 현재 구축하는 모든 에이전트 호출은 세 단계를 거칩니다:
raw model output
↓
[PARSE] – 텍스트에서 구조를 추출
...
제가 실제로 사용하는 TypeScript 구현체는 다음과 같습니다:
import { z } from "zod";
// 1. 기대하는 스키마를 정의합니다
...
```
(?:json)?\s*([\s\S]*?)
{% raw %}
```/) ||
raw.match(/(\{[\s\S]*\}|\[[\s\S]*\])/);
const jsonString = jsonMatch ? jsonMatch[1] ?? jsonMatch[0] : raw.trim();
...
AgentOutput<T> 판별 가능한 유니온 (discriminated union)은 호출자가 성공 경로 (happy path)와 실패 경로 (failure paths)를 모두 처리하도록 강제합니다. output.ok를 먼저 확인하지 않고 실수로 output.data에 접근하는 것은 불가능합니다.
실제 에이전트 호출에 적용하기
import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic();
...
실제로 작동하는 재시도 (retry) 로직
모든 검증 실패가 영구적인 것은 아닙니다. 때때로 모델은 첫 번째 시도에서 잘못된 형식의 JSON을 생성하지만, 재시도 시에는 올바르게 생성하기도 합니다. 핵심은 어떤 실패가 재시도할 가치가 있는지 구분하는 것입니다.
async function analyzeWithRetry(
feedback: string,
maxAttempts = 3
...
재시도 프롬프트에 검증 오류를 모델에게 다시 전달하는 패턴은 특히 효과적입니다. 맹목적으로 재시도하는 대신, 모델에게 무엇이 잘못되었는지 알려주는 것입니다. 제 경험상, 첫 번째 시도에서 검증 실패가 발생했을 때 이 방식을 사용하면 약 80%의 확률로 두 번째 시도에서 유효한 출력을 얻을 수 있었습니다.
검증 실패 시 로그에 남겨야 할 것
프로덕션 환경에서 검증이 실패할 때는 문제를 이해하고 수정할 수 있을 만큼 충분한 정보가 필요하지만, 개인 식별 정보 (PII)를 로그에 남기거나 스토리지 비용을 과도하게 발생시킬 정도로 많아서는 안 됩니다.
// 좋음: 구조화되어 있고, 쿼리가 가능하며, 안전함
console.error(JSON.stringify({
event: "agent_validation_failure",
...
일주일 동안 프로덕션 로그를 살펴보면 패턴이 보일 것입니다. 예를 들어, 모델이 특정 카테고리의 입력에 대해 confidence 필드를 지속적으로 누락할 수도 있습니다. 또는 입력에 줄바꿈이 포함될 때 배열을 문자열로 반환할 수도 있습니다. 이러한 패턴은 프롬프트를 강화하거나 추가적인 강제 변환 (coercion) 로직을 추가해야 할 지점을 알려줍니다.
당장 배포만 하고 싶을 때를 위한 10분 버전
Zod가 과하다고 느껴진다면, 가장 흔한 실패를 잡아낼 수 있는 최소한의 버전은 다음과 같습니다:
import json
from typing import TypedDict
...
```
"):
text = text.split("
```")[1]
if text.startswith("json"):
text = text[4:]
...
Zod만큼 조합 가능(Composable)하지는 않지만, 누락된 키(missing keys), 잘못된 열거형 값(wrong enum values), 범위를 벗어난 숫자(out-of-range numbers)와 같은 일반적인 실패 모드(failure modes)를 잡아냅니다.
원칙 (The principle)
LLM은 확률적(Probabilistic)입니다. 정중하게 요청하더라도 구조화된 출력(structured output)이 유효할 것이라고 보장하지 않습니다. 프로덕션 에이전트(Production agent)에는 코드가 작동하기 전에 모든 출력을 유효(valid) 또는 유효하지 않음(invalid)으로 분류하는 결정론적 레이어(Deterministic layer)가 필요합니다. 해당 레이어를 먼저 구축하고, 실패 사례를 로그로 남기며, 실패 데이터를 통해 프롬프트(Prompt)의 어느 부분을 개선해야 하는지 파악하세요.
검증 레이어(Validation layer)는 작업 속도를 늦추는 것이 아니라, 에이전트를 디버깅 가능(debuggable)하게 만듭니다. 이것이 없다면 당신은 눈을 가리고 비행하는 것과 같습니다.
저는 무료 Reliable Agent Field Guide에서 검증 패턴(validation patterns), 재시도 로직(retry logic), 그리고 프로덕션 신뢰성(production reliability)을 다룹니다: penloomstudio.com/field-guide.html
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기