당신은 모델의 API를 호출하지 않고도 LLM 기반 기능에 대해 28개의 유닛 테스트를 실행할 수 있는 내부 도구를 구축했습니다. 모델을 가짜(fake)로 대체 가능한 인터페이스로 감싸고, 결정론적(deterministic), 휴리스틱(heuristic), 그리고 LLM 기반 평가라는 세 가지 계층을 추가함으로써 이를 구현했습니다.

표준적인 단언(assertion) 방식은 LLM이 산문 형태의 텍스트를 생성하는 순간 무너집니다. 동일한 프롬프트라도 실행할 때마다 문장이 달라질 수 있기 때문에, assertEqual(output, expected)는 모델이 올바르게 동작했을 때조차 실패로 간주합니다. 대부분의 엔지니어링 팀은 검증 없이 기능을 출시하거나, 끊임없이 변하는 대상을 마치 정적인 라이브러리인 것처럼 취급하며 모델 자체를 테스트하려고 시도합니다.

이 문제가 중요한 이유

이제 LLM은 이메일 발송, 고객 지원 답변, 콘텐츠 생성 등 고객 접점 워크플로우의 핵심에 자리 잡고 있습니다. 단 하나의 환각(hallucination)된 사실이나 유출된 식별자만으로도 브랜드 평판을 훼손하고, 개인 데이터를 노출하거나, 컴플라이언스 위반을 초래할 수 있습니다. 신뢰할 수 있는 테스트 전략이 없다면, 팀은 불안정한(flaky) 실패를 추적하는 데 시간을 허비하거나 운영 환경에서만 나타나는 버그를 출시하게 됩니다.

접근 방식: 모델의 책임을 축소하라

첫 번째 단계는 LLM이 실제로 수행하는 작업을 제한하는 것이었습니다. 저자의 시스템에서 모델은 오직 아웃리치(outreach) 메시지의 초안만 작성합니다. 모든 라우팅 로직, 상태 관리 및 안전 점검은 일반적인 코드로 유지됩니다. 모델의 역할을 단일하고 명확하게 정의된 출력으로 한정함으로써, 주변 시스템은 결정론적이고 테스트 가능한 상태를 유지할 수 있습니다.

이를 가능하게 하기 위해 LLM은 프로바이더 인터페이스(provider interface) 뒤에 위치하며, 테스트 시에는 가짜(fake) 버전을 사용할 수 있습니다. 운영 환경의 구현체는 외부 API를 호출하지만, 테스트 스위트에서는 가벼운 가짜 객체가 미리 준비된 응답(canned response)을 반환합니다. 나머지 코드는 인터페이스와만 상호작용하므로, 네트워크를 전혀 사용하지 않고도 유닛 테스트를 통해 전체 워크플로우를 실행할 수 있습니다. 그 결과, 28개의 테스트가 검증할 수 있는 예측 가능한 핵심 로직이 만들어졌습니다.

정직한 평가 하네스(Evaluation Harness)

범위를 좁혔음에도 불구하고 모델의 출력은 여전히 비결정론적입니다. 따라서 저자는 각 계층이 서로 다른 유형의 리스크를 처리하는 3단계 평가 하네스를 구축했습니다.

  • 레이어 1 – 결정론적 점검(Deterministic checks) 단순한 정규 표현식 규칙을 사용하여 잘못된 건물 ID나 금지된 토큰과 같은 구체적인 오류를 잡아냅니다. 이 점검은 빠르며 이진(binary) 방식의 통과/실패 결과를 제공합니다.

  • 레이어 2 – 휴리스틱 점검(Heuristic checks) 스크립트가 환각된 숫자나 날짜를 찾아내어 명백한 사실 왜곡을 잡아냅니다. 다만 수치적 단서가 없는 허위 주장은 놓칠 수 있으며, 저자 또한 이 한계를 솔직하게 인정합니다.

  • 레이어 3 – LLM judge 보조 모델이 어조와 전문성을 평가합니다. 이 단계는 또 다른 확률론적 시스템에 의존하기 때문에, 결정론적 규칙을 적용하는 것이 불가능한 주관적인 측면에 대해서만 사용됩니다.

하네스의 핵심은 평가에 사용되는 데이터셋입니다. 저자는 알려진 실패 패턴(특정 함정 및 도메인 지식)을 인코딩하여, 하네스가 실제 사례에서 나타났던 실수들을 정확히 테스트하도록 했습니다. 이는 모든 것을 잡아내는 마법 같은 도구가 아니라, 타겟팅된 안전망입니다.

팀을 위한 시사점

  • LLM의 역할을 최소화하세요. 책임이 적을수록 격리(isolation)와 테스트가 쉬워집니다.
  • 라우팅, 상태 및 안전 로직은 코드로 구현하세요. 전통적인 로직은 결정론적이며 완전히 테스트 가능한 상태로 유지됩니다.
  • 모델을 가짜로 대체 가능한 인터페이스로 노출하세요. 유닛 테스트가 외부 호출 없이 실행되므로 테스트 스위트가 빠르고 신뢰할 수 있습니다.
  • 평가를 계층화하세요. 결정론적 규칙으로 시작하여, 알려진 환각에 대해서는 휴리스틱을 추가하고, 주관적인 품질 점검을 위해서만 LLM judge를 예약해 두세요.
  • 한계를 명시하세요. 어떤 계층도 완벽을 보장하지 않습니다. 하네스는 명시적으로 탐지하도록 프로그래밍된 것만 잡아낼 수 있습니다.

반론: 모델 자체는 여전히 유닛 테스트할 수 없다

저자는 모델이 끊임없이 변하는 대상(moving target)이라는 점을 인정합니다. 심지어 LLM judge 레이어조차 자신이 평가하려는 대상과 동일한 비결정론성을 상속받습니다. 결과적으로, 이 시스템이 출시 전에 모든 환각이나 정책 위반을 잡아낼 것이라고 결코 보장할 수는 없습니다. 이 접근 방식은 리스크를 제거하는 것이 아니라 줄이는 것이며, 새로운 실패 모드가 나타날 때마다 평가 데이터를 최신 상태로 유지하는 팀의 역량에 의존합니다.

요약

LLM의 정확한 출력을 단언(assert)하는 전통적인 유닛 테스트를 작성할 수는 없지만, 모델의 영향력을 제한하고, 인터페이스를 교체 가능하게 만들며, 계층적이고 투명한 점검을 통해 출력을 스크리닝하는 시스템을 구축할 수는 있습니다. 이러한 조합은 자칫 불안정할 수 있는 컴포넌트를 더 크고 테스트 가능한 애플리케이션의 예측 가능한 일부로 탈바꿈시킵니다.