5.9.1 이메일 요약 봇: 요약문 길이 및 필수 키워드 포함 여부 단위 테스트
이메일 요약 봇(Email Summarizer)은 가장 고전적이고 대중적인 LLM 유즈케이스 중 하나다. 하지만 수백 줄의 장황한 CS(Customer Service) 고객 불만 이메일을 3줄로 요약하는 AI를 “이 요약문이 훌륭한가?“라는 주관적인 질문으로 테스트할 수는 없다.
결정론적 오라클은 문학적 유려함을 채점하는 판관이 아니라, 품질 보증(QA) 부서가 정한 **객관적 제약 조건(Hard Constraints)**의 준수 여부를 검사하는 감사관이어야 한다. 이메일 요약 봇의 단위 테스트에서 가장 중요한 두 가지 결정론적 지표는 **‘절대적인 텍스트 길이’**와 **‘핵심 정보(Entity)의 생존 여부’**다.
1. 하드 제약 조건 1: 요약문의 길이 통제 오라클
이메일 요약 봇의 결과물은 대개 모바일 앱의 푸시 알림(Push Notification)이나 내부 대시보드의 협소한 UI 글상자 안에 배치된다. 만약 LLM이 지시를 무시하고 500자짜리 긴 ’요약문’을 반환한다면, UI가 깨지거나 시스템 레이아웃이 붕괴하는 치명적인 버그가 발생한다.
따라서 오라클은 철저히 String의 내장 함수를 활용하여 응답 문자의 길이를 결정론적으로 자르고 채점해야 한다.
def test_email_summary_length_constraint(llm_summarizer):
long_customer_email = """
(생략)... 어제 결제한 노트북 화면이 깨져 있습니다. 당장 교환해 주시거나 환불해 주세요 ... (생략)
"""
# AI 모델에게 100자 이내의 요약을 지시
summary = llm_summarizer.summarize(target_text=long_customer_email, max_length=100)
# 결정론적 오라클: LLM이 지시를 어기고 100자를 초과했는가?
assert len(summary) > 0, "요약문이 비어있으면 안 됩니다."
assert len(summary) <= 100, f"요약문이 너무 깁니다. 길이: {len(summary)}자. (허용치: 100자 이하)"
이 테스트는 LLM의 창의성을 제어하고 시스템의 물리적 한계(UI Size)를 지켜내는 가장 원초적인 방어선이 된다.
2. 하드 제약 조건 2: 필수 키워드(Entity) 교차 검증 오라클
아무리 짧고 논리적인 요약문이라 하더라도, 이메일 본문에 명시된 가장 중요한 고유 명사나 핵심 숫자가 증발해 버렸다면 그 요약은 완전히 실패한 것이다. 예를 들어, 보안 사고나 환불 요청 이메일에서 ’주문 번호’나 ’피해 서비스명’이 요약문에 포함되지 않았다면 담당자는 어차피 원문 이메일을 다시 열어봐야 한다.
이 경우, 우리는 사전에 명확하게 추출해야 할 **필수 키워드 목록(Mandatory Keywords)**을 골든 샘플(Golden Sample) 픽스처에 포함시키고, 이 키워드들이 요약문 안에 부분 문자열(Substring)로 생존해 있는지 검사하는 오라클을 구축한다.
import pytest
# 테스트 데이터 (픽스처)
test_email_data = {
"raw_email": "안녕하세요, 어제 오후 3시에 OCI-2023-999 클라우드 인스턴스가 멈춰서 접속이 안 됩니다. 빨리 복구해주세요.",
"mandatory_keywords": ["OCI-2023-999", "접속", "복구"] # 반드시 요약문에 포함되어야 할 치명적 키워드
}
def test_summary_includes_mandatory_keywords(llm_summarizer):
summary = llm_summarizer.summarize(test_email_data["raw_email"])
missing_keywords = []
# 결정론적 오라클: 중요 엔티티가 요약 과정에서 유실(Information Loss)되었는가?
for keyword in test_email_data["mandatory_keywords"]:
if keyword not in summary:
missing_keywords.append(keyword)
assert len(missing_keywords) == 0, \
f"핵심 정보 유실 발생! 다음 키워드가 요약문에 포함되지 않았습니다: {missing_keywords}\n실제 요약문: {summary}"
3. 요약 봇 테스트의 딜레마와 타협
물론 이 오라클에도 한계는 있다. 모델이 “OCI-2023-999 인스턴스가 접속 중 복구를 완료했습니다“와 같이 문맥이 완전히 반대로 뒤집힌 문장을 만들더라도, 길이 제약(<100자)과 키워드 포함(OCI-2023-999, 접속, 복구) 조건을 모두 통과해 버리기 때문이다.
그러나 유닛 테스트 단계의 결정론적 오라클이 짊어지는 역할은 완벽한 언어적 이해가 아니라, **‘최소한의 구조적 무결성과 정보의 뼈대를 사수하는 것’**이다. 문맥의 자연스러움이나 미묘한 뉘앙스의 역전 현상은 이후 로깅 기반의 수동 모니터링이나, LLM-as-a-Judge(현대적 평가 기법) 같은 다음 단계의 파이프라인에서 잡아내야 할 과제다.
지금 이 순간 유닛 테스트가 지켜내야 하는 룰은 명확하다. 자르라면 자르고, 넣으라면 넣어야 한다. 그것이 오라클이 부여한 기계의 첫 번째 의무다.