Web Project/e-commerce

실시간 AI 챗봇 시스템 구축

hmmmmmmmmmmmm 2025. 8. 21. 08:26

 

※ 개요

LangChain + ChromaDB + OpenAI GPT  조합으로, Markdown 문서 기반 지식베이스와 OpenAI GPT-4를 활용해 24시간 즉각적인 고객 응대가 가능한 시스템 구축

 

※ 시스템 아키텍처

프로젝트 구조
├── Django Backend
│   ├── services/
│   │   └── chatbot_service.py (벡터DB + 문서 처리)
│   ├── views/
│   │   └── chatbot_views.py (API 엔드포인트)
│   ├── docs/ (MD 파일 저장소)
│   │   ├── faq.md
│   │   ├── payment.md
│   │   ├── refund_policy.md
│   │   └── shipping.md
│   └── chroma_db/ (벡터 데이터베이스)
└── React Frontend
    └── screens/
        └── ChatbotScreen.js (대화형 UI)

 

 

※ 핵심 구현

 

1. 벡터DB 기반 문서 검색 시스템

# base/services/chatbot_service.py

import os
from pathlib import Path
from langchain.text_splitter import MarkdownHeaderTextSplitter
from langchain_chroma import Chroma
from langchain_openai import OpenAIEmbeddings
from langchain.schema import Document
from langchain_community.vectorstores.utils import filter_complex_metadata
from django.conf import settings
import logging

logger = logging.getLogger(__name__)

# 경로 설정
BASE_DIR = Path(settings.BASE_DIR)
DOCS_PATH = BASE_DIR / "docs"
DB_PATH = Path(os.getenv('CHROMA_DB_PATH', str(BASE_DIR / "chroma_db")))

# 전역 벡터DB 인스턴스 (메모리 기반 싱글톤)
_vectordb_instance = None
_is_initialized = False

 

핵심 설계 포인트:

1. 메모리 기반 벡터DB: Windows 파일 잠금 문제 회피

2. 싱글톤 패턴: 서버 전체에서 하나의 인스턴스만 유지

3. 환경변수 지원: 유연한 경로 설정

 

2. Markdown 문서 처리 및 임베딩 생성

# base/services/chatbot_service.py

def build_vector_db(force_rebuild=False):
    """
    📌 .md 파일들을 읽어서 메모리 기반 벡터DB를 생성하는 함수
    """
    global _vectordb_instance, _is_initialized
    
    # docs 폴더 체크
    folder_exists, md_files = check_docs_folder()
    if not folder_exists:
        raise FileNotFoundError(f"Docs 폴더를 찾을 수 없습니다: {DOCS_PATH}")
    
    docs = []
    splitter = MarkdownHeaderTextSplitter(
        headers_to_split_on=[
            ("#", "header1"),
            ("##", "header2"),
            ("###", "header3"),
        ]
    )
    
    # 각 MD 파일 처리
    for file_path in md_files:
        with open(file_path, "r", encoding="utf-8") as f:
            text = f.read()
        
        # 헤더 기준으로 문서 분할
        split_docs = splitter.split_text(text)
        for i, doc in enumerate(split_docs):
            # 메타데이터 단순화 (Chroma 호환성)
            metadata = {
                "source": file_path.name,
                "chunk_id": i
            }
            
            docs.append(Document(
                page_content=content,
                metadata=metadata
            ))
    
    # OpenAI 임베딩으로 벡터화
    embeddings = OpenAIEmbeddings(api_key=settings.OPENAI_API_KEY)
    
    # 복잡한 메타데이터 필터링 후 벡터DB 생성
    filtered_docs = filter_complex_metadata(docs)
    _vectordb_instance = Chroma.from_documents(
        documents=filtered_docs,
        embedding=embeddings
        # persist_directory 없음 - 메모리에만 저장
    )
    
    _is_initialized = True
    logger.info(f"✅ 벡터DB 생성 완료: {len(docs)}개 문서")
    return _vectordb_instance

 

문서 처리 전략:

1. 헤더 기반 분할: #, ##, ### 단위로 청크 생성

2. 메타데이터 관리: 출처 파일명과 청크 ID 저장

3. 임베딩 생성: OpenAI API로 벡터 변환

4. 메모리 저장: 파일 시스템 대신 RAM 사용

 

 

3. 지능형 질의응답 API

# base/views/chatbot_views.py

from rest_framework.decorators import api_view
from rest_framework.response import Response
from langchain.chains import RetrievalQA
from langchain_openai import ChatOpenAI
from langchain.prompts import PromptTemplate

# 커스텀 프롬프트 템플릿
CUSTOM_PROMPT = PromptTemplate(
    template="""당신은 친절한 이커머스 고객 상담 챗봇입니다.
    
다음 문서 내용을 바탕으로 질문에 답변해주세요:
{context}

질문: {question}

답변 시 주의사항:
1. 문서에 있는 내용만을 바탕으로 정확하게 답변하세요
2. 문서에 없는 내용은 추측하지 마세요
3. 친절하고 이해하기 쉽게 설명하세요
4. 문서에 정보가 없다면 "죄송합니다. 해당 정보를 찾을 수 없습니다"라고 답변하세요

답변:""",
    input_variables=["context", "question"]
)

