법률 AI 검색 실험기 (9) — Source Router: 8개 컬렉션을 지능적으로 라우팅하기
법률 QA 시스템을 만들면서 가장 먼저 부딪힌 현실이 있다. 우리가 다루는 법률 데이터는 하나의 벡터 DB에 넣고 검색하면 끝나는 구조가 아니라는 것이다. 법령 조문, 판례, 공식 법령해석, 부처 실무 해석, 행정심판 재결례, 헌재 결정, 위원회 결정, 지자체 조례까지 --- 성격이 완전히 다른 8개 컬렉션, 총 300만 건 이상의 문서가 Qdrant에 올라가 있다.
이 글에서는 사용자 질문 하나가 들어왔을 때 어떤 컬렉션을 열고, 어떤 컬렉션은 닫아야 하는지를 판단하는 Source Router의 설계 과정과 최종 버전 선정까지의 여정을 정리한다.
하나의 벡터 DB가 아닌 8개 컬렉션
우리 시스템의 8개 컬렉션은 각각 역할이 다르다.
| 컬렉션 | 내용 | 역할 |
law_articles | 법령 조문 | 답변의 기본 근거 축 |
ordinance_articles | 지자체 조례/규칙 | 지역/인허가 질문 보강 |
precedents | 판례 | 실제 분쟁 적용과 판단 경향 |
legal_interpretations | 공식 법령해석 | 조문 문언의 공식 해석 |
ministry_interpretations | 부처 실무 해석 | 행정 실무 적용과 민원 회신 |
administrative_trials | 행정심판 재결례 | 처분/불복/구제 절차 |
constitutional_decisions | 헌재 결정 | 위헌성/기본권/헌법소원 |
committee_decisions | 위원회/권익위 결정 | 위원회 판단/민원 해결 |
같은 질문이라도 law_articles는 주근거이고, precedents는 보조근거다. 이렇게 성격이 다른 문서를 raw score 하나로 일렬 정렬하면 답변 구조가 쉽게 흔들린다. 법령 조문이 답변의 뼈대가 되어야 하는데, 의미적 유사도가 높은 판례가 상위권을 독점해버리는 식이다.
라우팅이 필요한 이유
RAG 시스템에서 query routing은 이미 널리 알려진 패턴이다. Towards Data Science의 글이나 최근 arxiv에 올라온 RAGRouter 논문에서도 확인할 수 있듯이, 복수의 데이터 소스를 가진 RAG 시스템에서는 사용자 질문의 의도를 분석해 적합한 데이터 소스로 라우팅하는 것이 핵심이다. 모든 소스를 동일 강도로 검색하면 노이즈가 늘고, 검색 비용도 올라간다.
우리 시스템에서 라우팅이 특히 중요한 이유는 세 가지였다.
첫째, 컬렉션 간 점수 비교가 불가능하다. 법령 조문과 판례의 임베딩 유사도 점수는 같은 의미가 아니다. 문서 길이, 표현 방식, 구조가 완전히 다르기 때문이다. 따라서 단순 점수 정렬이 아니라 lane 정책 중심의 병합이 필요하다.
둘째, 질문 유형에 따라 필요한 컬렉션이 다르다. "근로기준법상 해고 절차가 어떻게 되나요?"라는 질문에는 법령 조문과 판례가 중요하지만, "서울시 주차장 조례"라는 질문에는 조례 컬렉션이 핵심이다. 헌법소원에 대한 질문에는 헌재 결정이 열려야 한다.
셋째, 비용과 latency 문제다. 질문 하나에 8개 컬렉션을 전부 깊게 검색하면 불필요한 비용이 발생한다. 실제로 대부분의 질문은 2~4개 레인이면 충분하다.
Source Router 설계 과정
Lane 구조 설계
가장 먼저 8개 컬렉션을 세 가지 등급으로 분류했다.
Anchor Lane --- law_articles는 항상 검색하고, 가장 깊게 검색한다. 법률 QA에서 법령 조문은 답변의 중심 근거다.
Always-on Support Lane --- precedents, legal_interpretations, ministry_interpretations는 대부분의 질문에서 도움이 될 가능성이 높다. 항상 켜되, 기본 검색량은 작게 유지하고 질문 힌트가 강할 때만 더 깊게 검색한다.
Triggered Lane --- ordinance_articles, administrative_trials, constitutional_decisions, committee_decisions는 특정 질문 유형에서만 의미가 있다. 기본은 off이고, 질문에 직접적인 단서가 있을 때만 확장한다.
이 구조를 기반으로, Source Router는 질문을 받아 각 컬렉션의 활성 상태(off, support, expand)를 결정하는 역할을 맡게 되었다.
Prompt-only 접근
Source Router는 별도의 분류 모델을 학습시키지 않고, LLM 프롬프트만으로 구현했다. gemini-3.1-flash-lite-preview 모델에 질문을 넣으면 각 컬렉션의 활성화 수준과 confidence를 JSON으로 돌려주는 구조다. 이 접근을 택한 이유는 빠른 실험 반복이 가능하고, 법률 도메인의 미묘한 판단을 규칙 기반으로 커버하기 어렵기 때문이다.
버전별 평가와 선택 근거
48개 golden dataset 문항을 기준으로 v1부터 v1_6까지 총 7개 버전을 실험했다. 각 버전은 이전 버전의 실패 패턴을 분석한 뒤 프롬프트를 개선하는 방식으로 진행되었다.
전체 결과 비교
| Version | Exact Score | Recall Score | Critical Miss | Core FP | Perfect | 판단 |
v1 | 66.7% | 86.8% | 7 | 86 | 0/48 | 기준선, 과발화 심각 |
v1_1 | 90.2% | 90.2% | 20 | 16 | 27/48 | 과발화 크게 감소 |
v1_2 | 94.0% | 92.5% | 13 | 14 | 31/48 | triggered lane 개선 |
v1_3 | 96.4% | 93.8% | 10 | 11 | 37/48 | 실사용 후보 진입 |
v1_4 | 96.4% | 93.3% | 12 | 11 | 37/48 | exact 동점, recall 약간 후퇴 |
v1_5 | 92.3% | 94.8% | 5 | 22 | 25/48 | recall 실험, 너무 많이 열림 |
v1_6 | 96.4% | 96.0% | 4 | 10 | 37/48 | 최종 선택 |
각 버전의 역할
v1 -> v1_1: 초기 버전은 legal_interpretations, ministry_interpretations, precedents를 지나치게 자주 확장하는 과발화 문제가 심각했다. v1_1에서 source-specific 단서가 없으면 닫는 방향으로 전환하면서 core 과발화를 크게 줄였다.
v1_1 -> v1_2: 위원회, 심판청구 같은 단어의 오인식을 줄이고, direct source cue가 있는 경우 expand를 더 적극적으로 올렸다. triggered lane pass가 87.5%까지 상승했다.
v1_2 -> v1_3: precedents를 더 강하게 억제하고, 행정심판/헌재/위원회를 각각 독립 source로 읽도록 유도했다. 96.4%로 실사용 후보 수준에 도달했다.
v1_4: v1_3과 동일 최고점. prompt-only 최적화가 사실상 plateau에 도달했음을 확인했다.
v1_5: recall을 끌어올리기 위해 support lane을 공격적으로 열었다. critical miss는 5건으로 줄었지만, core false positive가 22건으로 급증하며 exact score가 92.3%로 떨어졌다. 방향은 맞았지만 균형이 깨졌다.
v1_6: v1_5의 recall 개선은 일부 유지하면서, core expansion을 다시 direct-request 중심으로 조였다. exact 최고점 구간을 회복하면서 recall도 96.0%로 가장 높았다.
Recall 우선 기준의 의미
최종 선택에서 가장 중요했던 기준 전환이 있다. 단순히 exact-match 총점만 보는 것이 아니라, recall-priority를 함께 보기로 한 것이다.
이유는 명확하다. 법률 QA에서 불필요한 source를 조금 더 여는 것과, 중요한 source를 놓쳐서 답변 근거를 아예 못 찾는 것은 심각도가 다르다. 판례가 핵심인 질문에서 판례 컬렉션이 닫혀 있으면 아무리 법령 조문을 잘 찾아도 부실한 답변이 된다. 반면 판례를 약간 과하게 열었더라도 후단의 merge와 rerank에서 걸러낼 수 있다.
이것은 RAG 시스템 전반에서 적용되는 원칙이기도 하다. 검색 단계에서는 precision보다 recall이 우선이다. 놓친 문서는 후단에서 복구할 수 없지만, 과하게 가져온 문서는 후단에서 걸러낼 수 있다. Aurelio Labs의 semantic-router나 LangChain 기반 routing 구현들도 결국 "적합한 소스를 빠짐없이 커버하는 것"을 최우선으로 둔다.
Recall-priority score는 이 원칙을 반영한 점수 체계다.
- 열어야 하는 lane을 안 연 경우를 더 크게 감점한다
expand가 필요한데support로만 준 경우도 약한 miss로 집계한다- false positive는 상대적으로 덜 무겁게 본다
이 기준으로 보면 v1_6는 exact-match 최고점 구간을 유지하면서 critical lane miss가 4건으로 가장 적었다. 놓치지 않으면서도 과하게 열지 않는 균형점이었다.
남은 과제와 한계
v1_6를 최종 선택으로 고정했지만 완벽하지는 않다. 세 가지 잔존 리스크가 있다.
confidence calibration: 현재 confidence 출력이 거의 항상 high다. 라우터가 확신이 낮을 때 이를 후단에 알릴 수 있어야 하는데, 아직 그 역할을 못 하고 있다.
precedents expand 편향: 남은 오차의 대부분이 판례 컬렉션을 약간 과하게 여는 데 집중되어 있다. source 종류를 크게 잘못 읽는 문제는 줄었지만, 보조 판례 lane을 공격적으로 여는 경향이 남아 있다.
prerewriter hint 정합성: 일부 문항에서 graphLawNames가 질문 주제와 맞지 않게 섞이는 현상이 있다. 이것은 source-router 자체보다 상위 단계인 prerewriter의 품질 문제다.
이 문제들은 prompt를 더 만지는 것보다 시스템의 다른 단계에서 해결하는 것이 더 효율적이라고 판단했다. confidence calibration 보정, prerewriter hint 정합성 점검, 필요하면 얇은 post-router guardrail을 추가하는 것이 다음 우선순위다.
결론
Source Router 설계에서 얻은 교훈을 정리하면 이렇다.
이질적인 근거를 같은 점수축으로 섞지 않는다. 8개 컬렉션을 flat search로 한 줄 정렬하는 대신, lane 역할을 나누고 source-aware merge를 하는 구조가 맞다.
검색 단계에서는 recall이 우선이다. 놓친 문서는 후단에서 복구할 수 없다. 과하게 가져온 문서는 걸러낼 수 있다. 이 비대칭이 recall-priority 기준의 핵심이다.
prompt-only 최적화에도 plateau가 있다. v1부터 v1_6까지 6번의 반복으로 66.7%에서 96.4%까지 올렸지만, 그 이후에는 프롬프트를 더 만지는 것보다 시스템의 다른 병목을 해결하는 것이 더 생산적이다. 언제 멈추고 넘어갈지 판단하는 것도 실험 설계의 일부다.
Source Router는 전체 파이프라인의 한 조각일 뿐이다. 하지만 이 조각이 잘못 판단하면 아무리 좋은 검색기와 답변 생성기가 있어도 소용없다. 300만 건의 법률 문서 앞에서, 어떤 문을 열지 결정하는 것이 결국 첫 번째 품질 관문이다.
Sources:
- Routing in RAG Driven Applications - Towards Data Science
- RAGRouter: Learning to Route Queries to Multiple Retrieval-Augmented Language Models
- Dynamic Routing in RAG: Directing User Queries to the Right Vector Store
- How to Build Helpful RAGs with Query Routing - Towards Data Science
- Aurelio Labs semantic-router - GitHub
- Optimizing RAG: Dynamic Query Routing for Multi-Source Answer Generation - Microsoft Q&A

