컴퓨터/LLM

LLM (3) – Chroma DB를 이용한 벡터 기반 문서 검색

달서비 2026. 3. 29. 23:13

요즘 LLM을 이용한 정보검색인 RAG(Retrieval-Augmented Generation, 검색 증강 생성)라는 방식에 관심이 커지고 있습니다. 다량의 텍스트 데이터 LLM에 질문해서 답을 구할 때 단순히 모든 문서를 통째로 붙여주는 방식은 속도, 정확도에서 여러 가지 이슈가 발생합니다. 이에 대한 가장 간단한 방법으로, 벡터로 임베딩한뒤 벡터 DB에 넣어 빠른 속도로 많은 데이터를 조회할 수 있습니다. 이번에는 그중에서도 가볍고 많이 사용하는 Chroma DB를 이용하여 문서 검색 시스템을 만들어보겠습니다.

 

3줄 요약

  1. 사용자의 질문을 컴퓨터가 이해하는 벡터 타입으로 변환한다
  2. Chroma DB에서 가장 유사한 문장을 찾는다
  3. 해당 문장을 LLM에 전달 후 근거를 바탕으로 답변을 생성한다.

 

벡터 DB란 무엇인가?

컴퓨터 - pixabay

일반적인 DB는 구조화된 숫자나 문자열을 저장하고, WHERE 절 등으로 필터링합니다. 반면 벡터 DB는 자연어처럼 모호하고 유사성이 중요한 데이터를 벡터 타입으로 바꾸어 저장한 뒤 유사도 기반으로 검색합니다.

"신입사원의 연차는 며칠인가요?" 라는 질문이 있다고 일반 DB와 벡터 DB는 다음과 같이 검색합니다.

  • 일반 DB : '신입사원 연차' 라는 단어가 포함되어 있어야 함
  • 벡터 DB : "신입사원의 연차는 며칠인가요?"라고 물으면 "신입사원 연차 규정" 문서를 찾아냄

자연어와 같이 모호하고 복잡한 데이터를 좌푯값으로 바꾸어 저장하고, 거리가 유사한 데이터를 빠르게 조회하는 것이 벡터 DB의 역할입니다.

 

임베딩(Embedding)은 무엇이고 또 왜 필요한가

임베딩 과정 - https://qdrant.tech/articles/what-are-embeddings/

임베딩은 자연어 등 복잡한 데이터를 벡터 타입으로 변환하는 과정을 말합니다. LLM은 모든 문장을 내부적으로 벡터 타입으로 변환하여 의미를 비교합니다. 해당 과정을 사전에 변환하고, 검색 요청이 들어오면 벡터 간 유사도를 계산하여 가장 가까운 문장을 찾는 방식이 임베딩입니다.

 

해당 과정을 이용하여 다음과 같은 효과를 가질 수 있습니다.

  • 데이터 차원 축소 - 고차원의 데이터를 저차원으로 압축하여 핵심 특징만 뽑아 효율적으로 압축합니다.
  • 데이터의 의미 보존 - 데이터 내부에 있는 구조와 의미를 보존하여 유사 개념을 쉽게 찾을 수 있습니다.
  • 노이즈 제거 및 일반화 - 저차원으로 압축하여 중요한 특징만 추출하여 모델의 일반화 성능을 향상시니다.

 

사과와 포도라는 단어가 글자 자체로 공통점이 없지만 벡터 공간으로 뿌려보면 과일이라는 좌표 근처로 모이게 됩니다. 임베딩은 비슷한 의미를 좌표로 두 개 만드는 과정입니다.

 

데이터베이스를 생성 및 데이터를 넣어보자

1. 클라이언트 및 컬렉션 생성

import chromadb
from chromadb.utils import embedding_functions

client = chromadb.Client()
collection = client.create_collection(name="company_rules")

# 2. 한국어 임베딩 모델 설정 (SentenceTransformer 활용)
embedding_func = embedding_functions.SentenceTransformerEmbeddingFunction(
    model_name="jhgan/ko-sroberta-multitask"
)

# 3. 컬렉션 생성 또는 로드
collection = client.get_or_create_collection(
    name="company_rules",
    embedding_function=embedding_func
)

여기서 컬렉션은 DB 기준으로 테이블과 같은 개념으로 보면 됩니다.

 

2. 데이터를 추가한다

collection.upsert(
    documents=["신입사원의 연차는 입사 1년 미만 시 매월 1일씩 발생합니다.", 
               "경조사 휴가는 본인 결혼 시 5일이 부여됩니다."],
    metadatas=[{"source": "hr_manual"}, {"source": "hr_manual"}],
    ids=["id1", "id2"]
)

사내 규정 데이터를 ID, 메타데이터를 함께 저장합니다.

upsert를 사용한 이유는 생성과 수정을 같이 하기 위함입니다.

 

3. 질문 및 유사도 기반 검색

results = collection.query(
    query_texts=["새로 입사했는데 휴가는 어떻게 쓰나요?"],
    n_results=1
)

print(results["documents"]) 
#['신입사원의 연차는 입사 1년 미만 시 매월 1일씩 발생합니다.']

 

3-1. 임계치를 넣은 질문 및 유사도 기반 검색

query_text = "새로 입사했는데 휴가는 어떻게 쓰나요?"
n_results = 3

results = collection.query(
    query_texts=[query_text],
    n_results=n_results
)

threshold = 0.3

# 결과값에서 거리(distances)와 documents를 가져와 필터링 수행
filtered_docs = []
for doc, dist in zip(results["documents"][0], results["distances"][0]):
    if dist <= threshold:
        filtered_docs.append(doc)
        print(f"[일치] 거리: {dist:.4f} / 내용: {doc}")
    else:
        print(f"[제외] 거리: {dist:.4f} (임계치 {threshold} 초과)")

# 최종 필터링된 결과
print(f"\n최종 검색 결과: {filtered_docs}")

여기서 임계치(threshold)는 0에 가까울수록 좋습니다. 이는 거리 기반 떄문 입니다. 저는 임의로 0.3이라는 데이터를 넣었고 아무 상관 없는 문서가 가장 벡터 유사도가 같다는 개념을 제거하는 용도라고 보면 좋습니다.

 

마지막으로

요즘 우리가 일반적으로 쓰는 Oracle, MySQL 등 데이터베이스에서 벡터 DB 기능을 제공합니다. 기존 SQL DB를 활용하면 데이터 관리의 연속성을 가져갈 수 있으며 Chroma DB 같은 DB를 사용하면 대규모 벡터 연산에 장점을 가집니다. 이에 따라 서비스의 규모와 목적에 맞는 선택이 필요합니다.

 

Reference

https://uracle.blog/2025/03/14/embedding/

 

임베딩(Embedding): AI가 데이터를 기억하는 방법 - 유라클 블로그

  임베딩은 고차원의 복잡한 데이터를 저차원의 연속적인 벡터 공간으로 변환하는 과정을 의미합니다. 예를 들어, 100×100 크기의 RGB 이미지는 각 픽셀이 빨강, 초록, 파랑 세 가지 색상 정보를

uracle.blog

 

https://qdrant.tech/articles/what-are-embeddings/

 

What are Vector Embeddings? - Revolutionize Your Search Experience - Qdrant

Discover the power of vector embeddings. Learn how to harness the potential of numerical machine learning representations to create a personalized Neural Search Service with FastEmbed.

qdrant.tech