@api_view(["POST"])
def chatbot_query(request):
    """
    📌 상담 챗봇 API 엔드포인트
    """
    question = request.data.get("question")
    
    # 벡터DB에서 관련 문서 검색
    vectordb = get_vector_db()
    retriever = vectordb.as_retriever(
        search_type="similarity",
        search_kwargs={"k": 3}  # 상위 3개 문서
    )
    
    # GPT-4 기반 답변 생성
    llm = ChatOpenAI(
        model="gpt-4o-mini",
        temperature=0.3,  # 일관된 답변
        api_key=settings.OPENAI_API_KEY
    )
    
    # RetrievalQA 체인 실행
    qa = RetrievalQA.from_chain_type(
        llm=llm,
        chain_type="stuff",
        retriever=retriever,
        return_source_documents=True,
        chain_type_kwargs={"prompt": CUSTOM_PROMPT}
    )
    
    result = qa({"query": question})
    
    # 참조 문서 정보 추가
    sources = []
    for doc in result.get("source_documents", []):
        sources.append({
            "source": doc.metadata.get("source"),
            "content_preview": doc.page_content[:200] + "..."
        })
    
    return Response({
        "question": question,
        "answer": result.get("result"),
        "sources": sources
    })

 

RAG (Retrieval-Augmented Generation) 파이프라인:

사용자 질문 → 벡터 검색 → 관련 문서 추출 → GPT-4 답변 생성 → 출처 포함 응답

 

 

4. 시스템 상태 모니터링

# base/views/chatbot_views.py

@api_view(["GET"])
def chatbot_status(request):
    """
    📌 챗봇 상태 확인 API
    """
    from base.services.chatbot_service import DOCS_PATH, _is_initialized, _vectordb_instance
    
    # docs 폴더 확인
    folder_exists, md_files = check_docs_folder()
    
    # 벡터DB 상태 확인
    if _is_initialized and _vectordb_instance:
        collection_count = _vectordb_instance._collection.count()
        db_status = "정상 (메모리)"
    else:
        collection_count = 0
        db_status = "초기화 필요"
    
    return Response({
        "status": "ok" if folder_exists and collection_count > 0 else "error",
        "docs_folder": {
            "exists": folder_exists,
            "path": str(DOCS_PATH),
            "md_files": [f.name for f in md_files]
        },
        "vector_db": {
            "status": db_status,
            "document_count": collection_count,
            "type": "memory-based"
        }
    })

 

 

※ 실행 및 배포

 

1. 환경 설정

# .env 파일
OPENAI_API_KEY=your_api_key_here
CHROMA_DB_PATH=C:\temp\chroma_db  # Windows 파일 잠금 회피

 

2. 패키지 설치

# Backend
pip install langchain langchain-chroma langchain-openai
pip install langchain-community chromadb

# Frontend
npm install axios react-bootstrap

 

3. 벡터DB 초기화

# Django Management Command
python manage.py shell

>>> from base.services.chatbot_service import build_vector_db
>>> vectordb = build_vector_db(force_rebuild=True)
>>> print(f"✅ {vectordb._collection.count()}개 문서 로드 완료")

 

4. 서버 실행

# Backend
python manage.py runserver

# Frontend
npm start

 

 

 

※ 실제 작동 예시

 

사용자 질문

"환불은 어떻게 하나요?"

 

시스템 처리 과정

# 1. 벡터 검색
관련 문서 검색: refund_policy.md (유사도 0.92)

# 2. 컨텍스트 추출
"환불 정책
- 구매 후 7일 이내 환불 가능
- 미사용 제품에 한함
- 배송비는 고객 부담..."

# 3. GPT-4 답변 생성
"환불은 구매 후 7일 이내에 가능합니다. 
제품이 미사용 상태여야 하며, 반품 배송비는 
고객님께서 부담하셔야 합니다..."

# 4. 응답 반환
{
    "answer": "환불은 구매 후 7일 이내...",
    "sources": [{
        "source": "refund_policy.md",
        "content_preview": "환불 정책..."
    }]
}

 

※ 트러블 슈팅

 

1. Windows 파일 잠금 문제

문제: ChromaDB SQLite 파일 접근 시 WinError 32

# 오류 메시지
[WinError 32] 다른 프로세스가 파일을 사용 중...

 

해결: 메모리 기반 벡터DB로 전환

# persist_directory 제거 → 메모리만 사용
_vectordb_instance = Chroma.from_documents(
    documents=docs,
    embedding=embeddings
    # persist_directory 없음
)

 

2. 메타데이터 딕셔너리 오류

문제: Chroma가 중첩된 딕셔너리 거부

# 오류 메시지
Expected metadata value to be str, int, float, bool, or None, got {}

 

해결: 메타데이터 평탄화

from langchain_community.vectorstores.utils import filter_complex_metadata

filtered_docs = filter_complex_metadata(docs)

 

 

3. 경로 설정 문제

문제: 상대 경로로 인한 docs 폴더 미발견

 

해결: Django BASE_DIR 기반 절대 경로

BASE_DIR = Path(settings.BASE_DIR)
DOCS_PATH = BASE_DIR / "docs"  # 절대 경로

 

 

 

※ 성능 최적화

 

1. 싱글톤 패턴

# 전역 인스턴스 재사용
if _is_initialized and _vectordb_instance:
    return _vectordb_instance

 

2. 검색 최적화

retriever = vectordb.as_retriever(
    search_type="similarity",
    search_kwargs={"k": 3}  # 상위 3개만
)

 

3. 프롬프트 엔지니어링

temperature=0.3  # 낮은 temperature로 일관성 확보