벡터 검색과 시맨틱 검색
전통적인 키워드 검색은 정확한 단어 매칭에 의존하지만, 시맨틱 검색은 텍스트의 의미를 벡터로 변환하여 유사도를 기반으로 검색합니다. 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:space | l2 | cosine | 텍스트 검색에는 코사인 유사도 권장 |
| hnsw:M | 16 | 32-64 | 검색 정확도 향상 (메모리 증가) |
| hnsw:construction_ef | 100 | 200 | 인덱스 품질 향상 |
| hnsw:search_ef | 10 | 50-100 | 검색 시 탐색 범위 |
- 청크 크기는 300-500자가 한국어에 적합하며, 오버랩은 10-20% 정도를 권장합니다
- 대용량 데이터에서는 ChromaDB 서버 모드를 사용하여 클라이언트-서버 구조로 분리하세요
- 임베딩 모델 선택이 검색 품질에 가장 큰 영향을 미치므로 도메인에 맞는 모델을 벤치마크하세요
댓글 0