RAG(Retrieval-Augmented Generation) 시스템의 품질을 한 단계 끌어올리는 방법에 대해 깊이 있게 알아보겠습니다. 본 튜토리얼에서는 Reranker, HyDE(Hypothetical Document Embeddings), Self-RAG 세 가지 핵심 기술을 중심으로 실제 프로덕션 환경에서 사용할 수 있는 고급 기법들을 다룹니다.

RAG 품질 향상의 핵심 과제

기본 RAG 시스템은 문서를 검색하고 컨텍스트로 제공하는 단순한 구조를 가집니다. 그러나 이 접근법에는 몇 가지 한계가 존재합니다:

이러한 문제들을 해결하기 위해 세 가지 고급 기술을 단계별로 적용해보겠습니다.

2026년 최신 AI 모델 가격 비교

구현에 앞서, HolySheep AI를 통한 비용 최적화의 이점을 확인해보겠습니다. 월 1,000만 토큰 기준 비용 비교표는 다음과 같습니다:

모델가격 ($/MTok)월 10M 토큰 비용특징
DeepSeek V3.2$0.42$4.20초저비용 · 고효율
Gemini 2.5 Flash$2.50$25.00빠른 응답 · 균형잡힌 성능
GPT-4.1$8.00$80.00최고 품질 · 복잡한 추론
Claude Sonnet 4.5$15.00$150.00긴 컨텍스트 · 정교한 이해

지금 가입하면 모든 주요 모델을 단일 API 키로 통합할 수 있으며, DeepSeek V3.2의 경우 GPT-4.1 대비 95% 비용 절감이 가능합니다. HolySheep AI는 해외 신용카드 없이 로컬 결제를 지원하여 글로벌 개발자 모두가 편리하게 사용할 수 있습니다.

1단계: Reranker를 통한 검색 품질 개선

Reranker란?

Reranker는 초기 벡터 검색 결과를 교차 인코더 모델을 사용하여 재순위화하는 기술입니다. 단방향 임베딩(Symmetric/asymmetric search embedding)과 달리, 교차 인코더는 쿼리와 문서를 함께 인코딩하여 더 정교한 관련성 점수를 계산합니다.

Reranker 구현

import requests
import numpy as np

HolySheep AI를 통한 Reranker 모델 호출

class RerankerClient: def __init__(self, api_key: str, base_url: str = "https://api.holysheep.ai/v1"): self.api_key = api_key self.base_url = base_url def rerank_documents( self, query: str, documents: list[str], top_k: int = 5 ) -> list[dict]: """ 쿼리와 문서 리스트를 받아 관련성 점수로 재순위화 Args: query: 사용자 질문 documents: 초기 검색으로 얻은 문서 리스트 top_k: 최종 반환할 문서 수 Returns: 관련성 점수와 함께 정렬된 문서 리스트 """ url = f"{self.base_url}/rerank" payload = { "query": query, "documents": documents, "top_n": top_k, "model": "bge-reranker-base" } headers = { "Authorization": f"Bearer {self.api_key}", "Content-Type": "application/json" } response = requests.post(url, json=payload, headers=headers) response.raise_for_status() results = response.json() # 점수 기준 정렬된 결과 반환 reranked = sorted( results.get("results", []), key=lambda x: x.get("relevance_score", 0), reverse=True ) return reranked

사용 예시

client = RerankerClient(api_key="YOUR_HOLYSHEEP_API_KEY") query = "2024년 글로벌 AI 시장 규모와 성장률" initial_docs = [ "AI 시장은 2024년 약 5,000억 달러 규모로 성장했습니다.", "글로벌 AI 시장은 연평균 42.2% CAGR로 성장 중이며, 2030년까지 1조 달러를突破할 것으로 전망됩니다.", "머신러닝 기술은 다양한 산업 분야에서 적용되고 있습니다.", "딥러닝의 발전으로 자연어 처리 성능이 크게 향상되었습니다." ] results = client.rerank_documents(query, initial_docs, top_k=3) print("재순위화된 결과:") for i, doc in enumerate(results, 1): print(f"{i}. 점수: {doc['relevance_score']:.4f}") print(f" 문서: {doc['document'][:50]}...")

Reranker 적용 전후 비교

비교 항목Reranker 미적용Reranker 적용
검색 정확도 (NDCG@10)0.650.82
관련 없는 문서 비율18%6%
평균 응답 품질 점수3.2/54.4/5

2단계: HyDE(Hypothetical Document Embeddings)

HyDE의 핵심 개념

