Skip to main content

Agentic RAG

Agentic RAG는 LLM을 자율적인 에이전트로 활용하는 RAG 아키텍처입니다. 기존 RAG가 고정된 파이프라인을 따르는 반면, Agentic RAG에서는 에이전트가 상황을 판단하여 검색, 웹 검색, 계산, API 호출 등 여러 도구 중 적절한 것을 선택하고 실행합니다.

핵심 아이디어

이전 RAG 아키텍처들은 그래프가 사전에 정의되어 있고, 조건부 엣지로 경로를 선택합니다. Agentic RAG는 이 구조를 근본적으로 바꿉니다.
  • 검색은 도구 중 하나: 벡터 검색, 웹 검색, SQL 쿼리, API 호출 등이 모두 도구(tool)로 등록됩니다
  • 에이전트가 판단: LLM이 질문을 분석하고, 어떤 도구를 어떤 순서로 사용할지 스스로 결정합니다
  • ReAct 패턴: Reasoning(추론) → Action(도구 실행) → Observation(결과 관찰) 사이클을 반복합니다
구분기존 RAGAgentic RAG
파이프라인사전 정의된 그래프에이전트가 동적으로 결정
검색파이프라인의 고정 단계여러 도구 중 하나
도구검색만 사용검색, 웹 검색, 계산, API 등
반복조건부 루프에이전트가 충분할 때까지 반복

동작 방식

에이전트가 중심에 위치하고, 도구들이 허브앤스포크(hub-and-spoke) 구조로 연결됩니다. 에이전트는 도구 실행 결과를 관찰한 뒤, 추가 도구 호출이 필요한지 판단하여 반복합니다.

LangGraph 구현

도구 정의

from langchain.chat_models import init_chat_model
from langchain_openai import OpenAIEmbeddings
from langchain_chroma import Chroma
from langchain_community.tools import TavilySearchResults
from langchain_core.tools import tool

llm = init_chat_model("gpt-4o-mini", temperature=0)

embeddings = OpenAIEmbeddings()
vectorstore = Chroma(collection_name="documents", embedding_function=embeddings)
retriever = vectorstore.as_retriever(search_kwargs={"k": 4})

@tool
def vector_search(query: str) -> str:
    """벡터 데이터베이스에서 관련 문서를 검색합니다."""
    docs = retriever.invoke(query)
    return "\n\n".join(doc.page_content for doc in docs)

@tool
def web_search(query: str) -> str:
    """웹에서 최신 정보를 검색합니다."""
    search = TavilySearchResults(max_results=3)
    results = search.invoke(query)
    return "\n\n".join(r["content"] for r in results)

@tool
def calculator(expression: str) -> str:
    """수학 계산을 수행합니다. 예: '2 + 3 * 4'"""
    import ast
    import operator

    # eval() 대신 안전한 AST 기반 파서를 사용합니다.
    # eval()은 임의 코드 실행이 가능하므로 프로덕션에서 절대 사용하지 마세요.
    ops = {
        ast.Add: operator.add, ast.Sub: operator.sub,
        ast.Mult: operator.mul, ast.Div: operator.truediv,
        ast.Pow: operator.pow, ast.USub: operator.neg,
    }

    def _eval(node):
        if isinstance(node, ast.Expression):
            return _eval(node.body)
        elif isinstance(node, ast.Constant) and isinstance(node.value, (int, float)):
            return node.value
        elif isinstance(node, ast.BinOp) and type(node.op) in ops:
            return ops[type(node.op)](_eval(node.left), _eval(node.right))
        elif isinstance(node, ast.UnaryOp) and type(node.op) in ops:
            return ops[type(node.op)](_eval(node.operand))
        raise ValueError(f"지원하지 않는 연산: {ast.dump(node)}")

    try:
        tree = ast.parse(expression, mode="eval")
        result = _eval(tree)
        return str(result)
    except Exception as e:
        return f"계산 오류: {e}"

tools = [vector_search, web_search, calculator]

에이전트 그래프

from typing import TypedDict, Annotated, Sequence
from langchain_core.messages import BaseMessage, HumanMessage, SystemMessage
from langgraph.graph import StateGraph, START, END
from langgraph.graph.message import add_messages
from langgraph.prebuilt import ToolNode, tools_condition

