TT Lab
시작하기
배우기 러닝패스 코스

LLM 엔지니어링

RAG — 세 부품 중 어디가 고장 났는지 먼저 가른다

TT Lab 에서 이어서 보기

한 줄 요약

RAG 는 검색기, 재순위기, 생성기 세 부품의 조합이며, "답변 품질이 나쁘다" 는 신고를 받으면 가장 먼저 할 일은 어느 부품에서 실패했는지 가르는 것이다. 그러려면 답변마다 무엇이 검색되었고 무엇이 모델에 들어갔는지가 기록되어 있어야 한다.

왜 이게 필요했나

사내 규정 챗봇이 "환불 신청 기한은 14일입니다" 라고 답했는데, 규정은 지난달 7일로 바뀌었다. 담당자는 먼저 프롬프트에 "최신 규정을 우선하라" 는 문장을 넣는다. 그래도 답은 그대로다. 나중에 보니 새 규정 문서는 색인에 들어가지도 않았고, 모델은 받은 문서대로 충실하게 답하고 있었다. 고칠 곳은 처음부터 색인이었다. RAG 개선에서 가장 흔한 시간 낭비가 이런 모양이다.

그래도 RAG 를 쓰는 이유는 분명하다. 모델은 학습 시점의 지식만 갖고 있고, 사내 문서나 최신 정보는 알지 못한다. 미세 조정으로 지식을 넣는 방법도 있지만 비싸고 느리고 갱신이 어렵다. RAG 는 질문이 오면 관련 문서를 먼저 찾아서 프롬프트에 넣고, 그 문서를 근거로 답하게 한다. 지식이 바뀌면 문서만 갈아 끼우면 된다. 다만 위 사례처럼 "문서만 갈아 끼우면 된다" 가 실제로 지켜지는지는 따로 확인해야 한다.

어떻게 동작하나

파이프라인은 세 부품으로 나뉜다.

검색기(Retriever). 질의와 비슷한 청크를 넓게 가져온다. 밀집 검색(임베딩)과 희소 검색(BM25 같은 키워드 방식)이 있고, 둘을 합친 하이브리드가 실무 표준이다. 서로 다른 종류의 실패를 하기 때문이다. 임베딩은 의역에 강하지만 제품 코드 같은 정확 일치에 약하고, 키워드는 그 반대다. 두 결과를 합칠 때는 점수 척도가 서로 달라 그대로 더할 수 없으므로 순위를 쓰는 방법이 널리 쓰인다. 역순위 융합(RRF)은 각 목록에서 문서가 받은 순위로 점수를 매겨 더한다.

RRF(d) = sum over lists of  1 / (k + rank(d))      k is commonly 60

k 는 상위권 몇 개가 결과를 독점하지 않도록 누르는 상수다. Elasticsearch 도 기본값으로 60 을 쓴다.

재순위기(Reranker). 검색기가 넓게 가져온 후보를 정밀하게 다시 줄 세운다. 질의와 문서를 함께 모델에 넣어 점수를 매기는 방식이라 정확하지만 느리므로, 상위 수십 건에만 적용한다.

생성기(Generator). 가져온 컨텍스트를 근거로 답을 만든다.

품질 문제를 진단할 때는 검색과 생성을 반드시 분리해서 본다. 정답 문서가 애초에 검색되지 않았다면 생성기를 아무리 손봐도 소용없고 검색 지표를 봐야 한다. 정답 문서가 컨텍스트에 있는데도 틀린 답이 나왔다면 그때가 생성기 문제이고 충실도를 봐야 한다.

지표 무엇을 재는가
Recall@K 상위 K 개 안에 정답 문서가 들어왔는가
MRR 첫 정답이 몇 위에 나왔는가 (순위 역수의 평균)
Precision@K 상위 K 개 중 관련 있는 문서의 비율
Faithfulness 답변이 컨텍스트에 근거하는가
Answer Relevancy 답변이 질문에 맞는가

