하이브리드 RAG: BM25와 벡터 검색을 결합한 고급 기술

하이브리드 RAG 썸네일 이미지

이전에 살펴보았던 로컬 RAG 구축 방법에 이어서 이번에는 하이브리드 RAG에 대해서 살펴보겠습니다. 하이브리드 RAG는 전통적인 BM25 키워드 검색과 시맨틱 벡터 검색의 장점을 결합한 방법으로, 두 방식의 강점을 모두 살려 문서 검색의 정확도와 재현율을 동시에 개선합니다.

하이브리드 RAG란?

하이브리드 RAG(Retrieval-Augmented Generation)는 LLM(대형언어모델) 앞단에 정보 검색(Retrieval) 과정을 추가하여 신뢰할 만한 출처 기반 답변을 생성하는 기술입니다. 여기서 기존의 RAG는 보통 키워드 기반 검색(BM25) または 벡터 임베딩 검색 중 하나를 사용해 관련 문서를 찾습니다.

BM25는 사용자가 입력한 키워드의 문서 내 빈도를 바탕으로 정확한 매칭을 수행하지만 문맥까지 이해하지는 못합니다. 반면 벡터 검색은 문장 전체를 임베딩한 뒤 코사인 유사도로 의미적 유사성을 계산하여, “사과”를 “과일”과 같은 개념으로 연관 지어 찾아냅니다.

하이브리드 검색은 이 둘을 함께 사용하여, 키워드와 의미 정보를 모두 활용합니다. 즉, BM25는 핵심 키워드 일치를 잡아내고, 벡터 검색은 문장/문맥의 유사성을 잡아내므로, 두 방법의 단점을 보완하며 더 정확하고 풍부한 검색 결과를 제공합니다.

왜 지금 이게 뜨는가

최근 RAG가 챗봇·AI 비서의 핵심으로 부상하며, 검색 정확도와 신뢰성이 더욱 중요해졌습니다. LLM은 막강하지만 훈련 데이터 외의 최신 지식은 알지 못하고, 때로는 근거 없는 답변(환각)을 내놓기도 합니다. 따라서 RAG를 통해 외부 지식 기반에서 정보를 찾아 LLM에 공급하면 답변의 신뢰도를 높일 수 있습니다.

그런데 단순히 벡터 검색만으로는 구체적인 키워드를 놓치거나, 노이즈성 결과가 섞일 수 있습니다. 이에 키워드 검색과 벡터 검색을 함께 쓰면 관련 정보를 더 폭넓게 찾을 수 있어 유용합니다. 최근에는 SQLite-vec 같은 경량 로컬 벡터 DB와 Ollama 같은 로컬 LLM 실행기가 나오면서, 클라우드 없이 데스크탑 수준에서도 완전히 로컬 환경으로 이 기술을 구현할 수 있게 되어 주목받고 있습니다.

전체 구조(그림으로 이해)

전체 구조

하이브리드 RAG의 전체 흐름은 다음과 같습니다. 사용자가 질문을 하면, 이 질의는 두 경로로 분기됩니다. 하나는 키워드 검색(FTS5/BM25) 경로로, 다른 하나는 벡터 검색 경로로 정보 탐색을 수행합니다. 두 결과를 받아 종합 순위화(예: RRF) 과정을 거쳐 중요한 문서들을 선별한 뒤, 해당 문서를 LLM의 입력(prompt)로 제공하여 답변을 생성합니다.

요약하자면, 사용자 질의(query)를 BM25 기반 키워드 검색과 임베딩 기반 벡터 검색을 통해 각각 검색한 후, 이를 결합하여 LLM에 컨텍스트로 제공합니다.

이 과정에서 BM25 검색은 간단한 단어 매칭을 통해 명시적인 키워드가 포함된 문서를 높은 점수로 찾아내며, 벡터 검색은 임베딩으로 계산된 문서 간 의미적 거리를 활용해 문장/구문의 뉘앙스까지 반영합니다. 그 결과 두 방법 모두에서 중요도가 높은 문서들이 LLM에 전달되어, 더 정확하고 다각적인 답변이 가능해집니다.

