15.4. 결정론적 오라클 코드의 리팩토링 및 최적화
결정론적 오라클(Deterministic Oracle)은 AI 모델이 뱉어내는 비결정적이고 확률론적인 아웃풋을 통제하기 위해, 수학적이고 규칙 기반(Rule-based)의 잣대로 맞서 싸우는 테스트 공학의 최후 방어선이다. 그러나 소프트웨어 생명주기가 길어짐에 따라, 이 방어선 자체가 거대하고 유지보수 불가능한 기술 부채(Technical Debt)의 온상이 되기 쉽다.
오라클 코드는 일반적인 비즈니스 로직과 달리, 끊임없이 진화하는 LLM의 미묘한 자연어 패턴 변경을 수용하기 위해 임시방편적인 정규표현식(Regular Expression) 추가와 예외 처리 분기(if-else)가 무질서하게 증식하는 경향이 있다. 본 절에서는 통제 불능 상태에 빠진 방대한 오라클 코드를 어떻게 구조화하고, 연산 리소스를 최적화하며, 가독성 높은 아키텍처로 리팩토링(Refactoring)할 것인지에 대한 공학적 원칙을 제시한다.
1. 스파게티 오라클(Spaghetti Oracle)의 징후
리팩토링의 첫걸음은 현재 시스템 코드 내에 악취(Code Smell)가 진동하고 있음을 인지하는 것이다. 오라클 코드에서 발견되는 대표적인 안티 패턴(Anti-pattern)은 다음과 같다.
- 정규 표현식의 무한 증식 (Regex Proliferation): LLM이 “결과는 100입니다”, “100 도출됨”, “정답: 100“과 같이 변덕스러운 출력을 내놓을 때마다, 테스트 통과를 위해
re.match(r"결과는 (\d+)|(\d+) 도출됨|정답:\s*(\d+)", response)처럼 거대한 논리합(OR) 조건이 덧붙어 코드의 가독성이 완전히 파괴된 상태. - 신경망 의존적 분기 (Neural-Network Dependent Branching):
if model_version == "gpt-4-0314": ... elif model_version == "gpt-4-0613": ...와 같이, 특정 모델의 일시적인 버릇(Quirk)을 보정하기 위해 테스트 코드가 모델 버전에 강결합(Tight-Coupling)되어 있는 상태. - 거대 함수 (God Function) 오라클: 하나의 유닛 테스트 함수 내에서 토큰 길이 검사, JSON 스키마 검사, 금지어 검사, 의미론적 코사인 유사도(Cosine Similarity) 검사가 순서 없이 뒤섞여 실행되어 단일 책임 원칙(Single Responsibility Principle, SRP)이 철저하게 붕괴된 상태.
2. 오라클 리팩토링 아키텍처: 다단계 파이프라인 (Multi-stage Pipeline)
스파게티 오라클을 우아하게 재편하기 위해서는, 검증 로직을 단일 함수가 아닌 독립적이고 구성 가능한(Composable) 파이프라인 형태로 분리해야 한다. 이를 ‘오라클 체이닝(Oracle Chaining)’ 기법이라 한다.
graph TD
A[LLM Output Generation] --> B[단말 노드: Syntax & Format Oracle]
subgraph Oracle Pipeline
B -->|실패 시 즉각 반환 Fail-fast| C[정합성 노드: Schema Extraction Oracle]
C --> D[비즈니스 노드: Policy & Rule Oracle]
D --> E[의미 노드: Semantic Evaluator Oracle]
end
B -. 로직 분리 .-> B1(JSON Valid 체크)
C -. 로직 분리 .-> C1(Pydantic Type 변환)
D -. 로직 분리 .-> D1(환율 범위 초과 여부 체크)
E -. 로직 분리 .-> E1(LLM-as-a-Judge 코사인 유사도)
E --> F[최종 Test Pass / Fail]
style B fill:#e6f3ff,stroke:#4a90e2
style C fill:#e6f3ff,stroke:#4a90e2
style D fill:#e6f3ff,stroke:#4a90e2
style E fill:#f9ebf9,stroke:#9b59b6
2.1 단일 책임 원칙(SRP) 기반의 검증기 격리
위 다이어그램의 각 노드는 오직 하나의 검증 목표만을 가진다. 포맷이 무너진 응답(Syntax Failed)은 뒤이은 무거운 비즈니스 로직이나 LLM 판단기(Semantic Evaluator)로 넘어갈 필요가 없다. 이와 같은 Fail-fast 메커니즘은 복잡도를 낮출 뿐 아니라 과도한 토큰 API 호출 비용을 극적으로 절감(FinOps)시킨다.
2.2 폴리모픽(Polymorphic) 오라클 설계
검증기(Validator)들을 객체 지향의 인터페이스(Interface) 혹은 추상 클래스로 묶어, execute_oracle()이라는 다형성 메서드로 처리해라. 이는 모델이 뱉어내는 텍스트의 변동성에 맞춰 새로운 규칙 검증기를 추가할 때, 기존 테스트 루프 코드를 전혀 수정하지 않고 확장(Open-Closed Principle)할 수 있도록 보장한다.
3. 평가 메트릭 최적화 및 캐싱 (Metric Optimization & Caching)
오라클 코드가 무거운 이유는 단순히 로직이 복잡해서만이 아니라, 검증 과정 자체가 무거운 연산(Heavy Computation)을 동반하기 때문이다. ROUGE, BLEU, 혹은 BERTScore와 같은 임베딩(Embedding) 기반의 평가 모델은 테스트 실행 시간을 수백 배 지연시킨다.
- 결정론적 캐시(Deterministic Cache) 레이어 삽입: 동일한 입력 프롬프트와 동일한 Seed 값에 대해 동일한 텍스트 응답이 주어졌다면, 이전 슬라이드에서 계산해둔 BERTScore나 LLM-as-a-Judge의 평가 결과를 Redis 등의 인메모리에 해시(Hash) 캐싱하여 평가 연산을 건너뛰어야(Bypass) 한다.
- 경량 모델로의 위임(Distillation of Judges): 검증을 위해 반드시 파라미터가 1T에 달하는 거대 모델을 사용할 필요는 없다. 수천 건의 거대 모델 평가 로직 정답지를 모아, 7B 수준의 소형 로컬 모델(sLLM)을 미세조정(Fine-Tuning)하여 오라클 전용 판단 모델로 교체해라. 속도는 비약적으로 상승하고 보안은 강화되며 비용은 사실상 0으로 수렴한다.
4. 소결
검증 코드는 프로덕션 코드만큼이나, 때로는 그보다 더 아름답고 우아하게 작성되어야 한다. 테스트 코드가 스파게티 상태로 방치되는 순간, 개발팀은 테스트 실패 알람을 신뢰하지 않게 되며 이는 오라클 시스템의 즉각적인 사형 선고와 다름없다. 단일 책임 원칙에 입각한 다단계 검증 파이프라인 분리, 다형성 주입, 그리고 철저한 캐시 최적화는 AI 소프트웨어의 폭발적인 버전을 감당해 낼 수 있는, 단단하고 지속 가능한 결정론적 오라클을 구축하는 유일한 엔지니어링 해법이다.