티스토리 뷰

728x90
반응형

2025.08.16 - [RAG] - [LangChain] 문서 로딩과 전처리 가이드

 

이전 글(문서 로딩·전처리)에서 만든 docs/chunks를 이어서 사용합니다. 이 글에서는 벡터화(Vectorization) 원리, 벡터 데이터베이스 개요, 그리고 FAISS · Pinecone · Weaviate로 인덱싱/검색/리트리버를 구성하는 전 과정을 실습 코드로 정리합니다.

 

0. 설치

# 공통
pip install -U langchain langchain-community langchain-openai langchain-text-splitters

# FAISS (로컬)
pip install faiss-cpu  # Mac/리눅스. (윈도우는 'faiss-cpu' 미지원 → conda 권장)

# Pinecone (매니지드)
pip install -U pinecone-client langchain-pinecone

# Weaviate (클라우드/자가호스팅)
pip install -U weaviate-client langchain-weaviate

임베딩은 예시로 OpenAI를 사용합니다. (Hugging Face 임베딩으로 교체해도 동일한 흐름)

from langchain_openai import OpenAIEmbeddings
EMBED_MODEL = "text-embedding-3-small"  # 1536차원, 비용 대비 효율
emb = OpenAIEmbeddings(model=EMBED_MODEL)  # 환경변수 OPENAI_API_KEY 필요

1. 벡터화(Vectorization) 원리 빠르게 이해하기

목표: 사용자 질문과 문서 청크를 같은 벡터 공간에 임베딩해서, 가까운 것(유사한 것)을 빠르게 찾는 것.

  1. 청킹(Chunking): 긴 문서를 일정 길이(예: 500~1,000자)로 나눔 (이전 글 참고)
  2. 임베딩(Embedding): 각 청크 → 고정 길이 벡터(예: 1536-D)
  3. 인덱싱(Indexing): 벡터 DB에 저장 (ANN: HNSW, IVF-PQ 등 근사 최근접 탐색)
  4. 질의(Query): 사용자의 질문을 임베딩 → DB에서 가까운 벡터 k개 검색
  5. 후처리: 메타데이터 필터, 리랭킹, 중복 제거 등
  6. 생성(Generation): 검색 결과 컨텍스트를 프롬프트에 넣어 LLM으로 답변 생성

거리/유사도 지표

  • cosine(코사인 거리), dot(내적), L2(유클리드). 모델/플랫폼별 기본값이 다릅니다.
  • 같은 데이터·질문이라도 임베딩 모델/거리함수 조합에 따라 검색 결과가 달라질 수 있습니다.

ANN 인덱스

  • FAISS: IVF, HNSW, PQ 등 다양한 인덱스 타입 제공 (로컬)
  • Pinecone: 관리형 ANN 인프라 (서버리스/Pod), 필터/Namespace 등 운영 기능 강력
  • Weaviate: 그래프형 스키마 + HNSW, 필터/하이브리드 벡터라이저(옵션)

2. FAISS — 로컬 개발/파일 배포에 적합

장점: 빠르고 무료. 간단히 파일로 저장/배포.
단점: 서버리스가 아님(스케일링/동시성 한계), 멀티리전/권한관리 없음.

2-1. 인덱싱: 문서 → 벡터 DB

from langchain_community.vectorstores import FAISS

# chunks: 이전 글에서 전처리/청킹한 Document 리스트
faiss_store = FAISS.from_documents(documents=chunks, embedding=emb)

# 로컬 저장(필수 권한 경로)
faiss_store.save_local("indexes/faiss_kb")

2-2. 질의: 리트리버로 사용

# (다시 로드 가능)
faiss_store = FAISS.load_local(
    "indexes/faiss_kb", emb, allow_dangerous_deserialization=True
)

retriever = faiss_store.as_retriever(search_kwargs={"k": 4})
results = retriever.invoke("예약 취소 규정이 어떻게 되나요?")
for i, d in enumerate(results, 1):
    print(i, d.metadata.get("source"), d.page_content[:120])

팁: similarity_search_with_score(query, k=4)로 점수까지 확인 가능. 파일 배포 시 index.faiss + index.pkl 두 파일을 함께 배포하세요.


3. Pinecone — 매니지드 벡터 DB (서버리스/운영 편의)

장점: 자동 확장/고가용성, 메타데이터 필터, 네임스페이스(멀티테넌시), 서버리스 요금제.
단점: 유료, 리전/사양 고려 필요.

