최근 LLM을 활용한 애플리케이션 개발이 폭발적으로 늘어나고 있습니다.
처음에는 멋진 프롬프트 하나로 원하는 결과를 얻는 것 같지만, 서비스가 고도화될수록 프롬프트만으로는 제어할 수 없는 한계에 부딪히게 됩니다. 모델의 비대칭성, 환각 현상, 그리고 예기치 않은 출력 포맷 등 프로덕션 환경에서 마주하는 문제는 다양합니다.
이때 필요한 것이 바로 하네스(Harness) 패턴입니다. 마차가 도망가지 않도록 단단히 고정하는 마구(Harness)처럼, LLM 주변을 감싸며 입출력을 검증하고 예외 상황을 통제하는 안전장치를 뜻합니다.
하네스는 단순히 프롬프트를 넘겨주는 것을 넘어, 모델의 행동을 제어하고 시스템의 안정성을 보장하는 핵심 아키텍처입니다.
하네스가 필요한 이유
LLM은 강력하지만 본질적으로 확률적인 모델입니다. 프로덕션 환경에서 요구하는 엄격한 결정론적(Deterministic) 제어와는 거리가 있습니다.
- 모델의 출력이 깨지거나 JSON 형식을 지키지 않을 때
- 예상치 못한 토큰이나 민감한 정보가 포함되어 출력될 때
- 다단계(Multi-step) 작업 중 중간에 맥락을 잃어버릴 때
이러한 문제를 해결하기 위해 입출력 가드레일, 구조화된 파싱, 재시도(Retry) 로직 등을 하네스 계층에 구현해야 합니다.
구조화된 입출력과 파싱
LLM의 응답을 그대로 믿고 사용하는 것은 위험합니다. 엄격한 스키마를 정의하고, 응답이 그에 부합하는지 검증하는 과정이 필수적입니다.
// Zod 등을 활용한 LLM 출력 검증 예시
import { z } from 'zod';
const LLMResponseSchema = z.object({
status: z.enum(['success', 'retry', 'fail']),
result: z.string(),
});
만약 파싱에 실패한다면, 에러 메시지와 함께 모델에게 다시 요청을 보내는 피드백 루프를 하네스 내부에 구축할 수 있습니다.
에이전트 루프와 상태 관리
복잡한 작업을 수행하는 LLM 에이전트의 경우, 현재 상태(State)를 추적하고 다음 행동을 결정하는 오케스트레이션 루프가 하네스의 핵심 역할을 담당합니다.
[ User Input ] --> [ Harness / Guardrail ] --> [ LLM Call ]
^ |
|-- [ Output Validation ]--|
실무 적용 기준
LLM 기반 기능을 개발할 때 비즈니스 로직과 프롬프트 호출부를 완전히 분리하는 것이 좋습니다.
하네스 레이어를 두텁게 가져갈수록 프롬프트 의존성을 낮출 수 있으며, 추후 LLM 모델을 다른 버전이나 타사 모델로 교체할 때도 애플리케이션 전체를 수정할 필요 없이 안전하게 대응할 수 있습니다.