본문 바로가기
IT&Jobs/Study

[LANGCHAIN] LangChain FAISS 벡터 검색 구현 완벽 가이드 - 실전 예제와 성능 최적화

by jaeilpark 2026. 4. 15.
728x90
반응형

<aside> 📋 🖼️ 썸네일: LangChain FAISS 벡터검색 완전정복 📂 카테고리: 🤖 AI/LangChain 📋 타입: tutorial 🏷️ 태그: #LangChain #FAISS #벡터검색 #RAG #AI #임베딩 #Python #OpenAI 🔍 메타: LangChain FAISS로 고성능 벡터 검색 시스템 구축하기. 실무 예제, 성능 최적화, RAG 구현까지

</aside>


LangChain FAISS 벡터 검색 구현 완벽 가이드

RAG(Retrieval Augmented Generation) 시스템을 구축할 때 핵심은 효율적인 벡터 검색입니다. 오늘은 Facebook AI에서 개발한 FAISS(Facebook AI Similarity Search)를 LangChain과 함께 사용하는 실전 구현법을 다뤄보겠습니다.

🎯 왜 FAISS인가?

다른 벡터 저장소와 비교해보겠습니다:

| 벡터DB | 속도 | 메모리효율 | 확장성 | 설치복잡도 |

|--------|------|------------|--------|-----------|

| FAISS | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐⭐⭐ |

| Chroma | ⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ |

| Pinecone | ⭐⭐⭐⭐ | ⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ |

| Weaviate | ⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐ |

FAISS는 특히 로컬 환경에서 빠른 검색이 필요한 프로젝트에 최적입니다.

🛠 환경 설정

# 필수 패키지 설치
pip install langchain langchain-community faiss-cpu
pip install openai tiktoken

# GPU 사용시 (CUDA 필요)
# pip install faiss-gpu

📝 기본 구현

1. 문서 준비 및 청킹

from langchain.document_loaders import TextLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain.embeddings import OpenAIEmbeddings
from langchain.vectorstores import FAISS
import os

# OpenAI API 키 설정
os.environ["OPENAI_API_KEY"] = "your-api-key-here"

def load_and_split_documents(file_path: str):
    """
    문서를 로드하고 청킹하는 함수
    """
    # 문서 로드
    loader = TextLoader(file_path, encoding='utf-8')
    documents = loader.load()
    
    # 텍스트 분할
    text_splitter = RecursiveCharacterTextSplitter(
        chunk_size=1000,        # 청크 크기
        chunk_overlap=200,      # 청크 간 겹침
        length_function=len,
        separators=["\\n\\n", "\\n", " ", ""]
    )
    
    chunks = text_splitter.split_documents(documents)
    print(f"총 {len(chunks)}개의 청크 생성")
    
    return chunks

# 사용 예시
chunks = load_and_split_documents("./data/manual.txt")

2. FAISS 벡터 저장소 생성

def create_faiss_vectorstore(chunks, save_path: str = "./faiss_index"):
    """
    FAISS 벡터 저장소를 생성하고 저장
    """
    # 임베딩 모델 초기화
    embeddings = OpenAIEmbeddings(
        model="text-embedding-ada-002",
        chunk_size=1000  # 배치 크기
    )
    
    # FAISS 벡터스토어 생성
    print("벡터 임베딩 생성 중...")
    vectorstore = FAISS.from_documents(
        documents=chunks,
        embedding=embeddings
    )
    
    # 로컬에 저장
    vectorstore.save_local(save_path)
    print(f"벡터 저장소가 {save_path}에 저장됨")
    
    return vectorstore

# 벡터 저장소 생성
vectorstore = create_faiss_vectorstore(chunks)

3. 저장된 FAISS 인덱스 로드

def load_faiss_vectorstore(save_path: str = "./faiss_index"):
    """
    저장된 FAISS 벡터 저장소 로드
    """
    embeddings = OpenAIEmbeddings(model="text-embedding-ada-002")
    
    vectorstore = FAISS.load_local(
        save_path, 
        embeddings,
        allow_dangerous_deserialization=True  # 로컬 파일 신뢰
    )
    
    print(f"벡터 저장소 로드 완료: {vectorstore.index.ntotal}개 벡터")
    return vectorstore

# 저장된 인덱스 로드
vectorstore = load_faiss_vectorstore()

🔍 고급 검색 기능

1. 유사도 검색 (Similarity Search)

def similarity_search_demo(vectorstore, query: str, k: int = 5):
    """
    유사도 검색 데모
    """
    print(f"\\n🔍 검색 쿼리: {query}")
    print(f"상위 {k}개 결과:\\n")
    
    # 기본 유사도 검색
    results = vectorstore.similarity_search(query, k=k)
    
    for i, doc in enumerate(results, 1):
        print(f"[{i}] {doc.page_content[:200]}...")
        print(f"    메타데이터: {doc.metadata}")
        print()

