메인 콘텐츠로 건너뛰기
검색 증강 생성, 즉 RAG는 자체 문서로부터 답해야 하는 AI 애플리케이션을 만드는 데 가장 유용한 패턴 중 하나입니다. 모델이 기억만으로 답하게 하는 대신, 먼저 관련 원본 자료를 검색하고 그 context를 모델에 보내 인용과 함께 답하도록 합니다. 이 튜토리얼에서는 Python, 임베딩과 chat completion을 위한 Venice, 벡터 검색을 위한 Qdrant, 로컬 재순위를 위한 FastEmbed로 프라이빗 RAG 봇을 만듭니다. 마지막에는 파일을 ingest하고, 관련 청크를 검색하고, 재순위하며, 인용과 함께 답하는 로컬 문서 비서의 핵심 부품이 갖춰집니다. 동작 중인 RAG 봇 계속하기 전에: 이 글의 코드를 실행하려면 Venice API 키가 필요합니다. 환경 변수로 export하세요:
전체 코드 구현이 궁금하다면 GitHub 레포를 확인하세요.

현대적인 RAG 봇이 동작하는 방식

좋은 RAG 파이프라인은 “문서를 벡터 데이터베이스에 넣는 것” 이상입니다. 기본 흐름은 다음과 같습니다: 재순위 단계는 이를 기본 RAG 데모보다 훨씬 더 유용하게 만드는 업그레이드입니다. 벡터 검색은 빠르고 의미적으로 유사한 청크를 잘 찾지만, 주제에 인접하기만 하고 직접적으로 유용하지는 않은 구절을 반환할 수 있습니다. cross-encoder는 질문과 각 후보 청크를 함께 읽고, 그 청크가 실제로 질문에 얼마나 잘 답하는지를 점수화합니다.

의존성 설치

Venice가 OpenAI 호환 API를 노출하므로 OpenAI Python SDK를 사용합니다. FastEmbed 지원이 있는 Qdrant Python 클라이언트도 함께 사용합니다:
파일에 의존성을 두고 싶다면 같은 패키지로 requirements.txt를 만드세요:

모델 선택

rag_bot.py라는 파일을 만들고, 임포트, 데이터 구조, API URL, 모델 이름을 추가해 시작하세요:
임베딩 모델 이름은 의도적으로 OpenAI 호환입니다. Venice는 호환되는 임베딩 모델 이름을 Venice 호스팅 임베딩 모델에 매핑하므로, 기존 OpenAI SDK 코드는 보통 base_url과 API 키만 바꾸면 이전 가능합니다. 다음으로 사용 가능한 Venice 모델을 나열할 수 있습니다:
채팅 모델의 경우:

Venice와 Qdrant 클라이언트 생성

임베딩과 chat completion 모두를 위한 하나의 OpenAI 호환 Venice 클라이언트를 만드세요:
Qdrant의 경우 세 가지 유용한 모드가 있습니다: 프라이빗 로컬 봇은 디스크의 로컬 Qdrant 경로로 시작하세요:
프로덕션에서 배포를 처리하는 방법은 몇 가지가 있습니다. 다만 원격 Qdrant 배포를 사용한다면 문서 청크와 메타데이터가 거기에 저장된다는 점을 기억하세요. Venice는 추론 레이어를 프라이빗하게 유지할 수 있지만, 데이터에 맞는 Qdrant 배포를 선택하는 것은 여전히 사용자의 몫입니다.

문서 로드 및 청킹

이 튜토리얼에서는 봇이 로컬 파일이나 폴더를 ingest하게 합니다. .md, .rst, .txt 파일로 시작하세요:
파일이 로드되면 텍스트를 “청킹”해서 분할해야 합니다 — 데이터 청크로 나누는 것입니다. 단순한 전략은 청크를 균등하게 분할할 수 있습니다. 그러나 대부분의 경우 주어진 의미 경계에서 정보를 잃을 수 있어 RAG 시스템의 효과가 떨어질 수 있습니다. 여기서 사용할 청킹 전략은 모델이 일관된 context를 받도록 문단 또는 문장 경계를 선호합니다:
청크 크기 1000 문자와 150 문자 오버랩으로 시작하는 것이 혼합 Markdown과 텍스트 문서에 좋은 기본값입니다. 더 작은 청크는 정밀도를 개선할 수 있습니다. 더 큰 청크는 더 많은 context를 보존할 수 있습니다. 올바른 설정은 종종 저장하는 문서의 종류에 따라 다릅니다.

Venice로 문서 임베딩

청크가 있으면 배치로 임베딩합니다:
배치 처리가 중요합니다. 한 번에 하나씩 청크를 임베딩하는 것은 단순하지만 피할 수 있는 지연 시간을 추가합니다. 작업 부하에 맞춰 처리량을 튜닝할 수 있도록 배치 크기를 구성 가능하게 유지하세요.

Qdrant에 벡터 저장

