Agentic AI Design Patterns

CH14-환각 대신 근거로 말하게 하라 — AI 에이전트 검색 증강 생성(RAG) 설계

빌드 피벗(Build Pivot) 2026. 6. 10. 22:05
반응형
AGENTIC AI DESIGN PATTERNS · 14
환각 대신 근거로 말하게 하라 — 검색 증강 생성(RAG)
LLM의 4가지 한계와 RAG 파이프라인 6단계, 청킹·임베딩·벡터 DB·HNSW, Vector vs BM25 vs 하이브리드(RRF), Standard·Graph·Agentic 변형, ChromaDB+Gemini·LangGraph·pgvector+Ollama·Google ADK 구현 4종, CRM·ERP·Groupware 적용까지
🟢 입문~중급 ⏱️ 약 24분 🔧 Python · LangGraph · pgvector · Gemini 🏢 엔터프라이즈 🗓️ 최종 검토 2026-06-10

TL;DR — LLM은 학습 시점 이후를 모르고(Knowledge Cutoff), 모르는 건 그럴듯하게 지어내며(Hallucination), 우리 회사 내부 문서는 본 적도 없습니다. RAG(Retrieval-Augmented Generation, 검색 증강 생성)는 답을 만들기 전에 외부 지식 소스에서 근거를 검색해 그 위에서 답하게 하는 패턴입니다. 핵심 흐름은 질문 → 검색(Retrieve) → 증강(Augment) → 생성(Generate) 이고, 품질은 LLM이 아니라 청킹·임베딩·검색 전략이 좌우합니다. 프로덕션은 벡터 검색 + 키워드(BM25)를 결합한 하이브리드 검색, 복잡한 질문엔 검증·재검색 루프를 가진 Agentic RAG가 답입니다.

1. 왜 RAG인가 — 아는 척 대신 찾아보게 하라

지난 편에서 에이전트는 멈춰야 할 순간 사람을 부르는 법(휴먼 인 더 루프)을 배웠습니다. 그런데 사람을 부르기 한참 전에, 더 조용하고 더 흔한 사고가 있습니다 — 모르는 것을 아는 척하는 환각(Hallucination)입니다. "우리 회사 환불 규정이 어떻게 되죠?"라고 물으면, 안전장치 없는 LLM은 학습 데이터 어딘가의 다른 회사 규정을 조합해 그럴듯한 거짓말을 합니다. 본 적 없는 문서를 인용할 수는 없으니까요.

교재는 LLM의 한계를 네 가지로 정리합니다. Knowledge Cutoff(학습 시점 이후 정보 부재), Hallucination(사실과 다른 정보 생성), Domain Specificity(특정 분야 전문성 부족), Data Privacy(기업 내부 데이터 미반영). RAG(Retrieval-Augmented Generation)는 이 넷을 한 가지 아이디어로 공략합니다 — 답을 생성하기 전에 외부 지식 소스에서 관련 정보를 검색해 근거로 제공하는 것입니다.

🧠 LLM 단독 (파라미터 지식만)
Knowledge Cutoff — 학습 시점 이후 정보 부재
Hallucination — 사실과 다른 정보를 그럴듯하게 생성
Domain Specificity — 특정 분야 전문성 부족
Data Privacy — 기업 내부 데이터 미반영
📚 RAG (검색으로 증강)
실시간 데이터 검색 — 최신 문서를 그때그때 조회
근거 문서 기반 응답 — 검색된 컨텍스트 안에서만 답변
도메인 문서 연결 — 매뉴얼·규정·논문을 지식으로
프라이빗 지식베이스 — 내부 데이터를 재학습 없이 활용
그림 1. LLM 단독 vs RAG — 네 가지 한계와 네 가지 해법이 1:1로 대응한다

교재의 비유가 이 패턴을 정확히 잡아줍니다. RAG는 도서관 사서입니다. 사서의 머릿속 지식이 LLM의 학습된 파라미터라면, 도서관 장서는 Vector Database(외부 지식), 책 찾기는 Retrieval(검색), 답변 구성은 Generation(생성)이죠. 사서가 모든 책의 내용을 외우지 않아도, 어디서 찾을지 알고 찾은 정보를 바탕으로 답하는 것 — 그게 RAG의 작동 방식입니다. 모델을 재학습시키는 대신 도서관을 잘 정리해 두는 쪽이 훨씬 싸고 빠르며, 책(문서)을 바꾸면 답도 바로 바뀝니다.

엔터프라이즈 관점의 차이는 한 줄로 요약됩니다. LLM만 쓰면 "정책에 따르면..." (추측, 부정확), RAG를 적용하면 "2024년 인사정책 3조에 따르면..." (정확, 출처 명시). 근거와 출처가 생기는 순간, AI의 답변은 "참고"에서 "업무"가 됩니다.

2. 개념과 오해 — RAG는 "검색창 붙이기"가 아니다

📌 한 줄 정의 — RAG(Retrieval-Augmented Generation, 검색 증강 생성)는 LLM이 외부 지식 소스에서 관련 정보를 검색하여 응답을 생성하는 패턴입니다. 핵심 구성요소는 다섯 가지 — Embeddings(텍스트→벡터 변환) · Chunking(문서 분할) · Vector Database(임베딩 저장·유사도 검색) · Semantic Search(의미 기반 검색) · Generation(검색 컨텍스트 기반 생성)입니다.

시작 전에, 주니어가 자주 빠지는 오해 세 가지를 짚습니다. 셋 다 "RAG = LLM + 검색 한 줄"이라는 과소평가에서 나옵니다.

오해 ① "RAG는 LLM에 검색창 하나 붙이는 일이다"
실제로는 수집→전처리→임베딩→인덱싱→검색→생성의 6단계 파이프라인이고, 품질을 좌우하는 건 LLM이 아니라 청킹·검색 전략입니다. 잘못된 청킹은 검색 품질 저하와 컨텍스트 손실을 그대로 야기합니다(3·4절).
오해 ② "벡터 검색이 키워드 검색보다 항상 낫다"
벡터 검색은 동의어·문맥에 강하지만, 정확한 용어·코드·ID 검색은 키워드(BM25)가 강합니다. 그래서 프로덕션 권장은 어느 한쪽이 아니라 둘을 결합한 하이브리드 검색입니다(6절).
오해 ③ "RAG를 붙이면 환각이 사라진다"
줄어들 뿐, 사라지지 않습니다. 교재의 도전과제 목록에도 "여전히 허위 정보 생성"이 올라 있고, 해법으로 출처 명시와 팩트체크 레이어를 요구합니다. 검색된 문서를 무시하고 답하는 경우도 있어서, 프롬프트 제약("컨텍스트에 없으면 없다고 답하라")이 필수입니다(8절).

