에이전트형 RAG (Agentic RAG)

에이전트형 RAG (Agentic RAG)

출처: Agentic RAG — Hugging Face smolagents 공식 문서

질문에 답할 때, 그냥 답을 뱉는 모델보다 근거가 분명한 답이 훨씬 믿음직스러워요. 이 페이지는 검색(리트리벌) 능력을 가진 에이전트로 에이전트형 RAG 시스템을 만드는 방법을 소개해요.

RAG(검색 증강 생성)란 무엇인가요?

검색 증강 생성(RAG, Retrieval-Augmented Generation)은 대형 언어 모델의 힘과 외부 지식 검색을 결합해서 더 정확하고 사실에 근거한 답을 만드는 방식이에요. 핵심은 "LLM으로 사용자 질문에 답하되, 그 답의 근거를 지식 베이스에서 검색한 정보에 두는 것"이에요.

RAG를 왜 쓸까요?

RAG는 그냥 LLM이나 파인튜닝한 모델을 쓰는 것보다 여러 장점이 있어요.

  1. 사실 근거(Factual Grounding) — 답을 검색된 사실에 고정시켜서 환각을 줄여줘요
  2. 도메인 특화(Domain Specialization) — 모델을 다시 학습시키지 않고도 특정 분야 지식을 넣을 수 있어요
  3. 최신 지식(Knowledge Recency) — 모델의 학습 시점을 넘어선 정보에도 접근할 수 있어요
  4. 투명성(Transparency) — 생성된 내용에 출처를 달 수 있어요
  5. 통제(Control) — 모델이 어떤 정보에 접근할 수 있는지 정밀하게 조절할 수 있어요

기존 RAG의 한계

장점이 있긴 하지만, 기존 RAG 방식도 몇 가지 문제를 안고 있어요.

  • 단일 검색 단계 — 처음 검색 결과가 별로면 최종 생성 결과도 망가져요
  • 질문과 문서의 불일치 — 사용자 질문(보통 의문문)과 답이 담긴 문서(보통 평서문)가 잘 맞지 않을 수 있어요
  • 제한된 추론 — 단순한 RAG 파이프라인은 다단계 추론이나 질문 다듬기를 지원하지 않아요
  • 컨텍스트 윈도우 제약 — 검색된 문서가 모델의 컨텍스트 윈도우 안에 들어가야 해요

에이전트형 RAG: 더 강력한 접근

이런 한계를 검색 능력을 갖춘 에이전트로 극복할 수 있어요. 에이전트형 RAG는 RAG를 고정된 파이프라인이 아니라 상호작용하는 추론 중심의 과정으로 바꿔요.

에이전트형 RAG의 핵심 이점

검색 도구를 가진 에이전트는 이런 일을 할 수 있어요.

  1. 최적화된 검색 쿼리 생성 — 사용자 질문을 검색에 잘 맞는 쿼리로 바꿔줘요
  2. 여러 번 검색 수행 — 필요에 따라 반복적으로 정보를 검색해요
  3. 검색 내용에 대한 추론 — 여러 출처를 분석·종합해서 결론을 내려요
  4. 자기 비판과 개선 — 검색 결과를 평가하고 접근 방식을 조정해요

이 방식은 고급 RAG 기법을 자연스럽게 구현해요.

  • 가상 문서 임베딩(HyDE) — 사용자 쿼리를 그대로 쓰지 않고 검색에 최적화된 쿼리를 만들어요 (논문 참고)
  • 자가 쿼리 개선(Self-Query Refinement) — 에이전트가 초기 결과를 분석하고 다듬어진 쿼리로 후속 검색을 수행해요 (기법 참고)

에이전트형 RAG 시스템 만들기

Hugging Face Transformers 라이브러리에 관한 질문에 그 문서를 검색해서 답하는 에이전트를 단계별로 만들어 볼게요. 아래 코드를 따라 하거나, smolagents GitHub 저장소의 전체 예제(examples/rag.py)를 참고해도 돼요.

1단계: 필요한 의존성 설치

먼저 필요한 패키지를 설치해요.

pip install smolagents pandas langchain langchain-community sentence-transformers datasets python-dotenv rank_bm25 --upgrade

Hugging Face Inference API를 쓸 거라면 API 토큰을 설정해야 해요.

# 환경 변수(HF_TOKEN 포함) 로드
from dotenv import load_dotenv
load_dotenv()

2단계: 지식 베이스 준비

Hugging Face 문서 데이터셋을 불러와서 검색용으로 준비해요.

import datasets
from langchain.docstore.document import Document
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain_community.retrievers import BM25Retriever

# Hugging Face 문서 데이터셋 로드
knowledge_base = datasets.load_dataset("m-ric/huggingface_doc", split="train")

# Transformers 문서만 골라내기
knowledge_base = knowledge_base.filter(lambda row: row["source"].startswith("huggingface/transformers"))

# 데이터셋 항목을 Document 객체로 변환 (메타데이터 포함)
source_docs = [
    Document(page_content=doc["text"], metadata={"source": doc["source"].split("/")[1]})
    for doc in knowledge_base
]