하이브리드 RAG 설치/준비물

  • 운영체제: macOS (SQLite 3, FTS5는 macOS 기본 탑재)
  • Python 환경: Python 3 이상 (pip)
  • SQLite-vec 확장: pip install sqlite-vec 명령으로 설치 (SQLite에 벡터 검색 기능 추가). 또는 sqlite-vec GitHub에서 확장 파일 다운로드
  • Ollama: 로컬 LLM 실행기(예: Ollama 설치 및 로컬 모델 세팅)
  • 기타 라이브러리: sqlite3リクエスト 등 (Python 예제용)

실전 사용법 (Step-by-step)

1. 기본 데이터베이스 구성

문서 테이블(예: articles)을 만든 뒤, FTS5 인덱스를 생성합니다. 예를 들어 articles 테이블에 アイデンティティheadline 컬럼이 있을 때, 다음과 같이 가상 테이블을 생성하고 내용을 삽입할 수 있습니다.

-- FTS5 인덱스 생성 (BM25용)
CREATE VIRTUAL TABLE fts_articles USING fts5(
  headline,
  content='articles', content_rowid='id'
);
INSERT INTO fts_articles(rowid, headline)
  SELECT rowid, headline FROM articles;

이로써 fts_articles 테이블이 생성되어 headline MATCH '검색어' 형태의 키워드 검색이 가능합니다.

    2. 벡터 인덱스 생성

      SQLite-vec의 vec0 가상 테이블에 임베딩 벡터를 저장합니다. 예를 들어 문서마다 768차원 임베딩이 있다면, 다음과 같이 정의하고 삽입합니다.

      -- vec0 테이블 생성 (벡터 검색용)
      CREATE VIRTUAL TABLE vec_articles USING vec0(
        article_id INTEGER PRIMARY KEY,
        headline_embedding FLOAT[768]
      );
      INSERT INTO vec_articles(article_id, headline_embedding)
        SELECT rowid, lembed(headline) FROM articles;

      ここで lembed(text)Ollama または Granite 모델을 사용해 텍스트를 임베딩하는 함수입니다. 위처럼 삽입하면 각 문서의 임베딩이 vec_articles에 저장됩니다.

        3. 키워드 검색 예시

        SELECT rowid AS id, headline, rank
        FROM fts_articles
        WHERE fts_articles MATCH '예시 키워드';

        위 쿼리는 FTS5 인덱스를 이용해 ‘예시 키워드’가 포함된 문서를 랭크 순서대로 보여줍니다.

          4. 벡터 검색 예시

          쿼리 문장을 임베딩해 가까운 문서를 찾습니다. 예를 들어, Ollama 임베딩을 호출해 결과를 얻는다고 가정하면

          SELECT article_id, distance
          FROM vec_articles
          WHERE headline_embedding MATCH lembed('예시 질의')
          ORDER BY distance ASC
          LIMIT 10;

          이 쿼리는 lembed('예시 질의')로 변환된 질의 임베딩을 이용해 가장 유사한 상위 10개 문서를 반환합니다. SQLite-vec에서는 lembed() 함수와 KNN 방식으로 벡터 거리를 계산합니다.

          5. 결과 결합 및 재순위

          BM25 검색 결과와 벡터 검색 결과를 합쳐 최종 순위를 매깁니다. 간단한 방법은 FTS 결과와 벡터 결과를 합집합한 뒤 순서대로 나열하는 것이고, 고급 방법은 RRF(Reciprocal Rank Fusion)과 같은 기법입니다.

          예를 들어 RRF를 사용하면 각 문서의 BM25 순위와 벡터 순위를 (1/(k+rank)) 식으로 변환하여 합산합니다. SQL로 표현하면

          WITH
            vec_matches AS (
              SELECT article_id,
                     row_number() OVER (ORDER BY distance) AS vec_rank
              FROM vec_articles
              WHERE headline_embedding MATCH lembed(:query)
              AND k = :k
            ),
            fts_matches AS (
              SELECT rowid,
                     row_number() OVER (ORDER BY rank) AS fts_rank
              FROM fts_articles
              WHERE fts_articles MATCH :query
              LIMIT :k
            ),
            combined AS (
              SELECT
                coalesce(1.0/(:rrf_k + fts_rank), 0.0) * :w1 +
                coalesce(1.0/(:rrf_k + vec_rank), 0.0) * :w2
                AS rrf_score,
                COALESCE(fts_matches.rowid, vec_matches.article_id) AS id
              FROM vec_matches FULL OUTER JOIN fts_matches
                ON vec_matches.article_id = fts_matches.rowid
            )
          SELECT id, rrf_score FROM combined
          ORDER BY rrf_score DESC;

          이처럼 RRF를 이용하면 벡터 순위와 키워드 순위를 함께 고려한 복합 점수로 정렬이 가능합니다. 그 외에도, 교차 인코더(Cross-Encoder) 재순위를 통해 상위 문서에 대한 의미적 유사도를 세밀하게 다시 측정할 수도 있습니다.

            바로 써먹는 팁 3가지

            • 후보 개수와 k값 조정: 벡터 검색 시 k 값을 20–40 정도로 설정하고, 검색할 후보 문서 수를 200–400 정도로 유지하세요. 너무 적으면 중요한 문서를 놓칠 수 있고, 너무 많으면 처리 비용과 노이즈가 증가합니다.
            • 적절한 청크 길이와 임베딩 모델 선택: 문서 길이를 적당히 나눠(예: 약 250토큰) 임베딩하면 검색 정확도를 높일 수 있습니다. 사용 중인 임베딩 모델의 권장 문장 길이에 맞춰 분할하세요.
            • 재순위화로 정밀도 강화: RRF와 같은 융합(Fusion) 기법은 검색 결과의 재현율과 정밀도 균형을 잡아줍니다. 더 나아가 문장 수준의 의미를 잘 판단하는 BERT 계열 교차 인코더를 이용해 top-k 후보를 재정렬하면, 불필요한 노이즈를 줄여 검색 품질을 더욱 높일 수 있습니다.

            온라인 반응/후기 요약

            SQLite-vec와 로컬 LLM을 활용한 하이브리드 RAG에 대한 관심이 높습니다. 개발자 커뮤니티에서는 “데이터가 컴퓨터를 벗어나지 않는다”는 SQLite-vec의 장점과 Ollama 조합이 특히 주목받았습니다.

            예를 들어 SQLite-vec 창시자는 “SQLite를 사용하면 단 한 바이트도 컴퓨터를 벗어나지 않는다… 전체 스택이 100% 로컬이자 무료”라고 강조했습니다. 또한 어떤 개발자는 SQLite-vec과 Ollama, Apache 2.0 라이선스의 Granite 모델을 결합하여 “강력한 로컬 RAG 파이프라인”을 구현했다고 전하며 호평했습니다. GitHub와 Reddit에서도 SQLite-vec가 “다른 벡터 DB보다 설치가 간편하다”는 평을 얻고 있어, 로컬 하이브리드 RAG 기술이 빠르게 확산 중입니다.

            리스크 & 주의사항 (TOS/보안/비용)

            • 데이터/보안: 모든 처리를 로컬에서 수행하므로 데이터가 외부로 유출될 위험은 거의 없습니다. 기업 문서나 개인 데이터를 외부 API에 보내지 않기 때문에 기밀성을 유지할 수 있습니다.
            • 성능: SQLite-vec은 현재 브루트포스 방식만 지원하므로 수십만 개 이하 수준에서는 빠르게 동작하지만, 벡터 수가 너무 많아지면 성능이 저하될 수 있습니다. 필요 시 FP16/FP8 양자화 기능이나 데이터 분할(partition)을 활용해야 합니다.
            • 라이선스/비용: Ollama에서 사용하는 Granite 모델은 Apache 2.0 라이선스 등으로 비교적 사용 제약이 적습니다. 자체 GPU 없이 CPU만으로도 동작하지만, 답변 생성 과정에서 GPU를 사용할 경우 전기 요금이나 하드웨어 비용이 발생할 수 있습니다.
            • 정밀도 이슈: 하이브리드 검색은 여러 단계를 거치므로 잘못 설정하면 오히려 노이즈가 증가할 수 있습니다. 항상 검색 파라미터(krrf_k, 가중치 등)를 실험하여 튜닝하세요.

            よくある質問

            • Q: 하이브리드 검색이란 무엇인가요?
              A: 하이브리드 검색은 BM25 기반의 키워드 검색과 임베딩 기반의 시맨틱 검색을 결합한 방식입니다. 두 방법의 결과를 함께 사용해 문서의 연관성을 평가합니다.
            • Q: BM25 검색과 벡터 검색의 차이는 무엇인가요?
              A: BM25는 질의어 단어의 빈도와 역문서빈도를 이용해 정확한 키워드 매칭에 강점을 보입니다. 반면 벡터 검색은 문장이나 문서 전체를 임베딩하여 의미적 유사도를 계산하므로, 동의어나 비슷한 표현도 잘 찾아냅니다.
            • Q: RRF(Reciprocal Rank Fusion)란 무엇인가요?
              A: RRF는 여러 검색 결과 리스트를 통합하는 기법입니다. BM25와 벡터 검색 각각의 순위 순서(row number)만을 이용해 (1/(k+순위)) 공식을 계산하고, 두 점수를 합산하여 최종 순위를 만듭니다. 이렇게 하면 두 검색 방식 모두에서 높은 순위에 오른 문서가 우선시됩니다.
            • Q: SQLite에서 벡터 검색도 가능한가요?
              A: 네, SQLite-vec 확장을 사용하면 가능합니다. 위 예시처럼 CREATE VIRTUAL TABLE vec_articles USING vec0(...) 명령으로 벡터 컬럼을 만들고 임베딩을 저장하면, MATCH lembed(...) 구문으로 가까운 벡터를 검색할 수 있습니다. Postgres의 pgvector와 유사한 기능을 SQLite에서 구현할 수 있는 셈입니다.
            • Q: 하이브리드 검색을 사용하면 정말 성능이 좋아지나요?
              A: 일반적으로 검색 재현율(Recall)이 향상됩니다. 여러 연구에서 하이브리드 검색을 사용할 때 순수 키워드나 벡터만 쓰는 경우보다 약 10~20% 더 많은 관련 문서를 찾아냈다고 보고했습니다. 추가로 RRF 같은 기법을 쓰면 정밀도도 보완할 수 있습니다. 단, 모든 방법을 동시에 실행하므로 응답 속도는 느려질 수 있으므로 k 값 등은 적절히 조정해야 합니다.
            • Q: 하이브리드 검색의 단점은?
              A: 두 가지 검색을 모두 수행하므로 복잡도가 늘어나고, 전체적으로 지연(latency)이 증가할 수 있습니다. 또한 RRF나 교차 인코더 재순위와 같은 추가 단계가 필요해 설정이 까다로울 수 있습니다. 따라서, 꼭 필요한 경우에만 적용하고 파라미터를 튜닝해야 합니다.
            • Q: Ollama 없이도 되나요?
              A: Ollama는 벡터 임베딩과 LLM 생성을 위한 로컬 툴킷일 뿐입니다. OpenAI 등 외부 API로도 임베딩과 LLM 호출이 가능하지만, 비용이 들고 개인정보 이슈가 생길 수 있습니다. 로컬로 처리하려면 Ollama나 GPT4All 같은 로컬 LLM을 사용하는 것이 안전합니다. SQLite-vec는 Ollama 없이도 문서 임베딩만 로컬에 저장할 수 있다는 점이 강점입니다.

            仕上げ

            이번 글에서는 macOS 환경의 SQLite FTS5와 Ollama를 활용해 하이브리드 RAG를 구현하는 방법을 자세히 살펴봤습니다. BM25 키워드 검색과 벡터 임베딩 검색을 결합하여 자료 검색의 재현율과 정밀도를 함께 높일 수 있음을 확인했습니다. 다음 편에서는 이 검색 결과를 LLM에 입력하여 실제로 답변을 생성하는 과정과, 추가로 LLM 기반 재순위(Re-rank) あるいは 답변 요약(Summarization) 등을 통해 응답 품질을 한층 더 끌어올리는 방법을 다룰 예정입니다. 많은 기대 바랍니다!

            類似の投稿