법률 AI 검색 실험기 (4) — Query Rewriting: Prerewriter 도입과 모델 비교
벡터 검색 성능을 올리는 가장 쉬운 방법
RAG 파이프라인에서 retrieval 성능이 안 나올 때 가장 먼저 떠오르는 선택지는 보통 두 가지다. 임베딩 모델을 바꾸거나, 쿼리를 바꾸거나. 임베딩 모델 비교는 이미 별도로 진행했고, 이번에는 후자를 건드릴 차례였다.
업계에서는 이 접근을 보통 query rewriting이라고 부른다. 사용자의 원문 질문을 검색에 더 유리한 형태로 변환하는 것이다. Microsoft의 RAG 기법 정리 문서에서는 query rewriting을 "pre-retrieval" 단계의 핵심 기법으로 분류하고 있고, 최근 연구(arxiv 2501.07391)에서도 query expansion과 rewriting이 retrieval 품질에 미치는 영향을 체계적으로 조사하고 있다. HyDE(Hypothetical Document Embedding)처럼 가상 문서를 생성해서 검색하는 방법도 있고, Step-Back Prompting처럼 질문을 더 일반적인 형태로 바꾸는 접근도 있다.
내가 만든 파이프라인에서는 이 단계를 prerewriter라고 부른다. 검색 전에 쿼리를 다시 쓴다는 뜻 그대로다. 이번 글에서는 prerewriter V2의 설계 의도와, 네 가지 LLM 모델로 비교 실험한 결과를 정리한다.
Prerewriter V2 설계 의도
Prerewriter V1은 단순한 rephrasing이었다. V2에서 바꾼 핵심 설계는 크게 세 가지다.
첫째, 복수 정답을 더 강하게 고려한다. 법률 QA에서는 하나의 질문이 여러 조문, 여러 법률에 걸쳐 답을 가지는 경우가 많다. "상속 포기 절차와 그 효과는?"이라는 질문 하나에도 민법의 여러 조문이 관련된다. Prerewriter V2는 하나의 질문 안에서 여러 축--사람, 기관, 관계, 권리, 책임, 절차, 금액, 기간, 숫자--을 보존하도록 프롬프트를 설계했다.
둘째, 출력을 용도별로 분리한다. 단일 프롬프트 안에서 벡터 검색용과 그래프 검색용 출력을 함께 생성한다.
vector.searchQuery: 벡터 검색에 넣을 재작성된 쿼리vector.keywords: 벡터 검색 보조 키워드graph.keywords: 그래프 탐색용 키워드graph.lawNames: 그래프 탐색에 쓸 법률명
다만, 이번 실험에서는 vector 출력만 사용했다. 실제 검색은 벡터 DB의 hybrid(dense + sparse) 검색만 돌렸고, 그래프 검색은 사용하지 않았다. 그래프 검색 쪽은 별도 실험으로 분리할 예정이었다.
셋째, 기존 코드와 실험을 건드리지 않는다. 새 prerewriter만 별도로 추가하고, baseline(raw, 즉 원문 그대로 검색)과의 비교를 항상 유지했다.
실험 설계
질문셋은 전체 84개를 사용했다.
| 카테고리 | 문항 수 |
| direct | 20 |
| scenario | 20 |
| tax | 17 |
| multi | 11 |
| holdout | 16 |
임베딩 모델은 5종 전체를 돌렸다: bge-m3, pplx-embed-v1-0.6b, pplx-embed-v1-4b, pplx-embed-context-v1-0.6b, pplx-embed-context-v1-4b.
Prerewriter 모델은 두 차례에 걸쳐 총 4종을 비교했다.
- 1차 실험:
gemini-2.5-flash-lite,gpt-5-mini - 2차 실험:
gemini-2.5-flash,gpt-4.1-mini
평가 지표는 복수 정답 recall을 중심으로 봤다. @K는 상위 K개 청크 안에 정답 조문이 몇 개 포함되었는지, F@K는 복수 정답이 모두 포함된 질문("full match")이 몇 개인지를 뜻한다. 예를 들어 multi 카테고리에서 @50 = 30/31이라면, 상위 50개 청크 안에 전체 31개 정답 중 30개가 포함되었다는 의미다. F@50 = 10/11이면 11개 multi 질문 중 10개가 모든 정답을 상위 50개 안에서 찾았다는 뜻이다.
1차 실험: Gemini Flash Lite vs GPT-5 mini
결과 요약 (multi 카테고리, 복수 정답 기준)
| 임베딩 | 모드 | @10 | @30 | @50 | F@50 | 평균 지연 |
| pplx-v1-4b | raw | 19/31 | 26/31 | 30/31 | 10/11 | 324ms |
| pplx-v1-4b | gemini-flash-lite | 19/31 | 25/31 | 28/31 | 8/11 | 1,755ms |
| pplx-v1-4b | gpt-5-mini | 21/31 | 29/31 | 30/31 | 10/11 | 5,387ms |
| pplx-v1-0.6b | raw | 17/31 | 26/31 | 28/31 | 8/11 | 323ms |
| pplx-v1-0.6b | gpt-5-mini | 16/31 | 29/31 | 29/31 | 9/11 | 5,383ms |
| pplx-context-v1-0.6b | raw | 16/31 | 24/31 | 26/31 | 6/11 | 326ms |
| pplx-context-v1-0.6b | gpt-5-mini | 17/31 | 27/31 | 29/31 | 9/11 | 5,394ms |
해석
Gemini 2.5 Flash Lite는 전반적으로 실패였다. Raw보다 분명히 떨어지는 경우가 많았고, 특히 bge-m3와 pplx-v1-4b에서 손해가 컸다. 쿼리를 재작성했는데 오히려 원문보다 검색 품질이 나빠진 것이다.
GPT-5 mini는 일부 임베딩에서 실제로 개선을 보였다. 주목할 만한 변화를 정리하면:
pplx-embed-v1-0.6b: @50이 28/31에서 29/31로, F@50이 8/11에서 9/11로 상승pplx-embed-context-v1-0.6b: @50이 26/31에서 29/31로, F@50이 6/11에서 9/11로 상승. 이건 꽤 큰 점프다.pplx-embed-context-v1-4b: @50이 28/31에서 29/31로, F@50이 8/11에서 9/11로 상승
다만 최고 성능 조합인 pplx-embed-v1-4b에서는 @50 기준 30/31로 raw와 동일했다. @30, @40에서는 개선이 있었지만 천장을 뚫지는 못했다.
가장 큰 문제는 속도였다. GPT-5 mini prerewriter를 붙이면 multi 기준 5.2초에서 5.5초가 걸렸다. Raw는 0.16초에서 0.33초다. Retrieval 단계 하나에 5초를 더 쓰는 건 운영 환경에서 받아들이기 어렵다.
2차 실험: Gemini Flash vs GPT-4.1 mini
1차에서 flash-lite가 너무 약했기 때문에 상위 모델인 gemini-2.5-flash를 추가했고, GPT 쪽에서는 비용/속도 절충을 위해 gpt-4.1-mini를 넣었다.
결과 요약 (multi 카테고리, 복수 정답 기준)
| 임베딩 | 모드 | @10 | @30 | @50 | F@50 | 평균 지연 |
| pplx-v1-4b | raw | 19/31 | 26/31 | 30/31 | 10/11 | 376ms |
| pplx-v1-4b | gemini-flash | 18/31 | 26/31 | 29/31 | 9/11 | 3,613ms |
| pplx-v1-4b | gpt-4.1-mini | 20/31 | 26/31 | 28/31 | 8/11 | 2,894ms |
| pplx-v1-0.6b | gemini-flash | 19/31 | 28/31 | 28/31 | 8/11 | 3,601ms |
| pplx-context-v1-4b | gpt-4.1-mini | 17/31 | 24/31 | 29/31 | 9/11 | 2,835ms |
해석
Gemini 2.5 Flash는 Flash Lite보다 확실히 나았다. 특히 pplx-v1-0.6b에서 @20, @30이 꽤 올라갔다. 하지만 raw를 확실히 뒤집는 수준은 아니었다. 지연도 3.5초에서 3.6초대로 여전히 높았다.
GPT-4.1 mini는 GPT-5 mini보다 빨랐지만, 성능은 약했다. 지연이 2.7초에서 2.9초로 GPT-5 mini 대비 절반 가까이 줄었는데, 그만큼 rewriting 품질도 떨어진 것으로 보인다. 일부 임베딩에서 @40, @50은 괜찮았지만, 전체 최고 조합을 만들지는 못했다.
네 모델을 종합하면
모든 실험을 관통하는 결론은 명확했다.
순수 retrieval 최고 성능은 여전히 pplx-embed-v1-4b + raw(원문 그대로)였다. Multi @50 = 30/31, F@50 = 10/11. 어떤 prerewriter를 붙여도 이 조합을 일관되게 넘지 못했다.
네 모델의 포지션을 정리하면 이렇다:
| 모델 | 성능 개선 | 지연 | 종합 판단 |
| gemini-2.5-flash-lite | raw보다 하락 | ~1.7s | 탈락 |
| gemini-2.5-flash | flash-lite보다 개선, raw 미달 | ~3.5s | 보류 |
| gpt-4.1-mini | 부분 개선, gpt-5-mini 미달 | ~2.8s | 보류 |
| gpt-5-mini | 일부 임베딩에서 유의미 개선 | ~5.3s | 연구 후보 |
속도 대비 품질이 가장 좋았던 모델은 gpt-5-mini다. 특히 중소형 임베딩(pplx-embed-v1-0.6b, pplx-embed-context-v1-0.6b)과 조합했을 때 F@50 기준 6/11에서 9/11로 올라가는 등 눈에 띄는 개선이 있었다. 하지만 가장 강한 임베딩(pplx-embed-v1-4b)과 조합하면 이미 raw가 충분히 좋아서 prerewriter의 추가 가치가 제한적이었다.
Gemini 계열은 이 프롬프트 설계와 궁합이 안 맞았다. Flash Lite는 아예 역효과였고, Flash도 기대만큼은 아니었다. 모델 자체의 문제인지 프롬프트 최적화 부족인지는 이 실험만으로는 단정할 수 없다.
이 실험에서 확인한 것
Query rewriting이 RAG에서 유효한 기법이라는 건 업계 전반의 합의다. 하지만 "항상 좋아진다"는 보장은 없다. 이번 실험에서 확인한 결론은 세 가지다.
1. 강한 임베딩 + 원문이 약한 임베딩 + rewriting을 이긴다. pplx-embed-v1-4b raw가 어떤 prerewriter 조합보다 좋거나 동등했다. 임베딩 모델의 기본 역량이 충분하면 쿼리를 손대지 않는 것이 더 나을 수 있다.
2. Rewriting은 약한 임베딩을 보완하는 데 더 효과적이다. GPT-5 mini prerewriter가 가장 큰 효과를 보인 건 0.6b급 소형 임베딩과의 조합이었다. 임베딩 모델의 표현력이 부족한 부분을 쿼리 쪽에서 보완한 결과다.
3. 속도 비용을 무시할 수 없다. Prerewriter를 붙이면 retrieval 지연이 10배에서 16배로 뛴다. 후속 단계(answer generation, verification)의 지연까지 합치면 전체 파이프라인 응답 시간에 미치는 영향이 상당하다. 운영 환경에서는 이 trade-off를 무시할 수 없다.
결론적으로, prerewriter는 운영 기본값으로 채택하기에는 아직 설득력이 부족했다. 하지만 "쿼리를 바꾸면 검색이 달라진다"는 가능성 자체는 확인했고, 특히 소형 임베딩 환경이나 복수 정답 recall이 중요한 시나리오에서는 여전히 연구할 가치가 있다. 다음 단계에서는 source routing과 결합하거나, prerewriter의 프롬프트 자체를 더 정교하게 다듬는 방향을 검토할 예정이다.