작은 예로 계산해 보면 두 지표의 차이가 보인다. 질의 세 개의 정답이 각각 1위, 3위, 그리고 상위 3개 밖에서 나왔다고 하자. Recall@3 은 세 개 중 두 개가 들어왔으니 0.667 이다. MRR 은 순위의 역수 1, 1/3, 0 을 평균한 0.444 다. Recall 은 "들어왔는가" 만 보므로 1위와 3위를 구별하지 않고, MRR 은 그 차이를 벌점으로 준다. 컨텍스트에 상위 몇 개만 넣는 시스템이라면 둘 다 봐야 한다.

현장에서 무엇이 잘못되나

증상과 고장 난 부품을 짝지어 두면 조사가 빨라진다.

낡은 색인. 문서는 바뀌었는데 색인은 옛날 것이다. 증상은 "고쳤다는 내용이 반영되지 않는다" 이다. 문서를 지웠는데 청크가 색인에 남아 계속 검색되는 경우도 같다. 색인 갱신은 원본 변경과 한 묶음의 절차여야 하고, 청크마다 원본의 갱신 시각을 붙여 두어야 확인할 수 있다.

권한 누수. 사용자가 볼 수 없는 문서가 검색되어 답에 섞인다. 생성 뒤에 가리는 것으로는 늦다. 이미 모델이 그 내용을 읽고 요약했기 때문이다. 권한 필터는 반드시 검색 단계에서 건다.

버전 충돌. 옛 규정과 새 규정이 함께 검색되면 모델은 둘 중 하나를 고르거나 섞는다. 증상은 같은 질문에 답이 오락가락하는 것이다. 검색 전에 폐기된 문서를 걸러 내거나, 메타데이터의 시행일로 최신본만 남긴다.

경계에서 잘린 답. 정답 문장이 두 청크에 걸쳐 잘려 어느 쪽도 상위에 오르지 못한다. 청킹 전략의 문제이고, 다음 이론에서 다룬다.

컨텍스트에 있는데 틀린 답. 이때가 비로소 생성기 문제다. 관련 없는 청크가 너무 많거나, 정답 청크가 긴 컨텍스트의 가운데에 묻혀 있는 경우가 흔하다. 재순위로 개수를 줄이고 가장 관련 있는 청크를 앞에 둔다.

그럴듯한 지어내기. 코퍼스 밖의 질문에도 가장 비슷한 문서를 근거로 답을 만든다. 검색 점수가 임계값 미만이면 답하지 않고 그렇게 말하는 편이 훨씬 낫다. 임계값은 평가 집합으로 정한다. 너무 높으면 답할 수 있는 질문도 거절하고, 너무 낮으면 환각이 는다. 그리고 답변에 출처를 붙이는 것은 기능이 아니라 안전장치다. 사용자가 원본을 확인할 수 있으면 잘못된 답의 피해가 줄고, 어떤 청크가 잘못 검색되는지 운영 중에 관찰할 수 있다.

어떻게 확인하나

모든 답변에 대해 최소한 다음을 한 줄로 남긴다. 질의, 검색된 청크 id 와 점수, 실제로 모델에 넣은 청크 id, 답변, 답변이 인용한 출처. 이 기록이 없으면 신고가 들어왔을 때 어느 부품의 문제인지 가를 방법이 없다.

실패 사례를 모으면 세 칸으로 분류한다.

gold chunk not in top-K        -> retriever (index, chunking, query)
in top-K but not in context    -> reranker or context budget
in context but answer wrong    -> generator (prompt, ordering, noise)

평가 집합은 거창할 필요가 없다. 실제 사용자 질문 수십 개에 정답 문서를 붙여 두기만 해도 Recall@K 와 MRR 을 계산할 수 있고, 청킹이나 가중치를 바꿀 때마다 같은 숫자를 다시 재서 좋아졌는지 나빠졌는지 판단할 수 있다. 이 숫자 없이 프롬프트나 설정을 바꾸는 것은 느낌에 기대는 일이다.

이어서 읽을 것

바로 뒤의 이론에서 검색 단위를 정하는 청킹을 다룬다. 그 뒤 실습에서 문서를 청크로 나누고 TF-IDF 로 색인해 8개 질의를 검색한다. 정답 문서 목록과 비교해 Recall@3 과 MRR 을 계산하고, 컨텍스트를 조립하고, 답변이 컨텍스트에 근거하는지 어휘 겹침으로 판정하고, 코퍼스 밖 질문에는 답하지 않는 임계값 규칙을 구현한다.