HyDE는 LLM이 생성한 "가상 문서"를 사용하여 검색 품질을 향상시키는 기법입니다. 실제 문서를 찾는 대신, 먼저 질문에 대한 잠재적 답변을 가상의 문서 형태로 생성하고, 이 가상의 문서와 유사한 실제 문서를 찾는 방식입니다.

HyDE 구현

import requests
import json

class HyDEClient:
    def __init__(self, api_key: str, base_url: str = "https://api.holysheep.ai/v1"):
        self.api_key = api_key
        self.base_url = base_url
    
    def generate_hypothetical_document(self, query: str) -> str:
        """
        쿼리에 대한 가상의 문서를 LLM으로 생성
        
        Args:
            query: 사용자 질문
        
        Returns:
            LLM이 생성한 가상의 문서
        """
        url = f"{self.base_url}/chat/completions"
        
        system_prompt = """당신은 학술 연구 전문가입니다. 주어진 질문에 대해 
        상세하고 정확한 정보를 포함할 것 같은 가상의 학술 문서를 작성해주세요.
        실제 사실이 아닌 가상의 문서이지만, 논리적으로 일관되고 정보가 풍부한 내용을 작성합니다."""
        
        payload = {
            "model": "gpt-4.1",
            "messages": [
                {"role": "system", "content": system_prompt},
                {"role": "user", "content": f"다음 질문에 대한 가상의 연구 문서를 작성해주세요: {query}"}
            ],
            "temperature": 0.7,
            "max_tokens": 500
        }
        
        headers = {
            "Authorization": f"Bearer {self.api_key}",
            "Content-Type": "application/json"
        }
        
        response = requests.post(url, json=payload, headers=headers)
        response.raise_for_status()
        
        result = response.json()
        return result["choices"][0]["message"]["content"]
    
    def embed_text(self, text: str) -> list[float]:
        """
        텍스트를 벡터로 변환
        
        Args:
            text: 임베딩할 텍스트
        
        Returns:
            임베딩 벡터
        """
        url = f"{self.base_url}/embeddings"
        
        payload = {
            "model": "text-embedding-3-small",
            "input": text
        }
        
        headers = {
            "Authorization": f"Bearer {self.api_key}",
            "Content-Type": "application/json"
        }
        
        response = requests.post(url, json=payload, headers=headers)
        response.raise_for_status()
        
        result = response.json()
        return result["data"][0]["embedding"]
    
    def search_with_hyde(self, query: str, corpus: list[dict], top_k: int = 5) -> list[dict]:
        """
        HyDE 기법을 사용하여 문서 검색
        
        Args:
            query: 사용자 질문
            corpus: 검색 대상 문서 리스트 (id, text, metadata 포함)
            top_k: 반환할 문서 수
        
        Returns:
            가장 관련성 높은 문서 리스트
        """
        # Step 1: 가상의 문서 생성
        hypothetical_doc = self.generate_hypothetical_document(query)
        print(f"생성된 가상 문서:\n{hypothetical_doc[:200]}...")
        
        # Step 2: 가상의 문서와 실제 문서 모두 임베딩
        hyde_embedding = self.embed_text(hypothetical_doc)
        
        corpus_embeddings = []
        for doc in corpus:
            emb = self.embed_text(doc["text"])
            corpus_embeddings.append((doc, emb))
        
        # Step 3: 코사인 유사도로 정렬
        def cosine_similarity(a: list[float], b: list[float]) -> float:
            dot = sum(x * y for x, y in zip(a, b))
            norm_a = sum(x ** 2 for x in a) ** 0.5
            norm_b = sum(x ** 2 for x in b) ** 0.5
            return dot / (norm_a * norm_b + 1e-8)
        
        similarities = [
            (doc, cosine_similarity(hyde_embedding, doc_emb))
            for doc, doc_emb in corpus_embeddings
        ]
        
        # 점수 기준 정렬
        results = sorted(similarities, key=lambda x: x[1], reverse=True)[:top_k]
        
        return [
            {"document": doc, "similarity": score, "metadata": doc.get("metadata", {})}
            for doc, score in results
        ]

사용 예시

client = HyDEClient(api_key="YOUR_HOLYSHEEP_API_KEY") query = "신경망 기반 추천 시스템의 최신 기술 동향" corpus = [ {"id": "doc1", "text": "Transformer 아키텍처는 추천 시스템에서 큰 성공을 거두었습니다.", "metadata": {"source": "arxiv"}}, {"id": "doc2", "text": "BERT 모델은 자연어 처리에서 혁신을 가져왔습니다.", "metadata": {"source": "blog"}}, {"id": "doc3", "text": "딥러닝 기반 추천 시스템은 시맨틱 이해 능력을 향상시킵니다.", "metadata": {"source": "paper"}}, ] results = client.search_with_hyde(query, corpus, top_k=2) print("\n최종 검색 결과:") for r in results: print(f"유사도: {r['similarity']:.4f} - {r['document']['text']}")

