8.8.1 검증 실패 시 쿼리 재구성(Query Reformulation) 자동 트리거
RAG 파이프라인에서 가장 빈번하게 발생하는 “오라클 실패(Oracle Failure)“의 근본 원인은, 아이러니하게도 타겟 LLM이나 Vector DB의 결함이 아니라 사용자가 입력한 최초의 쿼리(Query) 자체의 품질에 있는 경우가 많다.
사용자들은 시스템이 찰떡같이 알아듣기를 기대하며 “그거 어떻게 신청해?”, “작년 기준 이자율“과 같이 대명사가 남발되거나 컨텍스트가 텅 빈 파편화된 문장(Fragmented Sentence)을 던진다. 이 엉성한 쿼리가 임베딩되어 Vector DB로 들어가면, 검색기는 엉뚱한 문서를 가져오게 되고(Context Relevance 실패), 타겟 모델은 당연히 대답을 하지 못하거나 환각을 일으켜 오라클의 가차 없는 철퇴(Reject)를 맞게 된다.
오라클이 ’Context Relevance’가 임계값 미달이라고 판정하여 트랜잭션을 차단했을 때, 시스템이 해야 할 첫 번째 자가 치유(Self-Healing) 동작은 에러 메시지를 띄우는 것이 아니라, 이 실패한 쿼리를 시스템이 이해할 수 있는 언어로 **자동 재구성(Query Reformulation)**하여 재검색(Retry) 루프를 돌리는 것이다.
1. 쿼리 재구성을 위한 오라클의 피드백 파싱
단순히 “쿼리를 다시 써라“는 지시는 의미가 없다. 오라클 시스템은 첫 번째 검색 실패 당시, LLM-as-a-Judge가 작성한 평가 이유(Reasoning) 로그를 파싱하여 무엇이 부족했는지 결손점(Missing Entity)을 정확히 짚어내야 한다.
- 실패한 원본 쿼리: “비용 청구 어떻게 해?”
- 오라클(Judge)의 실패 피드백 로그: “해당 질문과 관련된 문서를 검색했으나 매칭되지 않음. ’비용’의 주체(출장비, 식대, 도서구입비 등)가 명시되지 않아 문서 검색 범위를 특정할 수 없음.”
2. Router LLM 기반의 쿼리 확장 및 재구성 기법
오라클의 피드백을 전달받은 파이프라인 전단(Front-end)의 미니 모델(Router LLM)은 이 피드백을 기반으로 검색기가 좋아할 만한 고도로 밀집된(Dense) 형태의 쿼리로 원본을 재조립한다. 여기에는 크게 세 가지 기법이 동원된다.
- 동의어 확장(Synonym Expansion): 사용자의 어휘를 사내 공식 용어로 번역한다. (“비용 청구” \rightarrow “전표 처리”, “지출 결의서”, “정산”)
- 문맥 병합(Contextual Fusion): 만약 대화형 챗봇 환경이라면, 사용자가 이전에 했던 대화(“나 내일 제주도로 출장 가는데”)를 끌어와 현재의 파편화된 쿼리(“비용 청구 어떻게 해?”)와 결합하여 자급자족 가능한 완벽한 하나의 문장(“제주도 출장 시 발생하는 출장비의 지출 결의서 처리 방법은?”)으로 수복한다.
- 다중 쿼리 분해(Multi-Query Decomposition): “A제품과 B제품의 환불 규정 차이점“과 같이 비교를 요구하는 복합 쿼리의 경우, 단일 임베딩으로는 정확한 검색이 어렵다. 오라클은 이를 두 개의 직렬 쿼리(“A제품 환불 규정”, “B제품 환불 규정”)로 강제 분할하여 각각 검색을 돌리도록 트리거한다.
3. 재시도 제어 플로우 (Retry Control Flow)
오라클이 쿼리 재구성을 트리거하는 루프는 무한정 반복되어서는 안 된다. 시스템의 무한 루프(Infinite Loop)와 토큰 비용 폭발을 막기 위해 철저한 제어 플로우가 하드코딩 되어야 한다.
MAX_RETRIES = 2 # 최대 쿼리 재구성 횟수
def execute_rag_pipeline(user_query, retry_count=0):
docs = retriever.get_documents(user_query)
is_valid, feedback = oracle.evaluate_context_relevance(docs, user_query)
if is_valid:
return target_llm.generate(docs, user_query)
if not is_valid and retry_count < MAX_RETRIES:
# 오라클의 피드백을 기반으로 쿼리 재구성 트리거
better_query = query_rewriter_llm.rewrite(user_query, feedback)
print(f"오라클 필터링 감지. 쿼리 재구성 진행: {better_query}")
return execute_rag_pipeline(better_query, retry_count + 1)
else:
# 재시도 횟수를 모두 소진하면 최종 Refusal 프로토콜 가동
return trigger_safeguard_refusal()
이처럼 쿼리 재구성 자동 트리거는 사용자의 개입 없이 파이프라인 내부에서 찰나의 순간에 이루어진다. 사용자는 자신이 개떡같이 말한 질문이 시스템 내부에서 찰떡같은 엔터프라이즈 검색 언어로 번역되어, 한 번의 헛스윙(검색 실패)을 거친 뒤 결국 완벽한 정답으로 돌아오는 마법 같은 회복 탄력성(Resilience)을 경험하게 된다.