본문 바로가기
AI2025년 2월 20일5분 읽기

ChromaDB 벡터 검색 — Python으로 시맨틱 검색 구축

YS
김영삼
조회 114

벡터 검색과 시맨틱 검색

전통적인 키워드 검색은 정확한 단어 매칭에 의존하지만, 시맨틱 검색은 텍스트의 의미를 벡터로 변환하여 유사도를 기반으로 검색합니다. ChromaDB는 오픈소스 벡터 데이터베이스로 임베딩 저장, 인덱싱, 쿼리를 간단한 API로 제공합니다.

ChromaDB 설치와 기본 설정

pip install chromadb sentence-transformers

import chromadb
from chromadb.utils import embedding_functions

# 영속적 저장소로 클라이언트 생성
client = chromadb.PersistentClient(path="./chroma_db")

# 임베딩 함수 설정 (한국어 지원 모델)
embedding_fn = embedding_functions.SentenceTransformerEmbeddingFunction(
    model_name="jhgan/ko-sroberta-multitask"
)

# 컬렉션 생성
collection = client.get_or_create_collection(
    name="tech_docs",
    embedding_function=embedding_fn,
    metadata={"hnsw:space": "cosine"}
)

문서 인덱싱

문서를 적절한 크기의 청크로 분할한 뒤 ChromaDB에 저장합니다. 메타데이터를 함께 저장하면 필터링 검색이 가능합니다.

def chunk_text(text, chunk_size=500, overlap=50):
    """텍스트를 오버랩이 있는 청크로 분할"""
    chunks = []
    start = 0
    while start < len(text):
        end = start + chunk_size
        chunk = text[start:end]
        chunks.append(chunk)
        start = end - overlap
    return chunks

# 문서 추가
documents = [
    {"id": "doc1", "text": "FastAPI는 Python 기반 고성능 웹 프레임워크입니다...", "category": "backend"},
    {"id": "doc2", "text": "React 18의 Concurrent Mode는 렌더링을 중단 가능하게...", "category": "frontend"},
    {"id": "doc3", "text": "PostgreSQL의 JSONB 타입은 바이너리 형태로 저장되어...", "category": "database"},
]

for doc in documents:
    chunks = chunk_text(doc["text"])
    collection.add(
        ids=[f"{doc['id']}_chunk_{i}" for i in range(len(chunks))],
        documents=chunks,
        metadatas=[{"source": doc["id"], "category": doc["category"]}] * len(chunks)
    )

시맨틱 검색 쿼리

# 기본 시맨틱 검색
results = collection.query(
    query_texts=["파이썬 웹 개발 프레임워크 추천"],
    n_results=5
)

print(results["documents"])     # 유사 문서 텍스트
print(results["distances"])     # 코사인 거리
print(results["metadatas"])     # 메타데이터

# 메타데이터 필터와 함께 검색
results = collection.query(
    query_texts=["데이터베이스 성능 최적화"],
    n_results=3,
    where={"category": "database"},
    where_document={"$contains": "인덱스"}
)

RAG 파이프라인 통합

from openai import OpenAI

openai_client = OpenAI()

def rag_query(question: str) -> str:
    # 1. 벡터 검색으로 관련 문서 검색
    results = collection.query(
        query_texts=[question],
        n_results=3
    )
    context = "\n\n".join(results["documents"][0])

    # 2. LLM에 컨텍스트와 함께 질문
    response = openai_client.chat.completions.create(
        model="gpt-4o",
        messages=[
            {"role": "system", "content": f"다음 문서를 참고하여 질문에 답하세요:\n\n{context}"},
            {"role": "user", "content": question}
        ],
        temperature=0.3
    )
    return response.choices[0].message.content

answer = rag_query("FastAPI와 Django 중 어떤 것이 빠른가요?")
print(answer)

성능 최적화

설정기본값권장값설명
hnsw:spacel2cosine텍스트 검색에는 코사인 유사도 권장
hnsw:M1632-64검색 정확도 향상 (메모리 증가)
hnsw:construction_ef100200인덱스 품질 향상
hnsw:search_ef1050-100검색 시 탐색 범위
  • 청크 크기는 300-500자가 한국어에 적합하며, 오버랩은 10-20% 정도를 권장합니다
  • 대용량 데이터에서는 ChromaDB 서버 모드를 사용하여 클라이언트-서버 구조로 분리하세요
  • 임베딩 모델 선택이 검색 품질에 가장 큰 영향을 미치므로 도메인에 맞는 모델을 벤치마크하세요

댓글 0

아직 댓글이 없습니다.
Ctrl+Enter로 등록