문서를 넣었는데 RAG는 왜 틀릴까?
Spring AI RAG가 틀리는 원인을 문서 분할, 검색, Top-K, 유사도 기준, metadata, 답변 생성 단계로 나누어 쉽게 설명합니다.
Spring AI로 챗봇을 넘어 일하는 AI 만들기 6/9
Java·Spring 개발자가 LLM, RAG, Tool Calling, MCP를 공부하며 이해한 것들
학교 도서관에 2026년 과학 발표대회 안내문이 있습니다. 안내문에는 신청 마감일이 분명히 적혀 있습니다. 그런데 “올해 과학 발표대회 신청 마감일은 언제인가요?”라고 물었더니 사서가 2025년 안내문을 가져왔습니다.
책이 도서관에 있다는 사실만으로 정답이 보장되지는 않습니다. RAG도 같습니다. 올바른 문서가 준비되고, 검색되고, 현재 문맥에 포함되고, 답변에 반영되는 네 단계 중 하나만 실패해도 틀린 답이 나올 수 있습니다.
그래서 RAG가 틀렸을 때 Prompt부터 고치면 안 됩니다. 먼저 어느 단계에서 정답 근거가 사라졌는지 확인해야 합니다.
이 글의 학교, 행사, 안내문과 날짜는 흐름 이해를 위한 가상 예시입니다. 게시 흐름은 2026년 4월 9일이며, 개념과 코드는 2026년 8월 14일의 Spring AI 2.0.0 공식 문서를 기준으로 다시 확인했습니다. Spring AI 2.0.0이 게시일 당시 안정 버전이었다는 뜻은 아닙니다.
이 글은 공식 문서를 바탕으로 정리한 학습 기록입니다. 실제 모델, 임베딩 모델이나 Vector Database를 설치해 검색 정확도와 비용을 측정한 결과가 아닙니다.
문서가 있는데 왜 모른다고 할까?
5편에서는 RAG(Retrieval-Augmented Generation, 검색 증강 생성)가 질문과 관련된 문서를 찾아 모델에 전달하는 흐름을 살펴봤습니다. 하지만 문서를 검색할 수 있게 만드는 것과 올바른 문서를 찾는 것은 다른 문제입니다.
RAG는 틀린 문서를 근거로 답할 수 있습니다. 답을 찾지 못했다고 말하거나, 폐기된 규정을 최신 규정처럼 설명할 수도 있습니다. 문서에 없는 내용을 자연스럽게 덧붙이기도 합니다.
이를 단순히 “모델이 부족하다”라고 결론 내리면 원인을 놓칩니다. 정답 문서가 검색되지 않았다면 모델이나 Prompt를 바꿔도 사라진 근거가 돌아오지 않습니다.
도서관 비유에서 문서를 정리하는 일은 Ingestion(문서 수집·적재)과 Chunking(문서 분할)입니다. 사서가 질문을 검색하기 좋게 바꾸는 일은 Query Transformation(질문 변환), 자료를 찾는 과정은 Retrieval(검색)에 해당합니다.
가져올 자료 수가 Top-K이고, 관련성 통과 기준이 Similarity Threshold(유사도 임계값)입니다. 연도와 권한을 거르는 일은 Metadata Filtering(메타데이터 필터링), 검색 후보를 다시 정렬하는 일은 Re-ranking(재순위화)입니다. 자료를 읽고 대답하는 마지막 과정은 Generation(답변 생성)입니다.
최종 답변보다 검색된 조각을 먼저 본다
RAG의 실패를 다음 네 단계로 나누면 어디부터 확인할지 선명해집니다.
| 단계 | 확인할 질문 | 대표 실패 |
|---|---|---|
| 문서 준비 | 정답 근거가 온전히 저장됐는가? | 잘못된 Chunking, 오래된 문서 |
| 검색 | 정답 조각이 검색됐는가? | Top-K, threshold, 질문 표현 |
| 문맥 구성 | 검색된 근거가 모델에 전달됐는가? | 중복, 순서, 너무 긴 Context |
| 답변 생성 | 답변이 근거를 충실히 사용했는가? | 추측, 왜곡, 근거 없는 생성 |
가장 먼저 볼 것은 최종 문장이 아니라 검색된 Document 목록입니다. 정답 근거가 상위 검색 결과에 없다면 검색 이전 단계를 고쳐야 합니다. 근거가 검색됐는데 모델 입력에서 사라졌다면 문맥 구성 문제입니다.
정답 근거가 모델에 도착했는데 답이 틀렸을 때 비로소 생성 규칙과 모델을 살펴봅니다. 이 순서를 지키면 모든 문제를 Prompt 탓으로 돌리는 일을 피할 수 있습니다.
RAG 오답은 어디에서 시작될까?
같은 질문이 문서 준비, 검색, 문맥 구성, 답변 생성의 네 검문소를 지나는 모습을 비교합니다.
사용자 질문
올해 과학 발표대회 신청 마감일은 언제인가요?문서 준비
문서 조각 후보
- 2026년 과학 발표대회 안내 ✓
- 2025년 안내: 폐기됨
검색
Top-K · threshold · metadata
2026년·사용 중 문서 선택문맥 구성
모델에 전달된 Context
2026년 과학 발표대회 신청 마감일은 4월 30일입니다.
답변 생성
모델 답변
제공된 2026년 안내문에 따르면 마감일은 4월 30일입니다.흐름 확인 완료. ✓ 정답 근거가 네 단계를 통과했습니다. 그래도 별도 검증은 필요합니다.
첫 번째 실패: 문서를 잘못 나눴다
긴 규정집 전체를 매번 검색하고 모델에 전달하기는 어렵습니다. 그래서 문서를 검색 가능한 작은 조각으로 나누는데, 이 과정을 Chunking(문서 분할)이라고 합니다.
다음처럼 나누면 어떤 문제가 생길까요?
조각 1: 프로젝트 알파의 운영 배포는
조각 2: 서비스 책임자의 승인을 받아야 한다.
첫 조각에는 승인자가 없습니다. 두 번째 조각에는 프로젝트 이름이 없습니다. “프로젝트 알파의 승인자는?”이라는 질문과 완전하게 맞는 조각이 사라진 셈입니다.
조각이 너무 작으면 필요한 문맥이 끊깁니다. 너무 크면 여러 주제가 섞여 검색 신호가 흐려지고, 모델에 전달되는 Token과 비용도 늘 수 있습니다. 문서 제목, 항목명, 버전 같은 metadata가 빠지면 조각이 어느 규정에 속하는지도 판단하기 어렵습니다.
Spring AI의 TokenTextSplitter는 Token 수를 기준으로 문서를 나눕니다. 2.0.0 문서의 기본 목표 크기는 800 Token이지만, 이는 API 기본 설정이지 모든 문서의 권장 정답이 아닙니다. 한국어 규정집, 표, 코드와 FAQ는 구조가 다르므로 실제 질문 세트로 분할 결과를 검증해야 합니다.
Chunk를 작게 만들수록 검색이 항상 정확해지는 것도 아닙니다. 먼저 정답에 필요한 문장들이 같은 조각에 남는지 확인해야 합니다.
두 번째 실패: 질문과 문서의 표현이 다르다
사용자는 “운영 반영은 누가 허락하나요?”라고 묻습니다. 문서에는 “프로덕션 배포 승인자는 서비스 책임자다”라고 적혀 있습니다.
사람은 두 문장이 비슷하다고 이해합니다. 그러나 임베딩 모델과 검색 설정에 따라 정답 조각이 상위에 나오지 않을 수 있습니다. Embedding(임베딩)은 문장의 의미를 비교하기 위한 숫자 표현이지 모든 표현 차이를 완벽하게 해소하는 장치가 아닙니다.
Query Transformation(질문 변환)은 질문을 검색하기 좋은 형태로 바꾸는 단계입니다. Query Rewrite(질문 재작성)는 불필요하거나 모호한 표현을 정리합니다. Query Expansion(질문 확장)은 다른 표현의 검색 질문을 추가해 후보 범위를 넓힙니다.
Spring AI의 RewriteQueryTransformer는 모델을 이용해 질문을 검색 대상에 맞게 다시 씁니다. CompressionQueryTransformer는 대화 기록과 “그건 언제까지예요?” 같은 후속 질문을 하나의 독립적인 질문으로 압축할 때 쓸 수 있습니다.
변환도 공짜가 아닙니다. 모델 호출이 추가돼 비용과 지연이 늘 수 있고, 잘못 재작성하면 원래 의도가 바뀔 수 있습니다. 6편의 코드에서는 역할이 단순한 RewriteQueryTransformer 하나만 사용합니다.
세 번째 실패: Top-K가 너무 작거나 크다
Top-K는 검색 결과 중 상위 몇 개의 문서 조각을 가져올지 정하는 값입니다. 정답 조각이 검색 순위 3위인데 Top-K=1이면 정답을 놓칩니다. Top-K=5라면 포함될 수 있습니다.
이 숫자는 흐름 이해를 위한 가정입니다. Top-K를 크게 하면 항상 좋아지는 것은 아닙니다. 관련 없는 문서, 중복 조각과 서로 충돌하는 규정도 함께 들어올 수 있습니다.
너무 작음 → 필요한 근거 누락
너무 큼 → 잡음, Token 증가, 상충 문서 증가
인터넷에서 본 Top-K를 그대로 복사하기보다 실제 질문과 기대 문서로 만든 평가 세트에서 결정해야 합니다. 한두 질문이 잘 맞았다고 전체 검색 품질이 검증된 것도 아닙니다.
네 번째 실패: 유사도 임계값이 맞지 않는다
Similarity Threshold(유사도 임계값)는 기준보다 관련성이 낮은 검색 결과를 제외하는 경계입니다. 너무 높으면 사용할 문서가 하나도 남지 않을 수 있고, 너무 낮으면 관련 없는 조각까지 포함될 수 있습니다.
유사도 점수는 정답 확률이 아닙니다. 점수가 높다고 문서가 사실이거나 최신이라는 뜻도 아닙니다. 임베딩 모델과 VectorStore 구현이 다르면 점수의 의미와 분포도 달라질 수 있으므로 서로 다른 환경의 숫자를 그대로 비교하면 안 됩니다.
threshold를 높여도 사실성이 자동으로 높아지지 않습니다. 실제 질문과 정답 문서로 만든 평가 세트에서 검색 누락과 잡음의 균형을 확인해야 합니다.
다섯 번째 실패: 오래되거나 권한이 없는 문서를 찾았다
2025년 배포 규정과 2026년 배포 규정은 표현이 매우 비슷할 수 있습니다. 의미만 비교하면 오래된 문서가 상위에 나올 수 있습니다.
이때 year, version, status, department, tenantId, accessLevel, source 같은 metadata가 검색 범위를 좁히는 단서가 됩니다. Metadata Filtering(메타데이터 필터링)은 현재 사용 중인 연도와 부서처럼 검색할 문서의 범위를 제한합니다.
그러나 metadata filter는 단독 인증 기능이 아닙니다. 사용자의 권한은 서버에서 검증해야 합니다. 클라이언트가 보낸 tenantId나 accessLevel을 그대로 신뢰해 필터를 만들면 다른 조직의 문서가 섞일 수 있습니다.
삭제되거나 만료된 문서는 VectorStore에서도 갱신하거나 제거해야 합니다. 새 문서를 원본 저장소에 올렸다고 기존 색인이 자동으로 바뀐다고 단정해서도 안 됩니다. 원본과 색인의 버전이 맞는지 확인하는 절차가 필요합니다.
여섯 번째 실패: 관련 문서를 너무 많이 넣었다
검색 결과에 최신 규정, 폐기된 규정, 다른 부서 규정과 중복 조각이 함께 들어올 수 있습니다. 질문과 비슷하지만 정답은 아닌 문서도 섞입니다.
모두 모델에 전달한다고 더 똑똑해지는 것은 아닙니다. 잡음이 많으면 중요한 근거가 묻히고 Context Window를 불필요하게 사용합니다.
DocumentPostProcessor(검색 문서 후처리기)는 검색 뒤에 문서를 다시 정렬하거나, 중복·관련 없는 문서를 제거하고, 필요한 부분만 압축하는 단계에 사용할 수 있는 Spring AI 인터페이스입니다. Re-ranking(재순위화)은 처음 검색한 후보를 더 정교한 기준으로 다시 정렬하는 과정입니다.
Spring AI가 특정 재순위 모델을 항상 자동 실행하는 것은 아닙니다. RetrievalAugmentationAdvisor에 후처리기를 명시적으로 구성해야 하며, 재순위화도 정답을 새로 만드는 기술은 아닙니다.
일곱 번째 실패: 올바른 문서를 찾았지만 답변이 틀렸다
정답 근거가 Context에 있어도 모델은 문서에 없는 내용을 덧붙일 수 있습니다. 서로 다른 조각을 잘못 합치거나, 충돌하는 규정을 하나의 결론처럼 설명할 수도 있습니다.
3편에서 살펴본 것처럼 LLM은 전달받은 문서를 데이터베이스 답안처럼 그대로 반환하지 않습니다. 질문과 Context를 바탕으로 여전히 다음 Token을 선택해 답을 생성합니다. RAG를 사용해도 환각 가능성이 남는 이유입니다.
Prompt에는 “제공된 Context 안에서만 답한다”, “근거가 없으면 모른다고 말한다”, “사용한 source를 표시한다”, “문서가 충돌하면 충돌 사실을 알린다” 같은 규칙을 넣을 수 있습니다.
이 규칙은 유용하지만 절대 보장은 아닙니다. 좋은 Prompt도 검색되지 않은 근거를 복구할 수 없고, 전달된 내용의 진실성을 새로 만들어내지 못합니다.
Spring AI 2.0에서 검색 결과부터 확인하기
최종 답을 만들기 전에 VectorStore가 어떤 조각을 찾았는지 직접 확인할 수 있습니다.
import java.util.List;
import org.springframework.ai.document.Document;
import org.springframework.ai.vectorstore.SearchRequest;
SearchRequest searchRequest = SearchRequest.builder()
.query(question)
.topK(5)
.similarityThreshold(0.5)
.build();
List<Document> documents =
vectorStore.similaritySearch(searchRequest);
5와 0.5는 API 흐름을 보여주기 위한 가정일 뿐 권장 최적값이 아닙니다. 검색 결과에서는 Document ID, 내용 일부, source, version, status, 검색 순위와 구현체가 제공하는 유사도 점수를 확인합니다.
운영 로그에 문서 원문 전체나 개인정보를 남기지는 않습니다. 진단에 필요한 Document ID, source와 version처럼 최소한의 정보만 안전하게 기록하고 접근 권한과 보존 기간을 정해야 합니다.
VectorStoreDocumentRetriever로 검색 조건 묶기
모듈형 RAG 흐름에서는 검색 조건을 VectorStoreDocumentRetriever에 둘 수 있습니다.
import org.springframework.ai.rag.retrieval.search.DocumentRetriever;
import org.springframework.ai.rag.retrieval.search.VectorStoreDocumentRetriever;
DocumentRetriever retriever =
VectorStoreDocumentRetriever.builder()
.vectorStore(vectorStore)
.similarityThreshold(0.5)
.topK(5)
.build();
Spring AI 2.0.0 공식 API는 정적 Filter.Expression이나 요청 시점의 filter expression도 지원합니다. 권한 필터에 사용할 tenant와 사용자 범위는 인증된 서버 측 정보로 만들어야 합니다.
RetrievalAugmentationAdvisor로 RAG 단계 연결하기
RetrievalAugmentationAdvisor는 검색과 문맥 추가 단계를 모듈로 연결합니다.
import org.springframework.ai.chat.client.advisor.api.Advisor;
import org.springframework.ai.rag.advisor.RetrievalAugmentationAdvisor;
import org.springframework.ai.rag.retrieval.search.VectorStoreDocumentRetriever;
Advisor ragAdvisor = RetrievalAugmentationAdvisor.builder()
.documentRetriever(
VectorStoreDocumentRetriever.builder()
.vectorStore(vectorStore)
.similarityThreshold(0.5)
.topK(5)
.build()
)
.build();
String answer = chatClient.prompt()
.advisors(ragAdvisor)
.user(question)
.call()
.content();
이 예제도 Spring AI 2.0.0 공식 문서 기반의 학습 코드입니다. 현재 Astro 블로그 환경에서 모델과 VectorStore를 설치해 컴파일하거나 응답을 측정한 결과가 아닙니다.
RewriteQueryTransformer로 질문 하나만 재작성하기
모호한 질문을 검색용 질문으로 바꾸려면 RewriteQueryTransformer를 선택할 수 있습니다.
import org.springframework.ai.rag.preretrieval.query.transformation.QueryTransformer;
import org.springframework.ai.rag.preretrieval.query.transformation.RewriteQueryTransformer;
QueryTransformer queryTransformer = RewriteQueryTransformer.builder()
.chatClientBuilder(chatClientBuilder)
.build();
Advisor ragAdvisor = RetrievalAugmentationAdvisor.builder()
.queryTransformers(queryTransformer)
.documentRetriever(retriever)
.build();
재작성용 모델 호출은 비용과 지연을 추가합니다. 질문 의도를 잘못 바꿀 수도 있으므로 원본 질문과 변환된 질문을 평가할 수 있어야 합니다. CompressionQueryTransformer나 Query Expansion까지 한꺼번에 추가하기보다 현재 문제에 필요한 한 단계부터 검증하는 편이 낫습니다.
RAG를 평가하는 가장 단순한 방법
RAG 정확도를 감으로 판단하지 말고 작은 평가 세트부터 만듭니다. 다음은 이해를 위한 가상 예시입니다.
| 질문 | 기대 문서 | 기대 핵심 답변 |
|---|---|---|
| 프로젝트 알파의 승인자는? | 배포 규정 4조 | 서비스 책임자 |
| 규정 시행일은? | 배포 규정 부칙 | 2026-04-01 |
| 문서에 없는 질문 | 없음 | 확인할 수 없음 |
먼저 검색을 평가합니다. 질문은 “정답 근거가 검색 결과 안에 들어왔는가?”입니다.
Hit Rate@K는 상위 K개 안에 정답 문서가 하나라도 있는 비율입니다. Recall@K는 필요한 정답 문서 가운데 상위 K개 안에 들어온 비율입니다. MRR(Mean Reciprocal Rank, 평균 역순위)은 첫 정답 문서가 얼마나 앞에 나타나는지를 봅니다.
이 글에서는 지표 값을 만들지 않습니다. 중요한 것은 최종 답이 아니라 검색 단계만 따로 비교할 수 있다는 점입니다.
그다음 답변을 평가합니다. 질문과 관련 있는지, 검색 문서가 답을 뒷받침하는지, 문서에 없는 내용을 추가하지 않았는지, 답이 없을 때 제대로 거절했는지, 표시한 출처가 실제 근거와 일치하는지 확인합니다.
Spring AI는 Evaluator 인터페이스와 EvaluationRequest, EvaluationResponse를 제공합니다. RelevancyEvaluator는 질문·답변·Context를 이용해 관련성을 평가하고, FactCheckingEvaluator는 답변의 주장이 제공된 Context로 뒷받침되는지 평가할 수 있습니다.
평가 모델의 판정도 절대적인 정답은 아닙니다. 사람이 작성한 기대 결과, 금액·코드처럼 결정 가능한 규칙 검증, 회귀 테스트와 중요한 사례의 수동 검토를 함께 사용해야 합니다.
한 단계 더 깊게: 검색과 생성을 따로 기록한다
정답 문서가 검색되지 않았다면 Retrieval 실패입니다. 정답 문서는 검색됐지만 답이 틀렸다면 문맥 구성이나 Generation을 살펴봐야 합니다. 최종 정답 여부만 기록하면 어느 부분을 고칠지 알기 어렵습니다.
질문
→ 변환된 질문
→ 검색된 Document ID와 metadata
→ 모델에 전달된 Context
→ 최종 답변
→ 평가 결과
이 기록에도 개인정보와 사내 기밀이 들어갈 수 있습니다. Prompt와 문서 원문을 무분별하게 저장하지 말고, 진단에 필요한 최소 정보와 마스킹·접근 통제·보존 기간을 함께 설계해야 합니다.
설정도 한 번에 하나씩 바꿉니다. Chunk 크기, Top-K, threshold, Prompt와 모델을 동시에 바꾸면 무엇이 결과를 바꿨는지 알 수 없습니다.
- 고정된 평가 질문을 만든다.
- 기준 결과를 기록한다.
- 설정 하나만 변경한다.
- 검색과 답변 지표를 다시 측정한다.
- 비용과 지연도 함께 비교한다.
이것은 특정 설정을 추천하는 결과표가 아니라 원인을 분리하기 위한 실험 원칙입니다.
자주 생기는 오해
Chunk를 작게 만들수록 검색이 정확해지나요?
항상 그렇지 않습니다. 문맥이 끊기면 질문과 정답이 서로 다른 조각으로 갈라질 수 있습니다.
Top-K와 threshold를 높이면 사실성이 좋아지나요?
아닙니다. Top-K가 크면 잡음이 늘 수 있고, threshold가 높으면 필요한 문서가 사라질 수 있습니다. 유사도 점수는 정답 확률이 아닙니다.
Metadata filter가 권한을 보호하나요?
검색 범위를 좁히는 데 도움을 주지만 단독 인증 기능은 아닙니다. 서버가 인증된 사용자와 문서 권한을 별도로 확인해야 합니다.
Reranking이 정답을 만들어주나요?
아닙니다. 이미 찾은 후보의 순서를 다시 정할 뿐, 검색되지 않은 정답 근거를 새로 만들지는 않습니다.
LLM Evaluator가 통과하면 배포해도 되나요?
평가 모델도 틀릴 수 있습니다. 기대 결과, 코드 검증, 회귀 테스트와 사람의 검토가 함께 필요합니다.
세 줄 요약
- RAG 오답은 문서 준비, 검색, 문맥 구성, 답변 생성 중 어느 단계에서도 발생할 수 있습니다.
- Chunking, Top-K, 유사도 임계값과 metadata는 정답 근거가 모델에 도착하는지를 바꿉니다.
- 검색 결과와 최종 답변을 분리해 평가해야 무엇을 고칠지 알 수 있습니다.
다음 편의 제목은 AI가 답만 하지 않고 실제 일을 하게 하려면?입니다.
이제 AI는 회사 문서를 찾아 답할 수 있다. 그렇다면 조회나 등록 같은 실제 업무를 실행하게 하려면 어떻게 해야 할까?
7편에서는 Tool Calling이 모델의 요청과 애플리케이션의 실제 기능을 어떻게 연결하는지 살펴봅니다. 아직 글이 없으므로 링크는 만들지 않았습니다.