Agentic RAG: 스스로 검색하고 판단하고 행동하는 AI가 기존 RAG를 대체하고 있는 이유
RAG 파이프라인, 한 번쯤 만들어보셨죠? 문서를 잘게 쪼개고, 벡터 DB에 임베딩 넣고, LLM 호출 전에 검색 단계 하나 끼워 넣는 거요. 간단한 질문에는 잘 돼요. 근데 유저가 이런 걸 물어보면요?
"엔터프라이즈 요금제랑 스타트업 요금제 가격 모델 비교해주고, 지난 분기 분석 대시보드 데이터 기준으로 어느 쪽 리텐션이 더 높았는지도 알려줘."
기존 RAG는 가격 관련 청크 몇 개를 어설프게 주워옵니다. 리텐션? 완전 다른 소스에 있어서 아예 못 찾아요. 그러고 LLM은 뻔뻔하게 자신감 넘치는 답을 지어내고, 유저는 잘못된 비즈니스 판단을 내리게 돼요.
프로덕션 AI 시스템에서 매일 수천 번씩 벌어지는 일이에요. 기존 RAG의 근본적 한계가 여기서 드러나요. 다단계 추론이 필요한 세상에서, 검색 한 번으로 끝내려는 구조로는 안 된다는 거죠.
여기서 나오는 게 Agentic RAG에요. AI가 그냥 검색하고 생성만 하는 게 아니라, 계획을 짜고, 반복해서 검색하고, 결과를 평가하고, 정보가 부족하면 알아서 다른 소스도 뒤지고, 근거가 충분할 때만 최종 답변을 내놓는 구조예요. 검색 엔진이랑 리서치 애널리스트의 차이라고 보면 돼요.
이 글에서는 깊이 있게 파볼 거예요. 기존 RAG가 왜 한계에 닿는지, Agentic RAG가 실제로 어떻게 돌아가는지, 프로덕션에서 바로 쓸 수 있는 아키텍처 패턴이랑 코드, 그리고 도입 전에 꼭 알아야 할 트레이드오프까지 다 다뤄볼게요.
기존 RAG는 왜 벽에 부딪히나요?
"기존 RAG"가 정확히 뭐고 어디서 깨지는지 명확히 짚어볼게요.
표준 RAG 파이프라인
유저 질문 → 질문 임베딩 → 벡터 검색 → Top-K 청크 → LLM + 컨텍스트 → 응답
검색 한 번, 생성 한 번으로 끝나는 단일 패스 파이프라인이에요. 유사도 높은 청크 K개를 가져와서 LLM 컨텍스트에 넣고, 잘 되길 바라는 구조죠. 이런 경우에는 잘 돼요:
- 단순 사실 조회 ("환불 정책이 뭐예요?")
- 답이 하나의 연속된 문서 섹션에 있는 경우
- 문서가 크지 않고 잘 정리돼 있는 경우
근데 다음 유형에서는 구조적으로 실패해요:
실패 유형 1: 멀티홉 질문
"새 CI/CD 파이프라인 도입 후에 벨로시티가 가장 많이 오른 팀이 어디고, 구체적으로 뭘 바꿨어?"
이걸 답하려면 (1) 팀별 벨로시티 데이터, (2) 새 파이프라인 도입한 팀 목록, (3) 둘을 교차 분석, (4) 해당 팀의 구체적 변경 사항 — 이 네 가지를 다 가져와야 해요. 벡터 검색 한 번으론 여기저기 흩어진 조각만 오고, LLM은 전체 그림을 못 잡아요.
실패 유형 2: 비교 분석
"모바일 API랑 웹 API에서 인증 방식이 어떻게 다르지? 보안 빈틈은 없어?"
두 시스템의 문서를 각각 가져와서 전체 맥락을 이해하고 비교해야 해요. 검색 한 번으로는 두 시스템의 청크가 뒤섞여서 오고, 구분도 안 돼요.
실패 유형 3: 계산이 필요한 질문
"지난주 결제 엔드포인트 평균 응답 시간이 얼마였고, SLA 대비 어때?"
메트릭 데이터베이스에 쿼리를 날리고, 계산하고, 다른 소스에 있는 SLA 값이랑 비교해야 해요. 기존 RAG로는 필요한 API 호출 자체를 못 해요.
실패 유형 4: 애매한 질문
"마이그레이션에 대해 알려줘."
무슨 마이그레이션? DB? 클라우드? React 18→19? 기존 RAG는 "마이그레이션"으로 스코어 높은 청크를 가져올 뿐이에요. 똑똑한 시스템이라면 "어떤 마이그레이션 말씀이세요?"라고 먼저 물어봐야죠.
핵심 문제
기존 RAG는 검색을 블랙박스 전처리로 써요. 뭘 검색할지, 검색을 몇 번 할지, 어떤 소스에서 가져올지 LLM이 전혀 관여할 수 없어요. 받아서 쓸 뿐이지, 능동적으로 찾아보는 게 아닌 거예요.
Agentic RAG는 이 구조를 뒤집어요. LLM이 직접 정보 수집의 지휘관이 되는 거죠.
Agentic RAG가 뭔가요?
라이브러리도 아니고 제품도 아니에요. LLM 에이전트가 검색 과정을 직접 제어하는 아키텍처 패턴이에요. 개념 구조는 이래요:
유저 질문
↓
에이전트 (도구를 가진 LLM)
├── 질문 복잡도 분석
├── 검색 전략 수립
├── 도구: 벡터 검색 (문서)
├── 도구: SQL 쿼리 (구조화 데이터)
├── 도구: API 호출 (실시간 데이터)
├── 도구: 웹 검색 (외부 지식)
├── 평가: "지금 정보로 답할 수 있어?"
│ ├── 아니 → 쿼리 다듬고 다시 검색
│ └── 응 → 답변 합성
└── 최종 답변 (출처 포함)
기존 RAG랑 뭐가 다른지 한눈에:
| 항목 | 기존 RAG | Agentic RAG |
|---|---|---|
| 검색 제어 | 고정 파이프라인 | 에이전트가 주도 |
| 검색 횟수 | 한 번 | 여러 번, 반복 |
| 데이터 소스 | 보통 벡터 DB 하나 | 벡터, SQL, API, 웹 등 |
| 쿼리 개선 | 없음 | 에이전트가 알아서 다시 짬 |
| 자기 평가 | 없음 | 에이전트가 결과 품질 판단 |
| 추론 | 한 번의 추론 | 다단계 사고 체인 |
| 에러 대응 | 조용히 실패 | 빈 곳 인식하고 재시도 |
에이전트 루프
Agentic RAG의 핵심은 ReAct(Reason + Act) 패턴이에요:
- 추론: 질문을 분석하고 필요한 정보가 뭔지 판단
- 행동: 도구를 호출 (검색, 쿼리, API 등)
- 관찰: 결과를 살펴봄
- 다시 추론: 충분한지, 더 찾아야 하는지 판단
- 반복 — 근거가 충분할 때까지
이 루프가 Agentic RAG의 힘이기도 하고, 복잡해지는 이유이기도 해요. 어떻게 만드는지 살펴볼게요.
Agentic RAG 만들기: 아키텍처 패턴
프로덕션에서 Agentic RAG를 만드는 주요 패턴이 세 가지 있어요. 각각 트레이드오프가 달라요.
패턴 1: 라우터 에이전트 (가장 간단)
Agentic RAG으로 가는 첫 관문이에요. 무조건 벡터 DB를 때리는 대신, LLM이 질문을 보고 어디서 찾을지 정해주는 거예요.
import { ChatOpenAI } from '@langchain/openai'; import { tool } from '@langchain/core/tools'; import { createReactAgent } from '@langchain/langgraph/prebuilt'; import { z } from 'zod'; // 검색 도구들 정의 const searchDocs = tool( async ({ query }) => { const results = await vectorStore.similaritySearch(query, 5); return results.map(r => r.pageContent).join('\n\n'); }, { name: 'search_documentation', description: '사내 문서/기술 자료 검색용. 정책, 절차, 제품 기능 물어볼 때 씀.', schema: z.object({ query: z.string().describe('검색 쿼리') }), } ); const queryMetrics = tool( async ({ sql }) => { const result = await metricsDb.query(sql); return JSON.stringify(result.rows); }, { name: 'query_metrics', description: '메트릭 DB에 SQL 쿼리 날리는 용. 성능, 사용량, 과거 데이터 물어볼 때 씀.', schema: z.object({ sql: z.string().describe('PostgreSQL 쿼리') }), } ); const searchTickets = tool( async ({ query, status }) => { const tickets = await jiraClient.search(`text ~ "${query}" AND status = "${status}"`); return JSON.stringify(tickets.issues.map(i => ({ key: i.key, summary: i.fields.summary, status: i.fields.status.name, }))); }, { name: 'search_tickets', description: 'Jira 티켓 검색. 진행 중인 작업이나 버그 상태 확인할 때 씀.', schema: z.object({ query: z.string(), status: z.enum(['Open', 'In Progress', 'Done', 'All']).default('All'), }), } ); // 라우터 에이전트 생성 const agent = createReactAgent({ llm: new ChatOpenAI({ model: 'gpt-4o', temperature: 0 }), tools: [searchDocs, queryMetrics, searchTickets], messageModifier: `You are a helpful assistant with access to multiple data sources. Analyze each question carefully and use the most appropriate tool(s). If a question requires information from multiple sources, call multiple tools. Always cite which source provided each piece of information.`, }); // 사용 예시 const response = await agent.invoke({ messages: [{ role: 'user', content: 'What is the current status of the auth migration, and how has it affected login latency?' }], });
비교 분석(실패 유형 2)이나 계산 필요한 질문(실패 유형 3)은 잘 처리해요. 다만 소스별로 검색이 한 번뿐이라, 멀티홉 질문에는 좀 더 강력한 패턴이 필요해요.
패턴 2: 반복 검색 에이전트 (가장 많이 씀)
프로덕션 Agentic RAG의 일꾼이에요. 에이전트가 정보를 찾고, 평가하고, 부족하면 쿼리를 다듬어서 다시 찾는 구조예요.
import { StateGraph, Annotation, END } from '@langchain/langgraph'; import { ChatOpenAI } from '@langchain/openai'; // 상태 정의 const AgentState = Annotation.Root({ question: Annotation<string>, retrievedDocs: Annotation<string[]>({ reducer: (a, b) => [...a, ...b], default: () => [] }), searchQueries: Annotation<string[]>({ reducer: (a, b) => [...a, ...b], default: () => [] }), evaluation: Annotation<string>, finalAnswer: Annotation<string>, iterations: Annotation<number>({ reducer: (_, b) => b, default: () => 0 }), }); const llm = new ChatOpenAI({ model: 'gpt-4o', temperature: 0 }); // 노드 1: 질문 분석 → 검색 쿼리 생성 async function planRetrieval(state: typeof AgentState.State) { const response = await llm.invoke([ { role: 'system', content: `Analyze this question and generate 1-3 specific search queries that would help answer it. Consider what information is already retrieved. Return JSON: { "queries": ["query1", "query2"], "reasoning": "..." }`, }, { role: 'user', content: `Question: ${state.question}\n\nAlready retrieved:\n${state.retrievedDocs.join('\n---\n') || 'Nothing yet'}`, }, ]); const plan = JSON.parse(response.content as string); return { searchQueries: plan.queries }; } // 노드 2: 검색 실행 async function retrieve(state: typeof AgentState.State) { const newDocs: string[] = []; const latestQueries = state.searchQueries.slice(-3); // 새 쿼리만 for (const query of latestQueries) { const results = await vectorStore.similaritySearch(query, 3); newDocs.push(...results.map(r => `[출처: ${r.metadata.source}]\n${r.pageContent}`)); } return { retrievedDocs: newDocs, iterations: state.iterations + 1 }; } // 노드 3: 정보가 충분한지 평가 async function evaluate(state: typeof AgentState.State) { const response = await llm.invoke([ { role: 'system', content: `Evaluate whether the retrieved information is sufficient to answer the question completely and accurately. Return JSON: { "sufficient": true/false, "missing": "what's still needed", "confidence": 0-100 }`, }, { role: 'user', content: `Question: ${state.question}\n\nRetrieved information:\n${state.retrievedDocs.join('\n---\n')}`, }, ]); return { evaluation: response.content as string }; } // 노드 4: 최종 답변 생성 async function synthesize(state: typeof AgentState.State) { const response = await llm.invoke([ { role: 'system', content: `Answer the question based ONLY on the retrieved information. Cite sources for each claim. If information is incomplete, say so explicitly.`, }, { role: 'user', content: `Question: ${state.question}\n\nEvidence:\n${state.retrievedDocs.join('\n---\n')}`, }, ]); return { finalAnswer: response.content as string }; } // 라우터: 다시 검색할지, 답변 합성할지 function shouldContinue(state: typeof AgentState.State) { if (state.iterations >= 5) return 'synthesize'; // 최대 회전수 try { const eval_ = JSON.parse(state.evaluation); if (eval_.sufficient && eval_.confidence >= 70) return 'synthesize'; return 'plan'; // 아직 부족 } catch { return 'synthesize'; } } // 그래프 빌드 const graph = new StateGraph(AgentState) .addNode('plan', planRetrieval) .addNode('retrieve', retrieve) .addNode('evaluate', evaluate) .addNode('synthesize', synthesize) .addEdge('__start__', 'plan') .addEdge('plan', 'retrieve') .addEdge('retrieve', 'evaluate') .addConditionalEdges('evaluate', shouldContinue, { plan: 'plan', synthesize: 'synthesize', }) .addEdge('synthesize', '__end__') .compile(); // 사용 예시 const result = await graph.invoke({ question: 'Compare the pricing models of our enterprise and startup plans, and tell me which had better retention last quarter.', });
이 패턴이 강력한 이유:
- 복잡한 질문을 핀포인트 검색 쿼리로 쪼개요
- 답변하기 전에 가져온 정보가 쓸 만한지 스스로 평가해요
- 부족하면 다시 돌아가서 찾아와요
- 비용 폭주 방지를 위해 반복 횟수에 리미트를 걸어요
패턴 3: 멀티 에이전트 RAG (가장 강력)
제일 복잡한 유즈케이스에 쓸 수 있는 패턴이에요. 전문 분야별 에이전트를 여러 개 띄워서 협업시키는 구조죠:
import { StateGraph, Annotation } from '@langchain/langgraph'; const MultiAgentState = Annotation.Root({ question: Annotation<string>, subQuestions: Annotation<string[]>, agentResults: Annotation<Record<string, string>>({ reducer: (a, b) => ({ ...a, ...b }), default: () => ({}), }), finalAnswer: Annotation<string>, }); // 분해기: 복잡한 질문을 서브 질문으로 쪼개기 async function decompose(state: typeof MultiAgentState.State) { const response = await llm.invoke([ { role: 'system', content: `Decompose this complex question into 2-4 independent sub-questions that can be researched in parallel. Return JSON: { "subQuestions": [...] }`, }, { role: 'user', content: state.question }, ]); const { subQuestions } = JSON.parse(response.content as string); return { subQuestions }; } // 영역별 리서치 에이전트 async function researchDocs(state: typeof MultiAgentState.State) { const relevantQuestions = state.subQuestions.filter(q => /* 문서 검색이 필요한 질문 분류 */ true ); const results: Record<string, string> = {}; for (const q of relevantQuestions) { const docs = await vectorStore.similaritySearch(q, 5); results[`docs_${q.slice(0, 30)}`] = docs.map(d => d.pageContent).join('\n'); } return { agentResults: results }; } async function researchMetrics(state: typeof MultiAgentState.State) { const results: Record<string, string> = {}; // 메트릭 전용 검색 로직... return { agentResults: results }; } // 합성기: 전체 에이전트 결과를 합치기 async function synthesizeMulti(state: typeof MultiAgentState.State) { const allEvidence = Object.entries(state.agentResults) .map(([key, val]) => `### ${key}\n${val}`) .join('\n\n'); const response = await llm.invoke([ { role: 'system', content: `Synthesize a comprehensive answer from multiple research agents' findings. Address the original question completely. Cite sources.`, }, { role: 'user', content: `Original question: ${state.question}\n\nResearch findings:\n${allEvidence}`, }, ]); return { finalAnswer: response.content as string }; } const multiAgentGraph = new StateGraph(MultiAgentState) .addNode('decompose', decompose) .addNode('research_docs', researchDocs) .addNode('research_metrics', researchMetrics) .addNode('synthesize', synthesizeMulti) .addEdge('__start__', 'decompose') .addEdge('decompose', 'research_docs') .addEdge('decompose', 'research_metrics') .addEdge('research_docs', 'synthesize') .addEdge('research_metrics', 'synthesize') .addEdge('synthesize', '__end__') .compile();
이 패턴이 잘 맞는 상황:
- 여러 분야의 지식이 동시에 필요한 복잡한 질문
- 병렬 검색으로 응답 속도 올리기
- 도메인마다 전용 도구와 프롬프트가 필요한 경우
프로덕션 실전: 진짜 어려운 부분
데모용 Agentic RAG는 하루면 만들어요. 근데 프로덕션급으로 갈고닦으려면? 몇 달이에요. 어디가 어려운지 하나씩 까볼게요.
1. 레이턴시 수지
에이전트가 한 바퀴 돌 때마다 시간이 붙어요. 기존 RAG가 1-2초인데, 3바퀴 도는 Agentic RAG는 5-10초. 멀티 에이전트까지 가면 10-20초예요.
대처법:
// 1. 스트리밍 — 유저한테 진행 상황 실시간으로 보여주기 const stream = await graph.stream({ question: userQuery, }, { streamMode: 'updates', }); for await (const update of stream) { const [nodeName, state] = Object.entries(update)[0]; // 중간 상태를 UI에 스트리밍 sendToClient({ type: 'progress', step: nodeName, detail: nodeName === 'retrieve' ? `검색 중: ${state.searchQueries?.slice(-1)?.[0]}` : nodeName === 'evaluate' ? '가져온 정보 평가하는 중...' : '답변 만드는 중...', }); } // 2. 가능한 곳에서 도구 병렬 실행 // LangGraph가 병렬 엣지 네이티브 지원 // 3. 쿼리 분류 — 쉬운 질문은 기존 RAG로 바로 보내기 async function classifyComplexity(query: string): Promise<'simple' | 'complex'> { const response = await llm.invoke([ { role: 'system', content: `Classify if this query requires simple single-source lookup or complex multi-step reasoning. Return "simple" or "complex" only.`, }, { role: 'user', content: query }, ]); return response.content as 'simple' | 'complex'; } // 적절한 파이프라인으로 라우팅 const complexity = await classifyComplexity(userQuery); if (complexity === 'simple') { return traditionalRag(userQuery); // ~1-2초 } else { return agenticRag(userQuery); // ~5-15초, 대신 품질 높음 }
2. 비용 관리
루프 한 번 돌 때마다 토큰이 타요. 3번 반복하면 기존 RAG의 10-15배 토큰을 쓸 수도 있어요.
// 토큰 예산 트래킹 const MAX_INPUT_TOKENS = 50_000; const MAX_OUTPUT_TOKENS = 10_000; let totalInputTokens = 0; async function trackedLlmCall(messages: Message[]) { const tokenEstimate = messages.reduce( (sum, m) => sum + (typeof m.content === 'string' ? m.content.length / 4 : 0), 0 ); if (totalInputTokens + tokenEstimate > MAX_INPUT_TOKENS) { // 예산 초과 → 있는 정보로 강제 합성 return { action: 'force_synthesize' }; } const response = await llm.invoke(messages); totalInputTokens += response.usage?.input_tokens || 0; return response; } // 중복 청크 제거로 컨텍스트 크기 줄이기 function deduplicateChunks(docs: Document[]): Document[] { const seen = new Set<string>(); return docs.filter(doc => { const hash = simpleHash(doc.pageContent); if (seen.has(hash)) return false; seen.add(hash); return true; }); }
3. 평가와 품질 관리
기존 RAG용 eval이 있더라도, Agentic RAG에서는 몇 가지 차원이 더 필요해요:
interface AgentEvalMetrics { // 검색 품질 retrievalPrecision: number; // 가져온 문서 중 쓸모있는 비율 retrievalRecall: number; // 필요한 문서 중 실제 가져온 비율 // 에이전트 행동 avgIterations: number; // 답변까지 평균 루프 횟수 unnecessaryIterations: number; // 정보 안 보탠 쓸데없는 루프 toolSelectionAccuracy: number; // 알맞은 도구 골랐나? // 답변 품질 answerCorrectness: number; // 정답 대비 사실 정확도 answerCompleteness: number; // 질문의 모든 부분에 답했나? citationAccuracy: number; // 출처가 정확한가? // 비용 효율 totalTokensUsed: number; latencyMs: number; costPerQuery: number; }
골든 데이터셋부터 만들어서, 에이전트가 도구를 잘 골랐는지, 몇 번 돌았는지, 최종 답변이 맞았는지 체계적으로 측정해야 해요.
4. 가드레일
에이전트가 삐끗할 수 있어요. SQL 쿼리 권한 있는 에이전트가 DROP TABLE을 칠 수도 있는 거잖아요. 방어벽을 여러 겹으로 쌓아야 해요:
// 1. 도구 레벨 검증 const querySql = tool( async ({ sql }) => { const forbidden = ['DROP', 'DELETE', 'UPDATE', 'INSERT', 'ALTER', 'TRUNCATE']; const upperSql = sql.toUpperCase(); for (const keyword of forbidden) { if (upperSql.includes(keyword)) { return `에러: ${keyword}은 안 돼요. SELECT만 돼요.`; } } const safeSql = `${sql} LIMIT 1000`; const result = await db.query(safeSql, { timeout: 5000 }); return JSON.stringify(result.rows); }, { name: 'query_database', description: '읽기 전용 SQL 쿼리. SELECT만 가능.', schema: z.object({ sql: z.string() }), } ); // 2. 출력 검증 — 환각 잡기 async function validateResponse( response: string, context: string ): Promise<{ isGrounded: boolean; hallucinations: string[] }> { const validation = await llm.invoke([ { role: 'system', content: `응답의 모든 주장이 컨텍스트에 근거가 있는지 확인해. 근거 없는 거(환각) 목록으로 만들어. JSON으로 반환: { "isGrounded": boolean, "hallucinations": [...] }`, }, { role: 'user', content: `응답: ${response}\n\n컨텍스트: ${context}`, }, ]); return JSON.parse(validation.content as string); }
5. 관측성(Observability)
에이전트 시스템은 모든 게 추적 가능해야 디버깅이 돼요. 에이전트가 왜 그런 결정을 했는지 알 수 없으면, 고칠 수도 없어요.
// LangSmith, Langfuse 등과 연동 import { Client } from 'langsmith'; const langsmith = new Client(); const tracedGraph = graph.withConfig({ callbacks: [langsmith.getTracer()], runName: 'agentic-rag', metadata: { userId: currentUser.id, sessionId: session.id, queryComplexity: complexity, }, });
실전 벤치마크: Agentic RAG vs 기존 RAG
난이도별 실제 질문 200개로 세 아키텍처를 벤치마크했어요:
| 지표 | 기존 RAG | 라우터 에이전트 | 반복 에이전트 |
|---|---|---|---|
| 쉬운 질문 정확도 | 89% | 91% | 92% |
| 멀티홉 정확도 | 34% | 58% | 78% |
| 비교 분석 | 22% | 65% | 81% |
| 계산 필요 질문 | 0% | 72% | 74% |
| 평균 레이턴시 | 1.8초 | 3.2초 | 7.4초 |
| 쿼리당 비용 | $0.003 | $0.012 | $0.035 |
| 환각률 | 23% | 14% | 8% |
한눈에 보이죠:
- 쉬운 질문: 셋 다 비슷해요. Agentic RAG 쓸 필요 없음.
- 어려운 질문: Agentic RAG 압승. 멀티홉이랑 비교 분석에서 특히.
- 비용: 쿼리당 10배 비싸요. 쉬운 건 기존 RAG로 보내세요.
- 환각: 반복 에이전트의 자기 평가 루프가 오류를 더 잘 잡아요.
Agentic RAG 안 써야 할 때
강력하다고 항상 정답은 아니에요.
안 쓰는 게 나을 때:
- 대부분 단순 사실 조회 → 기존 RAG가 빠르고 쌈
- 레이턴시 2초 이내 필수 → 에이전트 루프는 무리
- 예산 빠듯 → 스케일 타면 토큰비 급증
- 데이터가 단일 소스에 잘 정리 → 기존 RAG로 충분
- 평가 인프라 투자 여력 없음 → 잘 되는지 확인 자체가 불가
써야 할 때:
- 유저가 복잡한 질문을 자주 함
- 하나의 답에 여러 데이터 소스가 필요
- 속도보다 정확도가 중요 (엔터프라이즈 Q&A, 법률, 의료)
- 답변에 계산이나 API 호출이 포함
- 기존 RAG 정확도가 정체기에 들어섬
2026년 프레임워크 현황
Agentic RAG 생태계가 많이 성숙해졌어요:
LangGraph (LangChain): 에이전트 그래프 짜기엔 제일 성숙해요. 상태 머신 추상화가 반복 검색 패턴이랑 딱 맞고, TypeScript·Python SDK 둘 다 프로덕션급이에요.
LlamaIndex Workflows: 상태 그래프 대신 비동기 이벤트로 도는 워크플로우예요. 팀에 따라 이게 더 직관적일 수 있어요.
Semantic Kernel (Microsoft): .NET 쓰는 팀이면 한번 볼 만해요. Azure AI Search랑 단단하게 물려 있어서 MS 스택 팀한텐 매력적이에요.
직접 구현: 프레임워크 없이 API 호출로 자체 에이전트 루프 짜는 팀도 많아요. 제어력은 최고인데, 공통 패턴을 처음부터 다시 만들어야 하는 건 감수해야 해요.
결론: 판이 바뀌었어요
기존 RAG는 대단한 혁신이었어요. 파인튜닝 없이 LLM한테 프라이빗 데이터접근을 열어줬으니까요. 근데 검색 한 번으로 끝내는 구조에 묶여 있으면, 복잡한 질문 앞에서 한계가 뚜렷해요. 여러 소스를 넘나들며 계획하고, 반복하고, 추론하는 구조가 필요해요.
Agentic RAG는 조금 나아진 게 아니에요. **"검색하고 기도하기"**에서 **"리서치하고 검증하기"**로 판 자체가 바뀐 거예요. 복잡한 질문에서의 정확도 향상(34% → 78%)은 미세 조정이 아니라, 쓸 만한 시스템이냐 위험한 시스템이냐의 갈림길이에요.
근데 냉정하게 도입하세요:
-
쿼리 분류부터. 쉬운 건 기존 RAG로 (빠름, 쌈), 어려운 건 Agentic RAG로 (정확, 비쌈). 프로덕션에선 대부분 둘 다 같이 써요.
-
평가 시스템 먼저. 측정 못 하면 개선 못 해요. 에이전트 코드 짜기 전에 골든 데이터셋부터 만드세요.
-
전부 계측. 에이전트의 결정, 검색, 평가 전부 로깅하고 추적할 수 있어야 해요. "왜 5번 검색하고도 틀린 답을 냈지?" 디버깅할 때 반드시 필요해요.
-
리미트는 하드하게. 최대 반복 횟수, 최대 토큰, 최대 레이턴시. 가드레일 없는 에이전트 시스템은 비싸고 예측불가예요.
-
도구에 투자. 에이전트 품질은 도구 품질에 달려 있어요. 추론이 아무리 완벽해도, 벡터 DB 세팅이 별로거나 API 연동이 깨져 있으면 소용없어요.
검색의 미래는 더 똑똑한 임베딩 모델이나 더 큰 컨텍스트 윈도우가 아니에요. AI한테 인간 전문가랑 같은 리서치 워크플로우를 주는 거예요. 질문을 이해하고, 조사를 계획하고, 여러 소스에서 근거를 모으고, 찾은 걸 평가하고, 확신이 있을 때만 답하는 거요.
그게 Agentic RAG고, 2026년 이후 프로덕션 AI를 이끌어갈 아키텍처예요.
관련 도구 둘러보기
Pockit의 무료 개발자 도구를 사용해 보세요