그래서 RAG를 제대로 적용하면 무엇이 달라질까요? 교재가 제시하는 적용 전후 성능 지표 예시입니다. 주목할 점은 응답 시간 — 검색 단계가 추가되니 오히려 느려집니다. 그 비용을 정확도·신뢰로 상쇄하는 게 RAG의 거래 조건입니다.

⚠️ 아래 수치는 학습 교재의 성능 지표 예시(개념적 벤치마크)이며 특정 시스템의 실측 결과가 아닙니다.
지표 RAG 이전 RAG 이후 변화
응답 정확도 65% 92% +42%
Hallucination 비율 25% 5% -80%
평균 응답 시간 2.1s 2.8s +33% (느려짐)
사용자 만족도 (1~5) 3.2 4.5 +41%
출처 명시율 0% 95% 신규 확보
그림 2. RAG 적용 전후 지표 예시 — 응답 시간 증가는 검색 단계 때문이며 정확도 향상으로 상쇄

3. RAG 파이프라인 6단계 — 도서관을 짓고, 책을 찾는다

RAG 시스템 구축은 여섯 단계로 정리됩니다. 앞의 넷(수집→전처리→임베딩→인덱싱)은 문서가 들어올 때 일어나는 "도서관 짓기"고, 뒤의 둘(검색→생성)은 질문이 들어올 때 일어나는 "책 찾아 답하기"입니다. 두 흐름의 주기가 다르다는 걸 인식하는 게 설계의 출발점입니다 — 인덱싱은 배치로, 검색·생성은 실시간으로 돕니다.

1
데이터 수집
Ingestion
PDF·웹·DB·API + 메타데이터 추출
2
전처리
Preprocessing
텍스트 정제 · 청킹 · 메타데이터 태깅
3
임베딩
Embedding
청크→벡터 변환 · 배치 처리
4
인덱싱
Indexing
Vector DB 저장 · 메타 인덱스 생성
5
검색
Retrieval
쿼리 임베딩 · Top-K · 리랭킹(선택)
6
생성
Generation
컨텍스트+질문→LLM · 출처 명시
1~4단계 = 문서가 들어올 때(인덱싱 파이프라인) · 5~6단계 = 질문이 들어올 때(서빙 파이프라인) — 노랑은 검색, 초록이 최종 생성
그림 3. RAG 구축 파이프라인 6단계 — 수집부터 출처 명시 응답까지

이 여섯 단계 중 어디가 가장 자주 무너질까요? 의외로 LLM(6단계)이 아닙니다. 2단계 청킹과 5단계 검색입니다. 청크가 엉망이면 아무리 좋은 임베딩·LLM도 엉뚱한 조각을 받아 들고, 검색이 약하면 정답 문서가 도서관에 있어도 꺼내오지 못합니다. 그래서 다음 세 절은 이 "재료"들을 하나씩 깊게 봅니다 — 청킹·임베딩(4절), 벡터 DB(5절), 검색 기법(6절).

4. 청킹과 임베딩 — 검색 품질은 자르는 데서 결정된다

임베딩(Embedding)은 텍스트를 고차원 벡터 공간의 숫자 배열로 바꾸는 일입니다. "고양이"와 "강아지"는 벡터 공간에서 가깝고, "자동차"는 멀리 놓입니다 — 의미가 비슷하면 벡터도 가깝다는 성질이 의미 검색의 토대죠. 모델에 따라 차원이 다릅니다. OpenAI text-embedding-3-large는 3072차원, Google models/embedding-001은 768차원, 오픈소스 경량 모델 all-MiniLM-L6-v2는 384차원입니다. 단, 차원·모델명은 교재 기준이고 임베딩 모델 라인업은 세대교체가 빠릅니다 — 예컨대 Google embedding-001은 이미 제공이 종료되고 후속 임베딩 모델로 대체되었으므로, 도입 시점의 공식 문서로 현행 모델을 확인하세요. 벡터 간 유사도는 코사인 유사도(방향 기반, 정규화됨)가 표준이고, 절대 거리 기반의 유클리드 거리, 빠르지만 정규화가 필요한 내적(Dot Product)도 쓰입니다.

그런데 100페이지 PDF를 통째로 임베딩하면 어떻게 될까요? 세부 정보가 평균에 묻혀 손실되고, 검색 정확도가 떨어지고, LLM 컨텍스트도 초과합니다. 그래서 청킹(Chunking) — 문서를 검색 가능한 작은 조각으로 분할하는 과정이 필요합니다. 교재는 네 가지 전략을 제시합니다.

전략 방식 적합한 경우
Fixed Size 고정 크기 고정 토큰/문자 수로 분할, 구현 단순·예측 가능 일반 텍스트, 빠른 처리
Recursive 재귀적 문단→문장 순 구분자 계층 분할, 자연스러운 경계 구조화된 문서
Semantic 의미 기반 임베딩 유사도로 경계 결정, 가장 정교·비용 높음 정교한 검색 필요 시
Document-based 구조 기반 섹션·장·제목 기준 분할, 원본 구조 보존 기술 문서, 매뉴얼
그림 4. 청킹 전략 4종 — 권장 시작점은 500토큰 + 50토큰 중복(10~20%)

초심자가 가장 자주 빠뜨리는 파라미터가 중복(Overlap)입니다. 청크 경계에서 문장이 끊기면, "고객 만족도를 높이기 위해서는" 까지가 청크 1에, "응답 시간 단축이 핵심이며"가 청크 2에 갈라져 들어갑니다 — 어느 쪽을 검색해도 반쪽짜리 문맥이죠. 청크 크기의 10~20%(500자면 50~100자)를 겹치게 자르면 경계 문맥이 양쪽에 모두 보존됩니다.

