RAG 문서 챗봇이 오답을 내놓는 이유: 임베딩(Embeddings), 청킹(Chunking) 및 컨텍스트(Context) 해결책
요약
RAG 기반 챗봇의 환각 현상을 방지하기 위해 검색 파이프라인을 평가 가능한 증거 체계로 구축하는 방법을 다룹니다. 임베딩, 청킹, 컨텍스트 윈도우의 한계를 지적하며 검색 재현율 측정과 리랭킹의 중요성을 강조합니다.
핵심 포인트
- 검색 결과가 답변에 필요한 구절을 포함하는지 재현율을 측정해야 함
- 의미론적 검색 후 후보군을 재순위화(Reranking)하는 과정이 필요함
- 청킹 크기와 메타데이터가 검색 품질에 미치는 영향을 관리해야 함
- 컨텍스트에 근거가 없는 경우 모델이 답변을 거절하도록 지침을 강화해야 함
RAG 기반의 '문서 질문(ask-your-docs)' 챗봇은 검색(retrieval)을 영리한 프롬프트가 아닌, 평가된 증거 파이프라인(evaluated evidence pipeline)으로 취급할 때 환각(hallucination) 현상을 멈춥니다.
저는 Python으로 '문서 질문' 기능을 구축하며 한 가지 단호한 규칙을 가지고 시작합니다. 증거를 검색하고, 이를 프롬프트 예산(prompt budget)에 맞게 배치하며, 증거가 없을 때는 모델이 "찾을 수 없음"이라고 말하게 하는 것입니다. 임베딩(Embeddings)과 청킹(Chunking)은 필수적이지만, 이것이 답변 가능한 구절이 생성기(generator)에 도달했음을 증명하지는 않습니다. 저는 RAG 데모가 실제 규칙이 포함된 섹션 대신 인접한 섹션을 인용하며 그럴듯하게 들리는 것을 목격한 후 이 차이를 깨달았습니다.
실질적인 해결책은 검색 평가(retrieval evaluation)를 동반한 소스 전용 생성(source-only generation)입니다. 문장 품질을 판단하기 전에 검색 재현율(retrieval recall)을 측정하고, 의미론적 검색(semantic search)을 통과한 후보들을 재순위화(rerank)하며, 채팅 요청 전에 토큰(token) 수를 계산하십시오. 채팅 모델은 선택된 컨텍스트(context)와 근거 없는 질문은 거절하라는 지침만을 받아야 합니다. 유창한 언어를 사실성(factuality)의 척도로 사용하지 마십시오.
작은 변화가 큰 효과를 가져옵니다.
임베딩, 청킹, 컨텍스트 윈도우(context window)가 있음에도 RAG 문서 챗봇이 오답을 내놓는 이유는 무엇인가요?
임베딩(embedding) 검색은 질문과 관련된 텍스트를 반환할 수는 있지만, 질문에 답이 되는 구절을 반환하지 못할 수도 있습니다. 문서 코퍼스(corpus)에서는 이름과 개념이 반복됩니다. 설치 페이지, 마이그레이션 가이드, API 레퍼런스가 모두 동일한 제품 용어에 대해 높은 점수를 받을 수 있습니다. 큰 청크(chunk)는 매칭을 모호하게 만들고, 너무 작은 청크는 답변을 바꾸는 조건을 제거할 수 있습니다. 그러면 컨텍스트 윈도우(context window)는 검색 실패를 생성 문제로 변질시키는데, 모델이 그럴듯한 자료를 보고 일반적인 지식으로 그 간극을 채워버리기 때문입니다.
저는 평가 실행(eval run) 중에 이 운영 버전의 사례를 목격했습니다. 429 Rate Limit(요청 제한) 오류가 재시도 루프(retry loop)에 의해 조용히 삼켜졌고, 그 결과 테스트 프레임워크(harness)는 3초 후에 응답을 기록했지만, 그 이전에 발생한 실패한 검색 요청(retrieval request)은 보존하지 않았습니다. 검색 결과와 응답 상태를 별도로 로그에 남기기 전까지는 그 답변이 프롬프트 실패(prompt failure)처럼 보였습니다. 저는 이제 깔끔해 보이는 완성된 답변(completion)이 파이프라인에 깨끗한 근거(evidence)가 있었다는 것을 의미한다고 가정하지 않습니다.
제 평가 세트(eval set)의 각 질문에 대해, 저는 예상되는 소스 구절(source passage), 상위 검색된 청크(retrieved chunks), 그들의 순위(ranks), 그리고 최종 답변을 저장합니다. 이렇게 하면 진단 과정이 유용한 방식으로 지루해집니다. 즉, 예상되는 구절이 후보 세트(candidate set)에 없다면 청킹(chunking), 메타데이터(metadata), 또는 검색 쿼리(retrieval query)를 변경해야 합니다. 만약 구절이 나타나지만 순위가 밀린다면, 리랭킹(reranking)이 다음 실험 대상입니다. 만약 최종 컨텍스트(context)에 구절이 존재함에도 불구하고 답변이 여전히 허위 주장을 만들어낸다면, 생성 지침(generation instruction)을 강화해야 합니다. 이것들은 서로 다른 실패 유형이므로, 단일한 "RAG 정확도" 수치는 제가 수행해야 할 작업들을 가려버립니다.
왜 팀들이 여전히 이 단계에서 생성 모델(generation models)을 교체하는 것으로 바로 건너뛰는지 모르겠습니다. 제 경험상, 근거 경로(evidence path)를 먼저 살펴보는 것이 우선순위가 되어야 합니다.
모델을 변경하기 전에 수행하는 실험
저의 베이스라인(baseline)은 의도적으로 단순합니다. 문서 청크(documentation chunks)를 임베딩(embed)하고, 작은 후보 세트(candidate set)를 검색한 뒤, 모델에게 오직 해당 청크들만을 바탕으로 답변하도록 요청합니다. 저는 평가 질문에 직접적인 사실, 부재하는 사실, 그리고 답변이 수식어(qualifier)에 따라 달라지는 질문들을 포함시킵니다. 부재하는 사실(absent-fact) 사례가 중요한 이유는, 근거에 기반(grounded)하기보다 그저 도움이 되는 것처럼 들리도록 학습된 챗봇의 실체를 드러내기 때문입니다.
저는 베이스라인(baseline)을 리랭킹(reranked)된 변형 모델과 비교합니다. 유용한 결과물은 단순히 하나의 답변이 더 좋게 들린다는 막연한 인상이 아니라, 지원하는 구절(supporting passage)이 최종 프롬프트(prompt)에 포함된 질문의 수와, 지원되지 않는 답변이 올바르게 "찾을 수 없음"으로 처리된 답변의 수를 합산한 것입니다. 토큰 수(Token counting) 또한 동일한 보고서에 포함되어야 합니다. 컨텍스트 윈도우(context window)는 예산과 같아서, 검색된 청크(chunks)를 맹목적으로 추가하다 보면 제가 중요하게 생각하는 구절이 그 예산을 벗어나거나 무관한 텍스트로 인해 희석될 수 있습니다.
다음은 제가 최종 생성 단계에서 사용하는 집중적인 Python 요청 패턴입니다. 이는 OpenAI 호환 인터페이스를 사용하며, 환경 변수에서 키를 읽어오고, Retry-After가 사용 가능한 경우 이를 준수하면서 지수 백오프(exponential backoff)를 통해 속도 제한(rate limit)에 재시도합니다. 실제 애플리케이션에서 context는 모든 시맨틱 검색(semantic-search) 결과가 아니라, 평가 및 리랭킹된 텍스트입니다.
import os
import time
...
중요한 제약 사항은 클라이언트 라이브러리가 아니라 프롬프트(prompt)입니다: 근거가 없으면 답변도 없어야 합니다. 이 패턴을 복사하기 전에 소스 구절 재현율(source-passage recall), 지원되지 않는 답변 비율(unsupported-answer rate), 조립된 컨텍스트 내의 토큰 수(tokens in the assembled context), 그리고 사용된 소스를 명시한 답변의 비율을 측정하십시오.
리랭킹(Reranking)과 토큰 수(Token counts)가 필요한 이유
리랭킹(Reranking)은 광범위한 재현율(recall)과 작은 증거 패킷(evidence packet) 사이를 잇는 가교 역할을 합니다. 저는 관련 청크(chunk)가 풀(pool)에 들어갈 기회를 가질 수 있도록 충분히 넓게 검색한 다음, 컨텍스트(context)를 구성하기 전에 정확한 질문을 바탕으로 후보군을 리랭킹합니다. 이는 모델이 해석해야 하는 유사 매칭(near-matches)의 수를 줄여줄 수 있으며, 이는 반복되는 용어가 등장하는 문서 관련 질문의 경우 생성 모델(generation-model)을 변경하는 것보다 종종 더 가치 있는 작업입니다.
토큰(token)을 계산하는 작업은 덜 화려해 보일 수 있지만, 조용한 실패 모드(failure mode)를 방지해 줍니다. 노트북(notebook) 환경에 들어가는 청크(chunk) 목록이 실제 운영 환경(production)에서는 시스템 지침(system instructions), 대화 기록(conversation history), 그리고 예상되는 완성문(expected completion)과 함께 배치될 때 공간이 부족할 수 있습니다. 저는 이러한 요소들을 위한 공간을 확보해 두고, POST /v1/ai/tokens/count를 사용하여 제안된 컨텍스트(context)의 양을 계산하며, 예산(budget) 내에 들어올 때까지 순위가 낮은 자료를 제거합니다. 이 엔드포인트(endpoint)는 모호한 컨텍스트 창(context-window)에 대한 우려를 테스트 가능한 관문(gate)으로 바꿔주기 때문에, 이와 같은 글에서 언급하고 싶은 몇 안 되는 플랫폼 세부 사항 중 하나입니다.
또한, 광범위한 백엔드 접점(backend surface)은 통합 과정의 번거로움(integration churn)을 줄여줄 수 있습니다. Infrai는 임베딩(embeddings), 재순위화(reranking), 토큰 계산(token counting), 그리고 OpenAI 호환 채팅 인터페이스(chat surface)를 하나의 일관된 API 계약(API contract) 아래 제공하므로, 이러한 평가 단계 중 하나를 추가하는 것이 또 다른 제공업체(provider)를 통합하는 대신 단순히 엔드포인트를 하나 더 추가하는 작업이 됩니다. 여러 실험이 진행 중인 Python RAG 서비스에서 저는 이 점을 높게 평가합니다. 실험은 검색(retrieval) 동작을 변경해야 하는 것이지, 클라이언트 배관(client plumbing)을 다시 작성하게 만들어서는 안 되기 때문입니다.
그럼에도 불구하고, 어떤 제공업체의 계약도 코퍼스(corpus) 작업(말뭉치 작업)을 대체할 수는 없습니다. 중복된 페이지, 오래된 소스 자료, 누락된 메타데이터(metadata), 그리고 쉬운 질문만 체크하는 평가 세트(eval set)는 계속해서 잘못된 확신을 만들어낼 것입니다.
문서 챗봇을 위한 RAG 스택은 어떻게 선택해야 할까요?
저는 스택을 비교할 때 벤더(vendor)의 데모 답변이 아니라, 증거(evidence)에 대해 저에게 얼마나 많은 제어권(control)을 부여하는지를 기준으로 삼겠습니다. OpenAI, Anthropic, Gemini는 Infrai와 함께 실제적인 선택지들입니다. 각 모델은 제가 이미 운영 중인 구성 요소와 제품의 운영 경계(operational boundaries)에 따라 합리적인 선택이 될 수 있습니다.
| 옵션 (Option) | 유용한 적합성 (Useful fit) | 주요 트레이드오프 (Main trade-off) |
|---|---|---|
| OpenAI | 해당 모델 API 및 클라이언트 생태계에 집중하는 팀 | 검색 (Retrieval), 평가 (Evaluation), 저장소 (Storage) 선택은 여전히 명시적인 설계가 필요함 |
| ... |
함정은 다음과 같습니다. 벡터 데이터베이스 (Vector database) 작업이 시스템의 중심일 때는 전용 벡터 데이터베이스를 고수할 것이며, 제공업체를 변경하는 것이 새로운 기능이 주는 이점보다 더 많은 리스크를 초래할 때는 기존의 OpenAI, Anthropic 또는 Gemini 통합 방식을 유지할 것입니다. Infrai는 모든 실험마다 별도의 SDK를 설치하지 않고도, RAG 루프의 인접 구성 요소들이 일반적인 REST API와 하나의 키를 공유하기를 원할 때 더 적합합니다. 지역적 요인, 컴플라이언스 (Compliance), 그리고 기존 계약 요구 사항에 따라 결과는 달라질 수 있습니다.
저의 최종 점검은 간단합니다. 시스템이 검색된 증거 (Retrieved evidence)를 보여줄 수 있는가? 토큰 예산 (Token budget)을 맞추기 위해 무엇이 누락되었는지 알려줄 수 있는가? 그리고 증거가 없는 질문에 대해 답변을 거부할 수 있는가? 만약 그렇지 않다면, 저는 그것을 아직 문서 어시스턴트 (Docs assistant)라고 부르지 않습니다.
참고 문헌 (References)
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기