본문 바로가기
사이드프로젝트

[사이드 프로젝트] #2. RAG 기반 '업무 지식' AI 챗봇 개발기 (3) - RAG 구축 삽질기

by Jaejin Sim 2025. 10. 29.
반응형

RAG(Retrieval-Augmented Generation) 개념을 배우고 예시 코드를 처음 적용했을 때는 정말 쉬워 보였습니다. "이거면 금방 끝나겠네!" 싶었죠.

하지만 현실은 역시 만만치 않았습니다.

❌ 첫 번째 문제: 부정확한 컨텍스트와 잘린 답변

처음에는 간단한 설정으로 시작했습니다.

# 초기 설정
text_splitter = RecursiveCharacterTextSplitter(
    chunk_size=250,      # 너무 작았음
    chunk_overlap=100,
    separators=["\n## ", "\n### ", "\n\n", "\n", ". ", " ", ""],
    add_start_index=True,
)

def retrieve_context(query: str):
    # k값도 너무 작았음
    retrieved_docs = vector_store.similarity_search(query, k=2)
    serialized = "\n\n".join(
        (f"Source: {doc.metadata}\nContent: {doc.page_content}")
        for doc in retrieved_docs
    )
    return serialized, retrieved_docs

테스트를 진행하자마자 문제점이 드러났습니다.

  • k=2가 너무 적었습니다. 관련성 높은 청크 2개만 가져오니, 정책이나 절차처럼 더 많은 컨텍스트가 필요한 질문에 제대로 답하지 못했습니다.
  • chunk_size=250이 너무 작았습니다. 중요한 정보가 여러 청크로 나뉘면서, AI의 답변에 "..."처럼 불완전한 내용이 포함되거나 문맥이 끊겨 정확한 답변 생성에 실패했습니다.

🔬 1차 해결 시도: 청크 크기 및 K값 증가

단순히 파라미터를 수정해 봤습니다.

  • chunk_size: 250 → 500으로 증가
  • chunk_overlap: 100 → 150으로 증가
  • k (검색 개수): 2 → 5로 증가

결과: 조금 나아지긴 했지만, 여전히 답변 품질이 만족스럽지 않았습니다. 😔 근본적인 원인은 다른 곳에 있었습니다.


💡 근본 원인 분석 및 향후 개선 계획

단순한 파라미터 튜닝이 아니라, RAG 시스템의 구조 자체를 개선해야 한다는 결론에 도달했습니다.

1. 메타데이터 활용 (필터링)

첫 번째 해결책은 각 문서에 **구조화된 정보(메타데이터)**를 추가하는 것이었습니다. '업무 지식' 문서는 그 특성상 카테고리, 관련 모듈, 서비스 제공자 등이 명확합니다.

문서 상단에 YAML 헤더 추가:

---
doc_id: payment_module_setup
title: A-Pay 결제 모듈 연동 가이드
category: 연동설정
service_provider: A-Pay
payment_methods: bank_transfer, credit_card
keywords: API, 웹훅, notification, URL
---

(문서 본문...)

청킹 시 메타데이터 추출 및 포함: 문서를 청킹하기 전에, 이 YAML 헤더를 파싱하여 metadata 객체로 만들고, create_documents 시 함께 주입합니다.

import re
import yaml
import json

def extract_metadata_from_content(content):
    """YAML 헤더에서 메타데이터 추출"""
    match = re.match(r"^---\n(.*?)\n---\n", content, re.DOTALL)
    if match:
        try:
            metadata = yaml.safe_load(match.group(1))
            clean_content = content[match.end():]

            # ChromaDB 등 벡터 DB 호환 메타데이터로 변환 (리스트, 딕셔너리 처리)
            cleaned_metadata = {}
            for key, value in metadata.items():
                if isinstance(value, (str, int, float, bool)) or value is None:
                    cleaned_metadata[key] = value
                elif isinstance(value, list):
                    cleaned_metadata[key] = ', '.join(str(item) for item in value)
                else:
                    cleaned_metadata[key] = str(value)
            
            return cleaned_metadata, clean_content
        except yaml.YAMLError as e:
            print(f"YAML 파싱 오류: {e}")
            return {}, content
    return {}, content

# 사용 예시:
metadata, clean_content = extract_metadata_from_content(content)
metadata['source'] = os.path.basename(filepath)
doc_chunks = text_splitter.create_documents([clean_content], metadatas=[metadata])

메타데이터 활용 검색: 이제 similarity_search에서 filter 옵션을 사용하여, 특정 서비스 제공자(A-Pay)의 문서 내에서만 검색하도록 범위를 좁힐 수 있습니다.

def retrieve_context(query: str, provider_name: str = None):
    """메타데이터 필터로 검색 정확도 향상"""

    if provider_name:
        # 예: 'A-Pay' 관련 문서만 검색
        retrieved_docs = vector_store.similarity_search(
            query,
            k=5,
            filter={"service_provider": {"$eq": provider_name}}
        )
    else:
        retrieved_docs = vector_store.similarity_search(query, k=5)

    return retrieved_docs

2. 하이브리드 검색 (Hybrid Search)

