'이 API 스펙 문서 읽고 클라이언트 코드 짜줘.' ChatGPT한테 회사 내부 API 문서를 컨텍스트로 던져본 적 있으시죠? 처음엔 감탄합니다. 타입도 잘 뽑고, 에러 핸들링도 그럴싸해요. 근데 PR 올리고 CI 돌리면 터집니다. 없는 엔드포인트 호출, deprecated된 쿼리 파라미터, 심지어 인증 방식도 틀려 있어요. 분명 문서를 줬는데요.
LLM은 놀라울 정도로 똑똑합니다. 하지만 당신의 데이터는 모릅니다. 아무리 GPT-4o가 뛰어나도 지난주에 업데이트된 우리 회사 API 문서는 학습한 적이 없죠. 이게 LLM을 실무에 적용할 때 부딪히는 가장 근본적인 벽입니다.
이 문제를 해결하는 가장 현실적인 방법이 RAG(Retrieval-Augmented Generation)입니다. RAG가 뜨는 이유는 간단해요. 모델을 재학습하지 않고도 최신 정보와 내부 데이터를 활용할 수 있거든요. GenAI를 프로덕션에 적용한 기업의 86%가 RAG나 유사한 증강 기법을 쓰고 있다는 통계도 있습니다.
이 글은 'RAG 완전 정복' 시리즈의 첫 번째 글입니다. 오늘은 RAG가 뭔지, 왜 필요한지, 어떻게 동작하는지 큰 그림을 그려볼게요. 수학 공식이나 복잡한 알고리즘 얘기는 최대한 피하고, 개발자 관점에서 '아, 이거 내 프로젝트에 써볼 만하겠는데?'라는 느낌이 들도록 설명해보겠습니다.
LLM의 세 가지 아킬레스건
LLM을 실무에 써보면 세 가지 근본적인 한계에 부딪힙니다. 하나씩 살펴볼게요.
첫 번째, 할루시네이션(Hallucination)입니다. LLM이 그럴듯하지만 틀린 정보를 만들어내는 현상이죠. Vectara의 Hallucination Leaderboard 기준, GPT-4, Claude, Gemini 같은 최신 모델들도 2.5~8.5%의 할루시네이션 비율을 보입니다. 일반적인 대화에서는 괜찮을 수도 있어요.
문제는 도메인 특화 태스크입니다. 스탠포드 연구진이 법률 관련 질문을 테스트했더니, LLM들이 69~88% 비율로 존재하지 않는 판례를 인용했어요. 120개 이상의 가짜 법원 케이스를 설득력 있게 지어냈다고 합니다. 코드 생성도 비슷해요. 없는 API를 호출하거나, deprecated된 메서드를 쓰거나, 심지어 존재하지 않는 npm 패키지를 import 하기도 하죠.
두 번째, 학습 데이터 컷오프입니다. LLM은 특정 시점까지의 데이터로 학습됩니다. GPT-4o의 학습 컷오프가 2024년 1월이라고 해봅시다. 그러면 2024년 2월에 나온 라이브러리 업데이트, 새로운 API 변경, 최신 보안 취약점 같은 건 모릅니다. 개발자한테 이건 꽤 심각한 문제예요. React 19가 나왔는데 LLM은 여전히 React 18 문법으로 코드를 짜주고 있으면 곤란하잖아요.
세 번째, 비공개 데이터 접근 불가입니다. 이게 실무에서 가장 큰 벽이에요. 우리 회사의 API 문서, 내부 위키, HR 정책, 고객 데이터 같은 건 인터넷에 공개되어 있지 않습니다. LLM이 아무리 똑똑해도, 학습할 기회가 없었던 정보는 알 수가 없어요. '우리 회사 연차 정책이 어떻게 돼?'라고 물으면 일반적인 한국 기업 연차 정책을 추측해서 대답하거나, 아예 모른다고 하거나, 최악의 경우 그럴듯하게 지어냅니다.
RAG를 도입하면 할루시네이션이 70%에서 90%까지 감소한다는 필드 스터디 결과가 있습니다. 완전히 없앨 수는 없지만, 미션 크리티컬한 워크플로우에서도 RAG를 쓸 수 있을 정도로 신뢰도가 올라갑니다.
Fine-tuning 하면 되지 않나요?
여기서 자연스럽게 드는 질문이 있어요. '그럼 우리 데이터로 모델을 Fine-tuning 하면 되는 거 아닌가요?' 맞는 말이기도 하고, 틀린 말이기도 합니다. Fine-tuning과 RAG는 해결하는 문제가 다르거든요.
Fine-tuning은 모델의 '행동 방식'을 바꾸는 겁니다. 특정 도메인의 어투, 응답 스타일, 전문 용어 사용 방식을 학습시키는 거죠. 의료 분야라면 의학 용어를 자연스럽게 쓰고, 법률 분야라면 판례 인용 스타일을 따르게 할 수 있어요. 하지만 Fine-tuning으로 새로운 '지식'을 주입하는 건 생각보다 어렵습니다. 모델이 특정 사실을 기억하게 만들려면 엄청난 양의 데이터와 학습 시간이 필요하고, 그마저도 불안정해요.
RAG는 모델에게 새로운 '지식'을 전달하는 겁니다. 모델 자체를 바꾸는 게 아니라, 질문할 때마다 관련 정보를 찾아서 함께 전달하는 방식이에요. 학생이 오픈북 시험을 보는 것과 같습니다. 모든 걸 외우지 않아도, 필요한 정보를 찾아볼 수 있으면 되니까요.
비유하자면 이래요. Fine-tuning은 의대를 졸업시키는 것이고, RAG는 의학 교과서를 옆에 두고 참고하게 하는 것입니다. 둘 다 의료 질문에 더 잘 답하게 해주지만, 방식이 완전히 다르죠. 실무에서는 둘을 조합해서 쓰기도 합니다. 도메인 특화 스타일은 Fine-tuning으로, 최신 지식은 RAG로 공급하는 식이에요.
- Fine-tuning이 적합한 경우: 특정 어투나 스타일이 필요할 때, 응답 형식을 일관되게 맞춰야 할 때, 도메인 전문 용어를 자연스럽게 쓰게 하고 싶을 때
- RAG가 적합한 경우: 최신 정보나 자주 바뀌는 데이터를 다룰 때, 내부 문서나 비공개 데이터를 활용할 때, 출처를 명확히 밝혀야 할 때, 빠르게 프로토타입을 만들어야 할 때
현실적으로 대부분의 기업 유스케이스는 RAG로 시작하는 게 맞습니다. Fine-tuning은 GPU 리소스와 학습 시간이 많이 들고, 데이터가 바뀔 때마다 다시 학습해야 합니다. RAG는 문서만 업데이트하면 바로 반영되니까요. 2025년 현재, '우선 RAG로 구축하고 필요하면 Fine-tuning을 얹는다'가 업계 표준 접근법이 되어가고 있습니다.
RAG란 무엇인가: 검색 + 생성의 결합
RAG는 Retrieval-Augmented Generation의 약자입니다. 직역하면 '검색으로 증강된 생성'인데, 풀어서 설명하면 이래요. LLM이 답변을 생성하기 전에, 관련 정보를 먼저 검색해서 참고하게 하는 방식입니다.
핵심 아이디어는 간단합니다. LLM에게 오픈북 시험을 보게 하는 거예요. 학생이 모든 걸 암기하지 않아도 교과서를 펼쳐보면서 시험을 볼 수 있잖아요. RAG도 마찬가지입니다. LLM이 모든 걸 학습하지 않아도, 필요한 정보를 실시간으로 찾아서 참고할 수 있게 해줍니다.
전통적인 LLM 호출은 이렇습니다: 사용자 질문 → LLM → 응답. LLM은 학습된 지식만으로 대답해요. RAG를 적용하면 이렇게 바뀝니다: 사용자 질문 → 관련 문서 검색 → 질문 + 검색 결과를 함께 LLM에 전달 → 응답. LLM이 검색된 문서를 '참고 자료'로 받아서 더 정확한 답변을 생성합니다.
개발자에게 익숙한 비유를 들어볼게요. 여러분이 새로운 라이브러리를 쓴다고 해봅시다. 공식 문서를 안 보고 감으로만 코드를 짜면 어떻게 될까요? 동작할 수도 있지만, 뭔가 이상한 코드가 나올 확률이 높죠. 문서를 펼쳐놓고 코딩하면 훨씬 정확해집니다. RAG는 LLM에게 '문서 펼쳐놓고 코딩해'라고 하는 것과 같아요.
“RAG는 LLM을 '더 똑똑하게' 만드는 게 아니라 '더 많이 알게' 만드는 기술입니다. 모델의 능력은 그대로지만, 참고할 수 있는 정보가 늘어나는 거죠.”
RAG 파이프라인 해부: 4단계 구조
RAG 시스템은 크게 4단계로 구성됩니다. Indexing(색인) → Retrieval(검색) → Augmentation(증강) → Generation(생성). 각 단계를 하나씩 살펴볼게요.
1단계: Indexing (색인). 검색을 하려면 먼저 검색 대상을 준비해야겠죠. 이 단계에서 문서들을 처리해서 검색 가능한 형태로 저장합니다. 보통 문서를 적당한 크기의 '청크(chunk)'로 나눕니다. 너무 길면 찾기 어렵고, 너무 짧으면 맥락이 없으니까요. 그리고 각 청크를 숫자 배열로 변환해서 데이터베이스에 저장합니다. 이 작업은 미리 한 번만 하면 되고, 문서가 추가되거나 바뀔 때만 다시 하면 됩니다.
2단계: Retrieval (검색). 사용자가 질문하면, 그 질문과 관련된 문서를 찾습니다. 여기서 재밌는 건, 키워드 매칭이 아니라 의미로 검색한다는 거예요. '연차 휴가 신청 방법'이라고 물으면 '휴가 신청 절차', '연차 사용법' 같은 문서들이 검색됩니다. '연차'라는 단어가 정확히 일치하지 않아도, 의미가 비슷하면 찾아내는 거죠. 구글 검색이랑 비슷하면서도 더 똑똑하다고 생각하시면 돼요.
3단계: Augmentation (증강). 검색된 문서 청크들을 사용자 질문과 합쳐서 프롬프트를 만듭니다. '아래 문서를 참고해서 질문에 답해줘. [검색된 문서들] 질문: [사용자 질문]' 이런 형태가 되는 거죠. 이 프롬프트가 LLM에게 전달됩니다.
4단계: Generation (생성). LLM이 증강된 프롬프트를 받아서 응답을 생성합니다. 이때 LLM은 자기가 원래 알던 지식과 방금 전달받은 문서 내용을 조합해서 답변합니다. 문서에 명확한 정보가 있으면 그걸 기반으로 답하고, 없으면 일반적인 지식으로 보완합니다.
// RAG 파이프라인을 간단하게 표현한 코드입니다
// 실제 구현은 더 복잡하지만, 전체 흐름을 이해하는 데 집중해주세요
async function ragPipeline(userQuestion: string) {
// 1단계 Indexing: 문서들을 미리 처리해서 저장해둔 상태라고 가정해요
// (이 작업은 서비스 시작 전에 한 번만 하면 됩니다)
// 2단계 Retrieval: 사용자 질문과 관련 있는 문서를 찾아옵니다
const relevantDocs = await vectorDB.search({
query: userQuestion, // 이 질문과 의미가 비슷한 문서를 찾아줘
topK: 5, // 가장 관련 있는 5개만 가져와
});
// 3단계 Augmentation: 찾은 문서와 질문을 합쳐서 프롬프트를 만들어요
// 이렇게 하면 LLM이 '참고 자료'를 보면서 답할 수 있죠
const context = relevantDocs
.map(doc => doc.content)
.join("\n\n");
const augmentedPrompt = `
아래 문서를 참고해서 질문에 답해주세요.
문서에 없는 내용은 추측하지 말고 '문서에 해당 내용이 없습니다'라고 답하세요.
[참고 문서]
${context}
[질문]
${userQuestion}
`;
// 4단계 Generation: 드디어 LLM이 답변을 생성합니다
// LLM은 위에서 전달받은 문서를 참고해서 더 정확하게 답해요
const response = await llm.generate(augmentedPrompt);
return response;
}임베딩과 벡터 검색: RAG의 핵심 기술
RAG 파이프라인에서 가장 중요한 건 2단계 검색입니다. 그리고 이 검색을 가능하게 하는 핵심 기술이 임베딩(Embedding)과 벡터 검색이에요. 어렵게 들릴 수 있는데, 최대한 쉽게 설명해볼게요.
임베딩이란? 쉽게 말해서 텍스트를 컴퓨터가 이해할 수 있는 숫자들로 바꾸는 것입니다. '강아지'라는 단어를 [0.2, -0.5, 0.8, ...] 같은 숫자 배열(벡터)로 표현하는 거죠. 그런데 아무 숫자나 넣는 게 아니에요. AI 모델이 단어의 의미를 파악해서 숫자를 만들어냅니다.
비유하자면 이래요. 우리가 '강아지'를 떠올리면 머릿속에 어떤 이미지가 그려지잖아요? 털 복슬복슬하고, 꼬리 흔들고, 짖는 동물. 임베딩은 이런 '의미'를 숫자로 표현하는 겁니다. 그래서 같은 의미를 가진 단어들은 비슷한 숫자 패턴을 갖게 되죠.
예를 들어, '강아지'와 '고양이'의 임베딩 벡터는 서로 비슷합니다. 둘 다 '반려동물'이니까요. 반면 '강아지'와 '자동차'의 벡터는 전혀 다르게 생겼어요. 의미가 비슷하면 숫자도 비슷하고, 의미가 다르면 숫자도 다른 거죠. 이게 임베딩의 핵심입니다.
벡터 검색은 이 원리를 활용합니다. 사용자 질문을 숫자 배열로 바꾸고, 미리 저장해둔 문서들의 숫자 배열과 비교해서 가장 비슷한 것을 찾는 거예요. '연차 신청하려면 어떻게 해요?'라는 질문과 '연차 휴가 신청 절차'라는 문서는 의미가 비슷하니까 숫자도 비슷하고, 검색 결과로 나오는 거죠.
import OpenAI from "openai";
const openai = new OpenAI();
// 텍스트를 숫자 배열(임베딩)로 변환하는 함수
async function getEmbedding(text: string): Promise<number[]> {
const response = await openai.embeddings.create({
model: "text-embedding-3-small", // OpenAI에서 제공하는 임베딩 모델
input: text,
});
// 1536개의 숫자로 이루어진 배열이 반환됩니다
// 이 숫자들이 텍스트의 '의미'를 담고 있어요
return response.data[0].embedding;
}
// 사용 예시: 문서와 질문을 각각 숫자 배열로 변환
const docEmbedding = await getEmbedding("연차 휴가 신청은 인사 시스템에서...");
const queryEmbedding = await getEmbedding("연차 신청 어떻게 해요?");
// 두 숫자 배열이 얼마나 비슷한지 계산하는 함수
// 결과가 1에 가까우면 = 의미가 비슷함
// 결과가 0에 가까우면 = 의미가 다름
function cosineSimilarity(a: number[], b: number[]): number {
let dot = 0, normA = 0, normB = 0;
for (let i = 0; i < a.length; i++) {
dot += a[i] * b[i];
normA += a[i] * a[i];
normB += b[i] * b[i];
}
return dot / (Math.sqrt(normA) * Math.sqrt(normB));
}
const similarity = cosineSimilarity(docEmbedding, queryEmbedding);
// 위 예시에서는 '연차'라는 공통 주제가 있으니 유사도가 높게 나올 거예요코드에 나온 '코사인 유사도'가 뭔지 궁금하실 거예요. 쉽게 말해서 두 숫자 배열이 얼마나 비슷한 '방향'을 가리키고 있는지 측정하는 방법입니다. 두 화살표가 비슷한 방향을 가리키면 1에 가깝고, 전혀 다른 방향이면 0에 가까워요. 수학적 세부사항은 몰라도 됩니다. 벡터 DB가 알아서 계산해주니까요. 중요한 건 '의미가 비슷하면 숫자가 1에 가깝다'는 것만 기억하시면 돼요.
실제로는 이 유사도 계산을 직접 하지 않고 벡터 데이터베이스에 맡깁니다. Pinecone, Qdrant 같은 전용 DB들이 있고, 익숙한 PostgreSQL에 pgvector 확장을 붙여서 쓸 수도 있어요. 문서들의 숫자 배열을 저장해두면, 질문과 가장 비슷한 것들을 순식간에 찾아줍니다. 수백만 개 문서에서도요.
간단한 RAG 예시: 내부 API 문서 검색
지금까지 설명한 내용을 구체적인 시나리오로 따라가 봅시다. 회사 내부 API 문서를 기반으로 코드 작성을 도와주는 어시스턴트를 만든다고 해볼게요. 개발자라면 바로 써먹을 수 있는 시나리오입니다.
상황: 개발자가 '주문 API에서 결제 상태 변경하려면 어떻게 해?'라고 질문합니다. 내부 API 문서에는 이런 내용이 있어요. 'PATCH /orders/{orderId}/payment-status로 요청. body에 { status: "completed" | "failed" | "refunded" } 필수. 인증은 X-Internal-Token 헤더 사용. Rate limit: 100 req/min.'
1단계 (Indexing, 미리 완료됨): API 문서를 엔드포인트 단위로 청크화합니다. 각 청크에는 엔드포인트 경로, 메서드, 파라미터, 응답 형식 정보가 담깁니다. 결제 상태 변경 API는 chunk_payment_status라고 합시다.
2단계 (Retrieval): 질문을 임베딩합니다. '주문', '결제', '상태 변경' 같은 의미가 벡터에 인코딩되고, 벡터 DB에서 가장 유사한 청크들을 검색합니다. chunk_payment_status, chunk_order_detail, chunk_auth_guide 등이 검색됩니다.
3단계 (Augmentation): 검색된 청크들과 질문을 합쳐서 프롬프트를 만듭니다. '아래 API 문서를 참고해서 질문에 답하세요. 코드 예시를 포함해주세요. [검색된 청크들] 질문: 주문 API에서 결제 상태 변경하려면 어떻게 해?'
4단계 (Generation): LLM이 문서 기반으로 응답합니다. 'PATCH /orders/{orderId}/payment-status 엔드포인트를 사용하면 됩니다. X-Internal-Token 헤더가 필요하고, body에 status 필드를 넣어주세요. 다음은 TypeScript 예시입니다...' 실제 문서에 있는 정확한 엔드포인트와 인증 방식을 알려주는 거죠.
RAG 없이 같은 질문을 했다면? LLM은 일반적인 REST API 패턴을 추측해서 대답했을 겁니다. 'PUT /orders/{id}/status를 사용하세요...' 같은 식으로요. 우리 API의 실제 경로(PATCH /orders/{orderId}/payment-status)나 커스텀 인증 헤더(X-Internal-Token)는 알 수 없었겠죠.
이 예시에서 검색된 청크가 응답의 '출처'가 됩니다. '이 코드는 payment-status API 문서를 참고했습니다'라고 출처를 밝힐 수 있어요. 할루시네이션이 발생하더라도 어느 문서를 잘못 해석했는지 추적이 가능합니다. 디버깅 관점에서 큰 장점이죠.
RAG의 한계와 주의점
RAG가 만능은 아닙니다. 현실적인 한계를 알고 시작하는 게 중요해요.
검색이 실패하면 답변도 실패합니다. RAG의 품질은 검색 품질에 달려 있어요. 관련 문서를 못 찾으면 LLM도 제대로 답할 수 없습니다. '청킹을 어떻게 할 것인가', '임베딩 모델을 뭘 쓸 것인가', '검색 알고리즘을 어떻게 튜닝할 것인가'에 따라 결과가 크게 달라집니다. 이 부분은 2편에서 자세히 다룰게요.
컨텍스트 윈도우 제약이 있습니다. 검색된 문서가 너무 길면 LLM에 다 전달할 수 없어요. GPT-4o의 컨텍스트 윈도우가 128K 토큰이라고 해도, 실제로 100페이지 분량의 문서를 한 번에 넣기는 어렵습니다. 검색 결과를 적절히 필터링하거나, 요약해서 전달하는 전략이 필요해요.
멀티홉 추론에 약합니다. '연차가 가장 많은 팀의 평균 근속 연수는?'처럼 여러 단계의 추론이 필요한 질문은 단순 RAG로 해결하기 어렵습니다. 이런 질문에는 RAG를 여러 번 호출하거나, 에이전트 기반 아키텍처를 적용해야 해요.
레이턴시가 증가합니다. 검색 단계가 추가되니까 응답 시간이 늘어납니다. 질문을 임베딩하는 데 100~300ms, 그 임베딩으로 벡터 DB를 검색하는 데 50~200ms 정도가 추가돼요. 실시간성이 중요한 서비스라면 캐싱이나 사전 처리 전략이 필요합니다.
이런 한계에도 불구하고, RAG는 현재 LLM을 실무에 적용하는 가장 현실적인 방법입니다. 완벽하지 않지만, 대부분의 기업 유스케이스에서 충분히 쓸 만합니다. 중요한 건 한계를 알고 적절히 대응하는 거예요.
마무리: 다음 단계로
이 글에서 다룬 내용을 정리해볼게요. LLM은 똑똑하지만 세 가지 근본적인 한계가 있습니다. 할루시네이션, 학습 컷오프, 비공개 데이터 접근 불가. RAG는 이 한계를 우회하는 가장 실용적인 방법입니다. LLM에게 오픈북 시험을 보게 하는 것처럼, 필요한 정보를 검색해서 함께 전달하는 방식이에요.
- RAG의 핵심: LLM이 답변하기 전에 관련 정보를 검색해서 참고하게 한다
- 4단계 구조: Indexing(색인) → Retrieval(검색) → Augmentation(증강) → Generation(생성)
- 임베딩: 텍스트를 숫자 배열로 바꿔서 '의미가 비슷한지' 비교할 수 있게 한다
- Fine-tuning과의 차이: Fine-tuning은 '행동 방식', RAG는 '지식'을 다룬다
'RAG 완전 정복' 시리즈는 계속됩니다. 2편에서는 실제 RAG 시스템을 구축할 때 내려야 하는 의사결정들을 다룹니다. 벡터 DB 선택(Pinecone vs Qdrant vs pgvector - 각각 언제 쓰는지), 청킹 전략(고정 크기 vs 시맨틱 분할의 trade-off), 임베딩 모델 비교(OpenAI vs Cohere vs 오픈소스)까지요. 'Hello World' 수준이 아니라 프로덕션에 배포할 수 있는 수준의 가이드를 준비하고 있습니다.
오늘 다룬 내용만으로도 간단한 RAG 프로토타입은 만들 수 있어요. OpenAI의 임베딩 API와 간단한 벡터 검색만 있으면 됩니다. LangChain이나 LlamaIndex 같은 프레임워크를 쓰면 더 빠르게 시작할 수 있고요. 회사 내부 문서 몇 개로 시작해서, '우리 데이터로 이게 되는구나'를 직접 경험해보시길 권합니다. 그 경험이 다음 단계로 나아가는 가장 좋은 동기부여가 될 거예요.
이 글이 도움이 되셨나요?
RAG 파이프라인 구축이나 LLM 기반 시스템 설계가 필요하신가요?
AI 도입에 대해 궁금한 점이 있으시다면 언제든 문의해 주세요. 전문가가 맞춤형 솔루션을 제안해 드립니다.