✂️ 중복 없는 청킹
청크 1: "...고객 만족도를 높이기 위해서는"
청크 2: "응답 시간 단축이 핵심이며, 이를 위해..."
❌ 경계에서 문맥 단절 — 어느 청크를 검색해도 반쪽
🧩 중복 있는 청킹 (10~20%)
청크 1: "...고객 만족도를 높이기 위해서는 응답 시간 단축이"
청크 2: "응답 시간 단축이 핵심이며, 이를 위해..."
✅ 겹친 구간이 문맥의 다리 — 어느 쪽이 검색돼도 온전
그림 5. 중복 없는 청킹 vs 중복 있는 청킹 — 50~100토큰의 겹침이 경계 문맥을 살린다

권장 시작점은 청크 500토큰 + 중복 50토큰입니다. 단, 문서 유형에 따라 조정합니다 — 교재 기준으로 법률 문서는 300(조항 단위가 짧음), 코드는 200(함수 단위)이 낫습니다. 너무 작으면 문맥 손실, 너무 크면 검색 정확도 저하라는 양쪽 절벽 사이에서 균형을 잡는 일이죠.

5. 벡터 DB와 HNSW — 수백만 벡터를 밀리초에 찾는 법

임베딩이 쌓이면 저장하고 검색할 곳이 필요합니다. 벡터 데이터베이스는 임베딩 벡터를 저장하고 유사도 검색을 수행하는 특수 DB입니다. 선택지는 풍부하고, 정답은 "규모·호스팅·기존 인프라"에 따라 갈립니다.

DB 유형 특징 적합한 경우
Pinecone 클라우드 완전 관리형, 빠른 시작 프로덕션, 확장성 필요
ChromaDB 오픈소스 경량, 로컬 개발 용이 프로토타입, 학습
Weaviate 오픈소스/클라우드 하이브리드 검색, GraphQL 지원 복잡한 쿼리
Milvus 오픈소스 대용량, 고성능 엔터프라이즈, 대규모
Qdrant 오픈소스/클라우드 Rust 기반, 필터링 강점 고성능, 복잡한 필터 검색
pgvector PostgreSQL 확장 기존 DB 활용, SQL 통합 PostgreSQL 사용 중인 조직
그림 6. 주요 벡터 데이터베이스 6종 비교 — 이번 편 구현은 ChromaDB(학습)와 pgvector(프로덕션)를 쓴다

벡터 DB의 심장은 인덱스 알고리즘입니다. 표준은 HNSW(Hierarchical Navigable Small World) — 벡터들을 여러 층의 그래프로 연결해 두고, 검색 시 최상위 레이어의 듬성듬성한 연결에서 시작해 가까운 노드로 이동하며 아래층으로 내려가는 방식입니다. 고속도로로 대략적인 방향을 잡고 국도로 좁혀 들어가는 내비게이션과 같은 원리로, 시간 복잡도 O(log N) — 수백만 벡터에서도 밀리초 안에 최근접 이웃을 찾습니다. 대안인 IVFFlat은 클러스터 기반 근사 검색으로 인덱스 빌드는 빠르지만 recall이 HNSW보다 낮고, Flat(전수 비교)은 정확하지만 느려 작은 데이터셋에만 씁니다.

💡 pgvector 메모 — PostgreSQL 확장 pgvector는 vector 타입으로 임베딩을 저장하고, 연산자 <->(L2 거리) · <=>(코사인 거리) · <#>(음의 내적)로 검색합니다. HNSW·IVFFlat 인덱스를 모두 지원하며, 10절에서 실제 스키마로 봅니다.

6. Vector vs BM25 vs Hybrid — 프로덕션은 왜 둘 다 쓰나

교재의 예시가 두 검색 방식의 차이를 정확히 보여줍니다. 질문은 "차량 정비 비용이 얼마인가요?"입니다.

🔤 키워드 검색 (BM25)
"차량"·"정비"·"비용" 단어가 포함된 문서를 매칭
"자동차 수리 요금" 문서는 못 찾음 — 단어 불일치
✅ 대신 정확한 용어·코드·ID 검색엔 강하고 빠름
🧭 의미 검색 (Vector Search)
질문의 '의미'를 벡터로 변환해 유사한 문서를 검색
"자동차 수리 요금"·"차량 관리 비용"도 발견 — 동의어 인식
❌ 대신 임베딩 계산 비용이 들고, 정확한 ID 매칭엔 약함
그림 7. 키워드 검색 vs 의미 검색 — 같은 질문, 다른 검색 결과

어느 쪽도 완전하지 않으니, 프로덕션의 답은 하이브리드 검색(Hybrid Search) — 두 결과를 결합하는 것입니다. 결합 방식의 표준은 RRF(Reciprocal Rank Fusion): 각 검색의 순위(rank)를 1 / (60 + rank) 점수로 바꿔 가중 합산합니다. 점수 스케일이 다른 두 검색을 "순위"라는 공통 단위로 환산하는 거죠. 가중치 alpha는 1.0이면 벡터만, 0.0이면 키워드만, 0.5가 균형(일반 권장), 0.7이 의미 검색 강조입니다.

  Vector Search BM25 (키워드) Hybrid
원리 의미 유사도 키워드 매칭 둘의 결합(RRF)
장점 동의어 인식 · 문맥 이해 정확한 매칭 · 빠른 속도 · 구현 간단 양쪽 장점 · 높은 정확도
단점 계산 비용 · 임베딩 필요 동의어 미인식 · 오타에 약함 복잡성 증가 · 튜닝 필요
적합 의미 기반 질문 · 탐색적 검색 정확한 용어 · 코드/ID 검색 엔터프라이즈 · 프로덕션
그림 8. 검색 기법 3종 비교 — 프로덕션 권장은 Hybrid

7. RAG 변형 3종 — Standard · Graph · Agentic

기본형 Standard RAGQuery → Embed → Search → Top-K → Generate 직선 흐름입니다. 구현이 간단하고 응답이 빠르지만, 한 번 검색해서 안 나오면 그걸로 끝 — 복잡한 질문에 한계가 있죠. 그래서 두 가지 확장이 나왔습니다.

Graph RAG는 벡터 대신(또는 함께) 지식 그래프를 씁니다. "A전자 CEO가 속한 그룹의 다른 계열사는?"이라는 질문은 유사 문서 검색으로는 풀기 어렵습니다 — CEO→A전자→그룹→계열사들처럼 엔티티 관계를 따라가는 다단계 추론(Graph Traversal)이 필요하니까요(교재의 지식 그래프 예시를 익명화한 것입니다). 조직도·네트워크·관계 질문이 많은 도메인의 선택지입니다.

