스키마 우선, 프롬프트는 그다음: 유효한 JSON만으로는 충분하지 않았다
요약
LLM 기반 게임 개발 과정에서 JSON 스키마 검증만으로는 해결할 수 없는 도메인 규칙 위반 문제를 다룹니다. 모델의 출력이 구조적으로는 유효하더라도 게임 로직에 어긋나는 경우를 방지하기 위한 도메인 검증의 중요성을 강조합니다.
핵심 포인트
- JSON 스키마는 데이터 타입과 키의 유효성만 보장함
- 도메인 검증은 게임 규칙 및 논리적 허용 여부를 판단함
- 모델의 출력을 신뢰하기 전 서버 측 정제 과정이 필수적임
- 결정론적 검증기를 통해 명시적인 에러 메시지를 활용할 것
지난 한 달 동안 저는 LLM(대규모 언어 모델)이 여러분과 함께 Codenames(코드네임) 게임을 즐기는 작은 웹 게임인 Codenames AI를 구축해 왔습니다. 추측자는 공개되지 않은 카드의 정체를 절대 볼 수 없습니다. 서버는 보드 상태와 힌트를 보내고, 모델은 신뢰도 점수와 짧은 설명을 포함한 구조화된 추측을 반환합니다.
처음 시작할 때, 저는 어려운 부분이 프롬프팅(Prompting)이라고 가정했습니다. 제 생각은 반은 맞았습니다. 모델로부터 어느 정도 합리적인 결과물을 얻어내는 것은 빨랐습니다. 하지만 시스템을 플레이어들에게 공개해도 안전하게 만드는 것은 그렇지 않았습니다.
저의 첫 번째 이정표는 책임감 있게 느껴졌습니다: 채팅 완성(Chat completion)에 response_format: { type: "json_object" }를 적용하고, 응답 본문에 Zod 스키마(Schema)를 사용하는 것이었습니다. JSON이 파싱되지 않거나 Zod 검증에 실패하면 재시도합니다. 그리고 배포합니다.
그러자 저는 모델이 스키마를 완벽하게 준수하면서도, 게임을 망쳐버릴 수 있는 수를 제안하는 것을 목격했습니다.
유효한 JSON, 유효하지 않은 게임
여기서 중요한 차이점이 있습니다.
JSON 스키마 (Zod를 통한)는 다음을 답변합니다: 모델이 내가 요청한 키(Key)와 타입(Type)을 반환했는가?
도메인 검증 (Domain validation)은 다음을 답변합니다: 이 출력값이 이 보드에서, 이 힌트에 대해, 이러한 규칙 하에 허용되는가?
이것들은 서로 다른 질문입니다.
게임을 테스트하고 실행하는 동안 제가 맞닥뜨린 세 가지 사례입니다:
1. 모델이 힌트를 추측값으로 그대로 되풀이함.
Codenames에서는 힌트 단어를 추측하는 것을 금지합니다. 모델은 가끔 이를 guesses[]에 넣곤 했습니다—자신만만하게, 그리고 깔끔한 설명 객체와 함께 말이죠. Zod는 매우 만족해했습니다. 하지만 게임은 아니었습니다.
2. 모델이 보드에 없는 단어를 환각(Hallucination)함.
완벽한 JSON입니다. 25개의 카드 그리드에 존재하지 않거나 이미 공개된 단어들로 가득 찬 추측 리스트였습니다. 다시 말하지만, 스키마상으로는 유효했습니다.
3. 스파이마스터(Spymaster)가 허용되지 않는 힌트를 반환함.
단일 단어 힌트는 코드네임과 일치해서는 안 되며, 코드네임의 부분 문자열(Substring)이 되어서도 안 되고(또는 그 반대도 마찬가지), 철자가 거의 유사해서도 안 됩니다. 모델은 인간 심판이라면 거부했을 힌트를 정기적으로 제안했습니다. 매번 유효한 JSON이었습니다.
저는 시스템 프롬프트(System prompt)에 문장을 추가하여 이 문제들을 해결하는 데 너무 많은 시간을 보냈습니다. 그것은 약간 도움이 되었습니다. 하지만 충분하지는 않았습니다.
신뢰성을 실제로 높인 것들
더 큰 성과는 제가 지루한 인프라(infrastructure)로 취급했던 코드 경로에서 나왔습니다.
신뢰하기 전의 정제 (Sanitization before trust). Zod가 추측 페이로드(guess payload)를 파싱한 후, 우리는 힌트의 메아리(clue echoes), 보드 외 단어(off-board words), 공개된 카드, 그리고 중복 항목들을 제거합니다. 그런 다음 살아남은 항목들에 맞춰 설명 배열(explanation array)을 다시 정렬합니다. 모델은 원하는 어떤 설명이든 반환할 수 있지만, 어떤 추측이 검증을 통과할지는 서버가 결정합니다.
명시적인 에러 문자열을 포함한 결정론적 검증기 (Deterministic validators with explicit error strings). 힌트 검증은 단순히 "유효하지 않음(invalid)"이라고 하는 대신, "힌트는 보드 단어의 부분 문자열(substring)이 될 수 없습니다"와 같은 메시지를 반환합니다. 이러한 문자열은 이미 실패한 힌트 단어의 제외 목록(exclude list)과 함께 rejectionFeedback로서 다음 시도에 다시 전달되며, 이를 통해 다음 시도에서 동일한 위반 사항을 반복하지 않도록 합니다.
불확실성에 대한 후처리 (Post-processing for uncertainty). 유효한 추측이라 할지라도 클라이언트가 이를 실행하기 전에 신뢰도 임계값(confidence threshold)에 의해 필터링됩니다. 만약 기준을 통과하는 것이 없다면, API는 빈 추측 목록을 반환합니다. 즉, AI 추측기(AI Guesser)가 약한 선택을 내놓는 대신 턴을 넘기는 것입니다. 이는 제품 결정(product decision)이지만, 앞선 계층들이 엉터리 결과가 성공인 것처럼 위장하는 것을 막아주었기에 가능했습니다.
이 중 그 어떤 것도 독자가 Codenames 게임을 알 필요를 요구하지 않았습니다. 이는 불변성(invariants)을 가진 모든 LLM 기능과 형태가 같습니다. 예를 들어 음수가 될 수 없는 인벤토리 수량, 반드시 존재해야 하는 사용자 ID, 상태 머신(state machines)과 일치해야 하는 액션 열거형(action enums) 등이 그러합니다.
실수, 놀라움, 그리고 트레이드오프 (Mistakes, surprises and tradeoffs)
실수: 구조화된 출력(structured output)을 가드레일(guardrail)로 취급한 것. 그것은 오직 형태(shape)만을 강제했습니다.
놀라움: 가장 어처구니없는 실패들(메아리치는 힌트, 보드 외 토큰)에 대해서는 프롬프트 엔지니어링(prompt engineering)보다 정제(sanitization)가 더 뛰어난 성능을 보였습니다. 저렴한 결정론적 필터가 "중요한 규칙"을 한 단락 더 추가하는 것보다 효과적이었습니다.
놀라움: 힌트가 실패한 "이유"를 포함한 재시도 피드백(Retry feedback)이 단순히 "다시 시도하세요"라고 하는 것보다 더 잘 작동했습니다. 서버가 위반 사항을 명시했을 때, 모델은 부분 문자열 위반을 더 빠르게 반복하지 않게 되었습니다.
트레이드오프: 재시도는 토큰을 소모합니다. 우리가 프롬프트 문제인지 아니면 누락된 규칙 때문인지를 파악하기 위해서는 시도당 검증 에러를 로깅(logging)하는 것이 필수적이었습니다.
트레이드오프 (Tradeoff): 정제 (Sanitization)는 드리프트 (drift)를 은폐할 수 있습니다. 잘못된 추측값을 조용히 삭제한다면, 무엇을 삭제하고 있는지 모니터링해야 합니다. 그렇지 않으면 검증기 (validator)가 모든 결정을 내리는 주체로 조용히 변질될 것입니다.
다음 프로젝트에서 내가 할 일
- 데이터 전송 규격 (wire shape) 정의 (JSON + 스키마 (schema)).
- 도메인 불변량 (domain invariants)을 테스트 케이스를 포함한 순수 함수 (pure functions)로 나열.
- 처음 50번의 실시간 호출 (live calls)에서 관찰된 실패 모드 (failure modes)에 대한 정제 (sanitization) 단계 추가.
- 그 이후에야 프롬프트의 미세한 차이 (prompt nuance)에 투자하고, 검증기 메시지를 재시도 (retries) 과정에 반영.
품질을 위해서는 프롬프트 엔지니어링 (Prompt engineering)이 여전히 중요합니다. 하지만 모델이 JSON 명세 (spec)는 따랐으나 현실을 무시함으로써 사용자가 게임에서 지거나, 돈을 잃거나, 데이터를 잃게 되는 상황에서는 프롬프트가 강제 집행 (enforcement)의 대체재가 될 수 없습니다.
핵심 요약 (Takeaway): 만약 당신의 LLM 통합이 "JSON을 파싱하고, 그걸로 끝내자" 수준에서 멈춘다면, 당신은 기능을 완성한 것이 아닙니다. 데모를 완성했을 뿐입니다.
이러한 교훈에 영감을 준 프로젝트를 확인하고 싶다면, Codenames AI를 시도해 보세요.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기