# 에이전틱 RAG 시스템 구축하기

코드는 이 노트북에서 따라할 수 있습니다. Google Colab에서 실행할 수 있습니다.

Retrieval Augmented Generation(RAG) 시스템은 데이터 검색과 생성 모델의 능력을 결합하여 상황에 맞는 답변을 제공합니다. 예를 들어, 사용자의 질문이 검색 엔진에 전달되고, 검색된 결과가 질문과 함께 모델에 제공됩니다. 모델은 질문과 검색된 정보를 바탕으로 답변을 생성합니다.

에이전틱 RAG(검색 증강 생성)는 **자율 에이전트와 동적 지식 검색**을 결합하여 기존 RAG 시스템을 확장합니다.

기존 RAG 시스템은 LLM이 검색된 데이터를 바탕으로 답변을 생성하지만, 에이전틱 RAG는 **검색과 생성 과정을 지능적으로 제어**하여 효율성과 정확성을 높입니다.

기존 RAG 시스템의 주요 한계는 **단일 검색 단계에 의존**하고, 사용자의 질문과 직접적인 의미적 유사성에만 집중하여 관련 정보를 놓칠 수 있다는 점입니다.

에이전틱 RAG는 에이전트가 검색 쿼리를 자율적으로 생성하고, 검색 결과를 비판적으로 평가하며, 여러 번의 검색 단계를 거쳐 더 맞춤화되고 포괄적인 출력을 생성할 수 있도록 합니다.

## DuckDuckGo를 활용한 기본 검색

이제 DuckDuckGo를 사용해 웹을 검색하는 간단한 에이전트를 만들어봅시다. 이 에이전트는 정보를 검색하고, 답변을 종합하여 질문에 답합니다. 에이전틱 RAG를 활용하면 Alfred의 에이전트는 다음과 같은 일을 할 수 있습니다:

* 최신 슈퍼히어로 파티 트렌드 검색
* 결과를 고급스러운 요소로 세분화
* 정보를 종합하여 완성된 계획 제안

Alfred의 에이전트가 이를 어떻게 수행하는지 살펴봅시다:

```python
from smolagents import CodeAgent, DuckDuckGoSearchTool, InferenceClientModel

# 검색 도구 초기화
search_tool = DuckDuckGoSearchTool()

# 모델 초기화
model = InferenceClientModel()

agent = CodeAgent(
    model=model,
    tools=[search_tool],
)

# 예시 사용법
response = agent.run(
    "Search for luxury superhero-themed party ideas, including decorations, entertainment, and catering."
)
print(response)
```

에이전트의 동작 과정:

1. **요청 분석:** Alfred의 에이전트는 쿼리의 핵심 요소(고급 슈퍼히어로 테마 파티, 장식, 엔터테인먼트, 케이터링 등)를 파악합니다.
2. **검색 수행:** DuckDuckGo를 활용해 Alfred의 고급스러운 취향에 맞는 최신 정보를 검색합니다.
3. **정보 종합:** 검색 결과를 바탕으로 파티의 모든 측면을 아우르는 실행 가능한 계획을 만듭니다.
4. **정보 저장:** 검색된 정보를 저장해, 이후 파티 준비 시 효율적으로 재활용할 수 있습니다.

## 맞춤형 지식 베이스 도구

특정 작업에는 맞춤형 지식 베이스가 매우 유용합니다. 벡터 데이터베이스에 저장된 기술 문서나 전문 지식을 쿼리하는 도구를 만들어봅시다. 의미적 검색을 통해 Alfred의 요구에 가장 적합한 정보를 찾을 수 있습니다.

벡터 데이터베이스는 텍스트나 기타 데이터를 머신러닝 모델로 임베딩(숫자 벡터)하여 저장합니다. 이를 통해 의미적으로 유사한 정보를 고차원 공간에서 찾을 수 있습니다.

이 접근법은 사전 정의된 지식과 의미적 검색을 결합해 상황에 맞는 솔루션을 제공합니다. Alfred는 전문 지식에 접근해 파티의 모든 세부 사항을 완벽하게 준비할 수 있습니다.

아래 예시에서는 BM25 검색기를 사용해 맞춤형 지식 베이스에서 파티 아이디어를 검색하는 도구를 만듭니다. `RecursiveCharacterTextSplitter`로 문서를 작은 청크로 나눠 검색 효율을 높입니다.