3-1. 인덱스 준비

from pinecone import Pinecone, ServerlessSpec

pc = Pinecone(api_key="<PINECONE_API_KEY>")
index_name = "kb-emb3-small"

# OpenAI text-embedding-3-small = 1536차원
if index_name not in [i.name for i in pc.list_indexes()]:
    pc.create_index(
        name=index_name,
        dimension=1536,
        metric="cosine",
        spec=ServerlessSpec(cloud="aws", region="us-east-1"),
    )

# 핸들
pc_index = pc.Index(index_name)

3-2. LangChain VectorStore로 업서트/검색

from langchain_pinecone import PineconeVectorStore

# ids는 재업서트/삭제를 고려해 명시적으로 관리 권장
ids = [d.metadata.get("id", f"doc-{i}") for i, d in enumerate(chunks)]

p_store = PineconeVectorStore(index_name=index_name, embedding=emb)
p_store.add_documents(documents=chunks, ids=ids)

# 메타데이터 필터 예시(서버리스에서는 Mongo식 연산자 사용: $eq, $in, ...)
retriever = p_store.as_retriever(
    search_kwargs={"k": 4, "filter": {"doc_type": {"$eq": "kb"}}}
)

docs = retriever.invoke("현금영수증 발급 기준 알려줘")
for d in docs:
    print(d.metadata.get("doc_type"), d.metadata.get("source"))

팁: 네임스페이스(namespace) 로 고객/언어/버전별 데이터 격리 가능. 대량 적재는 upsert 배치 사이즈를 조절하세요.


4. Weaviate — 스키마 지향 + 하이브리드 검색

장점: 스키마/필터 강력, 오픈소스 + 클라우드, 하이브리드(키워드+벡터) 지원.
단점: 운영/스키마 설계가 처음엔 낯설 수 있음.

4-1. 클라이언트 연결

import weaviate
client = weaviate.Client(
    url="https://<YOUR-WEAVIATE-ENDPOINT>",
    auth_client_secret=weaviate.AuthApiKey(api_key="<WCS_API_KEY>")
)

4-2. LangChain VectorStore로 적재/검색

from langchain_weaviate import WeaviateVectorStore

w_store = WeaviateVectorStore(
    client=client,
    index_name="KB",      # 클래스명
    text_key="text",      # page_content가 저장될 필드
    embedding=emb,
)

w_store.add_documents(chunks)

# 간단 검색
docs = w_store.similarity_search("포인트 환불 규정", k=4)
for d in docs:
    print(d.metadata.get("source"), d.page_content[:100])

팁: Weaviate는 기본 HNSW 인덱스. 외부 임베딩을 쓰면 클래스 스키마의 내장 벡터라이저를 끄고(vectorizer: none) 사용하세요. 하이브리드 검색은 BM25 + 벡터를 혼합해 정확도를 끌어올릴 수 있습니다.


5. Chroma — 빠른 개발·경량 운영(로컬/서버)

장점: 매우 간단한 API, 로컬 퍼시스턴스, Chroma Server(HTTP) 로 멀티 언어/멀티 프로세스 접근 가능.
단점: 대규모/고가용성·멀티리전 요구에는 한계. 동시 쓰기 패턴은 신중히 설계.

5-1. 인덱싱/퍼시스트

# 권장: 별도 패키지
from langchain_chroma import Chroma
# (대안) from langchain_community.vectorstores import Chroma

chroma_store = Chroma(
    collection_name="kb",
    persist_directory="indexes/chroma_kb",  # 로컬 디렉터리로 영속화
    embedding_function=emb,
)

# 적재
chroma_store.add_documents(chunks)
chroma_store.persist()  # 디스크에 저장

5-2. 검색/리트리버

# 재시작 시에도 같은 persist_directory/collection_name으로 로드 가능
chroma_store = Chroma(
    collection_name="kb",
    persist_directory="indexes/chroma_kb",
    embedding_function=emb,
)

retriever = chroma_store.as_retriever(search_kwargs={"k": 4})
results = retriever.invoke("취소/환불 정책 요약해줘")
for r in results:
    print(r.metadata.get("source"), r.page_content[:120])

팁: Chroma Server 모드(HTTP)로 띄우면 여러 서비스/언어에서 공용 접근 가능. 로컬 퍼시스턴스는 백업/락 전략을 함께 고려하세요.


6. Postgres + pgvector — RDB 통합형(권한/트랜잭션 재사용)

