문서 전처리와 키워드로 검색 품질 높이기
LangChain RAG에서 문서 정리, Chunking, metadata와 키워드 사전을 이용해 질문 표현이 달라도 필요한 문서를 찾는 검색 품질 개선 방법을 설명합니다.
LangChain으로 사내 문서 RAG 챗봇 만들기 6/9
문서를 준비하는 단계부터 검색, 대화 화면, 배포와 평가까지 하나씩 연결하는 학습 기록
이전 글 — Chroma에서 Pinecone으로 바꾸면 무엇이 달라질까?
학생은 “먹거리 부스는 언제 닫아요?”라고 물었습니다. 안내문에는 “식음 판매 운영 종료 시각은 18:00”이라고 적혀 있습니다. 사람은 같은 뜻이라고 알아차리지만 검색기는 원하는 문단을 놓칠 수 있습니다.
Vector Database를 바꾼다고 이 문제가 자동으로 해결되지는 않습니다. 검색 품질은 문서를 어떤 모양으로 저장했는지와 질문을 어떤 표현으로 찾는지에 함께 달려 있습니다.
전처리는 문장을 예쁘게 청소하는 작업만 뜻하지 않습니다. 제목·표·버전 같은 의미를 보존하고, 현장에서 쓰는 표현을 문서의 표현과 연결하는 일입니다.
이 글은 시리즈 순서에 맞춰 2026년 6월 11일에 배치했습니다. 개념과 API는 2026년 8월 16일 LangChain 공식 문서를 기준으로 확인했습니다. 아래 예시의 학교와 검색 결과는 이해를 위한 가정입니다.
검색 실패를 모델 탓으로 돌리기 전에
RAG의 최종 답변이 틀렸을 때 Chat Model부터 바꾸고 싶어집니다. 하지만 정답 문서가 검색되지 않았다면 더 좋은 Model도 읽을 근거가 없습니다.
먼저 다음 두 질문을 나눕니다.
- 정답 문서 조각이 검색 결과에 들어왔는가?
- 검색된 근거를 Model이 답변에 올바르게 사용했는가?
첫 질문이 실패했다면 Retrieval, 즉 검색 문제입니다. 이때 살펴볼 곳은 원본 문서, Chunking, Embedding, 키워드와 검색 조건입니다.
전처리는 무엇을 남기고 무엇을 지울까?
PDF에서 텍스트를 꺼내면 페이지 번호, 반복 머리말과 이상한 줄바꿈이 함께 들어올 수 있습니다. 이런 잡음은 검색 의미를 흐릴 수 있습니다.
반대로 무조건 지우면 안 되는 정보도 있습니다.
- 문서 제목과 절 제목
- 규정 연도와 버전
- 표의 열 이름과 행 관계
- 코드 블록의 언어와 파일명
- 출처, 부서, 상태와 접근 등급
- 문단 사이의 순서
예를 들어 표에서 행사 | 마감일이라는 열 이름을 지우고 과학 발표대회 | 6월 5일만 남기면 날짜의 의미가 약해집니다. 표를 문장으로 바꾸거나 열 이름을 각 행에 반복해 넣는 방식이 도움이 될 수 있습니다.
전처리의 목표는 글자를 줄이는 것이 아니라 질문에 답할 근거 단위를 보존하는 것입니다.
Chunking은 문장 자르기가 아니다
Chunking, 문서 분할은 긴 원문을 검색 가능한 조각으로 만드는 과정입니다. 조각이 너무 작으면 주어와 답이 갈라지고, 너무 크면 여러 주제가 섞입니다.
나쁜 분할 예시
조각 1: 축제 음식 판매는
조각 2: 오후 6시까지 운영한다.
첫 조각에는 시간이 없고 두 번째 조각에는 무엇의 운영 시간인지 없습니다. 제목이나 앞 문장을 각 조각에 함께 넣으면 의미를 보존하는 데 도움이 됩니다.
LangChain의 RecursiveCharacterTextSplitter는 문단과 줄바꿈 같은 구분자를 차례로 시도합니다. 하지만 한국어 규정집, 표와 코드는 구조가 다르므로 하나의 chunk_size가 모두에게 정답일 수 없습니다.
키워드 사전은 언제 도움이 될까?
Embedding은 의미가 비슷한 표현을 찾는 데 유용하지만 모든 조직 용어를 완벽히 알지는 못합니다. 사내 약어, 상품 코드와 오래된 이름은 일반 언어와 다를 수 있습니다.
예를 들어 다음 표현이 같은 업무를 가리킬 수 있습니다.
| 사용자의 말 | 문서의 표현 |
|---|---|
| 운영 반영 | 프로덕션 배포 |
| 먹거리 부스 | 식음 판매 부스 |
| 알파 승인자 | 프로젝트 알파 서비스 책임자 |
| F-204 | 축제 간식 예약번호 |
키워드 사전은 이런 표현을 같은 말로 강제로 바꾸는 목록이 아니라, 검색어에 문서 표현을 보충하는 도구로 사용할 수 있습니다.
TERM_ALIASES = {
"운영 반영": "프로덕션 배포",
"먹거리 부스": "식음 판매 부스",
"알파 승인자": "프로젝트 알파 서비스 책임자",
}
def expand_query(question: str) -> str:
additions = [
canonical
for alias, canonical in TERM_ALIASES.items()
if alias in question
]
return " ".join([question, *additions])
search_query = expand_query("운영 반영은 누가 승인해?")
이 코드는 원래 질문을 지우지 않고 관련 표현을 뒤에 보충합니다. 사전을 잘못 만들면 다른 의미의 문서가 더 많이 검색될 수 있으므로 변경 이력과 평가 질문이 필요합니다.
개인 이름이나 민감정보를 사전에 무분별하게 넣어서도 안 됩니다. 사전 자체가 조직 정보가 될 수 있으므로 접근 권한과 배포 범위를 정해야 합니다.
검색 품질 개선 흐름
질문과 문서의 말투가 달라도 만나게 하기
원문의 구조를 보존하고, 검색 전에 조직 용어를 보충한 뒤, 실제 결과로 효과를 확인합니다.
검색 근거가 보이는 RAG전처리와 키워드는 후보를 개선할 뿐 정답과 권한을 자동 보장하지 않습니다.
현재 단계: 원본 확인
metadata는 검색의 표지판이다
Embedding이 문장의 의미를 비교한다면 metadata는 문서의 조건을 나타냅니다. year=2026, status=active, department=festival처럼 검색 전에 범위를 좁힐 수 있습니다.
오래된 안내문과 최신 안내문은 문장이 거의 같을 수 있습니다. 의미 검색만 사용하면 오래된 문서가 위에 나올 수 있으므로 상태와 연도를 함께 확인해야 합니다.
하지만 metadata filter는 인증 기능이 아닙니다. 서버가 사용자의 소속과 권한을 확인한 뒤 허용된 범위를 만들어야 합니다. 클라이언트가 보낸 부서 값을 그대로 검색 조건으로 사용하면 안 됩니다.
의미 검색과 키워드 검색은 경쟁자가 아니다
Semantic Search, 의미 검색은 표현이 달라도 뜻이 가까운 문서를 찾는 데 강합니다. Keyword Search, 키워드 검색은 상품 코드, 고유명사와 정확한 문구에 강합니다.
두 점수를 섞는 Hybrid Search도 사용할 수 있습니다. 다만 “둘을 섞으면 무조건 정확하다”는 공식은 없습니다. 점수 정규화와 가중치, 중복 제거를 실제 질문으로 평가해야 합니다.
LangChain에는 BM25 같은 키워드 Retriever와 여러 Retriever 결과를 합치는 구성이 있습니다. 사용하는 Vector Store가 자체 Hybrid Search를 제공할 수도 있습니다. 구현을 고르기 전에 다음을 확인합니다.
- 질문에 정확한 코드나 이름이 자주 들어오는가?
- 동의어와 자연어 표현 차이가 큰가?
- 결과를 합칠 때 같은 문서가 중복되는가?
- 권한 Filter가 두 검색 경로 모두에 적용되는가?
- 실패했을 때 어느 검색 경로의 결과인지 추적할 수 있는가?
검색 결과를 눈으로 확인하는 가장 작은 습관
최종 답변만 출력하지 말고 개발 환경에서 검색된 문서의 안전한 식별 정보를 확인합니다.
question = "먹거리 부스는 언제 닫아요?"
search_query = expand_query(question)
documents = vector_store.similarity_search(
search_query,
k=4,
filter={"year": 2026, "status": "active"},
)
for rank, document in enumerate(documents, start=1):
print(rank, document.metadata.get("source"))
print(document.page_content[:120])
k=4와 120자는 흐름을 설명하기 위한 가정입니다. 운영 로그에 원문과 개인정보를 그대로 남기라는 뜻이 아닙니다. 문서 ID, 버전과 검색 순위처럼 필요한 정보만 보호된 로그에 기록합니다.
작은 평가 질문을 먼저 만들자
전처리 규칙을 바꿀 때마다 느낌으로 판단하면 좋아졌는지 알기 어렵습니다. 질문과 기대 문서를 짝지은 작은 Dataset을 만듭니다.
| 질문 | 기대 문서 | 확인할 핵심 |
|---|---|---|
| 먹거리 부스는 언제 닫아? | 축제 안내 3조 | 오후 6시 |
| 운영 반영 승인자는? | 배포 규정 4조 | 서비스 책임자 |
| F-204 상태 알려줘 | 예약 조회 결과 | 문서 검색 대상 아님 |
| 2025년 규정은 지금 유효해? | 버전 metadata | 폐기 상태 |
이 표는 이해를 위한 가상 평가 자료입니다. 마지막 질문처럼 Vector Store보다 업무 API가 맞는 질문도 구분해야 합니다.
정답 문서가 상위 K개 안에 들어오는 비율, 첫 정답이 몇 위에 있는지 같은 지표를 사용할 수 있습니다. 중요한 것은 숫자 이름보다 실패 질문을 열어보고 원인을 분류하는 일입니다.
전처리에서 자주 생기는 실패
모든 특수문자를 지웠다
코드의 점, 버전 번호와 표 구분자까지 사라질 수 있습니다. 문서 종류별 규칙을 나눠야 합니다.
사전의 모든 단어를 치환했다
원래 질문의 뜻이 바뀔 수 있습니다. 원문을 보존하고 보충어를 추가하는 방식을 먼저 검토합니다.
최신 문서를 추가만 했다
오래된 Chunk가 Vector Store에 남아 함께 검색될 수 있습니다. 원본 상태와 색인의 갱신·삭제를 연결해야 합니다.
좋은 답변 하나만 확인했다
한 질문의 성공은 전체 검색 품질을 증명하지 않습니다. 정상·동의어·오래된 문서·권한 경계 질문을 함께 넣어야 합니다.
한 단계 더 깊게: 원본과 파생 데이터를 구분하자
원본 PDF는 진실의 기준이 될 수 있지만 Chunk와 Embedding은 다시 만들 수 있는 파생 데이터입니다. 변환 규칙과 Embedding Model 버전을 기록하면 문제가 생겼을 때 재현하기 쉽습니다.
문서 ID에는 원본 ID, 버전과 조각 순서를 연결할 수 있습니다. 답변에 출처를 표시할 때도 사람이 실제 원문 위치를 찾을 수 있어야 합니다.
전처리 Pipeline을 바꾸면 기존 Index 위에 섞어 쓰기보다 새 Collection이나 Namespace에 적재하고 평가한 뒤 전환하는 방법을 검토할 수 있습니다. 실패 시 이전 버전으로 돌아갈 경계가 생깁니다.
세 줄 요약
- RAG 검색 품질은 Vector Database 이름보다 원문의 구조를 보존한 전처리와 의미 있는 Chunking에 크게 좌우됩니다.
- 키워드 사전과 metadata는 질문 표현·버전·권한 범위를 보완하지만 잘못 쓰면 검색 의도를 바꾸거나 문서를 노출할 수 있습니다.
- 고정된 질문과 기대 문서로 검색 결과를 먼저 평가해야 Chat Model을 바꾸기 전에 Retrieval 실패를 찾을 수 있습니다.
다음 편 예고
이제 검색 결과를 확인하는 Python 코드는 생겼습니다. 하지만 사용자가 매번 터미널을 열고 질문할 수는 없습니다.
다음 편 — Streamlit으로 대화·기억·Streaming 구현하기에서 검색 흐름을 실제 대화 화면에 연결합니다.