Agentic RAG는 검색 위에 추론 레이어를 얹습니다. 흐름이 분석 → 검색 → 검증 → (재검색) → 생성으로 바뀌고, 다섯 가지 핵심 기능이 추가됩니다 — Query Analysis(의도 파악·쿼리 확장), Multi-source Retrieval(여러 소스 검색), Source Validation(검색 결과 충분성·신뢰도 검증), Conflict Resolution(상충 정보 해결, 최신 정보 우선), Knowledge Gap Detection(정보 부족을 인식하고 재검색하거나 "모름"을 명시). 한 마디로, 검색을 "한 번의 함수 호출"이 아니라 에이전트의 의사결정 과정으로 다루는 겁니다.

📄 Standard RAG
Query → Retrieve → Generate
단순 Q&A · 빠른 응답 · 단일 소스 검색이면 충분할 때
🕸️ Graph RAG
지식 그래프 관계 추론
관계·네트워크 질문 · 다단계 추론 · 엔티티 간 연결 탐색
🤖 Agentic RAG
분석→검색→검증→재검색→생성
복잡한 분석 질문 · 출처 검증 필수 · 상충 정보 해결 · 높은 정확도 요구
그림 9. RAG 변형 선택 가이드 — 질문의 복잡도와 정확도 요구가 변형을 정한다

이제 코드로 갑니다. 구현은 네 가지 — ① ChromaDB+Gemini로 Standard RAG의 뼈대를 잡고, ② LangGraph로 Agentic RAG의 검증·재검색 루프를 만들고, ③ pgvector+Ollama로 프로덕션·온프레미스 스택을 구성하고, ④ Google ADK로 검색을 에이전트의 "도구"로 쥐여줍니다.

8. 구현 ① 기본 RAG — ChromaDB + Gemini로 뼈대 잡기

첫 구현(01_rag_basic.py)은 프레임워크 없이 RAG의 모든 부품을 직접 만들어 봅니다. 구성은 넷 — TextChunker(청킹) → GeminiEmbeddings(임베딩) → ChromaVectorStore(저장·검색, 없으면 인메모리 SimpleVectorStore로 폴백) → RAGPipeline(전체 조립). 생성 모델은 gemini-2.0-flash, 임베딩은 768차원 models/embedding-001입니다.

ℹ️ 본문의 모델 ID(gemini-2.0-flash · models/embedding-001)는 학습 교재 코드 기준입니다. 두 모델 모두 이후 제공이 종료되어 후속 모델로 대체되었으므로, 실제 실행 시에는 현행 Gemini 생성·임베딩 모델 ID로 바꿔 쓰세요. 패턴(파이프라인 구조·task_type 비대칭)은 모델이 바뀌어도 그대로입니다.
PYTHON · TextChunker — 문단 우선 분할 + 슬라이딩 윈도우
class TextChunker: def __init__(self, chunk_size=500, chunk_overlap=50, separator="\n\n"): ... def split_text(self, text, doc_id="doc") -> list[Chunk]: sections = text.split(self.separator) # 1) 문단 경계로 1차 분할 for section in sections: if len(section) > self.chunk_size: # 2) 큰 섹션은 슬라이딩 윈도우 for i in range(0, len(section), self.chunk_size - self.chunk_overlap): chunks.append(Chunk(content=section[i:i + self.chunk_size], doc_id=doc_id, chunk_index=chunk_index)) else: ... # 3) 작은 섹션은 chunk_size까지 병합, 끝 50자를 다음 청크에 중복

임베딩 클래스에서 놓치기 쉬운 디테일이 task_type 비대칭입니다. 같은 768차원이라도 저장할 문서검색 쿼리를 서로 다른 task_type으로 임베딩하면 검색 성능이 올라갑니다 — 문서는 "찾아질" 위치로, 쿼리는 "찾는" 위치로 최적화되는 비대칭 임베딩이죠.

PYTHON · GeminiEmbeddings — 문서용·쿼리용 task_type을 가른다
def embed_text(self, text): # 문서 저장(인덱싱)용 return genai.embed_content(model="models/embedding-001", content=text, task_type="retrieval_document")['embedding'] def embed_query(self, query): # 검색 쿼리용 return genai.embed_content(model="models/embedding-001", content=query, task_type="retrieval_query")['embedding'] # 같은 768차원이지만 용도별 최적화 — 비대칭 임베딩이 검색 품질을 높인다

파이프라인의 마지막 조각은 응답 생성과 신뢰도입니다. 검색된 청크를 [문서 N]으로 태깅해 LLM이 출처를 참조할 수 있게 하고, 프롬프트로 "컨텍스트 밖 정보 금지"를 못 박고, 최고 유사도 점수로 신뢰도를 매깁니다.

PYTHON · generate_response — 컨텍스트 태깅 + 제약 프롬프트 + 신뢰도
retrieved = self.retrieve(query, k=3) # 1) Top-K 검색 context = "\n\n".join( f"[문서 {i+1}] (유사도: {score:.2f})\n{chunk.content}" ...) prompt = f"""당신은 제공된 컨텍스트를 기반으로 정확하게 답변하는 AI 어시스턴트입니다. 1. 컨텍스트에 있는 정보만을 사용하여 답변하세요. 2. 컨텍스트에 없는 정보는 "제공된 문서에 해당 정보가 없습니다"라고 명시하세요. 3. 답변의 근거가 되는 문서를 언급하세요. 4. 확실하지 않은 내용은 추측하지 마세요. ===== 컨텍스트 =====\n{context}\n===== 질문 =====\n{query}""" max_score = max(score for _, score in retrieved) # 2) 신뢰도 산정 confidence = ("high" if max_score > 0.8 else "medium" if max_score > 0.5 else "low")

설계 결정을 정리하면 — 청크 500자(중복 50자, 10%), Top-K 3(컨텍스트 품질 vs 노이즈 균형), ChromaDB 우선(영속성 + HNSW 검색). ChromaDB는 유사도 대신 거리를 반환하므로 similarity = 1 / (1 + distance)로 변환하는 것도 잔디테일입니다. 여기까지가 Standard RAG의 전부 — 잘 동작하지만, 한 번 검색해서 부족하면 그대로 부족한 답이 나갑니다. 그 빈틈을 다음 구현이 메웁니다.