class AgentState(TypedDict):
    messages: Annotated[Sequence[BaseMessage], add_messages]

# 도구가 바인딩된 LLM
llm_with_tools = llm.bind_tools(tools)

SYSTEM_PROMPT = (
    "당신은 질문에 답변하는 AI 어시스턴트입니다.\n"
    "주어진 도구를 활용하여 정확한 정보를 수집한 뒤 답변하세요.\n"
    "- vector_search: 내부 문서에서 정보를 찾을 때 사용\n"
    "- web_search: 최신 정보나 내부 문서에 없는 정보가 필요할 때 사용\n"
    "- calculator: 수학 계산이 필요할 때 사용\n"
    "충분한 정보를 수집한 후에 답변하세요."
)

def agent(state: AgentState) -> AgentState:
    """에이전트가 도구 사용 여부를 판단하고 응답합니다."""
    messages = [SystemMessage(content=SYSTEM_PROMPT)] + state["messages"]
    response = llm_with_tools.invoke(messages)
    return {"messages": [response]}

# 그래프 구성
workflow = StateGraph(AgentState)

workflow.add_node("agent", agent)
workflow.add_node("tools", ToolNode(tools))

workflow.add_edge(START, "agent")
workflow.add_conditional_edges("agent", tools_condition)
workflow.add_edge("tools", "agent")

app = workflow.compile()
tools_condition은 LangGraph가 제공하는 내장 함수로, 에이전트의 응답에 도구 호출이 포함되어 있으면 tools 노드로, 아니면 END로 라우팅합니다.

실행

# 벡터 검색 활용
result = app.invoke({
    "messages": [HumanMessage(content="Self-RAG의 Reflection Token에 대해 설명해줘")]
})

# 웹 검색 활용
result = app.invoke({
    "messages": [HumanMessage(content="2024년 가장 주목받은 RAG 논문은?")]
})

# 복합 도구 활용
result = app.invoke({
    "messages": [HumanMessage(content="RAG 논문 수가 2023년 대비 2024년에 몇 배 증가했나?")]
})

# 마지막 메시지 출력
print(result["messages"][-1].content)

멀티 에이전트 확장

복잡한 시스템에서는 단일 에이전트 대신 여러 전문 에이전트를 조합할 수 있습니다.
전체 작업을 조율하는 중앙 에이전트입니다. 질문을 분석하여 적절한 전문 에이전트에게 작업을 분배하고, 각 에이전트의 결과를 취합하여 최종 답변을 구성합니다.
검색과 정보 수집을 전담합니다. 벡터 검색, 웹 검색, API 호출 등 다양한 도구를 사용하여 질문에 필요한 정보를 수집합니다.
수집된 정보를 분석하고 종합합니다. 정보 간 모순 확인, 신뢰도 평가, 핵심 인사이트 추출 등을 수행합니다.
분석 결과를 바탕으로 최종 답변을 구성합니다. 사용자의 질문 유형에 맞는 형식과 톤으로 답변을 작성합니다.
Agentic RAG는 에이전트가 도구를 자유롭게 호출하므로, 도구 호출 횟수에 제한을 두어야 합니다. LangGraph의 recursion_limit 파라미터로 최대 반복 횟수를 설정할 수 있습니다.
# 최대 반복 횟수 제한
app = workflow.compile()
result = app.invoke(
    {"messages": [HumanMessage(content="질문")]},
    config={"recursion_limit": 10},
)

기존 RAG 아키텍처와의 비교

항목Adaptive RAGAgentic RAG
파이프라인사전 정의된 그래프에이전트가 동적 결정
라우팅쿼리 복잡도 기반 분류에이전트가 상황별 판단
도구검색 + 웹 검색검색, 웹 검색, 계산, API 등 무제한
확장성새 경로 추가 시 그래프 수정 필요도구만 추가하면 확장
복잡도복잡한 조건부 엣지단순한 에이전트 루프

참고 논문

논문학회링크
Agentic Retrieval-Augmented Generation: A Survey on Agentic RAG (Singh et al., 2025)-arXiv 2501.09136