법률 AI 검색 실험기 (12) — Lane-based Retrieval 설계와 전체 회고
법률 QA 검색기를 만들면서 거쳐 온 설계 여정의 마지막 이야기다. 벡터 검색의 한계를 마주한 순간부터, 임베딩 선택, selector, rewriter, graph, source-router, 그리고 lane-based retrieval까지. 이 글에서는 최종 단계인 lane 구조 설계를 정리하고, 시리즈 전체를 돌아본다.
검색기 운영 설계의 최종 단계
query-prep 단계를 마무리하면서 자연스럽게 다음 질문이 떠올랐다. prerewriter와 source-router가 질문을 정리하고 어떤 소스를 열지 결정했다면, 그 다음은 무엇인가. 실제로 열린 소스들에서 문서를 가져오고, 정리하고, 합치는 구조를 어떻게 만들 것인가.
이전까지는 하나의 벡터 쿼리로 모든 컬렉션을 한 번에 검색하는 구조였다. 법령 조문, 판례, 해석례, 행정해석이 모두 같은 쿼리, 같은 점수축 위에서 경쟁했다. 이 방식의 문제는 명확했다. 조문은 요건과 효과 중심이고, 판례는 사실관계와 책임 귀속 중심이다. 같은 질문이라도 소스마다 잘 맞는 검색 표현이 다르다. 하나의 쿼리로 모든 소스를 커버하려는 시도는 결국 어딘가에서 recall 손실을 낳았다.
이 문제를 풀기 위해 도달한 구조가 lane-based retrieval이다.
Lane-based Retrieval 개념
lane-based retrieval의 핵심 아이디어는 단순하다. 소스마다 독립적인 검색 경로(lane)를 두고, 각 lane이 자기 소스에 맞는 방식으로 문서를 가져온 뒤, 후단에서 역할 기반으로 합치는 것이다.
전체 흐름은 아래와 같다.
질문
-> 공통 router (어떤 lane을 열지 결정)
-> 병렬 lane 실행
-> lane별 쿼리 변환
-> lane별 벡터 검색
-> 필요시 lane별 그래프 검색
-> lane별 rerank 또는 selection
-> 역할 기반 merge
-> 조건부 final rerank
-> answer
여기서 중요한 설계 판단이 몇 가지 있었다.
첫째, 법령 조문을 primary anchor로 둔다. 법률 QA에서 조문은 가장 기본적인 근거다. 다른 소스들은 이 anchor를 보강하는 support 역할이다.
둘째, 전체 후보를 하나의 점수축으로 flat merge하지 않는다. 예를 들어 여러 lane에서 각각 수십 개씩 회수하면 수백 개 이상의 raw 후보가 나온다. 이걸 한 번에 rerank하는 것은 비효율적일 뿐 아니라, 소스 역할이 다른 문서를 같은 기준으로 비교하는 것 자체가 부적절하다.
셋째, lane별로 먼저 정리하고, 후단에서 quota merge를 한다. 각 lane은 자기 소스 안에서 relevance를 판단한 뒤 topN을 내놓는다. 후단 merge는 이 topN들을 역할(anchor/support) 기준으로 조합한다.
이 구조는 RAG 분야에서 흔히 논의되는 multi-retriever fusion 패턴과 맥이 닿는다. LangChain의 MergerRetriever나 Pinecone의 two-stage retrieval 같은 접근법도 여러 검색 전략의 결과를 Reciprocal Rank Fusion(RRF) 등으로 합치는 구조를 쓴다. 다만 우리 설계에서 다른 점은, 단순히 semantic/lexical 같은 검색 방법의 차이가 아니라 소스 자체의 성격 차이를 lane 분리의 기준으로 삼았다는 것이다.
Lane Unit 실험
lane 구조를 세우면서 가장 먼저 부딪힌 질문은 "실험 단위를 어떻게 잡을 것인가"였다.
8개 lane 전부에 대해 lane-specific rewriter를 한 번에 만드는 방향을 먼저 검토했다. 결론부터 말하면, 이 방향은 잘못됐다. 여러 lane을 동시에 바꾸면 어떤 lane이 좋아졌고 어떤 lane이 망가졌는지 분리가 안 된다. rewrite만 따로 만들어서는 실제 retrieval이 개선됐는지 확인하기도 어렵다.
결론은 명확하다. 실험 단위는 lane 하나다. lane 내부의 rewrite, vector retrieval, 정리(selection/rerank)를 하나의 unit으로 묶어서 end-to-end로 평가한다.
질문 1개
-> 특정 lane
-> rewrite
-> vector
-> 정리
-> lane 결과 평가
이 구조에서 비교하는 것은 세 가지다.
- raw query를 그대로 넣었을 때
- 공통 rewrite를 넣었을 때
- lane-specific rewrite를 넣었을 때
그리고 비교 지점도 나눈다. vector 직후 후보의 품질, lane 정리 후 후보의 품질, merge 전 lane별 recall과 coverage.
lane 단위 실험 프레임을 먼저 만들고, 한 lane씩 검증해 나가는 것. 8개 rewrite를 한꺼번에 만드는 것보다 이 순서가 훨씬 현실적이었다.
Precedents Lane 설계
첫 실험 대상으로 precedents(판례) lane을 골랐다. 이유는 명확했다. 현재 공통 prerewriter가 law_articles 중심 성격이 강해서, 판례와의 성격 차이가 가장 크다. lane-specific rewrite의 효과를 관찰하기 가장 좋은 대상이다.
공통 rewriter의 한계
공통 prerewriter를 판례 검색에 그대로 쓰면 한계가 보였다. 공통 rewriter는 조문명, 요건, 효과, 절차 중심으로 질의를 정리하는 경향이 있다. 판례 검색에서 중요한 사실관계, 책임 귀속, 위법성 판단, 예외 적용 같은 표현이 따로 강조되지 않았다.
실제 검색을 확인해 보면, raw 질문을 그대로 넣으면 generic한 손해배상/구상금류 결과가 많이 섞였다. 공통 searchQuery를 넣으면 약간 더 정돈되지만 여전히 generic 판례가 많았다. 쟁점을 분리한 subQuery를 넣으면 의미가 있었지만, 잘못 만든 subQuery는 noise를 크게 늘렸다.
Precedents Prerewriter
이 관찰에서 도출한 방향은, 판례 전용 prerewriter를 완전히 독립된 rewriter로 만드는 것이 아니라 공통 의미 구조를 받아서 판례형으로 변환하는 adapter로 두는 것이었다.
입력: 원문 질문 + 공통 prerewriter 결과 (queryType, searchQuery, subQueries, keywords) 출력: precedents 전용 searchQuery, keywords, 조건부 subQueries
판례용 searchQuery의 원칙은 조문형과 다르다. 핵심 사실관계, 책임 주체, 위법성/책임 귀속 포인트, 손해 범위 순으로 반영한다. "택배 기사 개인과 회사의 배상 책임"이나 "운송 중 사고에서 사용자 책임 성립 여부" 같은 판례형 표현이 허용된다.
subQueries는 더 보수적이다. single 질문이면 기본 0개, multi_issue일 때만 1~2개, 최대 2개. 판례 subQuery는 적고 날카로워야 한다.
Rerank의 필요성
precedents lane에서 rerank가 특히 중요한 이유는 판례 title이 generic하기 때문이다. "손해배상(기)" 같은 제목만으로는 질문과의 관련도를 판단할 수 없다. summary와 holding을 읽어야 실제 관련도를 알 수 있다.
따라서 precedents lane은 small-window rerank를 기본으로 두되, 비용이 부담되면 support 모드에서는 light selection, expand 모드에서만 full rerank를 거는 fallback을 설계했다.
시리즈 전체 회고
이 시리즈를 시작한 건 벡터 검색의 한계를 마주한 순간이었다. 법률 도메인의 질문을 벡터 DB에 그대로 넣으면, 의미적으로 비슷해 보이지만 법적으로는 전혀 다른 문서가 상위에 올라왔다. "임대차 보증금 반환"을 질문했는데 "매매 대금 반환" 판례가 나오는 식이다. 벡터 유사도만으로는 법률 도메인의 정밀한 검색 요구를 충족할 수 없었다.
그래서 시작한 여정이 결국 여기까지 왔다.
임베딩 모델 선택. 도메인 특화 임베딩을 쓸지, 범용 모델을 쓸지 고민했다. 결국 pplx-embed-v1-4b를 선택했고, 모델 자체보다 쿼리 표현의 품질이 더 중요하다는 것을 확인했다.
Prerewriter. raw 질문을 그대로 검색에 넣는 대신, 질문의 의도를 정리하고 보조 신호를 생성하는 전처리층을 두었다. 중요한 판단은 prerewriter가 질문을 "대체"하는 것이 아니라 "보조"하는 것이라는 점이었다. raw 질문을 버리지 않는다.
Source-Router. 여러 법률 소스 중 어떤 것을 열지 결정하는 router를 설계했다. 여러 버전을 실험하면서 확인한 핵심은 recall-priority 관점이다. 불필요한 소스를 여는 것보다, 필요한 소스를 닫아버리는 것이 훨씬 치명적이다. 최종 버전을 선택한 이유도 critical lane miss가 가장 낮았기 때문이다.
Lane-based Retrieval. 그리고 마지막으로, 소스마다 독립적인 검색 경로를 두고 역할 기반으로 합치는 구조에 도달했다.
핵심 설계 원칙
이 과정에서 확인한 설계 원칙을 정리한다.
한 번에 모든 것을 바꾸지 않는다. prerewriter, router, filter, retrieval, rerank를 동시에 바꾸면 무엇이 효과를 냈는지 알 수 없다. 한 축만 바꾸고, 전체를 다시 평가하고, 고정하고, 다음으로 넘어간다. 이것이 유일하게 신뢰할 수 있는 방법이다.
Recall이 precision보다 먼저다. 법률 QA에서 관련 근거를 놓치는 것은 잘못된 근거를 포함하는 것보다 위험하다. 잘못된 근거는 answer 단계에서 걸러낼 수 있지만, 놓친 근거는 복구가 안 된다.
소스의 역할이 다르면 검색도 달라야 한다. 조문, 판례, 해석례는 같은 법률 도메인이지만 문서 구조와 검색 의도가 다르다. 하나의 쿼리로 모든 소스를 커버하려는 시도는 결국 어딘가에서 타협을 낳는다. lane 분리는 이 근본적 차이를 인정하는 설계다.
실험 프레임이 실험보다 먼저다. 좋은 아이디어가 있어도 그것을 검증할 틀이 없으면 소용없다. lane unit 실험 러너를 먼저 만들고, 비교 가능한 평가 포맷을 정의한 뒤에야 실제 실험이 의미를 갖는다.
비용과 구조는 트레이드오프다. lane별 LLM rewrite, lane별 rerank는 논리적으로 맞지만, 모든 lane에 다 붙이면 비용과 latency가 감당이 안 된다. 현실적 제약 안에서 필요한 곳에만 선택적으로 적용하는 것이 운영 설계의 핵심이다.
앞으로의 방향
현재 시점에서 query-prep은 일단 마감이다. prerewriter와 source-router v1_6이 고정됐고, filter는 payload 기준 재설계가 필요할 때 다시 연다.
다음 핵심 작업은 query-prep 결과를 downstream에 안정적으로 넘기는 것이다. 구체적으로는 prerewriter + source-router 출력 계약을 고정하고, retrieval이 merged plan만 보도록 인계 기준을 정리하고, retrieval에서 answer까지 end-to-end로 검증하는 것이다.
lane-based retrieval 구조 자체는 precedents lane부터 한 lane씩 검증해 나갈 계획이다. precedents lane에서 lane-specific rewrite와 small-window rerank의 효과가 확인되면, 해석례와 행정해석 lane으로 확장한다.
더 먼 미래에는 lane-aware query-prep, 즉 handoff 자체를 lane별로 분화하는 방향도 열려 있다. 공통 query에 lane별 override를 얹는 구조가 가장 현실적인 확장 경로로 보인다.
이 시리즈를 통해 확인한 결론은 명확하다. 법률 도메인 RAG에서 검색 품질을 올리는 핵심은 더 좋은 임베딩 모델이 아니라, 질문의 의도를 정확히 파악하고 소스의 성격에 맞게 검색 전략을 분화하는 구조적 설계다. 모델은 바뀌어도 이 구조적 판단은 남는다.
이 글은 법률 QA 검색기 시리즈의 마지막 글입니다.

