RAG(Retrieval-Augmented Generation)란?

LLM이 학습하지 않은 내부 데이터를 검색해서 답변에 활용하는 패턴입니다. 이커머스 상품 데이터를 자연어로 검색할 수 있게 만들었습니다.

Live: chat.lemuel.co.kr

아키텍처

사용자 질문 → Intent 분석 → Vector 유사도 검색 (pgvector)
                                    ↓
                           관련 문서 추출 (Top-K)
                                    ↓
                    프롬프트 = 시스템 지시 + 문서 컨텍스트 + 질문
                                    ↓
                         Gemini LLM → 답변 생성

핵심 구현

1. 벡터 임베딩

상품 등록 시 이벤트 리스너가 자동으로 임베딩을 생성합니다:

@EventListener
public void onProductCreated(ProductEmbeddingEvent event) {
    String text = String.format("%s %s 카테고리:%s 가격:%d",
        product.getName(), product.getDescription(),
        product.getCategory().getName(), product.getPrice());
    
    Document doc = new Document(text, metadata);
    vectorStore.add(List.of(doc));  // pgvector에 저장
}

2. RAG 검색 + 답변 생성

public AnswerResponse ask(String question) {
    // 1. 벡터 유사도 검색
    List<Document> relevantDocs = vectorStore.similaritySearch(
        SearchRequest.builder()
            .query(question)
            .topK(5)
            .similarityThreshold(0.0)
            .build()
    );
    
    // 2. 프롬프트 구성 (문서 + 질문)
    String prompt = String.format(RAG_PROMPT_TEMPLATE, 
        docsToText(relevantDocs), question);
    
    // 3. LLM 답변 생성
    return chatClient.prompt().user(prompt).call().content();
}

3. Intent 분석

자연어 질문의 의도를 분류하여 검색 전략을 최적화합니다:

// "3만원 이하 가방" → PRICE_RANGE + CATEGORY
// "인기 있는 상품" → POPULARITY
// "빨간색 티셔츠" → COLOR + CATEGORY

기술 스택

구분 기술
LLM Google Gemini (Spring AI)
Vector DB PostgreSQL + pgvector
Embedding Spring AI EmbeddingModel
Framework Spring Boot 4.0

배운 점

  • RAG의 품질은 임베딩 텍스트 구성에 크게 좌우됨 — 상품명+설명+카테고리+가격을 포함해야 정확도 향상
  • Top-K와 similarity threshold 조합이 중요 — 너무 엄격하면 결과 없음, 너무 느슨하면 노이즈
  • 프롬프트에 “문서에 없는 내용은 답변하지 마세요”를 명시해야 환각(hallucination) 방지