RAG 기반 테스트 시리즈 — 파트 4: 엣지 케이스(Edge Cases) — 무엇이 RAG를 망가뜨리는가 & 어떻게 잡아낼 것인가
요약
RAG 시스템의 안정성을 위협하는 엣지 케이스를 분석하고 이를 테스트하는 방법을 다룹니다. 특히 검색 결과가 없는 '빈 검색 결과' 상황에서 발생하는 침묵하는 실패(silent failure)를 방지하기 위한 전략을 제시합니다.
핵심 포인트
- 사용자의 모호하거나 모순된 쿼리는 RAG 시스템을 망가뜨리는 주요 원인임
- 빈 검색 결과 발생 시 LLM이 거짓 답변을 생성하는 '침묵하는 실패' 주의 필요
- 리트리버의 유사도 점수 임계값 설정과 저품질 컨텍스트 대응 테스트가 핵심
- Ragas, ChromaDB 등을 활용한 체계적인 엣지 케이스 검증 프로세스 구축
RAG 기반 테스트 시리즈 — 파트 4: 엣지 케이스(Edge Cases) — 무엇이 RAG를 망가뜨리는가 & 어떻게 잡아낼 것인가
"사용자들은 당신의 해피 패스(happy path)를 절대 읽지 않을 것입니다. 하지만 그들은 당신이 테스트하지 않은 모든 엣지 케이스(edge case)를 찾아낼 것입니다."
파트 2에서는 검색 품질(retrieval quality)을 테스트했습니다 — 즉, 올바른 문서가 가져와지는지 확인했습니다.
파트 3에서는 충실도(faithfulness)를 테스트했습니다 — 즉, LLM이 내용을 지어내는 대신 검색된 내용을 사용하는지 확인했습니다.
이 두 테스트는 한 가지를 가정합니다: 시스템이 의도된 대로 사용되고 있다는 것.
실제 사용자들은 그렇게 하지 않습니다.
그들은 지식 베이스(knowledge base)가 구축될 때 고려되지 않았던 질문을 던집니다. 모호하거나, 불분명하거나, 모순된 쿼리(query)를 보냅니다. 같은 질문을 다섯 가지 다른 방식으로 다시 표현합니다. 그리고 가끔은 시스템이 설계되거나 테스트되지 않은 영역으로 몰아넣기도 합니다.
엣지 케이스(Edge cases)는 실제 운영 중인 RAG 시스템이 실제로 망가지는 지점입니다. 🔴
이 시리즈에서 7.5년의 QA 본능이 가장 중요하게 작용하는 부분이기도 합니다. 적대적(adversarially)으로 생각하기 위해 ML 배경지식이 필요한 것은 아닙니다. 그저 모든 유능한 테스터가 던지는 질문을 던지기만 하면 됩니다 👇
"상황이 잘못되면 어떻게 될까?"
이제 알아봅시다. 🎯
🗺️ 우리가 다룰 엣지 케이스들
이번 파트에서 테스트할 내용은 다음과 같습니다:
엣지 케이스 1 — 빈 검색 결과 (Empty Retrieval)
지식 베이스에 관련 문서가 없음.
시스템이 이를 인정하는가 — 아니면 자신 있게 거짓말을 하는가?
...
각각은 실제 실패 모드(failure mode)입니다. 각각은 고유한 테스트가 필요합니다. 🛠️
⚙️ 설정
파트 2와 3에서 사용했던 것과 동일한 스택을 계속 사용하겠습니다:
pip install ragas
pip install openai
pip install chromadb
...
그리고 우리가 계속 구축해 온 것과 동일한 임포트(imports)를 사용합니다:
import chromadb
import pytest
from chromadb.utils import embedding_functions
...
🔴 엣지 케이스 1 — 빈 검색 결과 (Empty Retrieval)
그것은 무엇인가?
사용자가 질문을 합니다. 검색기(retriever)가 지식 베이스를 검색합니다. 관련 있는 것이 발견되지 않거나 — 유사도 점수(similarity score)가 너무 낮아서 반환된 결과가 사실상 노이즈(noise)인 상태입니다.
일어나야 하는 일: 시스템이 "그에 대한 정보가 없습니다" 또는 이와 유사한 답변을 해야 합니다.
실제로 발생하는 일 (테스트하지 않았을 때): LLM은 거의 비어 있거나 무관한 컨텍스트 (context)를 전달받고, 매우 확신에 찬 태도로 완전히 조작된 답변을 생성합니다. 😬
이를 **"침묵하는 실패 (silent failure)"**라고 부릅니다. 이는 시스템의 표면적인 수준에서는 아무것도 "고장 나지" 않기 때문에 가장 위험한 엣지 케이스 (edge case)입니다. 응답은 돌아오고, 겉보기에는 멀쩡해 보입니다. 하지만 실제로는 그렇지 않습니다.
테스트 방법
여기서 핵심은 두 가지를 테스트하는 것입니다:
- 리트리버 (retriever)가 의미 있는 유사도 점수 (similarity score)를 반환하는가 — 그리고 우리는 그 점수에 임계값 (threshold)을 설정하는가?
- 저품질의 컨텍스트 (context)가 LLM에 도달했을 때, 답변이 적절하게 불확실성을 유지하는가?
# 지식 베이스 (knowledge base) 테스트 — 가격에 관한 내용은 의도적으로 포함하지 않음
client = chromadb.Client()
embedding_fn = embedding_functions.OpenAIEmbeddingFunction(
...
이제 지식 베이스에 존재하지 않는 내용으로 쿼리 (query)를 날려보세요:
def get_retrieval_with_scores(collection, query, n_results=3):
"""
문서와 해당 거리 점수 (distance scores)를 함께 검색합니다.
...
임계값 (thresholds)에 관한 중요한 참고 사항: 적절한 유사도 임계값은 사용 중인 임베딩 모델 (embedding model)과 데이터에 따라 달라집니다. 0.8이라는 수치를 맹목적으로 복사하지 마세요. 테스트 스위트 (test suite)를 실행하고, 알려진 관련 쿼리 대 무관한 쿼리에 대한 거리 분포 (distance distribution)를 살펴본 후 그에 따라 임계값을 설정하십시오. 이것은 추측하는 것이 아니라 보정 (calibrate)하는 것입니다.
빈 컨텍스트 (empty context)에 대한 LLM의 동작 테스트
리트리버 (retrieval) 레이어가 빈 결과를 올바르게 식별하더라도, 희소한 컨텍스트 (sparse context)가 실수로 LLM에 도달할 경우 어떤 일이 발생하는지도 테스트해야 합니다:
def test_llm_uncertainty_on_empty_context():
"""
컨텍스트가 비어 있거나 무관할 때, LLM의 답변은 적절하게...
⚠️ 엣지 케이스 2 — 충돌하는 컨텍스트 (Conflicting Context)
그것은 무엇인가?
서로 모순되는 내용을 담고 있는 두 개의 문서가 검색되는 경우입니다. 이는 실제 지식 베이스 (knowledge bases)에서 생각보다 자주 발생합니다. 완전히 전파되지 않은 정책 업데이트, 상충하는 FAQ, 버전 관리 문제 등이 그 예입니다.
문서 A: "환불은 14일 이내에 가능합니다." ← 이전 정책
문서 B: "환불은 30일 이내에 가능합니다." ← 새로운 정책
...
기대되는 동작 (What should happen): LLM이 충돌을 감지하거나, 가장 최근의/권위 있는 문서를 따릅니다.
실제 발생하는 동작 (What actually happens): LLM이 임의로 하나를 선택하며 — 때로는 잘못된 것을 선택하며 — 매우 확신에 찬 태도를 보입니다.
테스트 방법 (How to Test It)
def test_conflicting_context_behaviour():
"""
검색된 두 문서가 충돌할 때, 다음을 테스트합니다:
...
여기서 왜 엄격한 단언(hard assertion)을 하지 않나요? 충돌하는 컨텍스트 (Conflicting context)는 특수한 사례입니다. 두 답변 모두 소스 중 하나에는 "충실(faithful)"할 수 있기 때문입니다. 진짜 테스트는 시스템이 조용히 하나를 선택하는 대신, 사용자에게 충돌 사실을 드러내는지 (surfaces the conflict) 여부입니다. 이는 부분적으로는 제품 결정 사항(시스템이 충돌을 인정해야 하는가?)이며, 부분적으로는 테스트 결정 사항(제품이 정의한 특정 동작을 단언해야 함)입니다. 이를 로그로 남기고 검토한 다음, 시스템이 해야 하는 동작을 바탕으로 단언(assertion)을 작성하세요.
예방책: 지식 베이스 (Knowledge base)에 문서 버전 관리 메타데이터를 추가하고, 충돌이 발생할 경우 리트리버 (Retriever)가 가장 최근에 업데이트된 문서를 우선하도록 설정하세요. 그런 다음, 더 최신 문서가 항상 더 높은 순위로 매겨지는지 확인하는 테스트를 작성합니다.
def test_newer_document_ranked_higher():
"""
두 문서가 충돌할 때, 더 최신의
...
🌍 엣지 케이스 3 — 범위를 벗어난 질의 (Out-of-Scope Queries)
그것은 무엇인가? (What Is It?)
사용자가 귀하의 RAG 시스템이 구축된 도메인(Domain)을 완전히 벗어난 질문을 하는 경우입니다.
고객 지원 봇에게 시를 써달라고 요청하는 경우. 인사(HR) 정책 봇에게 경쟁사 제품에 대해 묻는 경우. 기술 문서 봇에게 의학적 조언을 구하는 경우 등이 해당됩니다.
기대되는 동작 (What should happen): 시스템이 정중하게 거절하거나 다른 곳으로 안내합니다.
실제 발생하는 동작 (What actually happens): 본질적으로 도움이 되고자 하는 언어 모델인 LLM은, 귀하의 지식 베이스에 근거(grounding)를 전혀 두지 않은 채 오로지 자신의 학습 데이터(training data)에만 의존하여 어떻게든 답변하려고 시도합니다.
테스트 방법 (How to Test It)
out_of_scope_test_cases = [
{
"question": "웹사이트를 스크래핑하는 Python 스크립트를 작성해 줄 수 있나요?",
...
참고: 이 테스트의 품질은 시스템 프롬프트 (system prompt)의 품질에 달려 있습니다. 만약 당신의 LLM이 모든 것에 답변하도록 지시받았다면, 실제로 그렇게 할 것입니다. 위의 테스트는 시스템 프롬프트가 제 역할을 수행하고 있는지 검증합니다. 만약 테스트가 실패한다면, 해결책은 테스트가 아니라 프롬프트에 있습니다.
🧩 엣지 케이스 4 — 부분적 컨텍스트 (Partial Context)
그것은 무엇인가? (What Is It?)
지식 베이스 (knowledge base)에 쿼리 (query)와 관련된 정보가 일부 존재하지만, 질문에 완전히 답변하기에는 충분하지 않은 상태를 의미합니다. 이는 복잡한 다중 파트 질문에서 자주 발생합니다.
사용자: "환불 정책은 무엇이며, 환불 요청을 위해 고객 지원팀에 어떻게 연락해야 하나요?"
지식 베이스 보유 정보:
...
기대 결과: LLM은 알고 있는 부분에 대해서는 답변하고, 나머지 부분에 대해서는 정보가 없음을 명시적으로 밝혀야 합니다.
실제 발생하는 현상: LLM은 누락된 세부 정보(포털 URL, 전화번호, 이메일 주소 등)를 지어냅니다. 모두 허구이지만 매우 그럴듯하게 들립니다.
테스트 방법 (How to Test It)
def test_partial_context_completeness():
"""
컨텍스트가 질문에 부분적으로만 답변할 때,
...
💣 엣지 케이스 5 — 적대적 쿼리 (Adversarial Queries)
그것은 무엇인가? (What Is It?)
RAG 시스템을 혼란시키거나, 오도하거나, 악용하기 위해 의도적으로 설계된 쿼리 (queries)입니다. 이는 덜 흔하...
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기