
검색 전 쿼리 재작성(Query Rewriting): 대부분이 놓치는 저비용의 재현율(Recall) 승리
요약
RAG 시스템에서 검색 성능을 높이기 위해 쿼리 재작성(Query Rewriting)을 활용하는 방법을 설명합니다. 임베딩 모델이나 리랭커를 교체하는 고비용 방식 대신, 저렴한 LLM 호출을 통해 쿼리를 개선하여 재현율을 높이는 전략을 다룹니다.
핵심 포인트
- 사용자의 짧고 모호한 쿼리는 검색 결과의 정확도를 떨어뜨림
- 쿼리 재작성은 인덱스 수정보다 비용 효율적인 검색 최적화 방법임
- 멀티 쿼리 확장과 스텝백 재작성이 주요 패턴으로 제시됨
- 검색 전 단계에서 쿼리를 개선하여 리트리벌의 재현율을 극대화함
- 도서: RAG Pocket Guide: Retrieval, Chunking, and Reranking Patterns for Production
- 저자의 다른 저서: Thinking in Go (2권 시리즈) — Complete Guide to Go Programming + Hexagonal Architecture in Go
- 내 프로젝트: Hermes IDE | GitHub — Claude Code 및 기타 AI 코딩 도구를 사용하여 작업하는 개발자를 위한 IDE
- 나에 대하여: xgabriel.com | GitHub
사용자가 지원 봇에 "how do I cancel"(어떻게 취소하나요)라고 입력합니다. 여러분의 리트리버(Retriever)는 이 세 단어를 임베딩(Embedding)하고, 코사인 유사도 검색(Cosine search)을 실행하여 결제 취소, 회의 초대 취소, 그리고 변경 로그(Changelog)에 나타나는 "cancel"이라는 단어에 관한 다섯 개의 청크(Chunk)를 모델에 전달합니다. 사용자가 실제로 원했던 구독 취소에 관한 청크는 7위에 머물렀습니다. 모델은 전달받은 내용으로 답변합니다. 답변은 틀렸고, 팀의 그 누구도 그 이유를 알 수 없습니다.
문제는 쿼리(Query)였습니다. 쿼리가 너무 짧고, 모호하며, 코퍼스(Corpus)가 작성된 문맥(Context)이 결여되어 있었습니다. 대부분의 팀은 이를 해결하기 위해 더 큰 임베딩 모델이나 리랭커(Reranker)를 사용하려고 합니다. 둘 다 도움이 됩니다. 하지만 둘 다 인덱스(Index)에 도달하기 전에 쿼리를 수정하는 방법보다 더 비용이 많이 듭니다. 그것이 바로 쿼리 재작성(Query rewriting)입니다.
쿼리 재작성은 리트리벌(Retrieval) 앞에 위치합니다. 가공되지 않은 사용자 쿼리를 가져와서 저렴한 LLM 호출 한 번을 거친 뒤, 더 나은 쿼리로 검색을 수행하는 것입니다. 두 가지 패턴이 대부분의 이득을 가져다줍니다: 멀티 쿼리 확장(Multi-query expansion)과 스텝백 재작성(Step-back rewriting)입니다. 여기서는 각 방식이 어떻게 작동하는지, 비용은 얼마인지, 그리고 지연 시간(Latency)을 감수할 가치가 있는지 결정하는 방법을 설명합니다.
원문 쿼리가 나쁜 검색 키인 이유
사용자의 문구 그대로를 임베딩하는 것은 사용자가 질문을 구성한 방식이 문서가 답변을 구성한 방식과 일치한다고 가정하는 것입니다. 하지만 실제로는 거의 그렇지 않습니다.
사용자들은 짧고 어휘적으로 빈약한 쿼리(query)를 작성합니다. "covid policy", "refund", "how do I cancel"와 같은 식입니다. 반면 당신의 문서들은 몇 달 전 다른 누군가에 의해 작성되었으며, 서로 다른 어휘와 전체적인 맥락을 담고 있습니다. "how do I cancel"의 임베딩(embedding)은 코퍼스(corpus) 내의 모든 취소 관련 내용이 밀집된 구역에 위치하게 됩니다. 단 하나의 관련 청크(chunk)가 그 안에 존재할 수는 있지만, 최상단에 위치하지는 않으며, 당신의 top-k 컷오프(cut-off) 과정에서 버려지게 됩니다.
재작성(Rewriting)은 비용이 많이 드는 인덱스(index) 측면 대신, 비용이 저렴한 쿼리 측면에서 이러한 불일치를 해결합니다. 즉, 검색 키(search key)를 재구성하여 정답에 더 가깝게 도달하도록 만드는 것입니다.
멀티 쿼리 확장 (Multi-query expansion)
핵심 아이디어: 하나의 쿼리는 인덱스에 대한 단 한 번의 시도입니다. 동일한 의도를 가진 여러 가지 표현을 생성하여 각각 검색을 수행한 뒤, 그 결과들을 병합합니다. 더 많은 표현을 생성할수록 어휘적(lexical) 및 의미적(semantic) 표면적이 넓어지며, 이는 그중 하나가 올바른 청크 근처에 도달할 확률이 높아짐을 의미합니다.
LLM에게 몇 가지 변형된 쿼리를 요청하고, 각 쿼리를 개별 검색으로 실행한 다음, 상호 순위 결합(Reciprocal Rank Fusion, RRF)을 통해 순위를 융합합니다. RRF는 여러 변형 쿼리가 공통적으로 동의하는 문서에 가산점을 부여하므로, 특정 검색에서 1위를 차지하지 못하더라도 4번의 검색 중 3번에서 나타난 청크는 상단으로 떠오르게 됩니다.
from collections import defaultdict
from openai import OpenAI
...
이 방식이 실제로 도움이 될지, 아니면 단순히 토큰(token)만 낭비할지는 두 가지 세부 사항에 의해 결정됩니다. 첫째, 항상 원래의 쿼리를 세트에 포함시켜야 합니다. LLM은 때때로 의도에서 벗어날 수 있으며, 원래 쿼리는 중심을 잡아주는 닻(anchor) 역할을 하기 때문입니다. 둘째, 단순 합집합 후 중복 제거(union-and-dedupe)가 아닌 RRF를 사용해야 합니다. 단순 합집합은 순위(rank order)를 버리게 되는데, 순위는 합의된 문서 중 어떤 것을 신뢰해야 하는지 알려주는 유일한 신호입니다.
멀티 쿼리는 사용자의 의도가 모호하여 몇 번의 재표현을 통해 이를 탐색해야 하는 모호하거나 개념 중심적인 쿼리에서 제 역할을 다합니다. 변형된 쿼리들은 병렬로 실행되므로, 지연 시간(latency) 비용은 N번의 검색을 순차적으로 수행하는 것이 아니라, '한 번의 LLM 호출 + N개의 검색 중 가장 느린 검색 시간'만큼만 발생합니다.
스텝백 재작성 (Step-back rewriting)
스텝백 재작성 (Step-back rewriting)
스텝백(Step-back)은 반대 방향으로 움직입니다. 동일한 질문의 변형을 더 많이 만드는 대신, LLM에게 추상화 수준을 한 단계 높여 더 넓은 질문을 먼저 던지도록 요청하는 것입니다.
이 기술은 2023년 Google DeepMind의 논문인 Take a Step Back에서 유래되었습니다. 이 논문은 모델에게 구체적인 질문을 하기 전에 일반적인 원칙(general principle)에 대해 추론하도록 프롬프팅하는 것이 추론 벤치마크(reasoning benchmarks)에서 답변 성능을 향상시킨다는 것을 발견했습니다. 동일한 방식이 검색(retrieval)에도 도움이 됩니다. "tier 3 기업 계정의 연체료는 얼마인가요?"와 같은 좁은 범위의 쿼리는 너무 구체적이어서, 매칭되는 청크(chunk)가 거의 정확히 그 단어들을 포함하고 있어야 합니다. 반면 스텝백 버전인 "계정 등급별로 결제 연체를 어떻게 처리하나요?"는 문맥 속에서 실제로 연체료를 정의하는 정책 섹션을 검색해 옵니다.
STEP_BACK_PROMPT = """주어진 구체적인 사용자 질문을 바탕으로,
그 질문에 답하는 데 필요한 문맥을 포함하고 있는
더 넓고 일반적인 질문을 작성하세요. 다음만 반환하세요...
`retrieve` 함수가 원래의 쿼리와 스텝백 쿼리 모두로 검색한 뒤 이를 융합(fuse)한다는 점에 주목하세요. 여러분은 넓은 문맥을 담은 청크와, 존재한다면 구체적인 청크를 모두 얻기를 원합니다. 스텝백 쿼리는 답변의 틀을 형성하는 섹션을 가져오고, 원래 쿼리는 정확한 일치 항목이 있을 때 이를 가져옵니다. 이 둘이 결합되어 모델에게 해당 조항과 그 조항이 놓인 문맥을 함께 제공합니다.
스텝백은 계약서, 정책 매뉴얼, 기술 사양서와 같이 구조화된 문서에 대해 전문 용어가 많이 포함된 좁은 범위의 쿼리를 처리할 때 빛을 발합니다. 즉, 답변이 의미를 갖기 위해 주변 문맥이 반드시 필요한 경우에 효과적입니다.
## 재작성이 실제로 가져다주는 이점
원문 연구와 RAG 평가(RAG eval) 조사에서 발표된 수치들은 단 하나의 마법 같은 수치를 제시하기보다는 일관된 형태를 보여줍니다. 스텝백 논문은 추론 작업에서 두 자릿수의 정확도(accuracy) 향상을 보고했습니다. RRF(Reciprocal Rank Fusion)를 활용한 멀티 쿼리 확장(Multi-query expansion)은 정보 검색(IR) 문헌에서 오랫동안 입증된 재현율(recall) 개선 방법이며, 향상 폭은 원래 쿼리가 얼마나 빈약한지에 따라 크게 달라집니다.
솔직한 프레임링(framing): 재작성(rewriting)은 쿼리가 짧거나, 모호하거나, 코퍼스(corpus)와 다른 어휘로 작성되었을 때 재현율(recall)을 가장 많이 향상시킵니다. 반면 쿼리가 이미 길고, 구체적이며, 문서들과 어휘적으로 유사할 때는 재현율 향상 폭이 가장 적습니다. 만약 사용자들이 문서와 유사한 완전한 문장을 붙여넣는다면, 개선 효과를 거의 볼 수 없을 것입니다. 하지만 검색창에 세 단어 정도만 입력한다면, 그 이득은 매우 클 수 있습니다. 여러분의 실제 쿼리 로그를 직접 측정해 보기 전까지는 자신이 어느 경우에 해당하는지 알 수 없습니다.
이러한 측정이 대부분의 팀이 건너뛰는 부분이며, 이 때문에 그들이 추상적인 수준에서 재작성기(rewriter)에 대해 논쟁하는 것입니다. 실제 쿼리 200개를 추출하고, 골드 문서(gold documents)를 수동으로 라벨링한 뒤, 재작성을 적용했을 때와 적용하지 않았을 때의 recall@10을 실행해 보십시오. 라벨링에 투자한 일주일의 시간은, 트래픽에 아무런 도움이 되지 않는 재작성기를 배포하는 것을 막아주는 첫 순간에 그 가치를 충분히 증명할 것입니다.
## 이를 위해 지불해야 하는 지연 시간(latency)
재현율(recall)은 결정 사항의 절반일 뿐입니다. 나머지 절반은 여러분이 핫 패스(hot path)에 방금 추가한 LLM 호출입니다.
| 방식 | LLM 호출 | p50 추가 시간 | 검색 횟수 |
| --- | --- | --- | --- |
| 재작성 없음 | 0 | 0 ms | 1 |
| ... |
재작성은 한 번의 추가적인 LLM 왕복(round trip)을 의미하며, `gpt-4o-mini`와 같은 소형 모델의 경우 일반적으로 250~350ms가 소요됩니다. 추가된 검색들은 병렬로 실행되므로, 비용은 검색들의 합계가 아니라 가장 느린 검색 하나만큼 발생합니다. 이미 생성(generation) 호출을 기다려야 하는 대화형 챗봇의 경우, 답변이 스트리밍되는 데 걸리는 몇 초에 비하면 300ms의 재작성 시간은 대개 눈에 띄지 않습니다. 하지만 높은 QPS(초당 쿼리 수)를 요구하는 저지연(low-latency) 검색창의 경우 300ms는 매우 큰 수치이며, 원본 후보군(raw candidates)에 대한 리랭커(reranker)를 사용하는 것이 더 적은 비용으로 동일한 격차를 메울 수 있는지 테스트해야 합니다.
비용을 낮게 유지하는 세 가지 방법:
- **재작성 결과 캐싱 (Cache rewrites).** 인기 있는 쿼리는 반복됩니다. 재작성 결과물에 키 기반 캐시 (Keyed cache)를 적용하면 일반적인 사례에서 LLM 호출을 완전히 제거할 수 있습니다.
- **쿼리 길이에 따른 게이팅 (Gate on query length).** 쿼리가 이미 길고 구체적이라면 재작성을 건너뜁니다. 짧은 쿼리야말로 개선의 이점이 있는 지점입니다.
- **품질을 유지하는 가장 작은 모델 사용.** 재작성은 좁은 범위의 작업입니다. 미니 모델 (Mini model)은 대개 훨씬 적은 지연 시간 (Latency)으로 이 작업에서 프런티어 모델 (Frontier model)과 대등한 성능을 보여줍니다.
## 실용적인 기본 설정
만약 하나의 재작성기 (Rewriter)를 배포한다면, 쿼리 길이에 따라 게이팅을 적용하고 재작성 캐시를 갖춘 RRF 기반의 멀티 쿼리 확장 (Multi-query expansion) 방식을 배포하십시오. 이는 재현율 (Recall)을 악화시키는 경우가 거의 없으며, 병렬 검색을 통해 지연 시간을 제한된 범위 내로 유지하고, 캐시는 정상 상태 (Steady-state) 비용을 거의 제로에 가깝게 줄여줍니다. 코퍼스 (Corpus)가 구조화되어 있고 쿼리가 좁은 범위라면 스텝백 (Step-back) 기법을 추가하십시오.
그다음 측정하십시오. 귀하의 트래픽에 도움이 되는 재작성 방식은 경험적인 문제이지, 레딧 (Reddit)의 누군가가 다른 코퍼스를 위해 선택한 기본값이 아닙니다. 200개 쿼리 평가 세트 (Eval)를 한 번 구축하여 모든 변경 사항에 대해 실행하면, 추측 없이 재작성기를 교체할 수 있습니다.
서두에 언급한 팀은 여전히 잘못된 취소 정책을 인용하는 봇을 배포하고 있습니다. 해결책은 그들이 이미 가지고 있던 인덱스 (Index) 앞에 LLM 호출을 하나 추가하는 것이었습니다.
## 이 내용이 유용했다면
쿼리 재작성은 더 긴 파이프라인의 전면에 위치합니다. [RAG Pocket Guide](https://www.amazon.com/dp/B0GX2YDC5Z)에서는 재작성된 쿼리가 실제로 무엇을 찾아내는지 결정하는 청킹 (Chunking), 하이브리드 검색 (Hybrid retrieval), 리랭킹 (Reranking) 패턴과 함께 이를 다루며, 이 중 어떤 것이 귀하의 코퍼스에서 재현율을 변화시켰는지 알려주는 평가 방법론 (Eval methodology)도 포함하고 있습니다. 검색을 튜닝하고 있으며, 수많은 블로그 포스트 대신 하나의 사고 모델 (Mental model)을 원한다면 바로 이 책을 위한 것입니다.
[](https://www.amazon.com/dp/B0GX2YDC5Z)
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기