법률 AI 검색 실험기 (5) — Query Rewriting: 프롬프트 진화와 subQuery 실험
법률 QA 시스템의 검색 품질을 끌어올리기 위해 query rewriting 프롬프트를 반복 개선하고, sub-query decomposition까지 도입해 본 실험 기록이다. 결론부터 말하면, 프롬프트 개선은 효과가 있었지만 한계가 명확했고, sub-query 전략은 기대만큼의 돌파구가 되지 못했다.
배경: V2까지의 상황
이전 글에서 다뤘듯이, prerewriter V2는 Gemini 2.5 Flash-Lite 모델 기준으로 raw query 대비 오히려 성능이 떨어지는 경우가 많았다. 쿼리를 다시 써주는 것이 항상 좋은 것은 아니라는 교훈을 얻은 셈이다. 그래서 V3에서는 프롬프트 자체를 근본적으로 재설계했다.
한편 당시 최고 baseline은 pplx-embed-v1-4b 임베딩 모델에 raw query를 그대로 넣는 조합이었다. Recall@50 기준 30/31, Full Recall@50 기준 10/11. prerewriter가 이 baseline을 넘지 못하면 존재 의의가 없는 상황이었다.
참고: 이 30/31 수치는 초기 평가 기준 기준이다. 이후 multi-2 문항의 정답 정의를 재검토하면서(민법 766조 → 756조) benchmark를 수정했고, graph 검색과 결합하여 최종 31/31을 달성했다. 자세한 내용은 이후 "1차 아키텍처 확정: 실험에서 운영으로" 편에서 다룬다.
V3: 프롬프트 개선의 효과
V3 프롬프트는 2026년 3월 9일에 prerewriter-unified-v3.ts로 구현해서 평가했다. 모델은 동일하게 Gemini 2.5 Flash-Lite, 임베딩 5종 전체에 대해 복수정답 질문셋으로 측정했다.
핵심 결과 (Recall@50 / Full Recall@50)
| 임베딩 모델 | raw | V3 prerewrite |
| bge-m3 | 28/31, 8/11 | 28/31, 8/11 |
| pplx-embed-v1-0.6b | 28/31, 8/11 | 29/31, 9/11 |
| pplx-embed-v1-4b | 30/31, 10/11 | 29/31, 9/11 |
| pplx-embed-context-v1-0.6b | 26/31, 6/11 | 28/31, 8/11 |
| pplx-embed-context-v1-4b | 28/31, 8/11 | 27/31, 7/11 |
V2 대비 확실한 개선이 있었다. V2는 raw보다 성능을 깎아먹는 경우가 잦았는데, V3는 최소한 그런 참사는 없었다. 특히 두 가지 임베딩에서 의미 있는 향상을 확인했다.
pplx-embed-v1-0.6b: 28/31에서 29/31로, Full Recall도 8/11에서 9/11로 상승pplx-embed-context-v1-0.6b: 26/31에서 28/31로, Full Recall은 6/11에서 8/11로 대폭 상승
다만 주의할 점이 있었다. 최고 성능 임베딩인 pplx-embed-v1-4b에서는 오히려 V3가 raw보다 약간 낮았다. 그리고 평균 latency가 +1.3~1.4초 증가했다. 검색 한 건당 1.5초가 추가되는 것은 운영 환경에서 무시할 수 없는 비용이다.
V3의 판단: 프롬프트 방향은 맞다. V2보다 확실히 낫다. 그러나 전체 최고 baseline(pplx-embed-v1-4b raw)을 대체하기에는 아직 부족하다.
V4: sub-query decomposition 도입
V3에서 단일 쿼리 rewriting의 한계를 체감한 뒤, 다음 시도로 sub-query decomposition을 도입했다. 이것이 V4다.
sub-query decomposition이란
RAG 시스템에서 널리 연구되는 기법으로, 복잡한 질문을 더 단순한 하위 질문들로 분해한 뒤 각각에 대해 검색을 수행하고 결과를 병합하는 방식이다. 최근 연구들에 따르면, multi-hop 질문에서 관련 사실이 여러 문서에 분산되어 있을 때 표준 RAG가 충분한 정보를 검색하지 못하는 문제를 해결하기 위해 고안되었다. Question Decomposition for Retrieval-Augmented Generation (ACL 2025) 연구에서는 질문 분해와 reranking을 결합했을 때 MRR@10 기준 +36.7%, 답변 F1 기준 +11.6%의 개선을 보고하기도 했다.
법률 QA에서도 이 접근이 유효할 것이라는 가설이 있었다. "임대차 계약 해지 시 보증금 반환 절차와 기한은?"처럼 여러 축(해지 절차, 보증금 반환, 기한)을 동시에 묻는 질문이 많기 때문이다. 각 축별로 검색하면 놓치는 조문이 줄지 않을까.
V4 설계
V4 프롬프트(prerewriter-unified-v4.ts)는 기존 단일 searchQuery 외에 구조화된 출력을 도입했다.
queryType: 질문 유형 분류vector.subQueries: 하위 질문 목록vector.keywords: 검색 키워드graph.keywords: 그래프 검색용 키워드graph.lawNames: 관련 법률명
검색 시에는 메인 searchQuery와 각 subQuery를 개별 검색한 뒤 RRF(Reciprocal Rank Fusion)로 병합했다. 그래프 검색은 이번 실험에서 켜지 않았고, vector 경로만 평가했다.
결과 (Recall@50 / Full Recall@50)
| 임베딩 모델 | raw | V3 | V4 |
| bge-m3 | 28/31, 8/11 | 28/31, 8/11 | 27/31, 7/11 |
| pplx-embed-v1-0.6b | 28/31, 8/11 | 29/31, 9/11 | 29/31, 9/11 |
| pplx-embed-v1-4b | 30/31, 10/11 | 29/31, 9/11 | 29/31, 9/11 |
| pplx-embed-context-v1-0.6b | 26/31, 6/11 | 28/31, 8/11 | 27/31, 7/11 |
| pplx-embed-context-v1-4b | 28/31, 8/11 | 27/31, 7/11 | 28/31, 8/11 |
sub-query를 실제로 검색 병합에 넣어봤지만, V3 대비 전반적으로 나아지지 않았다. 최고치가 같은 경우도 있었지만 Recall@10 같은 초기 정밀도 지표에서는 오히려 크게 악화됐다.
특히 pplx-embed-v1-4b의 Recall@10이 V3의 21/31에서 V4의 14/31로 떨어졌다. sub-query들이 오히려 노이즈를 끌어들여 상위 랭킹을 흐트러뜨린 것이다. Latency도 ~2.2초까지 올라가 V3의 ~1.5초보다 더 나빠졌다.
세 버전 비교: 무엇을 배웠나
V2에서 V4까지의 여정을 요약하면 이렇다.
| 버전 | 접근 | 최고 Recall@50 | 최고 Full@50 | Latency 추가 | 판정 |
| raw (baseline) | 없음 | 30/31 | 10/11 | 0ms | 최고 |
| V2 | 기본 rewriting | raw 이하 | raw 이하 | +1.2s | 실패 |
| V3 | 프롬프트 재설계 | 29/31 | 9/11 | +1.4s | 부분 개선 |
| V4 | sub-query + RRF 병합 | 29/31 | 9/11 | +2.2s | 개선 없음 |
핵심 발견은 세 가지다.
첫째, 프롬프트 품질은 중요하다. V2에서 V3로의 개선은 프롬프트 설계만으로도 retrieval 품질을 의미 있게 올릴 수 있음을 보여줬다. "잘 쓴" rewriting은 약한 임베딩 모델의 성능을 끌어올리는 효과가 있었다.
둘째, sub-query decomposition은 만능이 아니다. 학술 연구에서 보고된 큰 폭의 개선이 우리 도메인에서는 재현되지 않았다. 분해된 하위 질문들이 오히려 노이즈를 증폭시키는 현상이 관찰됐다. 이전에 clean 실험에서도 few-shot 치팅 조건에서만 좋아 보이고 일반화 조건에서는 하락했던 이력이 있었는데, V4에서도 같은 패턴이 반복됐다.
셋째, prerewriter 단독으로는 retrieval miss를 해결할 수 없다. 어떤 프롬프트를 써도 pplx-embed-v1-4b raw의 30/31, 10/11을 넘지 못했다. 문제의 본질이 "쿼리를 얼마나 잘 다시 쓰느냐"가 아니라 "Top-50 안에 아예 들어오지 못하는 조문을 어떻게 회수하느냐"에 있다는 결론에 도달했다.
한계 인식과 다음 가설
이 실험들을 거치며 팀 내 토론에서 도출한 핵심 인식은 이렇다: 현재 병목은 selection miss가 아니라 retrieval miss다. selection miss(Top-50 안에 있는데 최종 답변에서 빠지는 것)는 이미 상당 부분 완화됐고, 진짜 문제는 Top-50 안에 아예 들어오지 않는 조문이었다.
이 관점에서 네 가지 다음 가설을 세웠다.
1. 임베딩 모델 재평가 (우선순위 1위)
retrieval quality를 직접 건드리는 가장 근본적인 접근이다. 기존 임베딩 평가가 단일정답 위주였기 때문에, 복수정답 Recall@20/50과 holdout domain noise 기준으로 다시 평가하면 더 나은 모델이 있을 수 있다. 만약 K20에서 현재 K50 수준 recall이 나오면 노이즈 감소, selection miss 감소, retrieval miss 완화까지 기대할 수 있다.
2. retrieval-stage 웹검색 보조 (우선순위 2위)
웹검색을 답변 단계가 아니라 검색 단계의 병렬 레인으로 사용하는 것이다. 과거 실험에서 retrieval-stage web augment가 Recall@50을 29/31에서 30/31로, Full Recall을 9/11에서 10/11로 올린 적이 있었다. 웹에서 법률명, 절차명, 숨은 facet 같은 힌트를 먼저 얻고 이를 내부 DB 문서로 다시 매핑/검증하는 구조다.
3. 원문 유지형 query 분할 (우선순위 3위)
sub-query decomposition 자체를 버리는 것은 아니다. 다만 원문 query를 대체하는 방식이 아니라, 원문 검색 결과에 추가 후보를 확장하는 보조 수단으로만 한정해야 한다는 교훈을 얻었다. V4의 실패는 "분해 결과로 원문을 대체한" 설계에서 비롯된 측면이 크다.
4. Graph DB (Neo4j) 도입 (우선순위 4위)
장기적으로는 검토 가치가 있지만, 지금 당장 정확도 개선 카드로 보기는 어렵다. 현재 문제는 저장소가 MongoDB인지 Neo4j인지가 아니라, 그래프가 "조문과 개념", "조문과 절차", "조문과 기간/요건/효과" 같은 의미 관계를 알고 있는지의 문제이기 때문이다.
돌아보며
prerewriter V3, V4 실험을 통해 query rewriting의 가능성과 한계를 동시에 확인했다. 프롬프트 개선은 분명히 효과가 있었고, sub-query decomposition이라는 학술적으로 검증된 기법도 직접 시도해 봤다. 그러나 우리 도메인에서는 기대한 만큼의 돌파구가 되지 못했다.
가장 큰 수확은 "문제의 본질이 어디에 있는지"를 명확히 한 것이다. prerewriter를 아무리 고도화해도 임베딩 모델의 retrieval quality라는 천장을 넘을 수 없다. 다음 단계는 그 천장 자체를 올리는 작업이 되어야 한다.
참고 자료:

