Blog

RAG는 왜 정답 문서를 찾지 못할까?

9 minute read

Series 1 — RAG는 원하는 문서를 제대로 찾고 있는가? (2/5)

지난 글은 실패 유형표로 끝났습니다. 정답이 1위가 아닌 33개 질문을 “1위에 무엇이 왔나”로 나눈 여섯 줄짜리 표였고, 각 줄이 다음 실험의 가설이라고 썼습니다. 그 표를 보고 나면 손이 먼저 갑니다. 키워드 검색을 섞어 볼까, 청킹을 해 볼까, 아니면 그냥 더 좋은 임베딩 모델을 써 볼까.

이번 글은 그 손을 한 번 멈추는 글입니다. 무엇을 고치기 전에 무엇이 반복되는지부터 세어야 합니다. 특히 “모델을 바꾸면 나아지겠지”라는 기대는 검증 없이 가장 자주 실행되는 선택이라서, 이번 실험의 목표를 한 문장으로 정하면 이렇습니다. 모델을 교체하기 전에 먼저 어떤 유형의 실패가 반복되는지 알아야 한다.

검색은 다시 돌리지 않았습니다. 지난 실험이 남긴 질문별 결과 파일(76개 질문 × 2개 모델의 첫 정답 순위와 top-5 문서)과 코퍼스 메타데이터만 읽었습니다. 코드와 데이터는 rag-quality-labexperiments/02-failure-taxonomy에 있습니다.

실패를 무엇으로 볼 것인가

첫 결정은 분모입니다. “검색 실패”를 정답이 top-5 밖인 질문으로 잡으면 16개, 1위가 아닌 질문으로 잡으면 33개입니다.

1위가 아닌 쪽으로 잡았습니다. LLM에 문서 하나만 넘기는 파이프라인에서는 2위도 실패이고, 5개를 넘기는 파이프라인에서도 2위와 5위 사이의 17개는 “검색은 됐는데 순위가 나쁜” 후보군이라 따로 셀 가치가 있습니다. 대신 심각도를 같이 적었습니다. 2~5위, 6~10위, 그리고 top-10에도 없음. 33개는 17, 10, 6으로 갈립니다.

손으로 센 표를 믿지 않기로 했다

지난 글의 유형표는 제가 33개를 눈으로 보고 센 것입니다. 이번에는 같은 일을 규칙으로 다시 했습니다. 이유는 두 가지입니다. 재현이 안 되는 표는 다음 실험에서 “줄었다”고 말할 근거가 못 되고, 손으로 세면 눈에 띄는 것만 세게 됩니다. 뒤에서 보겠지만 실제로 그랬습니다.

분류는 세 층으로 나눴습니다. 앞의 둘은 코드가 붙이고 셋째는 사람이 붙입니다.

증상: 1위에 무엇이 왔나. 코퍼스의 문서마다 자격 코드(D-8)와 업무항목(체류기간 연장허가)이 메타데이터로 있어서, 정답 문서와 1위 문서의 둘을 비교하면 기계적으로 나뉩니다.

이름규칙
NEAR_DUPLICATE_CONFUSION다른 자격, 같은 업무항목. D-8 연장허가 대신 D-1 연장허가
WRONG_SECTION같은 자격, 다른 업무항목. E-10 연장허가 대신 E-10 상한
WRONG_DOCUMENT자격도 업무항목도 다름
APPENDIX_INTRUSION1위와 정답 중 한쪽만 부록 문서

넷째 줄은 로드맵에 없던 유형이고, 이 코퍼스를 다시 들여다보다 생겼습니다. 672개 문서 중 388개, 그러니까 58%가 매뉴얼 뒤쪽에 붙은 “외국국적동포” 부록입니다. 이 문서들은 자격 코드가 없고 제목이 “가. 대상”, “□ 도입 방법”처럼 상위 맥락을 잃은 헤딩뿐입니다. 지난 실험 때는 이 비율을 몰랐습니다. 로드맵에 있던 OUTDATED와 LANGUAGE_MISMATCH는 이 코퍼스에서 나올 수 없어서(매뉴얼이 한 버전이고 질문도 문서도 한국어) 분류표에 자리만 남기고 0건으로 뒀습니다.

심각도. 위에서 말한 2~5위, 6~10위, 없음입니다. 증상과 독립된 축으로 둔 이유는 “top-10에 없으면서 1위는 다른 자격의 같은 항목”인 질문이 있어서입니다. 로드맵의 NO_HIT을 증상 하나로 넣으면 그 질문에서 1위가 무엇이었는지가 사라집니다.

원인: 왜 그랬나. 여기는 규칙으로 안 됩니다. 33개 질문마다 정답 문서 본문과 1위 문서 본문을 읽고 하나씩 붙였습니다. 라벨 파일(causes.csv)에 질문마다 원인 하나와 메모 한 줄이 있습니다.

증상으로 세면

bge-m32~5위6~10위없음
APPENDIX_INTRUSION54110
NEAR_DUPLICATE_CONFUSION5128
WRONG_SECTION6208
WRONG_DOCUMENT1337
1710633

지난 글에서 “부록이 밀어냄 6건”이라고 셌던 것이 규칙으로 세니 10건입니다. 손으로 셀 때는 부록 제목이 눈에 확 띄는 것만 셌던 겁니다. “코드가 둘인 질문 3건”도 정규식으로 세니 5건이고, 5개 전부 실패했습니다. 손 집계는 가설로는 충분했지만 숫자로는 믿을 게 못 됐습니다.

KURE-v1은 36개 중 APPENDIX 11, NEAR_DUPLICATE 8, WRONG_SECTION 13, WRONG_DOCUMENT 4입니다. 모델을 바꾸면 WRONG_SECTION만 8에서 13으로 늘고 나머지는 거의 그대로입니다. 이 숫자를 잠깐 기억해 두시면 좋습니다.

원인으로 세면

증상표는 재현되지만 무엇을 고칠지는 말해 주지 않습니다. 33개를 읽고 붙인 원인은 여덟 가지로 모였습니다.

원인건수
CODE_CONFUSION8“D-8 연장 서류” → D-1 연장허가가 1위. “다른 비자에서 H-2로 변경” → H-1 변경허가가 1위
ITEM_BOUNDARY7“E-10 재고용하면 최대 몇 년” → 상한 항목이 1위인데 본문이 “3년” 두 글자. 답(4년 10개월)은 연장허가에
QUERY_NO_CODE5“항공기 조종사 채용하려면 어떤 비자” → top-10 밖
QUERY_TWO_CODES4“D-8으로 3년 체류하면 F-2 받을 수 있나” → D-8 상한(67자)이 1위
LONG_GOLD_DOC3“E-7 여권 갱신 신고 기한” → 11.6만 자 문서, top-10 밖
EXACT_TERM_MISS2“조선용접공 E-7 자격요건” → 본문에 그 단어가 있는 문서가 7위
HEADING_LOST_CONTEXT2정답 제목이 “41. K-STAR 비자트랙 제도”인데 본문은 공통 유의사항
OTHER2뒤에서 따로

CODE_CONFUSION은 지난 글에서 본 그 현상입니다. 연장허가 서류 목록은 36개 자격마다 반복되고, 임베딩은 D-8과 D-1을 가르는 코드 한 토큰을 뜻의 차이로 보지 않습니다. 이번에 하나 더 보인 것은 H-1과 H-2입니다. “다른 비자에서 H-2로 자격변경 가능한가요”의 1위는 관광취업(H-1) 변경허가 문서였고, 그 문서는 “원칙적으로 자격변경 제한”이라고 말합니다. 정답(H-2로는 변경 불가)과 뜻이 비슷하고 코드가 한 글자 다릅니다.

ITEM_BOUNDARY는 답이 있는 업무항목을 질문에서 유추할 수 없는 경우입니다. “최대 몇 년 체류”라고 물으면 임베딩은 “체류기간 상한” 항목으로 갑니다. 당연합니다. 그런데 이 매뉴얼에서 상한 문서는 “3년”, “2년 이내”처럼 몇 글자짜리이고, 재고용이나 연구유학 같은 예외는 연장허가 문서에 있습니다. “방문동거 F-1 해당자 목록”의 답이 “활동범위” 항목에 있는 것도 같은 문제입니다. 사용자가 쓰는 말과 문서가 항목을 나눈 기준이 다릅니다.

여기서 증상과 원인이 다른 축이라는 것이 드러납니다. “D-10 연장 신청할 때 은행 잔고증명 기준 금액”의 1위는 부록의 “재정능력 기준 특례”라서 증상은 APPENDIX_INTRUSION입니다. 그런데 top-10을 열어 보면 2위부터 7위까지가 D-8, D-9, E-10, D-1의 연장허가이고 정답인 D-10 연장허가는 10위입니다. 원인은 CODE_CONFUSION입니다. 증상만 보면 “부록을 치우자”가 되고, 원인을 보면 “코드를 가르게 하자”가 됩니다. 둘을 한 표에 섞으면 이런 질문이 부록 문제로 잘못 분류됩니다.

모델을 바꾸면 무엇이 남나

이번 실험의 핵심 표입니다. bge-m3에서 실패한 33개가 KURE-v1에서도 실패하는지 원인별로 셌습니다.

원인bge-m3 실패KURE-v1도 실패KURE-v1 순위 중앙값
CODE_CONFUSION884
ITEM_BOUNDARY775
LONG_GOLD_DOC33top-10 밖
QUERY_NO_CODE53top-10 밖
QUERY_TWO_CODES436
EXACT_TERM_MISS212
HEADING_LOST_CONTEXT20 
OTHER21 

코드 혼동, 항목 경계, 긴 문서는 한 건도 빠지지 않고 그대로 실패합니다. 이 셋이 33개 중 18개입니다. 한국어 검색 데이터로 추가 학습한 모델도 D-8과 D-1을 가르지 못하고, “최대 몇 년”을 상한 항목 말고 연장허가로 보내지 못하고, 11만 자 문서에서 둘째 문단을 꺼내지 못합니다. 모델의 문제가 아니라 코퍼스와 질문의 모양이 만드는 문제라서 그렇습니다.

반대로 부록 헤딩 문제 2건은 KURE-v1이 다 맞혔고, 두 모델의 실패 집합 차이(bge-m3만 7개, KURE-v1만 10개)는 대부분 WRONG_SECTION에서 납니다. 지난 글에서 “평균은 비슷한데 맞히는 질문이 다르다”고 했는데, 다른 부분이 어디이고 같은 부분이 어디인지가 이제 보입니다.

실패한 것만 보지 말고 76개 전부를 나눠 보면

원인 라벨은 실패 33개에만 있습니다. 그래서 “유형별 Recall@5”를 내려면 다른 것이 필요합니다. 76개 질문 전부에 자동으로 붙는 속성으로 나눴습니다. 질문에 자격 코드가 몇 개인가, 정답 문서가 몇 자인가, 정답이 부록인가.

속성별 Recall@5

코드 없는 질문 9개는 Recall@5가 0.44(KURE-v1 0.56)이고 코드가 하나인 62개는 0.85입니다. 정답이 5천 자 이상인 7개는 0.57(KURE-v1은 0.14)입니다. 두 모델에서 똑같이 갈립니다.

그런데 “정답이 부록에 있다”는 것 자체는 차이가 없습니다. 부록 정답 16개의 Recall@5는 0.81, 본문 정답 60개는 0.78입니다. 위에서 부록 관여 실패가 10건이라고 했는데, 그건 부록 정답을 못 찾은 게 아니라 본문 정답을 부록 문서가 밀어낸 경우입니다. “E-7 직원 배우자 F-3가 취업하려면 어떤 허가”의 정답은 동반(F-3) 자격외활동 문서(4,900자)인데, 1위는 지역특화 E-7-4R 배우자에 관한 부록 문단(168자)입니다. 짧고 제목이 질문과 닮은 문서가 길고 답이 들어 있는 문서를 이깁니다. 실패 33개 중 26개에서 1위 문서가 정답보다 짧았습니다(중앙값 356자 대 1,484자).

코드 없는 질문에서 하나 더 확인한 것이 있습니다. “항공기조종사”, “기업부설연구소”, “무역업 고유번호”, “임금체불”, “상시근로자 300인 미만”은 전부 정답 문서 본문에 글자 그대로 있습니다. 그런데도 top-10 밖이거나 4위입니다. 코드가 없으면 임베딩이 붙잡을 닻이 없고, 용어가 그대로 일치한다는 사실은 임베딩이 보지 않습니다. 이건 키워드 검색이 잘하는 일이고, 실험 06의 첫 질문이 됩니다.

하다가 틀린 것들

“코드가 없는 문서 = 부록”으로 규칙을 짰다가 고쳤습니다. 코드 없는 394개 중 6개는 부록이 아니라 매뉴얼 앞쪽의 “공통” 섹션이었고, “체류기간이 여권 유효기간보다 길게 나올 수 있나”의 정답이 거기 있었습니다. 섹션 이름으로 판정하도록 바꿨습니다.

질문에서 코드를 세는 정규식은 처음에 단어 경계(\b)를 썼는데, “D-8으로”처럼 코드 뒤에 한글이 바로 붙으면 파이썬은 한글도 단어 문자로 봐서 경계가 안 잡힙니다. 코드 둘인 질문이 1개로 나와서 알았습니다. 경계를 빼니 5개가 됐고 지난 글의 “9개 코드 없는 질문”과도 맞았습니다.

부록 안에서 이웃 문단끼리 헷갈린 5건을 처음엔 APPENDIX_INTRUSION에 넣었다가 WRONG_SECTION으로 옮겼습니다. 부록의 헤딩이 자격 문서의 업무항목 역할을 하니까 “같은 섹션, 다른 항목”이 맞습니다. 이렇게 하고 나니 APPENDIX_INTRUSION은 “본문 정답을 부록이 밀어낸 10건”이라는 한 문장으로 설명됩니다.

그리고 실패 33개 중 4개는 검색이 아니라 라벨 문제였습니다. 셋은 지난 글의 “정답은 하나” 규칙이 만든 것입니다. “산재 치료로 G-1으로 바꿨던 E-9 근로자가 다시 E-9로 돌아올 수 있나”는 G-1 문서에도 회복 절차가 있을 수 있고, “H-2 허용 업종에 건설업 포함되나”는 1위로 온 문서도 건설업 허용을 담고 있고, “D-3 연수기간 최대 몇 년 연장”은 1위인 상한 문서의 “2년 이내”가 사실상 답입니다. 넷째는 성격이 다릅니다. “H-2로 입국한 동포 외국인등록 시 건강진단서 내야 하나”의 답은 “안 낸다”입니다. 그래서 정답 문서에 건강진단서라는 단어가 없고, 그 단어가 있는 별첨 서식이 1위로 옵니다. 부정형 질문은 검색이 구조적으로 불리합니다. 넷 다 라벨을 고치지 않고 적어 뒀습니다. 다중 정답과 부정형 질문은 평가셋을 다루는 실험 34의 소재입니다.

이 실험에서 남는 것

목표 문장으로 돌아가면, 이 코퍼스에서 임베딩 모델을 바꾸는 것은 다음 할 일이 아닙니다. 실패의 절반 이상이 모델을 바꿔도 그대로 남고, 남는 이유가 코퍼스의 모양(36개 자격에 같은 항목이 반복되고, 상한 문서는 두 글자이고, 부록이 58%이고, 긴 문서는 11만 자)과 질문의 모양(코드가 없거나 둘이거나, 항목 이름과 다른 말을 쓰거나)에 있기 때문입니다. 원인표의 각 줄은 이제 실험 번호를 가집니다. 긴 문서는 03(청킹), 항목 경계는 04(구조 청킹), 코드 혼동과 용어 불일치는 06·07(BM25, 하이브리드), 부록 침입은 08(메타데이터 필터), 2~5위 17건은 09(리랭커)입니다.

방법에서 남는 것은 세 층 구조입니다. 증상은 규칙으로 세서 재현되게 하고, 원인은 사람이 본문을 읽고 붙이고, 속성은 실패만이 아니라 전체 질문에 붙여서 비율을 냅니다. 이 셋을 한 표에 섞으면 잔고증명 질문처럼 코드 문제가 부록 문제로 보이고, 손으로만 세면 부록 6건이 실제로는 10건인 것을 놓칩니다.

76개는 여전히 적습니다. 원인별 건수 8과 7과 5의 차이는 뜻이 없고, 8 중 8이 남는다와 2 중 0이 남는다의 차이만 뜻이 있습니다. 그 해상도로 말할 수 있는 문장은 “이 검색기의 실패는 대부분 모델이 아니라 코퍼스와 질문의 모양에서 온다”입니다.

재현은 이렇게 합니다. 실험 01의 결과 파일이 커밋돼 있어서 첫 줄은 생략해도 됩니다.

uv run experiments/01-retrieval-metrics/run.py
uv run experiments/02-failure-taxonomy/run.py

다음 글은 원인표에서 모델을 바꿔도 남은 세 줄 중 하나, 긴 문서에서 시작합니다. 11만 자 문서를 자르면 그 3건이 사라지는지, 그리고 자르면서 다른 무엇이 깨지는지.