자율적으로 “찾고-검증하고-다시 찾는” 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) 셋업 (의존성/환경)
1
2
3
4
5
6
7
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)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
# 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)