# 검색 성능을 위해 문서를 작은 청크로 분할
text_splitter = RecursiveCharacterTextSplitter(
    chunk_size=500,  # 청크당 문자 수
    chunk_overlap=50,  # 컨텍스트 유지를 위한 청크 간 겹침
    add_start_index=True,
    strip_whitespace=True,
    separators=["\n\n", "\n", ".", " ", ""],  # 분할 우선순위
)
docs_processed = text_splitter.split_documents(source_docs)

print(f"Knowledge base prepared with {len(docs_processed)} document chunks")

3단계: 검색 도구(Retriever Tool) 만들기

이제 에이전트가 지식 베이스에서 정보를 검색할 수 있는 맞춤 도구를 만들어요.

from smolagents import Tool

class RetrieverTool(Tool):
    name = "retriever"
    description = "Uses semantic search to retrieve the parts of transformers documentation that could be most relevant to answer your query."
    inputs = {
        "query": {
            "type": "string",
            "description": "The query to perform. This should be semantically close to your target documents. Use the affirmative form rather than a question.",
        }
    }
    output_type = "string"

    def __init__(self, docs, **kwargs):
        super().__init__(**kwargs)
        # 처리된 문서로 검색기 초기화
        self.retriever = BM25Retriever.from_documents(
            docs, k=10  # 가장 관련성 높은 문서 10개 반환
        )

    def forward(self, query: str) -> str:
        """제공된 쿼리를 기반으로 검색 실행"""
        assert isinstance(query, str), "Your search query must be a string"

        # 관련 문서 검색
        docs = self.retriever.invoke(query)

        # 가독성을 위해 검색된 문서 포맷팅
        return "\nRetrieved documents:\n" + "".join(
            [
                f"\n\n===== Document {str(i)} =====\n" + doc.page_content
                for i, doc in enumerate(docs)
            ]
        )

# 처리된 문서로 검색 도구 초기화
retriever_tool = RetrieverTool(docs_processed)

[!TIP] 여기서는 BM25(어휘 기반 검색)를 단순함과 속도를 위해 사용해요. 운영 시스템에서는 임베딩을 활용한 의미 검색이 검색 품질을 높여줄 수 있어요. 고품질 임베딩 모델은 MTEB 리더보드에서 확인할 수 있어요.

4단계: 고급 검색 에이전트 만들기

이제 검색 도구를 사용해서 질문에 답할 수 있는 에이전트를 만들어요.

from smolagents import InferenceClientModel, CodeAgent

# 검색 도구를 가진 에이전트 초기화
agent = CodeAgent(
    tools=[retriever_tool],  # 에이전트가 사용할 도구 목록
    model=InferenceClientModel(),  # 기본 모델 "Qwen/Qwen3-Next-80B-A3B-Thinking"
    max_steps=4,  # 추론 단계 수 제한
    verbosity_level=2,  # 상세한 에이전트 추론 표시
)

# 특정 모델을 쓰려면 이렇게 지정할 수 있어요:
# model=InferenceClientModel(model_id="meta-llama/Llama-3.3-70B-Instruct")

[!TIP] Inference Providers는 서버리스 추론 파트너가 제공하는 수백 개의 모델에 접근을 제공해요. 지원되는 공급자 목록은 여기에서 확인할 수 있어요.

5단계: 에이전트 실행해서 질문에 답하기

Transformers에 관한 질문에 에이전트로 답해 볼게요.

# 정보 검색이 필요한 질문
question = "For a transformers model training, which is slower, the forward or the backward pass?"

# 에이전트 실행해서 답 얻기
agent_output = agent.run(question)

# 최종 답 표시
print("\nFinal answer:")
print(agent_output)

에이전트형 RAG의 실제 활용

에이전트형 RAG 시스템은 다양한 곳에 적용할 수 있어요.

  1. 기술 문서 도우미 — 복잡한 기술 문서를 사용자가 쉽게 탐색하도록 도와줘요
  2. 연구 논문 분석 — 과학 논문에서 정보를 뽑아 종합해요
  3. 법률 문서 검토 — 법적 문서에서 관련 판례와 조항을 찾아줘요
  4. 고객 지원 — 제품 문서와 지식 베이스를 바탕으로 질문에 답해요
  5. 교육 튜터링 — 교과서와 학습 자료를 바탕으로 설명을 제공해요

정리하며

에이전트형 RAG는 기존 RAG 파이프라인보다 한 단계 더 나아간 방식이에요. LLM 에이전트의 추론 능력과 검색 시스템의 사실 근거를 결합하면, 더 강력하고 유연하며 정확한 정보 시스템을 만들 수 있어요.

이 페이지에서 보여준 접근은:

  • 단일 단계 검색의 한계를 넘어서고
  • 지식 베이스와 더 자연스럽게 상호작용하며
  • 자기 비판과 쿼리 개선을 통한 지속적 개선의 틀을 제공해요

여러분만의 에이전트형 RAG를 만들 때는 다양한 검색 방법과 에이전트 구조, 지식 소스를 실험해 보면서 자신의 사용 사례에 맞는 최적 구성을 찾아보세요.