RAG · 하이브리드 검색 (RAG + Hybrid Search)

상황

사내 지식(위키·문서)을 검색하는 기능을 만들었는데, 순수 벡터 검색만 넣으니 실제 씬에서 답답한 순간이 옵니다. "문서에 그 단어가 그대로 있는데 알고리즘 점수가 이상해" 같은 경우요. 벡터 검색은 의미가 비슷한 걸 잘 찾지만, 정확한 키워드·약어·코드명은 놓치는 경우가 있어요. 반대로 키워드만 쓰면 말이 달라져 있으면(동의어·의역) 전혀 못 찾고요.

우리의 위키 DB는 이 문제를 두 검색을 섞는 하이브리드로 풀었습니다. 키워드 패턴 매칭(PostgreSQL pg_trgm)과 벡터 유사도(pgvector cosine)를 하나의 쿼리에서 함께 보는 방식이에요.

질문

키워드만으론 말이 바뀌면 못 찾고, 벡터만으론 정확한 단어를 놓치는데, 둘 다 살리려면 어떻게 해야 할까?

설명

정답은 두 점수를 오케스트레이션해서 하나의 결과로 합치는 것입니다. 정확한 용어(고유명사, 코드, 제품명)가 꽂히는 경우는 키워드가 강하고, 자연어로 설명을 검색하는 경우는 벡터가 강해요. 그래서 둘 다 계산한 뒤 가중합으로 순위를 합칩니다.

핵심 요소를 짚어보면:

  • 임베딩: 문서 본문을 벡터로 만들어 두는 것. 검색 시 질문도 같은 임베딩으로 만들어 유사도를 잽니다. 우리는 외부 임베딩 API(LiteLLM 프록시)로 만들어 embedding 컬럼에 저장해요.
  • 키워드 유사도: pg_trgm은 문자열이 얼마나 '비슷한 글자 조각'을 갖는지 봐서 오타·부분 일치에도 강합니다. similarity() 함수로 0~1 점수를 줍니다.
  • 벡터 유사도: embedding <=> ? 연산자로 코사인 거리를 냅니다.
  • 가중 합치기: 두 정규화된 점수에 가중치를 줘 합쳐 ORDER BY 합니다. 검색 의도가 "정확 용어" 쪽이면 키워드 가중치를, "개념 설명" 쪽이면 벡터를 높여요.

이렇게 하면 "정확히 그 단어가 있는 글"과 "그걸 설명하는 다른 글"이 한 결과 안에서 적절히 섞여 나옵니다. 정확 용어 검색 품질이 떨어져도 벡터가 보완하고, 그 반대도 마찬가지예요.

예시

한 개의 SQL에서 키워드 점수와 벡터 점수를 함께 내는 골격입니다.

SELECT id, title, path,
       similarity(title, :q)                        AS kw_score,
       1 - (embedding <=> :q_embedding)            AS vec_score,
       (0.5 * similarity(title, :q)
      + 0.5 * (1 - (embedding <=> :q_embedding)))   AS score
FROM documents
WHERE lang = 'ko'
  AND (similarity(title, :q) > 0.2
       OR embedding <=> :q_embedding < 0.8)
ORDER BY score DESC
LIMIT 20;
  • :q 는 사용자 검색어, :q_embedding 은 그 검색어의 임베딩 벡터예요.
  • embedding <=> :q_embedding 은 코사인 거리(0이면 같음)라서, 유사도로 바꾸려면 1 - 로 뒤집습니다.
  • WHERE 절에서 두 후보군을 얻고 나서 ORDER BY 로 섞습니다.

놓치기 쉬운 포인트 두 가지예요.

  1. 검색어도 같은 임베딩으로 만들어야 합니다. 문서 임베딩 모델과 똑같은 모델·같은 차원이어야 유사도가 의미가 있어요.
  2. 가중치는 상황에 따라 조절합니다. 우리는 기본 0.5/0.5에서, 정확한 용어를 우선해야 하는 곳은 키워드 쪽을 높게 잡아요.

확인

  • 문서 임베딩이 채워져 있는지 (embedding NULL이 아닌지)
  • 검색어도 같은 임베딩 모델로 변환되는지
  • 정확한 용어 검색에서 키워드 매치가 상위에 오는지
  • 자연어 설명 검색에서 벡터 매치가 상위에 오는지
  • 두 검색이 분리된 결과가 아니라 한 목록에 섞여 나오는지

임베딩 생성은 우리 파이프라인에서 별도 스크립트(embed.py)로 수행돼요. 새 문서를 시드한 뒤엔 이 단계를 돌려야 검색에 잡힙니다.