자율적으로 “찾고-검증하고-다시 찾는” Agentic RAG 구현법 (2026년 6월 실전 패턴)
들어가며
전통적인 RAG는 보통 (1) 한 번 검색 → (2) 컨텍스트 주입 → (3) 답변 생성으로 끝납니다. 하지만 실무 질문은 대개 “검색 쿼리를 어떻게 쪼개야 하지?”, “지금 가져온 근거가 부족한데?”, “서로 충돌하는 문서가 있는데?” 같은 후속 의사결정이 필요합니다. 이 지점에서 Agentic RAG(=RAG를 tool call로 만들고, 모델이 반복/수정/중단을 결정하는 구조)가 힘을 발휘합니다. LangGraph 기반으로 iterative retrieval + self-correction + answer grading을 그래프 루프로 엮는 패턴이 2026년 상반기 실전 레퍼런스로 많이 공유되고 있습니다. (docs.langchain.com)
언제 쓰면 좋은가
- 질문이 모호하거나(“최근 정책 변경 요약해줘”), 검색 결과 품질 편차가 크고 재질의/재탐색이 자주 필요한 도메인
- “근거 충분성(evidence sufficiency)”이나 “충돌 해결”처럼 답을 만들기 전의 검증 루프가 필요한 경우(기술 문서/규정/법무/금융/헬스케어) (arxiv.org)
- 운영 단계에서 관측/트레이싱/평가를 붙여 “싼 경로(단발 RAG)”와 “비싼 경로(Agentic)”를 라우팅하고 싶은 경우 (callsphere.ai)
언제 쓰면 안 되는가
- FAQ처럼 검색 난이도가 낮고 정답 근거가 명확한 케이스(Agentic 루프가 비용만 증가)
- SLA가 빡빡한 온라인 트래픽에서 “최악의 경우 반복 3~5회” 같은 변동성을 감당 못 하는 경우
- 보안/거버넌스가 준비되지 않은 상태에서 툴을 마구 붙이는 경우(자율 에이전트는 공격 표면이 급증한다는 경고가 2026년에 강하게 나옵니다) (techradar.com)
🔧 핵심 개념
주요 개념 정의
- Agentic RAG: retrieval을 “전처리 단계”가 아니라 에이전트가 호출하는 Tool로 노출하고, 검색→판정→재검색→정리→답변을 반복 루프로 구성하는 RAG 패턴 (detached-node.dev)
- Self-correction / Query rewrite: 검색 결과가 빈약하거나 엉뚱하면, 에이전트가 쿼리를 재작성해 재시도 (callsphere.ai)
- Retrieval grading / Evidence sufficiency: “가져온 근거로 답이 가능한가?”를 점수화/판정하고 부족하면 루프를 계속(최근 연구에선 evidence sufficiency를 정교하게 스코어링하는 프레임도 제안) (arxiv.org)
- Bounded autonomy(자율성 경계): 모델이 무한 루프를 돌지 않도록 max_iterations, timeout, budget을 런타임이 강제
- Hosted retrieval vs Bring-your-own retrieval: OpenAI Responses API의
file_search처럼 “벡터스토어+검색을 호스팅”에 맡길지, Qdrant/pgvector/Elastic 등 “직접 운영”할지 (platform.openai.com)
내부 작동 방식(구조/흐름)
2026년 실전에서 많이 쓰는 구조는 “그래프 기반 상태 머신”입니다(LangGraph가 대표적). 핵심은 노드 간 전이가 ‘모델 판단’ + ‘하드 가드레일’의 합으로 결정된다는 점입니다. (docs.langchain.com)
대표 플로우(단일 에이전트, iterative retrieval):
1) Planner(옵션): 질문을 sub-queries로 분해하거나, 어떤 retriever를 쓸지(내부 KB vs 웹) 결정
2) Retrieve(tool): vector search / keyword search / hybrid search 실행
3) Grade Retrieval: (a) 관련성, (b) 다양성, (c) 최신성, (d) 상충 여부 등을 평가
4) Rewrite Query: 부족하면 쿼리 수정 후 2)로 회귀
5) Synthesize Answer: 근거를 인용/요약하며 답변 생성
6) Answer Check: 근거 대비 주장 과대/환각 여부를 간단히 재평가 후 반환
여기서 중요한 차별점은 “retrieve-then-generate”가 아니라 retrieve가 ‘행동(act)’이 되어 ReAct 루프에 들어간다는 겁니다. (detached-node.dev)
또한 최신 연구들은 retrieval 인터페이스 자체를 계층화하거나(A-RAG), 그래프/인용 검증 루프까지 포함(TechGraphRAG)하는 방향으로 확장 중입니다. (arxiv.org)
다른 접근과의 차이점
- Vanilla RAG: 단발성이라 싸고 단순하지만, 실패 시 “그냥 틀린 답”이 나오기 쉬움
- Multi-query RAG(비에이전트): 여러 쿼리로 한 번에 많이 가져오지만, 여전히 “부족하면 재시도” 같은 동적 제어가 약함
- Agentic RAG: 성공률이 올라가지만(특히 어려운 질문), 토큰/지연/보안 리스크가 증가하며 운영 난이도가 높음. 엔터프라이즈가 파일럿에서 못 넘어가는 이유로 운영성/ROI가 자주 지목됩니다. (itpro.com)
💻 실전 코드
아래 예시는 “사내 기술문서 KB + 장애 티켓 요약” 같은 현실 시나리오를 가정합니다.
- 목표: “장애 현상”을 입력하면
1) KB에서 관련 Runbook/ADR을 찾고
2) 부족하면 쿼리를 재작성해 재검색하고
3) 충분한 근거가 모이면 “원인/조치/검증 체크리스트” 형태로 답을 생성 - 스택: LangGraph(오케스트레이션) + OpenAI Responses API의
file_search(호스팅 retrieval)file_search는 벡터스토어에 업로드된 파일을 대상으로 검색하고, 결과를 모델 컨텍스트에 주입하는 managed primitive로 소개됩니다. (platform.openai.com)- LangGraph는 “retriever tool을 언제 쓸지/루프를 어떻게 돌지”를 그래프로 구성하는 문서를 제공합니다. (docs.langchain.com)
1) 셋업 (의존성/환경)
python -m venv .venv
source .venv/bin/activate
pip install -U langgraph langchain-openai langchain-core pydantic python-dotenv
export OPENAI_API_KEY="..."
# 사전에 OpenAI Vector Store를 만들고 문서(런북/ADR/티켓 가이드)를 업로드해둔다.
export OPENAI_VECTOR_STORE_ID="vs_..."
2) 그래프: retrieve → grade → (rewrite|answer)
# agentic_rag_incident.py
from __future__ import annotations
import os
from typing import List, Literal, Optional, TypedDict
from pydantic import BaseModel, Field
from langgraph.graph import StateGraph, END
from langchain_openai import ChatOpenAI
# 주의: 아래 코드는 "구조"가 핵심이며,
# OpenAI file_search tool 연결은 프로젝트에 맞게 감싸는 형태로 쓰는 걸 권장합니다.
# (Responses API의 file_search 개념/설정은 공식 가이드를 참고) ([platform.openai.com](https://platform.openai.com/docs/guides/tools-file-search/?utm_source=openai))
class RetrievedChunk(BaseModel):
doc_id: str
title: str
snippet: str
class AgentState(TypedDict):
incident: str
query: str
iter: int
retrieved: List[RetrievedChunk]
decision: Optional[str]
answer: Optional[str]
class RetrievalGrade(BaseModel):
verdict: Literal["good", "bad"] = Field(..., description="검색 결과가 답변에 충분하면 good")
reason: str
improved_query: Optional[str] = Field(None, description="bad일 때 재검색용 쿼리")
llm = ChatOpenAI(model="gpt-4.1-mini", temperature=0)
VECTOR_STORE_ID = os.environ["OPENAI_VECTOR_STORE_ID"]
def file_search(query: str, k: int = 6) -> List[RetrievedChunk]:
"""
실제 운영에서는 OpenAI Responses API의 file_search를 호출해 결과를 받아오세요.
file_search는 벡터스토어 기반 검색을 managed로 제공하는 방식입니다. ([platform.openai.com](https://platform.openai.com/docs/guides/tools-file-search/?utm_source=openai))
여기서는 '도메인 어댑터' 위치만 보여주기 위해 더미 형태로 둡니다.
"""
raise NotImplementedError("Connect to OpenAI Responses API file_search here.")
def node_retrieve(state: AgentState) -> AgentState:
chunks = file_search(state["query"], k=6)
state["retrieved"] = chunks
state["iter"] += 1
return state
def node_grade(state: AgentState) -> AgentState:
# retrieved를 너무 길게 넣지 말고 "제목+스니펫" 위주로.
context = "\n\n".join(
[f"[{c.doc_id}] {c.title}\n{c.snippet}" for c in state["retrieved"]]
)[:8000]
prompt = f"""
You are grading whether the retrieved context is sufficient to answer an incident question.
Incident:
{state["incident"]}
Search query used:
{state["query"]}
Retrieved context:
{context}
Return JSON with:
- verdict: good|bad
- reason
- improved_query (only if bad): rewrite query with missing constraints, components, error codes, and synonyms.
"""
grade = llm.with_structured_output(RetrievalGrade).invoke(prompt)
state["decision"] = grade.verdict
if grade.verdict == "bad" and grade.improved_query:
state["query"] = grade.improved_query
return state
def node_answer(state: AgentState) -> AgentState:
context = "\n\n".join(
[f"[{c.doc_id}] {c.title}\n{c.snippet}" for c in state["retrieved"]]
)[:12000]
prompt = f"""
You are an SRE assistant. Answer using ONLY the retrieved context.
If context is insufficient, say what is missing and propose next retrieval targets.
Incident:
{state["incident"]}
Context:
{context}
Write:
1) Likely root cause (with doc refs)
2) Immediate mitigation steps
3) Verification checklist
4) What evidence is missing (if any)
"""
state["answer"] = llm.invoke(prompt).content
return state
def route_after_grade(state: AgentState) -> str:
# 하드 가드레일: 최대 3회까지만 루프
if state["decision"] == "good":
return "answer"
if state["iter"] >= 3:
return "answer"
return "retrieve"
def build_graph():
g = StateGraph(AgentState)
g.add_node("retrieve", node_retrieve)
g.add_node("grade", node_grade)
g.add_node("answer", node_answer)
g.set_entry_point("retrieve")
g.add_edge("retrieve", "grade")
g.add_conditional_edges("grade", route_after_grade, {
"retrieve": "retrieve",
"answer": "answer",
})
g.add_edge("answer", END)
return g.compile()
if __name__ == "__main__":
app = build_graph()
incident = (
"배포 이후 결제 API에서 502가 급증했고, 로그에 upstream reset이 보입니다. "
"Envoy/Ingress 경로에서 발생하는 것 같고, 특정 리전에서만 재현됩니다."
)
init: AgentState = {
"incident": incident,
"query": "payment api 502 upstream reset envoy ingress regional",
"iter": 0,
"retrieved": [],
"decision": None,
"answer": None,
}
out = app.invoke(init)
print(out["answer"])
예상 출력(형태)
- 근거 문서 ID를 인용하며 “가능성 높은 원인 2~3개 + 즉시 조치 + 검증 체크리스트”
- 3회 이내로 근거가 부족하면 “부족한 증거(예: Envoy upstream cluster 설정/배포 diff/특정 리전 LB 로그)”를 명시하고, 다음 검색 타깃을 제안
3) 확장: “cheap path vs agentic path” 라우팅
실무에서는 모든 요청을 Agentic 루프로 보내면 비용이 터집니다. LangGraph 기반 사례에서도 cheap path(단발 RAG)와 agentic path(반복/검증)를 나눠 라우팅하며 비용 분석을 강조합니다. (callsphere.ai)
간단한 휴리스틱은:
- 질문 길이/불확실성(“같은데/같습니다/아마”) + 과거 실패율 + 도메인(규정/보안/법무)을 feature로 점수화
- 점수 낮으면 단발 RAG, 높으면 Agentic
⚡ 실전 팁 & 함정
Best Practice
1) 자율성 예산을 코드로 강제하라
max_iterations,timeout,max_tool_calls,token_budget를 런타임에서 하드하게 제한- 자율 에이전트는 운영 관점에서 “무한 루프/비용 폭주”가 제일 먼저 터집니다.
2) Retrieval 품질을 “점수/판정”으로 외부화하라
- “그럴듯한 답변”이 아니라, 근거 충분성을 먼저 통과시키는 게 Agentic RAG의 핵심입니다. 최근 TechGraphRAG처럼 evidence sufficiency 스코어링을 체계화하는 시도도 이 흐름입니다. (arxiv.org)
3) 관측(Observability)을 처음부터
- 최소한: 각 iteration의 query, top-k 문서, grade verdict, 최종 답변, 비용/지연을 로그로 남기세요.
- 에이전트는 “디버깅 불가능하면 운영 불가능”입니다(파일럿에서 못 넘어가는 원인 중 하나). (itpro.com)
흔한 함정/안티패턴
- 에이전트에게 ‘판단’을 다 맡김: “좋을 때까지 찾아”는 위험합니다. 종료 조건을 런타임이 가져가야 합니다.
- 컨텍스트 무한 비대화: iteration이 늘수록 retrieved를 누적하면 토큰이 폭발합니다. “요약/압축/중복 제거” 전략이 필요하며, 커뮤니티에서도 context compression을 주요 개선점으로 공유합니다. (reddit.com)
- 보안 없이 툴 확장: MCP/내부 API/사내 위키를 도구로 붙일수록 공격 표면이 늘고, 프롬프트 인젝션/데이터 유출 리스크가 커집니다. 2026년엔 “자율 에이전트가 보안 위기”라는 톤의 경고가 뚜렷합니다. (techradar.com)
비용/성능/안정성 트레이드오프
- 비용: (iteration 수) × (retrieval + grade + answer)로 선형 증가
- 지연: 검색이 네트워크/스토리지 지연을 포함하므로 tail latency가 커짐
- 안정성: 성공률은 올라가지만, 실패 모드가 복잡(잘못된 재질의/잘못된 중단/근거 과신)
- 따라서 “항상 Agentic”가 아니라, 라우팅 + 예산 + 관측 + 평가가 한 세트입니다. (callsphere.ai)
🚀 마무리
2026년 6월 기준 Agentic RAG의 실전 구현은 크게 한 줄로 요약됩니다:
- RAG를 ‘파이프라인’이 아니라 ‘에이전트가 쓰는 Tool’로 격상하고,
- LangGraph 같은 state machine/graph로 retrieve→grade→rewrite→answer 루프를 만들며, (docs.langchain.com)
max_iterations/budget같은 bounded autonomy로 운영 가능한 형태로 “가둬서” 쓰는 것.
도입 판단 기준(현실적인 체크리스트):
- (필수) 단발 RAG 실패율이 눈에 띄게 높다 / 재질의 니즈가 반복된다
- (필수) 비용·지연 예산과 관측 체계를 갖췄다(트레이싱/로그/평가)
- (권장) “cheap path vs agentic path” 라우팅으로 ROI를 관리할 수 있다 (callsphere.ai)
다음 학습 추천:
- LangGraph의 agentic RAG 공식 가이드로 그래프 패턴을 몸에 익히고 (docs.langchain.com)
- OpenAI Responses API의
file_search/hosted retrieval 옵션을 비교해 “직접 운영 vs 매니지드” 결정을 하며 (platform.openai.com) - 연구 트렌드는 A-RAG/TechGraphRAG처럼 “retrieval 인터페이스 계층화 + 증거 충분성/인용 검증”으로 확장되는 방향을 훑어보면 좋습니다. (arxiv.org)