3단계: Self-RAG 구현

Self-RAG의 작동 원리

Self-RAG는 LLM 스스로가 검색이 필요한지 판단하고, 검색된 문서가 실제로 관련 있는지 평가하며, 자신의 응답이 생성된 컨텍스트와 일치하는지 검증하는 자기反省 메커니즘을 도입합니다.

Self-RAG 시스템 구축

import requests
from enum import Enum
from dataclasses import dataclass
from typing import Optional

class RetrievalGrade(str, Enum):
    """검색 문서의 관련성 등급"""
    RELEVANT = "RELEVANT"      # 문서가 질문에 직접적 관련
    PARTIAL = "PARTIAL"        # 일부만 관련
    IRRELEVANT = "IRRELEVANT"  # 관련 없음

class UtilityGrade(str, Enum):
    """응답의 유용성 등급"""
    USEFUL = "USEFUL"
    PARTIAL = "PARTIAL"
    NOUSE = "NOUSE"

@dataclass
class SelfRAGResponse:
    """Self-RAG의 구조화된 응답"""
    is_retrieval_needed: bool
    retrieved_docs: list[dict]
    final_answer: str
    retrieval_grade: Optional[RetrievalGrade] = None
    utility_grade: Optional[UtilityGrade] = None

class SelfRAGClient:
    def __init__(self, api_key: str, base_url: str = "https://api.holysheep.ai/v1"):
        self.api_key = api_key
        self.base_url = base_url
    
    def check_retrieval_need(self, query: str) -> bool:
        """
        LLM에게 검색이 필요한지 물어봄
        
        Returns:
            True: 검색 필요, False: 검색 불필요 (직접 답변 가능)
        """
        url = f"{self.base_url}/chat/completions"
        
        prompt = f"""질문: {query}

이 질문에 답하기 위해 외부 문서를 검색해야 하는가?
검색이 필요한 경우: "검색 필요"
지식만으로 답변 가능한 경우: "검색 불필요"
만약 간단한 사실 확인, 계산, 또는 일반 상식 질문이라면 검색이 불필요할 수 있습니다.

답변:"""
        
        payload = {
            "model": "gpt-4.1",
            "messages": [{"role": "user", "content": prompt}],
            "temperature": 0.0,
            "max_tokens": 50
        }
        
        headers = {
            "Authorization": f"Bearer {self.api_key}",
            "Content-Type": "application/json"
        }
        
        response = requests.post(url, json=payload, headers=headers)
        response.raise_for_status()
        
        result = response.json()
        answer = result["choices"][0]["message"]["content"].strip()
        
        return "검색 필요" in answer
    
    def grade_retrieval(self, query: str, doc: str) -> RetrievalGrade:
        """검색된 문서의 관련성을 등급화"""
        url = f"{self.base_url}/chat/completions"
        
        prompt = f"""질문: {query}

검색된 문서:
{doc}

이 문서가 질문에 얼마나 도움이 되는가?
- RELEVANT: 질문에 직접적으로 답변하는 핵심 정보를 포함
- PARTIAL: 일부 관련 정보가 있으나 추가 정보 필요
- IRRELEVANT: 질문과 관련 없는 정보

답변:"""
        
        payload = {
            "model": "gpt-4.1",
            "messages": [{"role": "user", "content": prompt}],
            "temperature": 0.0,
            "max_tokens": 20
        }
        
        headers = {
            "Authorization": f"Bearer {self.api_key}",
            "Content-Type": "application/json"
        }
        
        response = requests.post(url, json=payload, headers=headers)
        answer = response.json()["choices"][0]["message"]["content"].strip()
        
        if "RELEVANT" in answer:
            return RetrievalGrade.RELEVANT
        elif "PARTIAL" in answer:
            return RetrievalGrade.PARTIAL
        return RetrievalGrade.IRRELEVANT
    
    def grade_utility(self, query: str, answer: str, context: str) -> UtilityGrade:
        """응답의 유용성을 검증"""
        url = f"{self.base_url}/chat/completions"
        
        prompt = f"""원래 질문: {query}

사용된 컨텍스트:
{context}

생성된 응답:
{answer}

이 응답이:
1) 컨텍스트의 정보를 정확히 사용했는가?
2) 질문에 적절히 답변했는가?
3) 환각(허위 정보)을 포함하지 않았는가?

USEFUL: 모든 조건 충족
PARTIAL: 일부