의미 기반의 벡터 검색과 키워드 기반의 **BM25 (키워드 검색)**를 결합하여 정확도를 높이는 방식입니다. (이 부분은 향후 적용 예정입니다.)

3. 문서 재구조화 (⭐ 핵심)

이게 핵심이었습니다! 청킹과 메타데이터로도 해결이 어려운 근본 원인은 문서 구조 자체였습니다.

  • 기존 문서: "사람이 읽기 좋은" 서술형 가이드
  • 필요한 문서: "AI가 검색하고 답변하기 좋은" 구조

특히 업무 지식 관련 질문은 패턴이 명확하고 FAQ 작성이 용이하므로, 문서를 하이브리드(Hybrid) 형식으로 재정의하기로 결정했습니다.


🎯 핵심 해결 전략: 문서 재구조화 (계획)

AI가 빠르고 정확하게 답변을 찾을 수 있도록 문서 포맷 자체를 변경할 예정입니다.

새로운 문서 구조 (예시):

---
doc_id: apay_bank_transfer_setup
title: A-Pay 가상계좌 연동 및 알림 설정
type: hybrid
service_provider: A-Pay
payment_methods: bank_transfer
---

# A-Pay 가상계좌 연동 설정 가이드

## 🎯 핵심 요약 (Quick Answer)

**알림(웹훅) URL:** `https://api.your-service.com/webhook/payment-notification`
**설정 위치:** A-Pay 관리자 > 상점 관리 > 알림 설정 > URL 등록
**예상 소요 시간:** 약 5분

---

## 📋 자주 묻는 질문 (FAQ)

### Q1: 결제 알림(웹훅) URL이 무엇인가요?
고객이 결제를 완료하면 A-Pay가 상점 서버로 결제 정보를 전송(POST)하는 고유 주소입니다.
표준 URL: `https://api.your-service.com/webhook/payment-notification`

### Q2: 결제 알림이 오지 않아요.
1. URL이 정확히 등록되었는지 확인 (오타, http/https)
2. 서버 방화벽이 A-Pay IP 대역을 차단하는지 확인
3. HTTPS 인증서가 유효한지 확인

---

## 📚 상세 가이드

### 1. 웹훅 시스템 이해하기
[웹훅의 개념과 데이터 흐름 설명...]

### 2. 설정 방법 (Step-by-Step)
[관리자 페이지 로그인부터 설정 완료까지 상세 절차...]

---

## 🔧 기술 상세 (Advanced)

### API Spec
- Method: POST
- Content-Type: application/json
- Timeout: 5s

문서 구성 전략

문서 자체를 '하이브리드' 형식으로 만드는 것과 동시에, 자주 묻는 질문은 별도의 Q&A 파일로도 분리할 계획입니다.

📁 documents_optimized/
├── 📄 01_apay_연동_가이드.md (하이브리드 70%)
│   ├── Quick Answer (즉각적인 답변)
│   ├── FAQ (자주 묻는 질문)
│   ├── 상세 가이드 (절차)
│   └── 기술 상세 (개발자용)
│
├── 📄 02_qa_apay_알림_url.md (Q&A 30%)
│   └── Q: A-Pay 알림 URL은 무엇인가요? (특정 질문 최적화)
│
├── 📄 03_qa_apay_알림_누락.md (Q&A)
│   └── Q: A-Pay 알림이 누락돼요.
│
└── 📄 04_qa_apay_알림_테스트.md (Q&A)
    └── Q: A-Pay 알림 테스트 방법은?
  • 전략:
    • 하이브리드 문서: 전체적이고 완전한 정보를 제공합니다.
    • 단독 Q&A 문서: 자주 묻는 특정 질문에 대한 검색 정확도를 높입니다.
    • 결론: 둘 다 벡터 DB에 임베딩하여 검색 커버리지를 최대화합니다.

하이브리드 형식의 장점

  • 빠른 답변 (Quick Answer): "URL 알려줘" 같은 즉답이 필요한 질문에 즉시 대응합니다.
  • 일반 질문 (FAQ): 가장 흔한 질문들을 미리 준비하여 정확도를 높입니다.
  • 깊이 있는 정보 (상세 가이드): 복잡한 설정 절차나 개념 설명에 대응합니다.
  • 검색 다양성 (별도 Q&A): 같은 내용이라도 다른 방식의 질문(Q&A)으로 검색될 확률을 높입니다.

🚀 다음 단계 및 💭 현재까지 배운 점

RAG 구축은 생각보다 험난한 여정이었습니다. 간단한 파라미터 튜닝으로 끝날 줄 알았지만, 결국 **'데이터'**가 가장 중요하다는 근본적인 문제에 도달했습니다.

현재까지 배운 점:

  • RAG는 코드만의 문제가 아니다. 문서 구조(데이터)가 검색 품질에 가장 큰 영향을 미친다.
  • 메타데이터는 선택이 아닌 필수다. (특히 여러 카테고리가 섞인 업무 지식에서는)
  • 기술로 해결하려 하기 전에, 문서 구조부터 점검하는 것이 가장 효과적이다.

다음 단계:

  • [진행 예정] 모든 문서를 하이브리드 형식으로 재작성
  • [진행 예정] 메타데이터 필터링 기능 적용
  • [검토 예정] 하이브리드 검색 도입
반응형