# 검색 테스트
similarity_search_demo(
    vectorstore, 
    "VMware ESXi 설치 방법", 
    k=3
)

2. 스코어 기반 검색

def similarity_search_with_score(vectorstore, query: str, k: int = 3):
    """
    유사도 점수와 함께 검색
    """
    results_with_scores = vectorstore.similarity_search_with_score(query, k=k)
    
    print(f"\\n📊 스코어 기반 검색 결과 (쿼리: {query})\\n")
    
    for i, (doc, score) in enumerate(results_with_scores, 1):
        print(f"[{i}] 유사도 점수: {score:.4f}")
        print(f"    내용: {doc.page_content[:150]}...")
        print(f"    메타데이터: {doc.metadata}")
        print()

# 스코어 검색 테스트
similarity_search_with_score(
    vectorstore,
    "Docker 컨테이너 실행 명령어"
)

3. MMR(Maximum Marginal Relevance) 검색

def mmr_search_demo(vectorstore, query: str, k: int = 5, fetch_k: int = 20):
    """
    MMR 검색 - 다양성을 고려한 검색
    """
    print(f"\\n🎯 MMR 검색 (다양성 고려): {query}\\n")
    
    results = vectorstore.max_marginal_relevance_search(
        query,
        k=k,                    # 반환할 문서 수
        fetch_k=fetch_k,        # 초기 후보 문서 수
        lambda_mult=0.7         # 다양성 vs 유사성 균형 (0~1)
    )
    
    for i, doc in enumerate(results, 1):
        print(f"[{i}] {doc.page_content[:200]}...")
        print()

# MMR 검색 테스트
mmr_search_demo(
    vectorstore,
    "Python 웹 프레임워크",
    k=3,
    fetch_k=10
)

⚡ 성능 최적화 팁

1. 인덱스 타입 최적화

from langchain.vectorstores.faiss import FAISS
import faiss

