저는 실무에서 사내 문서 검색 시스템을 만들면서 의미 기반 검색만으로는 한계가 있다는 걸 절실히 깨달았습니다. "환불 규정"이라는 검색어로 "반품 정책" 문서를 찾지 못하는 식의 문제를 직접 겪었기 때문입니다. 이번 글에서는 제가 실제로 사용한 방식인 희소 검색(키워드 매칭)과 밀집 검색(임베딩)을 결합한 하이브리드 검색을 어떻게 구현했는지 단계별로 알려드리겠습니다. 모든 코드는 복사해서 바로 실행할 수 있도록 작성했습니다.

이 튜토리얼의 핵심은 HolySheep AI의 임베딩 API를 활용해 단 한 줄의 base_url 변경만으로 OpenAI 호환 모델에 접근하는 것입니다. 해외 신용카드 없이도 한국 로컬 결제 방식으로 가입할 수 있어 진입장벽이 매우 낮습니다.

하이브리드 검색이란 무엇인가?

검색에는 크게 두 가지 방식이 있습니다. 첫 번째 희소 검색(Sparse Retrieval)은 BM25처럼 단어 빈도를 기반으로 동작하며, 사용자가 입력한 정확한 키워드가 들어 있는 문서를 잘 찾아냅니다. 두 번째 밀집 검색(Dense Retrieval)은 텍스트를 고차원 벡터로 변환해 의미적 유사도로 문서를 찾는 방식입니다. 두 방식은 서로 다른 강점을 가지므로 결합하면 "키워드도 맞고 의미도 맞는" 최상의 결과를 얻을 수 있습니다.

사전 준비물

1단계: Python 가상환경 및 패키지 설치

먼저 프로젝트 폴더를 만들고 가상환경을 활성화합니다. 터미널에서 다음 명령을 순서대로 입력하세요.

mkdir hybrid-search-demo
cd hybrid-search-demo
python -m venv venv

macOS / Linux

source venv/bin/activate

Windows PowerShell

venv\Scripts\Activate.ps1 pip install requests numpy rank_bm25 python-dotenv

설치가 끝나면 위 네 가지 패키지가 준비됩니다. requests는 HTTP 통신, numpy는 벡터 계산, rank_bm25는 희소 검색 알고리즘을, python-dotenv는 환경변수 로딩을 담당합니다.

2단계: HolySheep에서 API 키 발급받기

HolySheep AI 웹사이트에 접속해 우측 상단의 가입 버튼을 누르면 이메일과 로컬 결제 수단(카카오페이, 토스페이 등)으로 빠르게 가입할 수 있습니다. 로그인 후 대시보드의 "API Keys" 메뉴에서 "Create new key" 버튼을 클릭하면 sk-hs-로 시작하는 키가 발급됩니다. 이 키를 안전한 곳에 복사해두세요.

3단계: 샘플 문서 준비

검색 대상이 될 샘플 문서들을 만듭니다. 작은 사내 FAQ 목록이라고 가정해 보겠습니다.

import json

예시 문서 8개 — 실제로는 회사 내부 문서를 불러오면 됩니다

documents = [ {"id": 1, "title": "환불 규정", "content": "구매 후 7일 이내 미사용 상품에 한해 전액 환불이 가능합니다."}, {"id": 2, "title": "반품 정책", "content": "제품 불량 시 배송비를 판매자가 부담하며 즉시 교환해드립니다."}, {"id": 3, "title": "배송 안내", "content": "오후 3시 이전 주문은 당일 발송되며 평균 2~3일 내 도착합니다."}, {"id": 4, "title": "회원 가입", "content": "이메일과 비밀번호로 가입 가능하며 소셜 로그인도 지원합니다."}, {"id": 5, "title": "결제 방법", "content": "신용카드, 체크카드, 카카오페이, 토스, 네이버페이 모두 지원합니다."}, {"id": 6, "title": "고객 센터", "content": "평일 오전 9시부터 오후 6시까지 전화 및 채팅 상담이 가능합니다."}, {"id": 7, "title": "개인정보 처리방침", "content": "수집한 정보는 서비스 운영 외 목적으로 사용되지 않습니다."}, {"id": 8, "title": "쿠폰 사용법", "content": "마이페이지에서 쿠폰을 등록하면 다음 결제 시 자동 적용됩니다."}, ] corpus_texts = [d["title"] + " " + d["content"] for d in documents] print(f"준비된 문서 수: {len(corpus_texts)}")

4단계: BM25 희소 검색 구현

rank_bm25 라이브러리는 BM25 알고리즘을 매우 간단한 인터페이스로 제공합니다.

from rank_bm25 import BM25Okapi
import re

def simple_tokenize(text):
    # 한국어는 추후 Kiwi로 교체할 예정 — 여기서는 공백 기준 단순 분리
    text = re.sub(r"[^가-힣a-zA-Z0-9 ]", " ", text)
    return text.lower().split()

tokenized_corpus = [simple_tokenize(t) for t in corpus_texts]
bm25 = BM25Okapi(tokenized_corpus)

def sparse_search(query, top_k=3):
    tokenized_query = simple_tokenize(query)
    scores = bm25.get_scores(tokenized_query)
    ranked = sorted(enumerate(scores), key=lambda x: x[1], reverse=True)[:top_k]
    return [(documents[i]["id"], documents[i]["title"], float(s)) for i, s in ranked]

테스트

query = "환불 받고 싶어요" print("BM25 결과:") for doc_id, title, score in sparse_search(query): print(f" [{doc_id}] {title} — 점수 {score:.3f}")

5단계: HolySheep API로 밀집 임베딩 만들기

이제 의미 기반 검색을 위한 임베딩 벡터를 생성합니다. HolySheep은 OpenAI 호환 엔드포인트를 제공하므로 기존 클라이언트 코드를 그대로 가져다 base_url만 바꾸면 됩니다.

import os
import requests
import numpy as np
from dotenv import load_dotenv

load_dotenv()  # .env 파일에서 환경변수 로드
HOLYSHEEP_API_KEY = os.environ.get("HOLYSHEEP_API_KEY",