9. 구현 ② Agentic RAG — LangGraph 검증·재검색 루프

두 번째 구현(02_langgraph_rag_agent.py)은 LangGraph StateGraph로 Agentic RAG를 만듭니다. 검색 과정 전체가 상태(RAGState)를 공유하는 노드들의 그래프가 되고, 검증 결과에 따라 재검색이냐 생성이냐를 조건부 엣지가 분기합니다.

1
쿼리 분석
analyze_query
의도·유형 파악 + 쿼리 확장 3개
2
다중 검색
retrieve
원본+확장 쿼리로 검색 · 중복 제거
3
검증
validate
충분한가? 신뢰도는? 재검색 필요?
4
생성
generate
유형별 프롬프트 · 출처 번호 인용
↺ 부족하면 requery → 재검색 (최대 2회)
검증(validate)이 "부족"이라고 판단하면 제안 쿼리로 requery → retrieve 루프를 돈다 — iteration ≥ max_iterations(2)면 강제로 생성 진행
그림 10. Agentic RAG 그래프 — 분석 → 검색 → 검증 → (재검색 루프) → 생성

첫 노드 analyze_query는 LLM에게 질문 자체를 분석시킵니다. 의도, 핵심 개념, 질문 유형(factual·comparison·explanation·how-to), 복잡도, 그리고 확장 쿼리(Query Expansion) 3개. "RAG 장점"이라는 질문이 "RAG의 이점과 장점"·"RAG 사용 이유"·"RAG가 해결하는 문제"로 변주되면, 그만큼 검색 그물이 넓어집니다.