```python
from langchain.docstore.document import Document
from langchain.text_splitter import RecursiveCharacterTextSplitter
from smolagents import Tool
from langchain_community.retrievers import BM25Retriever
from smolagents import CodeAgent, InferenceClientModel

class PartyPlanningRetrieverTool(Tool):
    name = "party_planning_retriever"
    description = "Alfred의 슈퍼히어로 테마 파티를 위한 아이디어를 의미적 검색으로 찾아줍니다."
    inputs = {
        "query": {
            "type": "string",
            "description": "파티 준비나 슈퍼히어로 테마와 관련된 쿼리입니다.",
        }
    }
    output_type = "string"

    def __init__(self, docs, **kwargs):
        super().__init__(**kwargs)
        self.retriever = BM25Retriever.from_documents(
            docs, k=5  # 상위 5개 문서 검색
        )

    def forward(self, query: str) -> str:
        assert isinstance(query, str), "검색 쿼리는 문자열이어야 합니다."

        docs = self.retriever.invoke(
            query,
        )
        return "\n검색된 아이디어:\n" + "".join(
            [
                f"\n\n===== 아이디어 {str(i)} =====\n" + doc.page_content
                for i, doc in enumerate(docs)
            ]
        )

# 파티 준비에 관한 지식 베이스 예시
party_ideas = [
    {"text": "고급 장식(금색 장식, 벨벳 커튼 등)이 있는 슈퍼히어로 테마 가면무도회.", "source": "Party Ideas 1"},
    {"text": "배트맨, 원더우먼 등 슈퍼히어로 테마 음악을 연주할 전문 DJ 고용.", "source": "Entertainment Ideas"},
    {"text": "케이터링 메뉴에 슈퍼히어로 이름을 붙인 요리 제공(예: '헐크의 그린 스무디', '아이언맨의 파워 스테이크').", "source": "Catering Ideas"},
    {"text": "장소 곳곳에 슈퍼히어로 로고와 고담 등 도시 프로젝션 장식.", "source": "Decoration Ideas"},
    {"text": "VR을 활용한 슈퍼히어로 시뮬레이션, 테마 게임 등 인터랙티브 체험 제공.", "source": "Entertainment Ideas"}
]

source_docs = [
    Document(page_content=doc["text"], metadata={"source": doc["source"]})
    for doc in party_ideas
]

# 문서를 작은 청크로 분할
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)

# 검색 도구 생성
party_planning_retriever = PartyPlanningRetrieverTool(docs_processed)

# 에이전트 초기화
agent = CodeAgent(tools=[party_planning_retriever], model=InferenceClientModel())

# 예시 사용법
response = agent.run(
    "Find ideas for a luxury superhero-themed party, including entertainment, catering, and decoration options."
)

print(response)
```

이 향상된 에이전트는 다음을 수행할 수 있습니다:
1. 먼저 문서에서 관련 정보 검색
2. 지식 베이스의 인사이트 결합
3. 대화 맥락을 메모리에 유지

## 고도화된 검색 기능

에이전틱 RAG 시스템을 구축할 때, 에이전트는 다음과 같은 고급 전략을 사용할 수 있습니다:

1. **쿼리 재구성(Query Reformulation):** 원본 쿼리 대신, 문서에 더 잘 맞는 최적화된 검색어 생성
2. **쿼리 분해(Query Decomposition):** 여러 정보를 포함한 쿼리를 분해해 각각 검색
3. **쿼리 확장(Query Expansion):** 쿼리를 다양한 표현으로 확장해 여러 방식으로 검색
4. **재정렬(Reranking):** Cross-Encoder 등으로 검색 결과와 쿼리의 의미적 관련성 점수 부여
5. **다단계 검색(Multi-Step Retrieval):** 초기 결과를 바탕으로 추가 검색 반복
6. **소스 통합(Source Integration):** 웹 검색, 로컬 문서 등 다양한 소스 결합
7. **결과 검증(Result Validation):** 검색된 내용의 적합성과 정확성 평가 후 답변에 포함

효과적인 에이전틱 RAG 시스템을 위해서는 여러 측면을 신중히 고려해야 합니다. 에이전트는 **쿼리 유형과 맥락에 따라 적절한 도구를 선택**해야 하며, 메모리 시스템을 통해 대화 이력을 관리하고 중복 검색을 방지해야 합니다. 또한, 예비 전략을 마련해 주요 검색 방법이 실패해도 가치를 제공할 수 있어야 하며, 검증 단계를 통해 검색 결과의 정확성과 적합성을 보장해야 합니다.

## 참고 자료

- [Agentic RAG: turbocharge your RAG with query reformulation and self-query! 🚀](https://huggingface.co/learn/cookbook/agent_rag) - smolagents로 에이전틱 RAG 시스템을 개발하는 레시피

