제6부 응용: 프롬프트·RAG·에이전트
6.3 RAG I — 기본 파이프라인
RAG를 파는 가장 흔한 문장은 "환각을 없앤다"이고, 그 문장은 틀렸다. 검색 증강 생성(Retrieval-Augmented Generation, RAG)이 실제로 하는 일은 그보다 조용하고 더 근본적이다 — 지식을 모델 가중치 밖으로 꺼내 인덱스에 두고, 질문이 올 때마다 관련 문서를 찾아와 그 문서에 근거해 답하게 한다. 원 개념은 Lewis 등이 2020년에 정식화했지만, 지금 사람들이 RAG라 부르는 것은 특정 아키텍처가 아니라 "검색 → 프롬프트에 증강 → 생성"이라는 시스템 패턴이다. 이 장이 남기려는 문장은 하나다. 검색이 못 찾은 것을 생성은 고칠 수 없다. RAG는 정확한 인출을 검색 엔진에, 언어 이해와 종합을 언어모델에 맡기는 역할 분담이고, 그래서 답변 품질의 상한은 검색 품질이 정한다. 환각은 줄지만 사라지지는 않는다 — 이 두 사실을 동시에 정직하게 붙들고 파이프라인 전체를 다룬다. rerank·hybrid·query 변환처럼 검색 품질(그 상한)을 높이는 고급 기법과 평가는 다음 장(6.4)의 몫이고, 여기서는 "왜·언제 RAG인가"와 "정확히 무엇이 어떤 순서로 일어나는가"에 무게를 싣는다. 앞 장(6.2)이 세운 임베딩·근사최근접(ANN) 검색·벡터 DB가 이 장의 검색 엔진이다.
이 장에 나오는 청크 크기·오버랩·top-k·토큰 수는 규모를 가늠하기 위한 개략값이다. 임베딩 모델의 최대 입력 길이, 벤더별 컨텍스트 창과 토큰 단가, "몇 토큰이 최적인가"는 시점·모델·도메인마다 다르고 수시로 바뀐다. 무게는 특정 숫자가 아니라 원리와 그 이유에 있다.
6.3.1 파라메트릭 지식의 네 가지 장벽
사전학습·정렬을 마친 언어모델은 방대한 지식을 가중치 안에 담고 있다. 그런데도 제품에서 모델 하나만으로는 곧장 장벽에 부딪힌다. 이 장벽들을 하나씩 정확히 보면 RAG가 무엇을 위한 도구인지가 저절로 드러난다.
첫째는 지식 컷오프다. 모델은 학습 데이터가 잘린 시점 이후를 모른다. 지난주 발표된 API 변경, 오늘 아침 뉴스, 이번 분기 실적은 가중치에 없다. 지식을 새로 굽자면 재학습·파인튜닝인데 이것은 느리고 비용이 크며(3.8), 세상이 바뀔 때마다 반복할 수 없다. RAG는 지식을 가중치 밖 인덱스에 두므로, 인덱스만 갱신하면 모델을 변경하지 않고 최신성을 유지한다.
둘째는 비공개·사내 데이터다. 모델은 공개 웹과 라이선스 코퍼스로 학습했으니 우리 회사의 위키·계약서·티켓·코드베이스·고객 이력을 원리상 모른다. 이 데이터를 파인튜닝으로 넣는 것도 가능하지만 두 가지가 걸린다. 데이터가 계속 바뀌고, "누가 어떤 문서를 볼 수 있나"라는 권한·격리를 파인튜닝된 가중치로 표현하기가 사실상 불가능하다. RAG는 사내 문서를 인덱스에 넣고 검색 단계에서 권한 필터를 걸 수 있어(6.3.4) 데이터 거버넌스와 궁합이 좋다.
셋째는 근거의 부재다. 파라메트릭 답변은 그럴듯한 문장을 내지만 그 문장이 어디서 나왔는지를 댈 수 없다. 규제·의료·법률·기업 지식 검색처럼 출처가 곧 신뢰인 도메인에서 이는 치명적이다. RAG는 검색한 원문을 프롬프트에 함께 넣고 인용으로 되돌려 주므로, 사용자가 답의 근거를 클릭해 확인할 수 있다. 실무에서 RAG를 택하는 가장 강한 이유가 환각 감소가 아니라 바로 이 감사 가능성인 경우가 많다 — 답이 맞느냐 이전에, 왜 그렇게 답했는지를 사람이 검증할 수 있느냐가 먼저다.
넷째는 규모와 정확 인출이다. 세상의 모든 문서를 가중치에 욱여넣을 수 없고, 넣어도 그중 하나를 정확히 그대로 인출하기 어렵다 — 가중치는 지식을 손실 압축하기 때문이다. 수백만 문서 규모에서 특정 사실을 글자 그대로 끌어오는 일은 신경망 인출보다 검색 인덱스가 압도적으로 잘한다. 계약서 4조 2항의 문구를 정확히 되찾는 것은 검색의 일이고, 그것을 읽어 질문에 맞게 풀어 주는 것은 언어모델의 일이다.
네 장벽은 한 문장으로 묶인다. 지식이 크고, 자주 바뀌고, 우리 것이고, 출처가 필요할수록 그것을 가중치에 굽기보다 인덱스에 두는 편이 낫다. RAG는 그 인덱스와 언어모델을 잇는 배관이다.
6.3.2 RAG가 하는 일과 못 하는 일 — 검색이라는 상한
한 줄 정의는 이렇다. RAG는 비파라메트릭 메모리(외부 인덱스)를 파라메트릭 모델(언어모델)에 붙여, 질문마다 관련 문서를 검색해 프롬프트에 넣고 그 근거로 생성하는 것이다. 이 정의에서 곧장 나오는, 그리고 이 장 내내 되돌아올 성질이 하나 있다 — 시스템의 품질이 검색·생성이라는 두 단계의 곱이 아니라, 검색이라는 상한에 갇힌다는 것이다.
왜 상한인가. 생성 단계는 프롬프트에 들어온 근거만 볼 수 있다. 정답을 담은 문서가 검색되지 못해 컨텍스트에 없으면, 아무리 좋은 모델도 그 사실을 알 길이 없다. 모델은 근거의 공백을 자기 기억으로 메꾸며 지어내거나, 근거로는 답할 수 없다고 물러설 뿐이다. 검색이 상위 결과에 정답을 포함하지 못하면 그 위 모든 단계가 무의미하다. 이 비대칭 때문에 실무에서 RAG 품질을 높이는 노력의 대부분은 생성 프롬프트를 다듬는 데가 아니라 파싱·청킹·검색을 고치는 데 투입하는 것이 옳다. 팀들이 흔히 거꾸로 한다 — 눈에 잘 보이는 프롬프트만 만지며 몇 %를 개선하는 동안, 정작 큰 손실은 검색이 애초에 정답을 포함하지 못한 데서 나온다.
그리고 이 장에서 미리, 그러나 조용히 분명하게 깨 둘 통념이 있다. RAG는 환각을 없애지 않는다. 근거를 제공해 부분적으로 줄일 뿐이다. 검색이 정답을 못 찾거나, 모델이 근거를 무시하거나, 근거 자체가 틀렸거나, 근거를 잘못 종합할 때 환각은 그대로 살아남는다 — 이 네 경로를 6.3.11에서 낱낱이 다룬다. 인용이 붙어 있다는 것은 답이 맞다는 보증이 아니라 사용자가 확인할 통로가 있다는 뜻일 뿐이고, 오히려 틀린 답에 그럴듯한 인용이 붙으면 사람이 더 쉽게 속는 역설까지 생긴다.
| 파라메트릭 지식의 장벽 | RAG가 하는 일 | 그래도 남는 한계 |
|---|---|---|
| 지식 컷오프 | 인덱스만 갱신해 최신 문서 인출 | 인덱스가 노후화되면 답도 똑같이 노후화됨 |
| 비공개·사내 데이터 | 사내 인덱스 + 검색 단계 권한 필터 | 인덱싱·권한 파이프라인을 직접 운영 |
| 근거 부재 | 원문 인용 → 감사 가능 | 모델이 인용을 조작·오배치할 수 있음 |
| 규모·정확 인출 | 정확 인출은 검색, 종합은 모델 | 검색이 못 찾으면 생성이 못 고침(상한) |
6.3.3 두 시간대 — 인덱싱 타임과 쿼리 타임
RAG를 처음 그릴 때 가장 흔한 혼동은 모든 단계를 요청 시점에 한다고 착각하는 것이다. 실제로는 성격이 완전히 다른 두 시간대가 있고, 이 둘을 갈라 놓아야 성능·비용·운영이 보인다. 이 구분은 5.1에서 한 요청을 프리필과 디코드로 가른 것과 같은 종류의 분할이다 — 부하가 큰 일을 앞으로 밀고 지연이 걸리는 곳은 경량으로 유지한다.
인덱싱 타임은 오프라인 배치다. 문서를 미리 수집·청킹·임베딩해 인덱스를 만든다. 문서가 추가·수정될 때만 실행하며 사용자 요청과 무관하다. 쿼리 타임은 온라인이고 요청당이다. 질문이 올 때마다 질문을 임베딩해 검색 → 증강 → 생성을 밟는다. 사용자가 기다리는 지연 예산이 걸리는 곳은 오직 여기다. 원칙은 하나다 — 임베딩과 인덱싱처럼 부하가 큰 일은 전부 인덱싱 타임으로 밀어 놓고, 쿼리 타임에는 경량 검색과 생성만 남긴다. 새 문서 1,000편을 임베딩하는 데 몇 분이 걸려도 그것이 밤사이 배치라면 아무도 기다리지 않지만, 그 일을 질문마다 한다면 모든 요청이 그만큼 느려진다.
일곱 단계 — 수집, 청킹, 임베딩, 인덱싱, 검색, 증강, 생성 — 중 앞 넷이 인덱싱 타임, 뒤 셋이 쿼리 타임이다. 이 장은 각 단계를 순서대로 다룬다. 검색과 증강 사이에 rerank·query 변환 같은 단계가 더 추가되는 것이 고급 RAG이고, 그것은 6.4이다.
두 시간대를 잇는 불변식이 하나 있고, 이것을 어기면 시스템이 조용히 붕괴한다. 인덱싱과 쿼리는 반드시 같은 임베딩 모델, 같은 정규화, 같은 벡터 공간을 써야 한다. 청크는 모델 A로 임베딩해 인덱스에 넣어 놓고 질문은 모델 B로 임베딩하면, 두 벡터가 애초에 다른 공간에 살아 거리가 의미를 잃고 검색이 붕괴한다(6.2). 여기서 나오는 운영상의 무게가 크다 — 임베딩 모델을 더 나은 것으로 바꾸고 싶으면 질문 쪽만 바꿔서는 안 되고 인덱스 전체를 재구축해야 한다. 이 재구축 비용이 모델 업그레이드 부담을 키우는 실무 함정이라, 임베딩 모델 선택은 처음부터 신중해야 한다.
6.3.4 수집·파싱 — 검색 품질의 실제 상한
파이프라인의 첫 단계는 원본 문서를 깨끗한 텍스트와 메타데이터로 바꾸는 일이다. 화려하지 않지만 검색 품질의 상한을 여기서 정한다. 파싱이 문서를 망가뜨리면 그 아래 임베딩·검색·생성이 아무리 좋아도 복구할 수 없다 — 6.3.2의 검색 상한 논리가 한 층 더 앞으로 이동해, 파싱이 검색의 상한이 된다.
파싱이 어디서 깨지는지는 문서 종류마다 다르지만 몇 가지는 어디서나 나온다. PDF의 2단 조판은 텍스트 추출 시 읽는 순서를 뒤섞는다 — "왼쪽 단 전체 → 오른쪽 단"이어야 할 것이 줄 단위로 좌우가 번갈아 붙어, 한 문장이 다른 문장 조각과 뒤엉킨다. 표는 특히 위험하다. 추출기가 셀 구조를 붕괴시키면 "2024년 매출 120억, 2025년 150억"이 "2024 2025 매출 120 150 억"처럼 숫자와 연도가 뒤섞여, 임베딩은 원문과 전혀 다른 것을 인코딩하고 검색은 잘못된 것을 되찾는다. 스캔 문서는 OCR을 거쳐야 하고 그 과정에서 0과 O, 1과 l이 뒤바뀐 오류가 그대로 인덱스에 기록된다. HTML·마크다운은 내비게이션·광고·푸터 같은 보일러플레이트를 걷어내지 않으면 노이즈가 청크의 절반을 차지해, 정작 본문 신호를 희석한다. 같은 문서의 여러 판본과 복사된 문단은 중복 제거를 안 하면 검색 상위를 똑같은 내용으로 가득 채워 다양성을 없앤다.
그래서 정규화는 공백·제어문자 정리, 보일러플레이트 제거, 중복 문서·문단 dedup, 필요하면 표·코드를 사람이 읽을 수 있는 텍스트로 직렬화하는 일까지를 포함한다. 그리고 이 단계에서 반드시 함께 뽑아 두어야 할 것이 메타데이터다. 메타데이터는 나중에 붙이려면 어렵거나 불가능하므로 수집 시점에 추출해 청크와 함께 저장하는 것이 표준이다.
| 메타데이터 | 쓰임 |
|---|---|
source·url·doc_id | 인용의 근거, 원문 링크 |
title·section·heading | 사람이 읽을 출처 표기, 구조 인지 청킹 |
created_at·updated_at | 최신성 필터·가중(오래된 문서 감점) |
author·acl·tenant | 권한 필터 — 누가 이 청크를 볼 수 있나 |
lang·doc_type | 언어·문서종류별 필터·라우팅 |
권한 메타데이터(acl)는 특히 그렇다. 검색 단계에서 사용자 권한으로 필터링하지 않으면 RAG가 그대로 데이터 유출 경로가 된다 — 인사팀 문서가 인덱스에 들어가 있고 필터가 없으면, 아무 직원이나 "내 상사 연봉은?"으로 그것을 끌어낼 수 있다. 사내 검색에서 흔한 사고이고, 보안 전반은 7.4에서 다룬다. 새 코퍼스를 붙일 때 가장 값싼 개선은 파싱 결과를 눈으로 표본 검수하는 것이다. 임베딩 모델을 튜닝하기 전에, 표가 제대로 텍스트가 됐는지 몇 편만 열어 보는 편이 훨씬 큰 이득을 준다.
6.3.5 청킹 — 문서를 어떻게 자르나
임베딩과 검색은 문서 전체가 아니라 조각(chunk) 단위로 이뤄진다. 왜 전부 다루지 않고 자르는가에는 세 이유가 겹친다. 임베딩 모델에는 최대 입력 길이가 있어 긴 문서 전체를 못 넣는다. 한 벡터가 너무 긴 텍스트를 요약하면 "이 문단이 질문과 관련 있다"는 신호가 무관한 주변 텍스트에 묻혀 검색의 변별력이 떨어진다 — 조각이 작을수록 한 조각이 한 주제에 가까워 검색이 뾰족해진다. 그리고 검색 결과 top-k개를 프롬프트에 넣어야 하는데 조각이 크면 몇 개만 넣어도 컨텍스트 창을 전부 차지한다. 자르는 방식에 따라 이 세 성질 전체가 달라지므로, 청킹은 RAG에서 가장 저평가된 조정 변수다.
6.3.5.1 청킹 전략의 계보
| 전략 | 방법 | 장점 | 단점 |
|---|---|---|---|
| 고정 크기(fixed) | $N$ 토큰(또는 문자)마다 기계적으로 자름 | 단순·빠름·예측 가능 | 문장·의미 경계 무시 → 문장 중간 절단 |
| 문장 기반(sentence) | 문장 경계로 자르고 목표 크기까지 묶음 | 문장이 안 잘림 | 문장 분리기가 약어·한국어에서 불완전 |
| 재귀적(recursive) | 구분자 계층(문단→문장→단어)을 순서대로 시도해 목표 크기까지 쪼갬 | 구조를 최대한 존중 + 크기 보장 | 구분자 설정이 문서 종류에 의존 |
| 구조 인지(structure-aware) | 마크다운 헤더·HTML 태그·코드 함수 단위로 자름 | 자연스러운 의미 단위 + 헤더를 메타데이터로 | 구조 없는 평문엔 못 씀 |
| 의미 기반(semantic) | 인접 문장 임베딩 유사도가 급락하는 지점(주제 전환)에서 분할 | 의미 경계에 맞음 | 임베딩 계산 비용 + 크기 불균일 |
이 다섯은 실은 하나의 축 위에 있다 — 얼마나 문서의 구조를 존중하느냐다. 고정 크기는 구조를 완전히 무시하는 극단이고, 의미 기반은 내용의 의미 경계까지 따라가는 반대 극단이며, 재귀적·구조 인지가 그 사이의 실용적 절충이다. 실무의 기본값은 대개 재귀적 청킹이다 — 문단 단위로 먼저 자르고, 문단이 목표 크기를 넘으면 문장으로, 그래도 넘치면 단어로 내려가며 분할한다. 문서에 구조가 있으면(마크다운·소스코드) 그 위에 구조 인지를 추가해 헤더나 함수 경계를 존중하고 그 헤더 자체를 메타데이터로 남긴다. 의미 기반은 잘 되면 좋지만 문장마다 임베딩을 계산해야 해 인덱싱 비용이 오르고 청크 크기가 불균일해지므로, 단순한 것으로 먼저 기준선을 세운 뒤 검색이 실제로 부족할 때 높여 보는 편이 낫다는 것이 내 견해다.
6.3.5.2 오버랩 — 경계에서 잘린 맥락 살리기
문서를 자르면 경계에 걸친 정보가 두 청크로 분할되어 어느 쪽에서도 온전치 않게 된다. "계약 해지는 30일 전 서면 통지로 한다"라는 문장을 하필 "30일 전"과 "서면 통지" 사이에서 가르면, 앞 청크에는 무엇을 하는지가 없고 뒤 청크에는 며칠 전인지가 없다. 이제 사용자가 "해지 통지는 며칠 전에 해야 하나요?"라고 물으면, 정답에 필요한 두 조각이 어느 한 청크에도 함께 들어 있지 않아 검색이 온전한 근거를 상위에 배치하지 못한다. 오버랩(overlap)은 인접 청크가 일부 토큰을 겹치게 해 이 손실을 완화한다 — 경계 근처의 문장이 양쪽 청크에 모두 들어가도록.
오버랩은 무비용이 아니다. 겹치는 만큼 청크 수와 저장·임베딩 비용이 늘고, 검색 결과에 거의 같은 두 청크가 나란히 검색되는 중복이 생겨 dedup(6.3.9)이 필요해진다. 전형적으로 청크 크기의 10~20%를 오버랩으로 두지만 최적값은 문서와 질문 유형에 달렸고, 늘린 오버랩이 정확히 몇 개의 청크를 더 만들어 비용을 얼마나 늘리는지는 다음 절에서 직접 계산한다.
6.3.5.3 청크 크기 트레이드오프
청크 크기 하나가 검색과 생성의 성격 전체를 바꾸고, 정답은 없다.
| 작은 청크 (예: 128~256 토큰) | 큰 청크 (예: 1024~2048 토큰) | |
|---|---|---|
| 검색 정밀도 | 높음 — 한 조각이 한 주제, 신호가 뾰족 | 낮음 — 여러 주제 섞여 의미 희석 |
| 맥락 충분성 | 낮음 — 답에 필요한 주변 문맥이 잘림 | 높음 — 한 조각에 문맥이 다 들어옴 |
| 같은 예산의 top-k | 많이 넣음(다양성↑) | 적게밖에 못 넣음 |
| 인덱스 크기·후보 수 | 청크 많음 → 인덱스 큼·후보 많음 | 청크 적음 |
| 잘 맞는 질문 | 사실 조회("환불 기한은?") | 종합·설명("환불 정책을 요약해줘") |
정밀도(작게)와 맥락(크게)은 근본적으로 상충한다. 작은 청크는 검색은 뾰족하지만 정답 주변의 문맥이 잘려 생성이 근거를 온전히 못 쓰고, 큰 청크는 문맥은 넉넉하지만 한 벡터에 여러 주제가 섞여 검색이 무뎌진다. 하나의 크기로 둘을 동시에 만족시킬 수는 없다. 실무의 절충은 두 크기를 분리하는 것이다 — 작은 청크로 임베딩·검색해 정밀도를 얻되, 검색된 작은 청크의 주변(부모 문서나 인접 청크)을 생성 시 함께 넣어 맥락을 회복한다. "작은 것으로 찾고 큰 것으로 답한다"는 이 부모-자식(parent-child, small-to-big) 패턴은 6.4에서 다룬다. 지금은 크기 하나로 정밀도와 맥락을 함께 살 수 없다는 상충만 확실히 해 두면 된다.
6.3.6 수계산 — 청크 수·인덱싱 비용·쿼리 예산
수치를 직접 계산해 청킹과 검색이 비용으로 얼마나 번지는지 보자. 모두 규모를 가늠하기 위한 개략값이고, 토큰과 문자의 환산·모델 한계는 실제와 다르다.
사내 매뉴얼 한 편이 $L = 10{,}000$ 토큰(대략 30쪽)이라 하자. 청크 크기 $c = 512$ 토큰, 오버랩 $o = 64$ 토큰(12.5%)으로 자른다. 보폭은 $s = c - o = 448$ 이고, 문서를 덮는 데 필요한 청크 수는
$$N_{\text{chunks}} = \left\lceil \frac{L - o}{\,c - o\,} \right\rceil = \left\lceil \frac{10{,}000 - 64}{448} \right\rceil = \lceil 22.18 \rceil = 23 \text{ 청크}$$
오버랩이 없다면($o=0$) $\lceil 10{,}000/512 \rceil = 20$ 청크다. 12.5%의 오버랩이 청크를 3개, 즉 15% 늘린 것이다. 임베딩할 총 토큰도 그만큼 는다 — 청크들의 토큰 합은 $N \cdot c = 23 \times 512 = 11{,}776$ 으로 원문 10,000보다 약 18% 많다. 겹친 부분을 두 번 임베딩하기 때문이다. 오버랩을 25%(128토큰)로 높이면 보폭이 384로 줄어 $N = \lceil 9{,}872/384 \rceil = 26$ 청크가 되고, 임베딩 토큰은 $26 \times 512 = 13{,}312$ 로 원문 대비 33% 초과로 증가한다. 오버랩을 조금 키우는 결정이 인덱싱 비용과 저장·검색 후보 수를 이렇게 직접 높인다.
규모를 대입해 보자. 매뉴얼이 1,000편이면 인덱스에는 약 $23 \times 1{,}000 = 23{,}000$ 청크가 쌓이고, 임베딩할 총 토큰은 $23{,}000 \times 512 \approx 1.18\text{M}$ 이다. 임베딩은 일회성 배치 비용이라 대개 감당되지만 — 임베딩 단가는 벤더·시점마다 다르니 여기선 토큰 수까지만 세고 금액은 곱하지 않는다 — 코퍼스가 크고 자주 재인덱싱하면 무시 못 할 반복 비용이 된다(7.5).
이제 쿼리 타임에서 한 질문이 컨텍스트 창을 어떻게 쓰는지 보자. top-k = 5, 청크 512 토큰이면 대략 이렇다.
| 항목 | 토큰(개략) |
|---|---|
| 검색 컨텍스트: $5 \times 512$ | 2,560 |
| 인용·구분 오버헤드(청크당 헤더·번호) | ~100 |
| 시스템·근거 지시 프롬프트 | ~300 |
| 사용자 질문 | ~50 |
| 입력 소계 | ~3,010 |
| 출력 예약(생성 여유) | ~800 |
| 총계 | ~3,810 |
8K 컨텍스트 창이면 넉넉하고 32K·128K면 여유가 크다. 문제는 이 검색 컨텍스트가 단순히 토큰이 아니라 프리필해야 할 입력이라는 데 있다. 여기서 5.1의 비용 모형이 곧장 걸린다. 검색 컨텍스트를 top-k = 10이나 청크 1024로 키우면 이 부분이 2,560에서 5,120 토큰으로 두 배가 된다. 8B급 모델의 프리필 연산은 토큰당 대략 $2P$ FLOP이므로(1.4의 $2P$ 규칙), 검색 컨텍스트만 처리하는 데 드는 연산은
$$2 \times 8\times10^9 \times 2{,}560 \approx 4.1\times10^{13} = 41\ \text{TFLOP}$$
이고, 컨텍스트를 두 배로 키우면 이것도 약 82 TFLOP으로 두 배가 된다. 여기까지는 선형이지만, 어텐션의 $O(L^2)$ 항이 추가되어(1.4) 검색 컨텍스트가 수만 토큰대로 커지면 이 제곱항이 지배해 프리필 시간, 즉 TTFT가 급격히 는다. 디코드 쪽도 문제가 된다 — 긴 컨텍스트는 매 디코드 스텝에서 읽어야 할 KV 캐시를 늘려 토큰당 시간(TPOT)을 높인다(5.1). top-k와 청크 크기를 키우면 회수(recall)는 오르지만 TTFT와 TPOT가 함께 오르고 요청당 비용도 오른다. 이 셋의 교환이 RAG 튜닝의 중심 조정 변수다.
감으로 챙길 것은 컨텍스트 토큰이 대략 top-k × 청크 크기라는 것, 그리고 이 곱을 컨텍스트 창의 일부(이를테면 30~50%) 안에 두고 나머지를 시스템·질문·출력에 남기는 편이 낫다는 것이다. "창이 128K니 다 채우자"는 유혹은 비용만 나쁜 것이 아니라 품질에도 해롭다 — 무관한 청크를 많이 넣을수록 정답이 잡음에 희석되고, 다음에 볼 lost in the middle로 가운데 묻힌다.
6.3.7 임베딩·인덱싱에서 RAG가 챙길 것
임베딩 모델·거리·ANN 알고리즘·벡터 DB의 원리는 6.2의 몫이라 여기서 되풀이하지 않는다. RAG 맥락에서 특히 물리는 세 가지만 다룬다.
첫째는 임베딩 일관성이라는 불변식의 재확인이다. 인덱싱과 쿼리가 같은 모델·같은 정규화를 써야 한다는 것(6.3.3)에 더해, 비대칭 검색용으로 학습된 임베딩 모델은 문서와 질의에 서로 다른 프리픽스를 요구하기도 한다. 예컨대 문서는 "passage: …", 질의는 "query: …" 접두어를 붙이도록 설계된 모델을 쓰면서 이 규약을 어기면, 같은 모델을 써도 검색이 눈에 띄게 나빠진다. 이런 세부는 모델 카드에 적혀 있으니 반드시 확인해야 한다.
둘째는 메타데이터 필터와의 결합이다. 벡터 검색을 6.3.4에서 붙여 둔 메타데이터 필터와 함께 적용한다 — "이 사용자가 볼 수 있는 문서 중, 최근 1년, 한국어에서 의미상 가까운 top-k". 필터를 벡터 검색 전에 적용할지(pre-filter) 후에 적용할지(post-filter)는 벡터 DB마다 성능·정확성 트레이드오프가 있다. 사전 필터는 후보 공간을 먼저 좁혀 정확하지만 인덱스 구조에 따라 느려질 수 있고, 사후 필터는 빠르지만 필터를 통과한 결과가 top-k에 부족하게 남을 수 있다. 어느 쪽이 맞는지는 벤더 구현에 달렸다.
셋째는 증분 인덱싱과 재구축의 비대칭이다. 문서가 바뀌면 해당 청크만 갱신(upsert)하거나 삭제하면 되지만, 임베딩 모델을 교체하면 벡터 공간 자체가 바뀌므로 전 인덱스를 재구축해야 한다. 이 재구축 비용이 모델 업그레이드 부담을 키워, 더 나은 임베딩 모델이 나와도 쉽게 못 갈아타게 하는 실무의 관성으로 작동한다. 인덱싱까지 끝나면 인덱싱 타임이 완료되고, 여기서부터는 요청마다 실행되는 쿼리 타임이다.
6.3.8 검색 — top-k와 회수
질문이 들어오면 질문을 임베딩해 인덱스에서 가장 가까운 top-k 청크를 가져온다(6.2). 여기서 정하는 조정 변수는 하나, 몇 개를 가져오느냐다. $k$ 자체가 회수(recall)와 잡음·비용의 교환이다. $k$ 가 작으면 정답 청크를 놓칠 위험이 크고, 크면 무관한 청크가 섞여 들어와 정밀도가 떨어지고 토큰·지연이 는다(6.3.6에서 이 곱을 계산했다). 전형적 출발점은 $k = 3\sim10$ 이며, rerank(6.4)를 쓰면 벡터 검색으로 넉넉히 $k = 20\sim50$ 을 뽑은 뒤 재정렬해 상위 3~5만 프롬프트에 넣는 2단 구조가 흔하다. 이 장은 rerank 없이 top-k를 바로 쓰는 기본형에 집중한다. 회수가 왜 이토록 결정적인가는 6.3.11에서 분명해진다 — 정답 청크가 애초에 top-k에 오르지 못하면 그 뒤 순서·인용·생성을 아무리 잘해도 소용이 없기 때문이다.
6.3.9 컨텍스트 구성 — 순서·중복·인용
top-k를 손에 쥐었다고 끝이 아니다. 가져온 청크들을 순위대로 무심코 이어 붙이면 되는 것이 아니라, 어떤 순서로, 어떻게 중복을 줄이고, 어떤 예산 안에, 어떤 인용을 붙여 넣느냐가 답변 품질을 다시 한 번 가른다.
순서가 성능을 바꾼다는 것은 직관에 반하지만 관찰된 사실이다. 관련 정보가 긴 컨텍스트의 가운데에 있으면 모델이 그것을 잘 못 쓴다 — 성능이 정보 위치에 따라 U자를 그려, 맨 앞과 맨 뒤는 잘 쓰고 가운데는 놓친다. Liu 등이 2023년에 lost in the middle이라 부른 이 현상은 모델·버전마다 정도가 다르고 롱컨텍스트 학습으로 완화되기도 하지만, 실무 함의는 분명하다. 검색 순위대로 무심코 나열하면 2~3위 청크가 하필 가운데로 가 묻힐 수 있으니, 가장 관련 높은 청크를 맨 앞이나 맨 뒤 같은 양 끝에 배치한다. 그리고 top-k를 키워 컨텍스트를 채울수록 정답이 잡음에 희석되므로, 많이 넣는 것이 좋은 것이 아니다.
중복 제거도 여기서 한다. 오버랩(6.3.5.2)이나 같은 문서의 여러 판본 때문에 거의 동일한 청크가 top-k를 채우면, 같은 정보를 반복해 넣어 예산만 낭비하고 다양성을 잃는다. 유사도가 매우 높은 청크는 하나만 남긴다(MMR 같은 다양성 재순위는 6.4). 그렇게 정리한 청크들을 6.3.6에서 나눈 예산 — 시스템·근거 지시, 검색 청크, 질문, 출력 예약 — 안에 넣고, 검색 청크가 예산을 넘으면 순위 낮은 것부터 자른다. 이때 각 청크에 번호와 출처를 붙여 넣는 것이 생성 단계에서 인용을 다는 근거가 된다.
[근거 1] (출처: 제품매뉴얼 v3, §4.2 "환불", 2026-05-01)
정품 등록 후 14일 이내 미개봉 상태에 한해 전액 환불이 가능하다. …
[근거 2] (출처: 고객지원 FAQ, "배송/반품", 2026-06-10)
반품 배송비는 단순 변심의 경우 고객 부담이며, …
청크에 붙인 번호와 출처 메타데이터가, 생성 프롬프트의 "답의 각 문장에 근거 번호를 달라"는 지시와 짝을 이룬다. 인용은 검색 단계에서 심어 두고 생성 단계에서 요구하는, 두 단계의 협업이다 — 한쪽만으로는 성립하지 않는다.
6.3.10 근거에 충실한 생성
마지막 단계에서 검색·구성한 컨텍스트를 담아 모델에 생성을 시킨다. 목표는 하나다 — 모델이 자기 기억이 아니라 제공된 근거에 의존해 답하고, 근거에 없으면 없다고 말하며, 출처를 인용하게 하는 것. 이것을 grounding(근거화)이라 부른다. 근거 프롬프트는 대개 세 지시로 이뤄진다. 제공된 근거에만 의존하라는 제한, 근거로 답할 수 없으면 그렇다고 말하라는 기권 허용, 각 주장 뒤에 근거 번호를 달라는 인용 형식이다.
[시스템]
당신은 사내 지식 검색 도우미다. 반드시 아래 규칙을 따른다.
1. 제공된 <근거>에만 의존해 답한다. 근거에 없는 사실은 만들지 않는다.
2. 근거로 답할 수 없으면 "제공된 자료로는 답할 수 없습니다"라고만 답한다.
3. 답의 각 문장 끝에 사용한 근거 번호를 [n] 형식으로 붙인다.
4. 근거들이 서로 충돌하면, 충돌을 명시하고 각 출처를 함께 제시한다.
[근거]
[근거 1] (출처: …) …본문…
[근거 2] (출처: …) …본문…
[근거 3] (출처: …) …본문…
[질문]
정품 등록 후 환불 기한과 배송비 부담은 어떻게 되나요?
이 프롬프트 패턴은 벤더 중립이다. 시스템·유저 역할의 정확한 필드명, 메시지 구조, 프롬프트 캐싱과 도구 연동 방식은 API마다 다르므로, Claude의 정확한 메시지 포맷·시스템 프롬프트·캐싱 스펙은 claude-api 스킬과 공식 문서를 단일 출처로 확인해야 하고 기억으로 단정해선 안 된다. 다른 벤더도 공식 문서 확인이 필요하다.
프롬프트를 이렇게 써 두어도 실제로 작동하게 만들려면 몇 가지를 더 알아야 한다. 기권은 프롬프트 한 줄로 완성되지 않는다. "모르면 모른다"를 넣어도 모델은 그럴듯한 답을 지어내려는 경향이 강한데, 이는 SFT·RLHF가 "도움되게"를 학습한 부작용이다(3.1). 근거에 답이 없을 때 실제로 기권하게 하려면 예시와 평가로 그 행동을 강화해야 한다 — 정합성(faithfulness) 평가는 6.4의 몫이다. 근거끼리 모순될 때(옛 정책 대 새 정책)도 모델이 하나를 몰래 고르지 않고 충돌을 표면화하도록 지시하고, 최신성 메타데이터로 새 문서에 가중을 줄 수 있다. 그리고 모델이 붙인 인용이 실제로 그 근거를 뒷받침하는지는 프롬프트만으로 보장되지 않는다 — 모델은 인용 번호를 잘못 달거나 근거에 없는 말에 인용을 붙일 수 있고, 인용-근거 일치 검증은 후처리·평가로 따로 해야 한다. 마지막으로 생성 파라미터도 이 목적에 맞춰야 한다. 사실 검색형 RAG는 대개 낮은 temperature가 어울린다 — 결정성을 높이고 창작을 줄인다(1.7). 다양성이 필요한 브레인스토밍형과는 반대다.
6.3.11 환각은 어디서 살아남나 — 네 경로
이 장에서 가장 중요한 정직성 대목이다. RAG를 붙였으니 이제 환각은 없다는 주장은 마케팅이지 사실이 아니다. RAG는 근거를 제공해 환각을 줄이지만, 아래 네 갈래에서 환각은 여전히 살아남는다. 이 갈림길을 하나의 흐름으로 그리면 어디서 무엇이 깨지는지가 한눈에 보인다.
경로 1은 검색 실패다. 정답을 담은 청크가 top-k에 오르지 못하면 컨텍스트에 근거가 없으니 모델은 기억으로 메꾸며 지어낸다. 원인은 여럿이다 — 정답이 두 청크로 분할된 청킹 문제, 질문의 표현과 문서의 표현이 어휘상 달라 벡터가 멀어진 임베딩 불일치, 너무 작은 $k$. 어휘 불일치는 특히 자주 온다. 문서가 "환급"이라 쓰고 사용자가 "돈 돌려받기"라 물으면 의미는 같아도 임베딩 거리가 벌어져 정답 청크가 상위에서 탈락할 수 있다 — 이런 어휘 간극을 메우는 것이 하이브리드 검색과 query 변환(6.4)이 겨냥하는 문제다. 어느 원인이든 결론은 같다. 검색이 포함하지 못한 것을 생성은 되살릴 수 없다.
경로 2는 근거 무시다. 근거가 프롬프트에 있어도 모델이 읽지 않고 기억을 따르기도 한다. 근거가 길거나 가운데 묻히면(lost in the middle) 더 심해진다 — 6.3.9의 순서·예산 관리가 정확성 문제이지 단순한 정리 문제가 아닌 이유가 여기 있다. 경로 3은 소스 오류다. 근거 자체가 틀렸거나 노후화됐으면 모델은 그것을 충실히 반영해 자신 있게 틀린 답에 그럴듯한 인용까지 붙인다. 인용이 있어 사용자가 오히려 더 믿게 되는 역설이 생기니, RAG에서 가장 위험한 실패 양상이다 — 인덱스가 노후화되면 답도 똑같이 노후화된다는 사실을 최신성 메타데이터와 재인덱싱 주기로 관리해야 한다. 경로 4는 오해석·오종합이다. 근거가 맞아도 모델이 잘못 종합하거나, 충돌하는 두 근거 중 틀린 쪽을 고르거나, 근거에 없는 추론을 슬쩍 삽입한다.
네 경로에서 얻는 것은 두 가지다. 검색 품질이 상한이라는 것 — 관련 문서가 검색되지 않으면 그 위 모든 단계가 무의미하므로, RAG 품질 개선의 대부분은 생성이 아니라 검색·청킹·파싱에서 나온다. 그리고 RAG는 환각을 감소시키지 제거하지 않는다는 것 — 인용은 감사 가능성을 줄 뿐 정답 보증이 아니다. 그래서 검색과 생성의 정합성을 측정·평가하는 일이 필수이고, 그것이 6.4이 하는 일이다. 평가 없이는 이 네 경로를 관측할 수도, 어느 경로가 우리 시스템을 아프게 하는지 알 수도 없다.
6.3.12 RAG vs 파인튜닝 vs 롱컨텍스트
지식을 모델에 주입하는 세 접근이 흔히 양자택일로 오해된다. 실제로는 서로 다른 문제를 푸는 다른 도구이고 대개 조합해 쓴다. 결을 구분해 두어야 "우리 문제엔 무엇을"을 논증으로 답할 수 있다.
| 축 | RAG | 파인튜닝(3.8) | 롱컨텍스트(5.6) |
|---|---|---|---|
| 무엇을 주입 | 동적 사실·지식(외부 코퍼스) | 행동·형식·스타일·도메인 어투·암묵 기술 | 이번 요청에 필요한 문서 전체 |
| 최신성 | 인덱스 갱신으로 즉시 | 재학습 필요(느림·비쌈) | 매번 프롬프트에 최신본 투입 |
| 출처·인용 | 가능(원문 인용) | 불가(가중치에 녹음) | 가능(넣은 문서를 인용) |
| 코퍼스 규모 | 큼(수백만 문서, 검색으로) | 학습셋 크기 제약 | 작음(컨텍스트 창 한도) |
| 요청당 비용 | 중간(검색 + 청크 토큰) | 낮음(추가 컨텍스트 없음) | 높음(긴 입력 = 제곱 prefill·KV) |
| 인프라 | 인덱싱·벡터 DB 운영 | 학습 파이프라인·GPU | 없음(프롬프트만) — 대신 토큰 비용 |
| 잘 맞는 문제 | 크고 바뀌는 지식에서 사실 조회·출처 필요 | 일관된 형식·톤·도메인 능력·구조적 과제 | 코퍼스가 작고 매 요청 전체가 필요할 때 |
| 대표 실패 | 검색 실패 시 환각(6.3.11) | 최신성·출처·대규모 사실 주입에 부적합 | 비용·lost in the middle·창 한계 |
세 도구를 가르는 기준을 문장으로 옮기면 이렇다. 지식이 크고 자주 바뀌고 출처가 필요하면 RAG가 기본이다 — 사내 문서 Q&A, 제품 지원, 법률·의료 검색이 여기다. 문제가 "무엇을 아는가"가 아니라 "어떻게 행동·출력하는가"라면, 이를테면 특정 JSON 스키마를 지키게 하거나 회사 말투를 입히거나 도메인 분류를 학습시키는 일이라면 파인튜닝이다. 지식을 넣으려고 파인튜닝하는 것은 대개 비효율이다 — 지식은 RAG로, 행동은 파인튜닝으로라는 경험칙이 여기서 나온다. 관련 문서가 소수이고 매 요청에 전부 필요하며 검색 인프라를 두기 싫다면 롱컨텍스트로 전부 넣는 편이 단순하지만, 문서 수와 요청량이 늘면 긴 입력의 제곱 프리필과 KV 비용(5.6)이 RAG보다 빠르게 나빠진다.
실전 시스템은 셋을 섞는다. 도메인 톤과 출력 형식은 파인튜닝으로 입히고, 사실은 RAG로 주입하며, 검색된 문서가 길면 롱컨텍스트로 전부 넣는다. "RAG냐 파인튜닝이냐"는 대개 잘못된 이분법이다. 이 선택이 순수한 기술 결정만은 아니라는 점도 정리해 둘 만하다 — 검색 인프라를 운영할 여력이 있는지, 파인튜닝 비용을 감당할 수 있는지, API 롱컨텍스트로 충분한지는 조직과 비용의 문제이기도 하고, 그 전략적 갈림은 9.1으로 이어진다.
여기까지가 작동하는 기본 RAG다 — 수집·청킹·임베딩·인덱싱에서 top-k 검색·컨텍스트 구성·근거 생성까지. 이것으로 이미 쓸 만한 시스템이 서지만, 6.3.11의 네 실패 경로가 말하듯 품질의 상한은 검색에 있고 그 상한은 아직 낮다. 그 상한을 높이는 rerank·하이브리드 검색·query 변환·graph RAG, 그리고 이 모든 것을 관측 가능하게 만드는 recall·정합성·인용 정확성의 평가가 다음 장의 일이다. 기본 파이프라인을 손에 쥐었으니, 이제 그것을 실서비스 품질로 높일 차례다.