RAG & 검색 증강 생성
Vercel 환경에서 RAG 파이프라인을 설계하고 운영하는 방법을 정리합니다.
RAG(Retrieval-Augmented Generation)는 모델의 지식을 외부 데이터로 보강하는 가장 보편적인 AI 패턴입니다. Vercel 환경에서는 AI SDK의 tool/resource 구조, AI Gateway의 모델 제어, Queues의 인덱싱 파이프라인을 조합해 RAG를 운영합니다.
RAG가 필요한 시점
| 조건 | RAG 적합도 | 이유 |
|---|---|---|
| 모델 학습 데이터에 없는 사내 정보 | 높음 | 외부 지식 보강 필수 |
| 정보가 자주 변경됨 | 높음 | fine-tuning보다 효율적 |
| 정확한 출처가 필요함 | 높음 | source attribution 가능 |
| 범용 지식만으로 충분함 | 낮음 | 불필요한 복잡도 |
| 실시간 데이터가 중요함 | 중간 | 인덱싱 지연 고려 필요 |
아키텍처
인덱싱 파이프라인
청킹 전략
| 전략 | 장점 | 적합한 문서 유형 |
|---|---|---|
| 고정 크기 (512 tok) | 구현 단순 | 균일한 텍스트 |
| 문단 기반 | 의미 단위 보존 | 구조화된 문서 |
| 재귀 분할 | 계층 구조 유지 | 긴 기술 문서 |
| 시맨틱 분할 | 의미적 경계 최적화 | 다양한 주제 혼합 |
Queues를 활용한 분산 인덱싱
import { embedMany } from 'ai'
import { handleCallback } from '@vercel/queue'
export const POST = handleCallback(async (job) => {
const { chunks, docId, metadata } = job
const { embeddings } = await embedMany({
model: 'openai/text-embedding-3-small',
values: chunks,
providerOptions: {
gateway: { tags: ['feature:rag-indexing', `doc:${docId}`] },
},
})
await vectorStore.upsert(
chunks.map((chunk, i) => ({
id: `${docId}#${i}`,
values: embeddings[i],
metadata: { ...metadata, chunk },
}))
)
})인덱싱 운영 기준
| 항목 | 권장 기본값 | 이유 |
|---|---|---|
| 청크 크기 | 256~512 토큰 | 검색 정밀도와 문맥 균형 |
| 청크 오버랩 | 10~20% | 경계 정보 손실 방지 |
| 임베딩 모델 | text-embedding-3-small 이상 | 비용 대비 성능 |
| 인덱싱 빈도 | 변경 감지 기반 또는 일 1회 | 신선도와 비용 균형 |
| 메타데이터 | docId, source, updatedAt 필수 | 출처 추적과 갱신 관리 |
쿼리 파이프라인
검색 + 리랭킹
단순 vector similarity만으로는 정밀도가 부족한 경우가 많습니다. 리랭킹을 추가해 품질을 높입니다.
import { generateText, tool } from 'ai'
import { z } from 'zod'
const searchDocs = tool({
description: '사내 문서를 검색합니다.',
inputSchema: z.object({ query: z.string() }),
execute: async ({ query }) => {
// 1. Vector search: top-k 후보
const candidates = await vectorStore.search({
query: await embed(query),
topK: 20,
})
// 2. Rerank: 정밀도 향상
const reranked = await reranker.rank({
query,
documents: candidates.map((c) => c.metadata.chunk),
topK: 5,
})
return reranked.map((r) => ({
content: r.document,
source: candidates[r.index].metadata.source,
score: r.score,
}))
},
})쿼리 변환 기법
| 기법 | 설명 | 효과 |
|---|---|---|
| Query rewriting | 사용자 질문을 검색에 적합하게 변환 | 검색 재현율 향상 |
| Hypothetical document | 가상 답변을 생성해 검색에 사용 | 의미 매칭 개선 |
| Multi-query | 여러 관점의 쿼리를 생성해 결합 | 커버리지 확대 |
| Step-back prompting | 추상화된 상위 질문으로 검색 | 넓은 문맥 확보 |
MCP resources로 RAG 노출
RAG 검색 결과를 MCP resources로 노출하면 에이전트가 자연스럽게 문맥을 활용할 수 있습니다.
- docs-server (MCP): resources로 검색 결과 공급
- searchDocs: 사내 문서 검색
- getDocument: 특정 문서 조회
- 모두 read-only, 앱이 주도AI Gateway와 RAG 비용 관리
| 구간 | 비용 요소 | 절감 방법 |
|---|---|---|
| 임베딩 | 문서량 × 토큰 단가 | 변경분만 재인덱싱 |
| 검색 + 리랭킹 | API 호출 또는 compute | 캐싱, top-k 제한 |
| 생성 | context + 응답 토큰 | 청크 수 제한, prompt caching |
| 전체 | project별 집계 필요 | Gateway 태그로 추적 |
실무 해석
RAG의 가장 흔한 실패는 모델이 아니라 검색입니다. "답이 틀렸다"는 피드백의 60% 이상이 실제로는 "필요한 문서를 못 찾았다"는 검색 실패입니다. 모델을 바꾸기 전에 검색 품질을 먼저 측정하세요.
평가 기준
| 지표 | 측정 방법 | 목표 예시 |
|---|---|---|
| Retrieval recall | golden set 기준 상위 5개 포함율 | 80% 이상 |
| Retrieval precision | 반환 문서 중 관련 문서 비율 | 60% 이상 |
| Answer faithfulness | 응답이 context에 기반하는 비율 | 90% 이상 |
| Answer relevance | 응답이 질문에 적합한 비율 | 85% 이상 |
| Source attribution | 출처가 올바르게 표시되는 비율 | 95% 이상 |
실패 모드와 대응
| 실패 모드 | 증상 | 대응 |
|---|---|---|
| Retrieval miss | 관련 문서를 못 찾음 | 청킹/쿼리 변환 개선 |
| Context overflow | 너무 많은 문서를 context에 | top-k 축소, 리랭킹 강화 |
| Hallucination | context에 없는 내용 생성 | 인용 강제, confidence check |
| Stale data | 오래된 정보로 답변 | 인덱싱 빈도 증가, 날짜 필터 |
| Embedding drift | 모델 변경 후 검색 품질 저하 | 전체 재인덱싱 |
ADR 스타일 결론
Decision
RAG는 인덱싱 파이프라인(Queues)과 쿼리 파이프라인(AI SDK tool + reranker)을 분리해 운영합니다. 검색 품질이 생성 품질을 결정하므로, 모델 튜닝보다 retrieval 평가를 우선합니다.
실무 체크리스트
- 인덱싱 파이프라인이 변경 감지 기반으로 작동하는가
- 청크 크기와 오버랩이 문서 유형에 맞게 설정됐는가
- 리랭킹이 적용돼 있는가
- retrieval recall/precision이 정기적으로 측정되는가
- 응답에 source attribution이 포함되는가
- 비용 태그가 인덱싱과 쿼리 모두에 적용되는가