어플리케이션이 특정 DB 브랜드에 종속되지 않게 만드는 핵심
- retriever = vectorstore.as_retriever()
- 이렇게 하면 내부가 FAISS인지 Chroma인지 상관없이, 애플리케이션은 오직 get_relevant_documents()라는 공통 메서드만 호출하면 됩니다.
"애플리케이션은 어디에 저장되어 있는가?를 묻지 않고, 오직 질문과 유사한 문서를 가져와라라는 명령(Retriever 인터페이스)만 내리면 된다.
App/RAG Chain
↓ (depends on)
Retriever Interface (LangChain BaseRetriever)
↑
VectorStore Adapter (infra)
↑
FAISS | Chroma | Pinecone ...
from langchain_core.retrievers import BaseRetriever
from langchain_core.runnables import RunnableLambda
def build_rag_chain(retriever: BaseRetriever, llm):
# 예시: retrieve -> prompt -> llm
def retrieve(q: str):
return retriever.get_relevant_documents(q)
return (
RunnableLambda(lambda q: {"question": q, "docs": retrieve(q)})
# 이후 prompt formatting + llm 호출...
)
앱 코드는 FAISS/Chroma/Pinecone 존재를 모름.
from dataclasses import dataclass
from langchain_core.embeddings import Embeddings
from langchain_core.retrievers import BaseRetriever
@dataclass(frozen=True)
class VectorStoreConfig:
backend: str # "faiss" | "chroma" | "pinecone" ...
persist_dir: str | None = None
collection: str | None = None
k: int = 5
search_type: str = "similarity" # "similarity" | "mmr"
score_threshold: float | None = None
def build_retriever(cfg: VectorStoreConfig, embeddings: Embeddings) -> BaseRetriever:
backend = cfg.backend.lower()
if backend == "faiss":
from langchain_community.vectorstores import FAISS
# (예시) 로드/생성 로직은 환경에 맞게 구성
vs = FAISS.load_local(cfg.persist_dir, embeddings, allow_dangerous_deserialization=True)
elif backend == "chroma":
from langchain_community.vectorstores import Chroma
vs = Chroma(
collection_name=cfg.collection or "default",
persist_directory=cfg.persist_dir,
embedding_function=embeddings,
)
elif backend == "pinecone":
from langchain_pinecone import PineconeVectorStore
vs = PineconeVectorStore(
index_name=cfg.collection or "default",
embedding=embeddings,
)
else:
raise ValueError(f"Unsupported backend: {cfg.backend}")
search_kwargs = {"k": cfg.k}
if cfg.score_threshold is not None:
search_kwargs["score_threshold"] = cfg.score_threshold
return vs.as_retriever(search_type=cfg.search_type, search_kwargs=search_kwargs)
교체는 config 한 줄로 끝남:
retriever = build_retriever(cfg, embeddings)
chain = build_rag_chain(retriever, llm)
LangChain의 BaseRetriever만으로도 충분하지만, 더 깔끔하게 하려면 앱 레이어는 아예 LangChain도 모르게 만들 수 있음.
from typing import Protocol, List
from langchain_core.documents import Document
class RetrieverPort(Protocol):
def retrieve(self, query: str) -> List[Document]: ...
infra에서 LangChain retriever를 감싸
class LangChainRetrieverAdapter:
def __init__(self, retriever):
self._retriever = retriever
def retrieve(self, query: str):
return self._retriever.get_relevant_documents(query)
앱은 RetrieverPort만 의존 → LangChain 교체/업그레이드에도 강해짐.