장점: SQL/트랜잭션/백업·권한 체계 재활용, 운영팀 친화적.
단점: 전용 벡터 DB 대비 대규모·고QPS에서 성능 한계.

사전 준비: DB에 pgvector 확장 설치

CREATE EXTENSION IF NOT EXISTS vector;

6-1. 인덱싱(적재)

신규 패키지 권장

from langchain_postgres import PGVector

CONN = "postgresql+psycopg2://user:pass@localhost:5432/vecdb"
COLL = "kb"

pg_store = PGVector.from_documents(
    documents=chunks,
    embedding=emb,
    collection_name=COLL,
    connection=CONN,  # 또는 connection_string=CONN
    use_jsonb=True,   # 메타데이터를 JSONB로 저장
)

(레거시 대안)

from langchain_community.vectorstores import PGVector

CONN = "postgresql+psycopg2://user:pass@localhost:5432/vecdb"
COLL = "kb"

pg_store = PGVector.from_documents(
    documents=chunks,
    embedding=emb,
    collection_name=COLL,
    connection_string=CONN,
)

6-2. 검색/리트리버

retriever = pg_store.as_retriever(
    search_kwargs={"k": 4, "filter": {"doc_type": "kb"}}
)
for d in retriever.invoke("포인트 전환 기준은?"):
    print(d.metadata.get("source"), d.page_content[:100])

팁: 최신 pgvector는 HNSW 인덱스를 지원합니다. 대량 적재 후 ANALYZE, 인덱스 파라미터(ef_search 등)를 튜닝해 보세요.


7. Redis Vector — 초저지연/TTL 캐시형

장점: 인메모리 기반 낮은 지연, TTL/세션/실시간 데이터에 적합(추천/랭킹 캐시 등).
단점: 메모리 비용, 영속성·백업 전략 필요. Redis Stack/Enterprise(벡터 검색 지원) 권장.

7-1. 인덱싱(적재)

from langchain_community.vectorstores import Redis

REDIS_URL = "redis://:password@localhost:6379"
INDEX = "kb"

redis_store = Redis.from_documents(
    documents=chunks,
    embedding=emb,
    index_name=INDEX,
    redis_url=REDIS_URL,
    distance_metric="COSINE",  # 또는 "L2", "IP"
)

7-2. 검색/리트리버

retriever = redis_store.as_retriever(search_kwargs={"k": 4})
for d in retriever.invoke("환불 처리 SLA는?"):
    print(d.metadata.get("source"), d.page_content[:120])

팁: TTL 컬렉션으로 세션성 데이터(예: 일시적 문맥)를 저장하면 메모리 회수가 쉬워집니다. 다중 서비스에서 쓰면 Keyspace/Prefix, 백업(RDB/AOF) 정책을 함께 설계하세요.


8. 하이브리드/앙상블 리트리버 (선택)

키워드 매칭이 강한 데이터(정책/규정)에는 BM25 + 벡터를 섞으면 리콜/정확도가 좋아집니다.

from langchain_community.retrievers import BM25Retriever
from langchain.retrievers import EnsembleRetriever

bm25 = BM25Retriever.from_documents(chunks); bm25.k = 4
vec_ret = faiss_store.as_retriever(search_kwargs={"k": 4})
ensemble = EnsembleRetriever(retrievers=[bm25, vec_ret], weights=[0.3, 0.7])

docs = ensemble.get_relevant_documents("체크인 시간 정책")

9. RAG 체인에 연결 (LCEL)

from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables import RunnablePassthrough
from langchain_openai import ChatOpenAI

llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)

PROMPT = ChatPromptTemplate.from_template(
    """
    너는 고객지원 봇이야. 다음 컨텍스트를 근거로 간결하고 정확히 답해.
    컨텍스트:
{context}

질문: {question}
    """
)

def format_docs(docs):
    return "

".join(f"[source: {d.metadata.get('source')}]
{d.page_content}" for d in docs)

# 예: 어떤 스토어 리트리버든 교체 가능 (FAISS/Chroma/Pinecone/Weaviate/PG/Redis)
retriever = faiss_store.as_retriever(search_kwargs={"k": 4})

rag_chain = (
    {"context": retriever | format_docs, "question": RunnablePassthrough()}
    | PROMPT
    | llm
    | StrOutputParser()
)

print(rag_chain.invoke("노쇼(No-show) 수수료는?"))