포인트를 삽입하기 전에 올바른 벡터 크기로 Qdrant 컬렉션을 만드세요. 벡터 크기를 알기 가장 쉬운 방법은 첫 배치를 임베딩한 다음 len(embeddings[0])을 사용하는 것입니다.
각 포인트는 벡터와 페이로드 메타데이터를 저장합니다. 페이로드는 원본 텍스트와 답변이 context가 어디서 왔는지 인용할 수 있도록 출처 경로를 포함합니다:
source, chunk_index, 콘텐츠에서 파생된 결정적 UUID를 사용하세요. 그러면 변경되지 않은 청크에 대해 반복 ingest가 멱등이 됩니다.

후보 청크 검색

질문 시점에 봇은 사용자의 질문을 임베딩하고 Qdrant에 상위 벡터 매칭을 요청합니다:
여기서 limit는 후보 수입니다. 다음 단계에서 재순위할 것이므로 보통 모델에 보낼 청크 수보다 더 높아야 합니다. 좋은 기본값은 8개 후보를 검색하고 상위 4개를 chat 모델로 보내는 것입니다.

FastEmbed로 재순위

이제 검색이 훨씬 더 똑똑하게 느껴지게 만드는 부분을 추가합니다.
임베딩 검색과 cross-encoder 재순위의 중요한 차이는 점수화가 어떻게 일어나는가입니다. 임베딩 검색은 질문의 벡터 하나를 각 청크의 벡터 하나와 비교합니다. 빠르고 확장 가능합니다. cross-encoder는 질문과 청크를 함께 평가합니다. 더 느리지만 관련성을 더 직접적으로 판단할 수 있습니다. 그래서 보통의 패턴은 다음과 같습니다:
  1. 벡터 검색으로 더 큰 후보 집합 검색.
  2. 그 후보들을 로컬에서 재순위.
  3. 상위 몇 개 청크를 언어 모델로 전송.
좋은 시작점은 candidate_k=8, top_k=4입니다. 올바른 출처가 종종 근처에 있지만 최종 context에 들어가지 못한다면 candidate_k를 늘리세요.

Venice Chat Completion으로 답변

context가 선택되면 출처 번호로 포맷하세요:
그런 다음 context를 Venice chat 모델로 보내세요:
system prompt에 주목하세요: 봇은 제공된 context로만 답하라고 지시받습니다. 이는 단순하지만 중요한 가드레일입니다. 검색된 문서가 답을 뒷받침하지 않을 때 RAG 비서가 일반 모델 지식으로 자신 있게 답해서는 안 됩니다.

봇 실행

조각들을 스크립트로 조립한 다음 rag_bot.py로 저장하세요. 첫 번째 단순 실행은 자체 파일을 ingest하기 전에 파이프라인을 검증할 수 있도록 몇 가지 내장 샘플 문서를 사용할 수 있습니다:
자체 문서를 ingest하려면:
디스크에 로컬 Qdrant 컬렉션을 유지하고 대화형 채팅을 시작하려면:
스크립트는 답변을 출력한 다음, 벡터 및 재순위 점수와 함께 출처를 출력합니다:
모델에 실제로 전달된 텍스트를 확인하고 싶다면 다음을 추가하세요:

유용한 CLI 옵션

코드 수정 없이 봇을 튜닝할 수 있도록 주요 검색 손잡이를 CLI 옵션으로 노출하세요: 반복적인 로컬 개발에서 흔한 흐름:
그런 다음 ingest 없이 후속 질문:

프라이버시 노트

프라이빗 RAG 설정의 경우 각 레이어를 별도로 생각하세요: 이 튜토리얼의 가장 프라이빗한 기본값은 추론에 Venice, 디스크의 로컬 Qdrant, 로컬 FastEmbed 재순위입니다. 그러면 벡터 데이터베이스 페이로드를 제3자 벡터 스토어로 보내지 않고 실용적인 RAG 봇을 얻을 수 있습니다.

미리 처리할 흔한 에러

임베딩 모델을 바꾸면 Qdrant 컬렉션을 재생성하세요. 서로 다른 임베딩 모델은 서로 다른 차원의 벡터를 생성할 수 있고, Qdrant 컬렉션은 고정된 벡터 크기를 기대합니다.

다음으로 갈 곳

베이스라인이 동작하면 가장 영향이 큰 개선은 보통 다음과 같습니다:
  • PDF, HTML, 티켓, 내부 위키 페이지를 위한 문서 전용 로더 추가.
  • 제목, 헤딩, 날짜, 소유자, URL 같은 더 풍부한 메타데이터 저장.
  • 실제 질문에 대해 candidate_k, top_k, 청크 크기, 오버랩 튜닝.
  • 변경 전후 검색 품질을 측정할 수 있도록 평가 질문 추가.
  • 더 나은 대화형 채팅 경험을 위해 최종 Venice chat completion 스트리밍.
RAG 시스템은 데모하기는 쉽지만 평범하게 만들기도 놀랍도록 쉽습니다. 벡터 검색 + 재순위 패턴은 검색을 빠르게 유지하면서도 봇이 언어 모델에 올바른 context를 보낼 가능성을 높이는 강력한 기반입니다.