JSON · analyze_query_node의 출력 — LLM이 질문을 구조화한다
{ "intent": "두 RAG 방식의 차이점 비교", "key_concepts": ["Agentic RAG", "Standard RAG", "차이점"], "query_type": "comparison", # factual | comparison | explanation | how-to "complexity": "moderate", "rewritten_queries": [ # Query Expansion — 검색 그물 넓히기 "Agentic RAG Standard RAG 비교", "Agentic RAG 특징", "Standard RAG 한계"] }

검색 노드는 원본+확장 쿼리(최대 2개)로 각각 검색해 내용 해시로 중복을 제거하고, 검증 노드는 다시 LLM에게 묻습니다 — "이 검색 결과로 질문에 답할 수 있는가?" 충분성·부족한 정보·신뢰도(0~1)·재검색 필요 여부·제안 쿼리가 JSON으로 나오고, iteration ≥ max_iterations(기본 2)면 무한 루프 방지를 위해 강제로 생성으로 넘어갑니다. 이 전체 구조가 그래프 선언 한 곳에 모입니다.

PYTHON · 그래프 빌드 — 조건부 엣지가 재검색 루프를 만든다
graph = StateGraph(RAGState) graph.add_node("analyze_query", analyze_query_node) graph.add_node("retrieve", retrieve_documents_node) graph.add_node("validate", validate_retrieval_node) graph.add_node("requery", requery_node) graph.add_node("generate", generate_response_node) graph.add_edge(START, "analyze_query") graph.add_edge("analyze_query", "retrieve") graph.add_edge("retrieve", "validate") graph.add_conditional_edges("validate", should_requery, {"requery": "requery", "generate": "generate"}) # 검증 결과로 분기 graph.add_edge("requery", "retrieve") # 재검색 루프 graph.add_edge("generate", END) app = graph.compile()

생성 노드도 한 끗이 다릅니다. 1단계에서 분류한 질문 유형별로 응답 지시를 바꿉니다 — factual이면 "간결·정확하게", comparison이면 "표/목록으로", how-to면 "단계별 가이드로". 검증 신뢰도가 0.5 미만이면 "검색된 정보가 제한적"이라는 주의 문구를 답변에 끼워 넣습니다. 학습 자료가 제시하는 Standard→Agentic 전환 효과의 개념적 비교는 이렇습니다.

⚠️ 아래 수치는 학습 자료의 개념적 성능 비교(예시)이며 특정 시스템의 실측 결과가 아닙니다.
지표 Standard RAG Agentic RAG 변화
검색 품질 70% 90% +29%
답변 완성도 65% 88% +35%
출처 명시율 80% 95% +19%
복잡 질문 처리 55% 85% +55%
평균 지연 시간 1.5s 3.2s +113% (느려짐)
그림 11. Standard vs Agentic RAG — 검증·재검색의 대가는 지연 시간, 보상은 품질

10. 구현 ③ pgvector + Ollama — 데이터가 밖으로 안 나가는 RAG

세 번째 구현(03_pgvector_rag.py)은 프로덕션 지향입니다. 별도 벡터 DB SaaS 대신 PostgreSQL + pgvector 확장을 쓰고, 임베딩(nomic-embed-text, 768차원)과 LLM(llama3.2)은 Ollama 로컬로 돌립니다. 문서가 외부 API로 한 번도 나가지 않는 구성이라 보안 요건이 까다로운 조직에 맞고, pgvector의 매력은 넷 — SQL 통합(메타데이터 조인·복합 쿼리), ACID 트랜잭션, 하이브리드 검색(벡터+키워드), 스케일(수백만 벡터 HNSW 인덱싱)입니다.

SQL · 스키마 + HNSW 인덱스 — 벡터가 일반 테이블의 한 컬럼이 된다
CREATE EXTENSION IF NOT EXISTS vector; -- pgvector 활성화 CREATE TABLE chunks ( id SERIAL PRIMARY KEY, doc_id VARCHAR(64) REFERENCES documents(id) ON DELETE CASCADE, chunk_index INTEGER NOT NULL, content TEXT NOT NULL, embedding vector(768), -- nomic-embed-text 차원 metadata JSONB DEFAULT '{}' ); CREATE INDEX chunks_embedding_idx ON chunks USING hnsw (embedding vector_cosine_ops) WITH (m = 16, ef_construction = 64); -- recall vs 속도 트레이드오프 권장값

검색은 그냥 SQL입니다. 코사인 거리 연산자 <=>를 쓰고, 1 - 거리로 유사도를 만들며, 임계값(0.5) 미만은 버립니다 — 문서 테이블과의 JOIN으로 출처까지 한 쿼리에 나오는 게 SQL 통합의 힘이죠.

SQL · 코사인 유사도 검색 — JOIN으로 출처까지 한 번에
SELECT c.content, d.source, 1 - (c.embedding <=> %s::vector) AS similarity -- <=> = 코사인 거리 FROM chunks c JOIN documents d ON c.doc_id = d.id WHERE 1 - (c.embedding <=> %s::vector) >= 0.5 -- 유사도 임계값 ORDER BY c.embedding <=> %s::vector LIMIT 5;

6절의 하이브리드 검색이 여기서 실물로 등장합니다. 벡터 순위(CTE ①)와 PostgreSQL Full-Text Search 순위(CTE ②)를 RRF로 합산하는 단일 쿼리입니다 — 벡터 가중 0.7, 키워드 0.3. FTS 쪽이 실패하면 벡터 검색으로 폴백하는 안전장치도 챙깁니다.

SQL · RRF 하이브리드 검색 — 벡터 순위 + 키워드 순위의 가중 합산
WITH vector_results AS ( -- ① 벡터 검색 순위 SELECT c.id, c.content, d.source, ROW_NUMBER() OVER (ORDER BY c.embedding <=> %s::vector) AS vector_rank FROM chunks c JOIN documents d ON c.doc_id = d.id LIMIT 20 ), keyword_results AS ( -- ② 키워드(FTS) 순위 SELECT c.id, c.content, d.source, ROW_NUMBER() OVER (ORDER BY ts_rank(to_tsvector('simple', c.content), plainto_tsquery('simple', %s)) DESC) AS keyword_rank FROM chunks c JOIN documents d ON c.doc_id = d.id WHERE to_tsvector('simple', c.content) @@ plainto_tsquery('simple', %s) LIMIT 20 ) SELECT COALESCE(v.content, k.content) AS content, (COALESCE(1.0 / (60 + v.vector_rank), 0) * 0.7 -- RRF: 벡터 가중 0.7 + COALESCE(1.0 / (60 + k.keyword_rank), 0) * 0.3) AS rrf_score FROM vector_results v FULL OUTER JOIN keyword_results k ON v.id = k.id ORDER BY rrf_score DESC LIMIT 5; -- FTS 실패 시 similarity_search()로 폴백

워크플로우는 9절보다 단순한 3노드 LangGraph — retrieve → evaluate → generate입니다. 평가 노드가 검색 결과의 평균 유사도를 재고, confidence < 0.3이고 재시도 여유(iteration < 2)가 있으면 재검색으로 돌아갑니다. LLM 호출은 LCEL 그대로 — prompt | llm | StrOutputParser(). 1편에서 배운 파이프 연산자가 로컬 LLM 위에서도 똑같이 동작합니다.

11. 구현 ④ Google ADK — 검색을 "도구"로 쥔 에이전트

네 번째 구현(04_google_adk_rag.py)은 발상이 다릅니다. ②·③이 검색을 그래프의 노드로 명시했다면, Google ADK는 검색을 에이전트가 쥔 도구(FunctionTool)로 만듭니다. pgvector 검색 함수를 도구로 등록하면, 언제 검색할지는 Gemini 에이전트가 스스로 판단합니다 — 5편(도구 사용)의 패턴이 RAG와 만나는 지점이죠.

PYTHON · FunctionTool — docstring이 설명서, 타입 힌트가 스키마
def search_knowledge_base(query: str, num_results: int = 5) -> str: """지식 베이스(pgvector)에서 관련 문서를 검색합니다. # ← docstring = 도구 설명 Args: query: 검색할 질문 또는 키워드 # ← 타입 힌트 = 파라미터 스키마 num_results: 반환할 결과 수 (기본값: 5) """ results = get_vector_store().search(query, k=num_results) return "\n".join( f"[결과 {i}] (유사도: {r.score:.3f}) 출처: {r.source}\n내용: {r.content[:300]}..." for i, r in enumerate(results, 1)) search_tool = FunctionTool(func=search_knowledge_base) # 함수 → 에이전트 도구 sources_tool = FunctionTool(func=get_document_sources) # 저장된 출처 목록 도구

RAG의 규율은 시스템 프롬프트(instruction)에 들어갑니다 — "답하기 전에 반드시 검색하라", "검색에 없으면 없다고 말하라", "출처를 명시하라", "유사도가 낮으면(< 0.3) 정보 부족을 알려라". 그리고 SequentialAgent로 질문 분석 에이전트를 앞단에 붙이면 ②의 쿼리 분석 단계가 ADK식으로 재현됩니다 — output_key로 분석 결과가 다음 에이전트에 전달되는 구조입니다.

PYTHON · LlmAgent + SequentialAgent — RAG 지침과 2단 파이프라인
rag_agent = LlmAgent( name="rag_assistant", model=MODEL_ID, instruction="""당신은 지식 베이스를 활용하는 RAG 에이전트입니다. 1. 답변 전에 반드시 search_knowledge_base 도구로 관련 정보를 검색하세요. 2. 검색 결과에 없는 내용은 "해당 정보가 지식 베이스에 없습니다"라고 답하세요. 3. 답변 시 참조한 출처(source)를 명시하세요. 4. 검색 유사도 점수가 낮으면(< 0.3) 정보 부족을 알리세요.""", tools=[search_tool, sources_tool], output_key="rag_response") # 결과를 세션 상태에 공유 pipeline = SequentialAgent( # 분석 → 검색·응답 2단 파이프라인 name="rag_pipeline", sub_agents=[query_analyzer, rag_agent], description="질문 분석 → 검색 → 응답 생성 파이프라인")

참고로 Gemini 임베딩의 task_type은 retrieval_document·retrieval_query 외에 semantic_similarity(문서 간 비교)·classification(분류)도 있어, 같은 임베딩 모델을 용도별로 달리 최적화할 수 있습니다. 실행은 Runner + InMemorySessionService 조합 — ADK의 표준 실행 패턴 그대로입니다.

12. 4구현 비교 — 같은 패턴, 네 가지 추상화

네 구현 모두 "검색해서 근거로 답한다"는 같은 패턴이지만, 흐름을 누가 제어하느냐가 다릅니다. ①은 직접 호출, ②·③은 개발자가 그래프로 명시(graph.invoke()), ④는 LLM이 도구 호출을 판단(Runner.run())합니다.

측면 ① ChromaDB+Gemini ② LangGraph ③ pgvector+Ollama ④ Google ADK
RAG 형태 Standard Agentic(분석·검증·재검색) Standard + 평가 루프 도구 호출형
벡터 스토어 ChromaDB / 인메모리 인메모리(데모) pgvector(HNSW) pgvector(HNSW)
임베딩 embedding-001 (768) embedding-001 (768) nomic-embed-text (768, 로컬) embedding-001 (768)
LLM gemini-2.0-flash gemini-2.0-flash llama3.2 (Ollama 로컬) Gemini (MODEL_ID)
검색 방식 코사인 Top-K 다중 쿼리 + 중복 제거 하이브리드(RRF) 코사인 Top-K(도구)
재검색 없음 검증 노드 분기 (최대 2회) confidence<0.3 시 (최대 2회) 에이전트 재량(도구 재호출)
흐름 제어 직접 호출 StateGraph + 조건부 엣지 StateGraph + LCEL LlmAgent + FunctionTool
적합 학습·프로토타입 복잡 질문·검증 필수 온프레미스·기존 PostgreSQL Gemini 생태계·도구 통합
그림 12. 구현 4종 비교 — 같은 목표, 다른 추상화 (모델 ID는 교재 코드 기준 · 버전에 따라 변동)
💡 고르는 법 — RAG를 처음 배운다면 ①로 부품을 이해하고, 검증·재검색이 필요한 복잡한 질문이 많으면 ②, 데이터를 외부로 못 보내거나 이미 PostgreSQL을 쓰고 있으면 ③, Gemini 생태계에서 여러 도구와 함께 굴릴 거면 ④입니다. 실무에서는 ③의 저장소 + ②의 검증 루프 조합이 흔한 종착점입니다.

13. 엔터프라이즈 — 사내 지식이 답변이 되는 순간

엔터프라이즈에서 RAG의 자리는 명확합니다 — 기업 내부 지식(매뉴얼·정책·이력·제품 정보)과 LLM 사이의 연결 레이어입니다. CRM·ERP·Groupware의 데이터 소스를 통합 벡터 DB로 모으되 도메인별 컬렉션을 분리하고, 접근 권한·메타데이터를 태깅하고, RAG 엔진(쿼리 라우팅·하이브리드 검색·컨텍스트 조립)을 거쳐 LLM 게이트웨이(감사 로깅·출처 명시)로 나가는 구조죠.

⚠️ 아래 수치는 적용 전략 문서의 도입 기대효과(목표치)이며 특정 기업의 실측 결과가 아닙니다.
시스템 핵심 적용 기대효과(목표치)
CRM
고객지원 · 영업 · 마케팅
매뉴얼·FAQ·티켓 이력 기반 자동 응답(의도 분류 → 소스 라우팅), 영업 미팅 준비 브리핑, 브랜드 가이드라인 준수 콘텐츠 응답 시간 70%↓ · 오답률 85%↓ · 상담사 부담 60%↓ · 미팅 준비 2시간→15분 · 제안서 1일→2시간 · Win Rate 15~20%↑
ERP
재무 · 구매 · 생산 · 인사
K-IFRS·세법·내부정책 조항 인용(Agentic RAG), 공급업체 분석·계약 협상 지원, SOP·품질 이력 트러블슈팅, HR 정책 안내(PII 필터) 규정 해석 오류 90%↓ · 결산 기간 30%↓ · 감사 준비 50%↓ · 규정 준수율 100%
Groupware
문서 · 메일 · 회의
전사 문서 시맨틱 검색(권한 필터·최신 버전 우선·리랭킹), 메일 스레드 컨텍스트·응답 초안, 회의록 생성·액션 아이템 추적 정보 검색 시간 70%↓ 목표 · 이전 결정사항·미완료 액션 자동 소환
그림 13. CRM·ERP·Groupware 적용 영역과 기대효과(목표치)

소비자용 RAG와 다른, 엔터프라이즈 고유의 요구 네 가지를 놓치면 안 됩니다. ① 접근 제어 — 검색 자체에 권한 필터를 겁니다(전사 공개·부서·프로젝트·개인 문서를 $or 조건으로 조립). 권한 없는 문서는 검색 결과에 아예 나오면 안 됩니다. ② 조항 정확 인용 — 재무·법무에선 "비슷한 내용"이 아니라 조항 번호가 필요합니다. ③ 감사 로깅 — 누가 무엇을 묻고 어떤 근거로 답했는지 기록. ④ 버전 관리 — 구버전 정책이 검색되면 사고이므로, 최신 버전 우선·버전 중복 제거가 필수입니다.

💰 12개월 ROI (목표치)
투자 $180,000 (인프라 $50K · 개발 $100K · 운영 $30K)
절감 $290,000 (고객지원 $120K · 영업 $80K · 규정준수 $50K · 온보딩 $40K)
1년 ROI 61% = ($290K − $180K) ÷ $180K
📈 3단계 도입 로드맵
Phase 1 · Foundation (1~2개월) 인프라 + 저위험 문서(HR 정책·FAQ) 파일럿
Phase 2 · Expansion (2~3개월) CRM·ERP·Groupware 확장 + Agentic·Hybrid·권한 강화
Phase 3 · Optimization (1~2개월) 캐싱·피드백 리랭킹·품질 모니터링 자동화
그림 14. ROI와 단계별 도입 로드맵 (목표치 기준) — 파일럿은 항상 저위험 문서부터

성공 지표(KPI)는 다섯 가지로 잡습니다 — 정보 검색 시간 -70%, 응답 정확도 90%+(전문가 검토 샘플링), 활용도 부서당 일 50+ 쿼리, 사용자 만족도 4.0/5.0+, 업무 시간 절감 월 500시간+. 그리고 기술보다 중요한 성공 요인이 둘 있습니다 — 데이터 품질(문서가 낡으면 RAG도 낡은 답을 합니다)과 문서화 문화(지식이 문서로 남아야 도서관에 꽂을 책이 생깁니다).

14. 케이스 스터디 — "이 장비 구매, 어떻게 회계처리하죠?"

재무팀 주니어가 신규 장비 구매 건의 회계처리 기준을 물었습니다. 일반 챗봇이라면 그럴듯한 일반론을 내놓고 끝이겠지만, 규정이 얽힌 재무 질문은 틀리면 감사 지적으로 돌아옵니다. 적용 전략 문서의 FinanceRAGAgent가 이 질문을 처리하는 여섯 단계를 따라가 봅니다.

① 민감도 분류 — 질문을 민감도 분류기에 먼저 통과. 고민감(high) 질문인데 요청자가 권한 역할(CFO·Controller)이 아니면 재무팀 검토로 에스컬레이션 — 지난 편의 HITL 게이트가 여기 붙습니다
② 쿼리 유형 분류·라우팅 — "회계처리" 질문 → K-IFRS 회계기준 스토어로. 세무 질문이면 세법, 결재 질문이면 내부정책 스토어로 — 도메인별 컬렉션 분리가 빛나는 지점
③ 다중 스토어 검색 — 라우팅된 스토어들에서 각각 k=3으로 검색해 관련 조항·과거 결산 사례를 수집
④ 규정 충돌 검사 — 검색된 문서 간 규정이 상충하면 우선순위 규칙(신법 우선·특별법 우선)으로 해소 — Agentic RAG의 Conflict Resolution이 실무 규칙으로 구체화된 형태
⑤ 조항 인용 응답 — "비슷한 내용"이 아니라 조항 번호를 정확히 인용한 답변 생성 — 감사 대응의 근거가 되는 형식
⑥ 감사 로그 — 질문·응답·요청자 역할을 감사 로그에 기록 — "누가, 무엇을, 어떤 근거로"가 추적 가능

이 흐름이 이번 편 전체의 축소판입니다. 도메인별 컬렉션(5절 벡터 DB), 라우팅과 다중 소스 검색(7절 Agentic), 충돌 해소와 출처 인용(7·8절), 권한과 감사(13절)가 한 줄기로 꿰입니다. 결과는 "정책에 따르면..."이라는 추측이 아니라, 조항 번호가 달린 근거 있는 답변 — 그리고 민감한 건은 사람에게 넘어가는 안전장치까지. RAG와 휴먼 인 더 루프는 이렇게 한 시스템 안에서 만납니다.

15. 베스트 프랙티스 — 도전과제, 고급 기법, 체크리스트

RAG는 만들기는 쉽고 잘 만들기는 어렵습니다. 교재가 정리한 6대 도전과제와 해법을 먼저 한 표로 봅니다.

도전과제 증상 해결책
청크 경계 문제 정보가 여러 청크에 분산 중복(Overlap) · Parent-Child 청킹
검색 품질 저하 관련 문서를 못 찾음 Hybrid 검색 · 쿼리 확장
상충 정보 문서 간 정보 불일치 날짜 기반 우선순위 · 출처 신뢰도
Latency 응답 시간 증가 캐싱 · 사전 계산 · 스트리밍
비용 임베딩·LLM 호출 비용 누적 로컬 모델(Ollama) · 배치 처리
Hallucination 여전히 허위 정보 생성 출처 명시 · 팩트체크 레이어
그림 15. RAG 6대 도전과제와 해결책

검색 품질을 한 단계 끌어올리는 고급 기법도 세 가지 알아두면 좋습니다. Query Expansion(LLM으로 질문을 3가지로 재작성해 검색 그물 확장 — 9절에서 구현), HyDE(Hypothetical Document Embeddings — LLM에게 "답이 될 만한 가상 문서"를 먼저 쓰게 하고 그 문서로 검색합니다. 질문보다 답변이 문서와 더 닮았기 때문), Parent-Child Retrieval(500토큰 Child 청크로 정밀하게 찾고, 매칭된 Child의 2000토큰 Parent를 반환 — 검색 정확도와 풍부한 컨텍스트를 동시에). 검색을 더 똑똑하게 만드는 다른 길로는 5. 도구 사용처럼 검색을 도구로 쥐여 주는 방법(11절)이 있고, 민감 도메인이라면 13. 휴먼 인 더 루프의 승인 게이트를 응답 앞에 세우세요.

✅ RAG 구축 체크리스트 (교재 10항목)
☐ 1. 데이터 소스 정의 및 접근 방법 결정
☐ 2. 청킹 전략 선택 (크기 · 중복 · 방식 — 시작점 500/50)
☐ 3. 임베딩 모델 선택 (비용 · 성능 · 다국어 지원)
☐ 4. Vector DB 선택 (규모 · 호스팅 · 기능 — 그림 6)
☐ 5. 검색 방식 결정 (Vector / BM25 / Hybrid — 프로덕션은 Hybrid)
☐ 6. RAG 변형 선택 (Standard / Graph / Agentic — 그림 9)
☐ 7. 프롬프트 템플릿 설계 ("컨텍스트에 없으면 없다고 답하라")
☐ 8. 평가 지표·테스트 케이스 정의 (정확도 · 환각률 · 출처 명시율)
☐ 9. 모니터링·피드백 루프 구축
☐ 10. 지속적 개선 프로세스 수립 (문서 갱신 주기 포함)
★ 마치며

순서대로 쪼개고(체이닝), 갈래를 나누고(라우팅), 병렬로 돌리고(병렬화), 스스로 비평하고(리플렉션), 도구를 쥐고(도구 사용), 계획하고(플래닝), 팀을 이루고(멀티 에이전트), 기억하고(메모리), 배우고(학습), 표준으로 연결되고(MCP), 스스로 채점하고(목표 설정), 넘어져도 일어서고(예외 처리), 멈춰야 할 순간 사람을 부르던(휴먼 인 더 루프) 에이전트 — 이번엔 아는 척 대신 찾아보고, 환각 대신 근거로 말하는 법(RAG)을 배웠습니다. 다음 편은 A2A 프로토콜(Agent2Agent): 서로 다른 회사·다른 프레임워크로 만든 에이전트들이 표준 프로토콜로 서로를 찾고 일을 맡기는 법입니다. 도구를 연결하는 표준이 MCP였다면, 에이전트끼리 연결하는 표준이 옵니다.

#RAG #검색증강생성 #벡터데이터베이스 #임베딩 #청킹 #HybridSearch #pgvector #AgenticRAG #LangGraph #AI에이전트
반응형