10. 1000명 규모 사내 서비스에서 무엇을 쓸까? (의사결정 가이드)

전제: 월간 수십만 쿼리, 청크 수 50만~수백만, 읽기 중심 + 가끔 업데이트, 사내 인증/접근 제어 필요.

[추천 시나리오]

  • 운영 편의/안정성 최우선(권장)Pinecone Serverless
    • 장점: 자동 확장, 고가용성, 손쉬운 메타데이터 필터/네임스페이스, SDK/에코시스템 성숙.
    • 내부 1,000명 규모에서 운영 인력 최소화SLA를 고려하면 총소유비용(TCO)이 낮아지는 경우가 많습니다.
  • 자가 호스팅 우선(보안/데이터 주권)Qdrant 또는 Milvus
    • Qdrant: 비교적 경량·설치/운영 쉬움, HNSW 성능 우수, ACL/샤딩/복제 지원(Cloud도 존재).
    • Milvus: 대규모(수억 벡터)·고성능에 강함. 다만 클러스터 운영 복잡도가 높음.
  • 경량/팀 자율 운영 + 중소 규모Chroma Server 또는 FAISS + (앱)서버 메모리
    • QPS가 낮고 데이터가 수십만 청크 이하, 단일 리전·내부 사용이라면 간단히 구축 가능.
    • 단, 동시 쓰기/롤백/백업/HA 요구가 높아지면 매니지드/클러스터로 전환을 고려.
  • 기존 검색 인프라와 통합Elasticsearch/OpenSearch kNN
    • 키워드/필터/로그 분석과의 동일 스택 유지. 하이브리드 검색 구성이 용이.
  • RDB 선호/간단 통합Postgres + pgvector
    • 트랜잭션·권한·백업 체계 재활용. 최신버전의 HNSW 인덱스 옵션으로 성능 개선.
    • 대규모·고QPS에는 전용 DB 대비 성능 한계가 있음.
  • 초저지연/세션 캐시/TTL 데이터Redis Vector
    • 인메모리 지연이 매우 낮음. 단, 메모리·영속성 비용/전략 필수.

결론(요약)
1000명 규모 사내 서비스에서 운영 인력 최소 + 고가용성을 원하면 Pinecone가 가장 무난합니다.
온프레미스비용 최적화/커스텀 컨트롤이 중요하면 Qdrant(간편) → Milvus(초대규모) 순으로 고려.
경량 PoC/중소 규모에는 Chroma(Server) 또는 FAISS도 충분히 실용적입니다.
사내 RDB 재사용이 중요하면 Postgres + pgvector, 초저지연이면 Redis Vector를 보조 저장소로.


11. 기타 주목할 벡터 DB 한눈에 보기

제품배포핵심 포인트언제 추천?

Qdrant OSS/Cloud HNSW, 쉬운 운영, 필터·샤딩·복제 자가 호스팅/중대형, 비용·통제 균형
Milvus OSS/Cloud 초대규모, 다채로운 인덱스(IVF, HNSW, DiskANN 등) 억 단위 벡터, 고성능·전담 운영팀
Elasticsearch/OpenSearch kNN OSS/Cloud 키워드+벡터, 성숙한 생태계 기존 ES 스택과 결합, 로그·필터 풍부
Postgres + pgvector OSS/Cloud SQL/트랜잭션·권한·백업 재사용 데이터 적당(수십만~수백만), 운영 단순
Redis Vector OSS/Cloud 인메모리, 낮은 지연 짧은 TTL/실시간 지연 민감 서비스

12. 언제 무엇을 쓰나? (업데이트)

상황 추천 스토어 이유
로컬 PoC/데스크톱/임베디드 FAISS·Chroma 빠르고 무료, 퍼시스트 쉬움
소규모~대규모 운영, 쉬운 스케일 Pinecone 서버리스/고가용성/메타필터/Namespace
스키마·복합 질의/자가 호스팅 옵션 Weaviate 강력한 스키마/필터, 오픈소스·클라우드
온프레미스/자체운영 중대형 Qdrant·Milvus 비용·통제·성능 균형(운영 복잡도 차등)
기존 검색 스택 유지 OpenSearch/Elasticsearch 키워드+벡터 하이브리드 손쉬움
RDB 환경 선호 Postgres+pgvector SQL 생태계 재활용, 운영 단순
초저지연/세션성 Redis Vector 인메모리 검색, TTL 캐시/랭킹 등에 적합

 

728x90
반응형