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.65 | 0.82 |
| 관련 없는 문서 비율 | 18% | 6% |
| 평균 응답 품질 점수 | 3.2/5 | 4.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: 일부