🧭 13. 벡터 검색 (Vector Search) 실습
🎯 학습 목표
이 실습에서는 LangChain + Pinecone + OpenAI 임베딩 모델을 이용해
한국어 영화 데이터셋에서 벡터 기반 검색을 구현합니다.
| 학습 포인트 | 설명 |
|---|---|
| 🔹 텍스트를 벡터 임베딩으로 변환 | OpenAI text-embedding-3-small 사용 |
| 🔹 벡터 DB 구축 | Pinecone 인덱스 생성 및 데이터 업서트 |
| 🔹 검색 수행 | KNN, 메타데이터 필터, 하이브리드 검색 실습 |
🧱 1. 임베딩(Embedding) 생성
텍스트를 고차원 숫자 벡터로 표현하여 의미적 유사도를 비교할 수 있게 만듭니다.
# 예시 한글 영화 데이터셋 정의
movies = [
{
"id": "movie1",
"title": "7번방의 선물",
"year": 2013,
"genre": "드라마",
"description": "억울한 누명을 쓰고 교도소에 수감된 아빠와 그의 어린 딸의 감동적인 스토리"
},
{
"id": "movie2",
"title": "미나리",
"year": 2020,
"genre": "드라마",
"description": "한국계 미국인 가족의 따뜻하고 감성적인 성장 이야기"
},
{
"id": "movie3",
"title": "기생충",
"year": 2019,
"genre": "드라마",
"description": "가난한 가족과 부자 가족 사이의 빈부격차를 그린 사회 풍자 드라마"
},
{
"id": "movie4",
"title": "범죄도시",
"year": 2017,
"genre": "범죄",
"description": "형사가 범죄 조직을 소탕하는 범죄 액션 영화"
},
{
"id": "movie5",
"title": "범죄도시 2",
"year": 2022,
"genre": "범죄",
"description": "형사와 범죄 조직의 대결을 그린 범죄 액션 영화의 속편"
},
{
"id": "movie6",
"title": "헤어질 결심",
"year": 2022,
"genre": "범죄",
"description": "산에서 발생한 의문의 죽음(살인 사건)을 수사하던 형사가 피의자에게 이끌리며 벌어지는 미스터리 멜로 영화"
},
{
"id": "movie7",
"title": "다만 악에서 구하소서",
"year": 2020,
"genre": "범죄",
"description": "청부 살인업자와 범죄 조직의 마지막 거래를 그린 범죄 액션 영화"
}
]
from dotenv import load_dotenv
from langchain_openai import OpenAIEmbeddings
# 환경변수 로드
load_dotenv()
embeddings = OpenAIEmbeddings(model="text-embedding-3-small")
movie_vectors = embeddings.embed_documents([m["description"] for m in movies])
print(len(movie_vectors[0])) # 1536차원 출력
💡 text-embedding-3-small 모델은 1536차원 벡터를 생성합니다.
🗃️ 2. Pinecone 인덱스 구성 및 업서트
import os
from pinecone import Pinecone,ServerlessSpec
pc = Pinecone(api_key=os.getenv("PINECONE_API_KEY"))
index_name = "movie-vector-index"
if pc.has_index(index_name):
pc.delete_index(index_name)
pc.create_index(
name=index_name,
dimension=1536,
metric="cosine",
spec=ServerlessSpec(
cloud="aws",
region="us-east-1"
)
)
index = pc.Index(index_name)
이후 영화 데이터를 임베딩과 함께 업서트합니다.
vector_data = [
(m["id"], v, {"title": m["title"], "genre": m["genre"], "year": m["year"], "description": m["description"]})
for m, v in zip(movies, movie_vectors)
]
index.upsert(vectors=vector_data)
🔍 3. 벡터 검색
쿼리와 의미적으로 유사한 항목을 찾습니다.
# 검색 쿼리 예시
query = "감성적인 드라마 영화 추천해줘"
# 쿼리 문장을 임베딩 벡터로 변환
query_vector = embeddings.embed_query(query)
# Pinecone에서 벡터 유사도 검색 수행 (코사인 유사도 기반)
# 상위 3개의 가장 가까운 벡터를 찾고, 메타데이터를 포함하여 반환
result = index.query(vector=query_vector, top_k=3, include_metadata=True)
# 결과 출력: 각 결과의 제목, 연도, 장르를 표시
for match in result["matches"]:
print(match["metadata"]["title"], match["metadata"]["year"],match["metadata"]["genre"])
📊 예시 결과
기생충 2019.0 드라마
7번방의 선물 2013.0 드라마
범죄도시 2017.0 범죄
➡️ 의미적으로 “감성적인 드라마”에 가까운 영화들이 추천됨.
🎯 4. 메타데이터 필터 검색
특정 조건(예: 연도, 장르 등)을 기반으로 검색 범위를 제한할 수 있습니다.
# 메타데이터 필터를 활용한 검색: 2020년 이후 개봉한 영화들 중 상위 3개 반환
filter_condition = {"year": {"$gte": 2020}}
query_vector = embeddings.embed_query("영화")
result = index.query(vector=query_vector, top_k=3, filter=filter_condition, include_metadata=True)
# 결과 출력: 각 결과의 제목, 연도, 장르를 표시
for match in result["matches"]:
print(match["metadata"]["title"], match["metadata"]["year"],match["metadata"]["genre"])
📊 예시 결과
범죄도시 2 2022.0 범죄
헤어질 결심 2022.0 범죄
다만 악에서 구하소서 2020.0 범죄
➡️ 필터 조건 year >= 2020을 만족하는 결과만 반환.
⚡ 5. 하이브리드 검색 (쿼리 + 필터)
자연어 쿼리와 구조화된 필터를 동시에 적용합니다.
hybrid_query = "2020년 이후의 범죄 영화 보여줘"
hybrid_vector = embeddings.embed_query(hybrid_query)
hybrid_filter = {"genre": {"$eq": "범죄"}, "year": {"$gte": 2020}}
result = index.query(vector=hybrid_vector, top_k=3, filter=hybrid_filter, include_metadata=True)
# 결과 출력: 각 결과의 제목, 연도, 장르를 표시
for match in result["matches"]:
print(match["metadata"]["title"], match["metadata"]["year"],match["metadata"]["genre"])
📊 예시 결과
다만 악에서 구하소서 2020.0 범죄
범죄도시 2 2022.0 범죄
헤어질 결심 2022.0 범죄
➡️ “쿼리 의미 + 필터 조건”을 모두 충족하는 검색 결과를 제공.
🧩 6. 실습 요약
| 검색 방식 | 설명 | 예시 |
|---|---|---|
| KNN 검색 | 의미적으로 가장 유사한 항목 검색 | “감성적인 드라마 영화” |
| 메타데이터 필터 | 특정 조건으로 제한 | year >= 2020 |
| 하이브리드 검색 | 쿼리 + 필터 결합 | “2020년 이후의 범죄 영화” |
💡 핵심 포인트
- Pinecone은 필터 조건을 만족하는 벡터 subset만 대상으로 유사도 계산을 수행.
- 실제 응용에서는 RAG, 추천 시스템, AI 검색 서비스 등에 필수로 사용됩니다.