def create_optimized_faiss_index(chunks, index_type="IVF"):
    """
    최적화된 FAISS 인덱스 생성
    """
    embeddings = OpenAIEmbeddings()
    
    if index_type == "IVF":
        # IVF (Inverted File) - 대용량 데이터용
        vectorstore = FAISS.from_documents(
            chunks, 
            embeddings,
            # 클러스터 수 설정 (데이터 크기의 sqrt 권장)
            index_kwargs={"nlist": min(100, len(chunks) // 10)}
        )
    elif index_type == "HNSW":
        # HNSW (Hierarchical Navigable Small World) - 정확도 우선
        # FAISS에서 직접 지원하지 않으므로 기본 L2 사용
        vectorstore = FAISS.from_documents(chunks, embeddings)
    
    return vectorstore

# 최적화된 인덱스 생성
optimized_vectorstore = create_optimized_faiss_index(chunks, "IVF")

2. 배치 처리로 메모리 최적화

def create_faiss_in_batches(chunks, batch_size: int = 100):
    """
    배치 단위로 FAISS 인덱스 생성 (대용량 데이터용)
    """
    embeddings = OpenAIEmbeddings()
    vectorstore = None
    
    for i in range(0, len(chunks), batch_size):
        batch = chunks[i:i + batch_size]
        print(f"배치 {i//batch_size + 1}/{(len(chunks)-1)//batch_size + 1} 처리 중...")
        
        if vectorstore is None:
            # 첫 번째 배치로 초기화
            vectorstore = FAISS.from_documents(batch, embeddings)
        else:
            # 기존 인덱스에 추가
            temp_vectorstore = FAISS.from_documents(batch, embeddings)
            vectorstore.merge_from(temp_vectorstore)
    
    return vectorstore

# 대용량 데이터 처리
# vectorstore = create_faiss_in_batches(chunks, batch_size=50)

🏗 실전 RAG 시스템 구축

from langchain.chains import RetrievalQA
from langchain.llms import OpenAI
from langchain.prompts import PromptTemplate

class InfraRAGSystem:
    def __init__(self, faiss_path: str):
        self.vectorstore = load_faiss_vectorstore(faiss_path)
        self.llm = OpenAI(temperature=0.1, max_tokens=1000)
        
        # 커스텀 프롬프트
        self.prompt_template = PromptTemplate(
            input_variables=["context", "question"],
            template="""
            다음 문서 내용을 바탕으로 질문에 답변하세요.
            IT 인프라 전문가의 관점에서 정확하고 구체적으로 답변해주세요.
            
            문서 내용:
            {context}
            
            질문: {question}
            
            답변:"""
        )
        
        # RetrievalQA 체인 생성
        self.qa_chain = RetrievalQA.from_chain_type(
            llm=self.llm,
            chain_type="stuff",
            retriever=self.vectorstore.as_retriever(
                search_type="mmr",
                search_kwargs={"k": 5, "fetch_k": 20}
            ),
            chain_type_kwargs={"prompt": self.prompt_template},
            return_source_documents=True
        )
    
    def ask(self, question: str):
        """질문하고 소스와 함께 답변 받기"""
        result = self.qa_chain({"query": question})
        
        print(f"\\n❓ 질문: {question}")
        print(f"\\n💡 답변:\\n{result['result']}")
        print("\\n📚 참조 문서:")
        
        for i, doc in enumerate(result['source_documents'], 1):
            print(f"[{i}] {doc.page_content[:100]}...")
        
        return result

# RAG 시스템 사용
rag = InfraRAGSystem("./faiss_index")
result = rag.ask("VMware ESXi에서 스냅샷 생성하는 방법은?")

🔧 운영 팁

1. 인덱스 상태 모니터링

def monitor_faiss_index(vectorstore):
    """FAISS 인덱스 상태 확인"""
    index = vectorstore.index
    
    print("=== FAISS 인덱스 정보 ===")
    print(f"총 벡터 수: {index.ntotal:,}")
    print(f"벡터 차원: {index.d}")
    print(f"인덱스 타입: {type(index).__name__}")
    print(f"메트릭 타입: {'L2' if index.metric_type == 1 else 'IP'}")
    print(f"메모리 사용량: {index.ntotal * index.d * 4 / 1024 / 1024:.2f} MB")

# 모니터링 실행
monitor_faiss_index(vectorstore)

2. 검색 성능 벤치마크

import time

def benchmark_search_performance(vectorstore, queries: list, k: int = 5):
    """검색 성능 벤치마크"""
    total_time = 0
    
    print("=== 검색 성능 테스트 ===")
    
    for i, query in enumerate(queries, 1):
        start_time = time.time()
        results = vectorstore.similarity_search(query, k=k)
        end_time = time.time()
        
        query_time = end_time - start_time
        total_time += query_time
        
        print(f"쿼리 {i}: {query_time:.3f}초 ({len(results)}개 결과)")
    
    avg_time = total_time / len(queries)
    print(f"\\n평균 검색 시간: {avg_time:.3f}초")
    print(f"초당 처리 가능 쿼리: {1/avg_time:.1f}개")

# 성능 테스트
test_queries = [
    "Docker 컨테이너 관리",
    "VMware vSphere 설정",
    "Linux 서버 모니터링",
    "Python 자동화 스크립트"
]

benchmark_search_performance(vectorstore, test_queries)

📈 실무 적용 시나리오

1. 기술문서 검색 시스템

  • 대상: 사내 기술문서, 매뉴얼, 운영 가이드
  • 청크 크기: 800-1200자 (문단 단위)
  • 검색 타입: MMR (다양한 관점의 답변)

2. 장애 해결 지식베이스

  • 대상: 장애 이력, 해결책, 운영 로그
  • 청크 크기: 500-800자 (이슈 단위)
  • 검색 타입: 유사도 + 스코어 (정확한 매칭)

3. 코드 검색 시스템

  • 대상: 스크립트, 설정파일, 코드 스니펫
  • 청크 크기: 함수/클래스 단위
  • 검색 타입: 하이브리드 (키워드 + 벡터)

🚨 주의사항 및 해결책

1. 메모리 부족 문제

# 해결책: 배치 처리 + 인덱스 압축
vectorstore = create_faiss_in_batches(chunks, batch_size=50)
# GPU 메모리가 부족하면 CPU 버전 사용

2. 검색 품질 저하

# 해결책: 청크 크기 조정 + 오버랩 증가
text_splitter = RecursiveCharacterTextSplitter(
    chunk_size=800,         # 더 작게
    chunk_overlap=150,      # 오버랩 증가
)

3. 속도 vs 정확도 트레이드오프

# 속도 우선: IVF 인덱스 + 적은 nprobe
# 정확도 우선: Flat 인덱스 + MMR 검색

🎯 결론

FAISS는 로컬 환경에서 빠른 벡터 검색이 필요할 때 최고의 선택입니다. 특히:

  • 로컬 개발환경에서 RAG 프로토타입 구축
  • 온프레미스 환경에서 보안이 중요한 시스템
  • 비용 절약이 필요한 소규모 프로젝트
  • 실시간 응답이 필요한 챗봇 시스템

다음 포스트에서는 Chroma와 FAISS 성능 비교프로덕션 배포 전략을 다뤄보겠습니다.

벡터 검색 관련 질문이나 실무 적용 경험이 있으시면 댓글로 공유해주세요! 🙌

728x90
반응형

댓글