목차
- 파인튜닝과 RAG, 근본적으로 뭐가 다른가
- 비용 구조가 완전히 다르다
- 정확도와 환각: 파인튜닝 vs RAG 선택의 핵심
- 데이터 업데이트 주기에 따른 판단
- 실전 하이브리드 구성: 둘 다 쓰는 법
- 실무에서 자주 마주치는 트러블슈팅
- 실전 적용 가이드
- 마치며: 이런 상황이라면 이렇게 시작하면 된다
데이터 준비하다가 3일을 날렸다. 회사에서 사내 법률 문서를 검색하는 챗봇을 만들어달라는 요청이 들어왔는데, 처음엔 당연히 파인튜닝이겠거니 했다. GPT-3.5 시절부터 파인튜닝이 정석이었으니까. 근데 법률 문서에서 Q&A 쌍 뽑다가 막혀서 3일을 통째로 버리고 나서야 — 파인튜닝 vs RAG 선택이 그렇게 단순한 문제가 아니라는 걸 깨달았다.
파인튜닝 vs RAG 선택은 "뭐가 더 좋냐"의 문제가 아니다. 데이터 특성, 예산, 업데이트 주기에 따라 답이 완전히 달라진다. 이 글은 두 방법을 직접 비교해보고, 어떤 상황에서 뭘 써야 하는지 내 기준으로 정리한 기록이다.
파인튜닝과 RAG, 근본적으로 뭐가 다른가
파인튜닝: 모델 자체를 바꾼다
파인튜닝은 사전 훈련된 LLM의 가중치를 도메인 데이터로 추가 학습시키는 거다. 모델이 "아는 것" 자체가 달라진다. OpenAI의 gpt-4o-mini 파인튜닝을 예로 들면, JSONL 형식으로 학습 데이터를 만들어서 업로드하고 학습을 돌린다.
{"messages": [{"role": "system", "content": "너는 한국 노동법 전문 상담사다."}, {"role": "user", "content": "연차 미사용 수당 계산법이 어떻게 되나요?"}, {"role": "assistant", "content": "통상임금을 기준으로 미사용 연차일수를 곱합니다. 통상임금에는 기본급, 고정수당이 포함되며..."}]}
이런 데이터를 수백~수천 개 만들어서 학습시키면, 모델이 노동법 관련 질문에 훨씬 정확하게 답한다. 문제는 이 데이터를 만드는 게 진짜 고역이라는 거다. 나는 법률 문서에서 Q&A 쌍을 뽑는 데만 일주일이 걸렸다. 자동화 스크립트를 짜도 사람이 검수해야 해서 시간이 줄지 않더라.
RAG: 모델은 그대로, 외부 지식을 붙인다
RAG(Retrieval-Augmented Generation)은 모델을 안 건드린다. 대신 질문이 들어오면 벡터 DB에서 관련 문서를 검색해서 프롬프트에 같이 넣어준다. 모델 입장에서는 "여기 참고자료 있으니까 이거 보고 답해"인 셈이다.
from langchain_openai import OpenAIEmbeddings, ChatOpenAI
from langchain_community.vectorstores import Chroma
from langchain.chains import RetrievalQA
# 문서 임베딩 후 벡터 DB에 저장
embeddings = OpenAIEmbeddings(model="text-embedding-3-small")
vectorstore = Chroma.from_documents(documents, embeddings)
# 검색 + 생성 체인 구성
qa_chain = RetrievalQA.from_chain_type(
llm=ChatOpenAI(model="gpt-4o"),
chain_type="stuff",
retriever=vectorstore.as_retriever(search_kwargs={"k": 4})
)
result = qa_chain.invoke({"query": "연차 미사용 수당 계산법"})
print(result["result"])
이게 기본 구조다. LangChain 0.3 기준인데, 버전마다 import 경로가 좀 다르니까 공식 문서를 확인하는 게 좋다. 솔직히 RAG 파이프라인 세팅은 파인튜닝 데이터 준비보다 체감상 3배는 빠르다.

비용 구조가 완전히 다르다
파인튜닝 vs RAG 선택에서 비용은 꽤 결정적인 요소다. 단순히 "뭐가 싸냐"가 아니라 비용이 발생하는 시점과 패턴이 다르다.
파인튜닝은 초기 비용이 크다. OpenAI 기준으로 gpt-4o-mini 파인튜닝이 토큰당 학습 비용이 따로 붙고, 학습된 모델의 추론 비용도 기본 모델보다 비싸다. 거기에 데이터 준비 인건비까지. 나 혼자 법률 데이터 500개 만드는 데 2주 걸렸는데, 이걸 인건비로 환산하면 꽤 아프다.
RAG는 초기 비용이 낮은 대신 운영 비용이 꾸준히 나간다. 벡터 DB 호스팅 비용, 임베딩 API 호출 비용, 검색할 때마다 드는 토큰 비용. 문서가 많아지면 이게 누적된다. Pinecone이나 Weaviate 같은 관리형 벡터 DB를 쓰면 월 고정비도 붙고.
비용 비교
| 비교 항목 | 파인튜닝 | RAG |
|---|---|---|
| 초기 세팅 비용 | 높음 (데이터 준비 + 학습) | 낮음 (임베딩 + 벡터 DB) |
| 추론 비용 | 모델 호출 비용만 | 검색 + 모델 호출 비용 |
| 데이터 업데이트 | 재학습 필요 (비용 재발생) | 문서 추가만 하면 됨 |
| 인프라 운영비 | 거의 없음 (API 사용 시) | 벡터 DB 호스팅 비용 |
| 인건비 | 데이터 라벨링 높음 | 청크 전략 설계 중간 |
내가 계산해봤을 때, 월 질의 1만 건 이하면 RAG가 싸다. 10만 건 넘어가면 파인튜닝 모델의 짧은 프롬프트가 토큰을 아껴줘서 역전되는 구간이 생긴다. 근데 이건 문서 크기나 검색 k값에 따라 많이 달라지니까 대략적인 감만 잡는 게 맞다.
정확도와 환각: 파인튜닝 vs RAG 선택의 핵심
파인튜닝의 강점과 한계
파인튜닝은 특정 도메인의 "말투"와 "판단 기준"을 학습시키기에 좋다. 의료 상담 봇이 "~하실 수 있으셔요" 대신 "~해야 합니다"로 답하게 만든다거나, 법률 용어를 정확하게 쓰게 만드는 데는 파인튜닝이 확실히 낫다.
근데 치명적인 약점이 있다. 학습 데이터에 없는 질문이 들어오면 자신 있게 틀린 답을 한다. 이게 환각(hallucination) 문제인데, 파인튜닝 모델은 "모른다"고 말하는 걸 잘 못한다. 내가 만든 법률 봇도 학습 데이터에 없는 특수 고용 관련 질문에 엉뚱한 조항을 인용하더라. 이거 발견하고 진짜 식겁했다.
RAG의 강점과 한계
RAG는 검색된 문서를 기반으로 답하니까 환각이 상대적으로 적다. 출처를 같이 보여줄 수도 있고. "이 문서의 3페이지에 따르면~" 이런 식으로 답하게 만들 수 있어서 사용자 신뢰도가 높다.
다만 검색이 잘 되어야 답도 잘 나온다. 당연한 소리 같지만 이게 생각보다 어렵다. 사용자가 "퇴직금 중간정산" 을 물어봤는데 벡터 검색이 "퇴직연금 DC형" 문서를 가져오면 답이 엉뚱해진다. 청크 사이즈, 오버랩, 임베딩 모델 선택 — 이 조합을 찾는 데 나는 이틀을 썼다. 솔직히 이건 좀 불편하다.
from langchain.text_splitter import RecursiveCharacterTextSplitter
# 청크 전략: 법률 문서 특성상 조항 단위로 끊는 게 좋았다
splitter = RecursiveCharacterTextSplitter(
chunk_size=800,
chunk_overlap=200,
separators=["\n제", "\n조", "\n\n", "\n", " "]
)
chunks = splitter.split_documents(raw_docs)
print(f"총 {len(chunks)}개 청크 생성")
실행하면 문서 크기에 따라 다르지만, 법률 문서 50개 기준으로 대략 400~600개 청크가 나온다. separators를 도메인에 맞게 커스텀하는 게 포인트인데, 법률 문서는 "제X조" 단위로 끊으니까 검색 정확도가 확 올라갔다. 이거 찾고 좀 뿌듯했다.
데이터 업데이트 주기에 따른 판단
이게 실무에서 파인튜닝 vs RAG 선택을 가르는 가장 현실적인 기준이다.
파인튜닝 모델은 데이터가 바뀌면 재학습을 돌려야 한다. 법률이 개정되면? 다시 데이터 만들고, 다시 학습시키고, 다시 배포하고. 시간도 돈도 든다. 월 1회 이상 데이터가 바뀌는 도메인이면 파인튜닝은 현실적으로 힘들다.
RAG는 문서만 바꿔 넣으면 된다. 새 법률 시행됐으면 PDF 임베딩해서 벡터 DB에 넣으면 끝이다. 10분이면 된다. 이 차이가 운영 단계에서는 압도적이다.
업데이트 흐름 비교
graph LR
A[데이터 변경 발생] --> B{업데이트 방식}
B -->|파인튜닝| C[데이터 재가공]
C --> D[모델 재학습 2~8시간]
D --> E[모델 교체 배포]
B -->|RAG| F[문서 임베딩]
F --> G[벡터 DB 업데이트 ~10분]
G --> H[즉시 반영]
내가 법률 봇 운영하면서 뼈저리게 느낀 건, 최소노동임금 개정 때마다 파인튜닝 데이터를 갱신하는 게 너무 번거롭다는 거였다. 결국 핵심 답변 톤은 파인튜닝으로 잡고, 최신 데이터는 RAG로 붙이는 하이브리드로 전환했다. 이건 뒤에서 더 자세히 다룬다.
실전 하이브리드 구성: 둘 다 쓰는 법
사실 현업에서는 파인튜닝이냐 RAG냐 양자택일보다, 둘을 섞는 경우가 많다. 파인튜닝으로 모델의 기본 톤과 판단 기준을 잡고, RAG로 최신 데이터를 공급하는 구조다.
하이브리드 아키텍처
from openai import OpenAI
client = OpenAI()
def hybrid_answer(user_query: str, retrieved_docs: list[str]) -> str:
context = "\n---\n".join(retrieved_docs)
response = client.chat.completions.create(
model="ft:gpt-4o-mini-2024-07-18:my-org::abc123", # 파인튜닝 모델
messages=[
{
"role": "system",
"content": (
"너는 한국 노동법 전문 상담사다. "
"아래 참고 문서를 기반으로 답변하되, "
"문서에 없는 내용은 '확인이 필요합니다'라고 답해라."
)
},
{
"role": "user",
"content": f"참고 문서:\n{context}\n\n질문: {user_query}"
}
],
temperature=0.3
)
return response.choices[0].message.content
여기서 포인트는 temperature=0.3이다. 법률 상담처럼 정확성이 중요한 도메인에서는 temperature를 낮게 잡아야 한다. 처음에 0.7로 뒀다가 같은 질문에 매번 다른 조항을 인용하길래 바로 내렸다.
언제 하이브리드가 맞나
모든 경우에 하이브리드가 정답은 아니다. 관리 포인트가 2개로 늘어나니까 혼자 운영하는 입장에서는 부담이다. 내 기준은 이렇다:
- 파인튜닝만: 데이터가 거의 안 바뀌고, 특수한 출력 형식이 필요할 때. 예를 들어 의료 보고서를 특정 포맷으로 생성하는 경우.
- RAG만: 데이터가 자주 바뀌고, 출처 표시가 필요할 때. 사내 문서 검색, 고객 지원 봇 등.
- 하이브리드: 도메인 특화 톤이 필요하면서 데이터도 자주 바뀔 때. 법률, 금융 상담 같은 케이스.

실무에서 자주 마주치는 트러블슈팅
파인튜닝: 과적합과 데이터 품질
파인튜닝에서 가장 흔한 문제는 과적합이다. 학습 데이터가 적으면(100개 미만) 모델이 학습 데이터를 통째로 외워버린다. 질문을 조금만 바꿔도 엉뚱한 답을 하는 거다.
OpenAI 파인튜닝 기준으로 최소 200~500개의 고품질 데이터는 있어야 한다고 OpenAI 공식 파인튜닝 가이드에서도 권장한다. 내 경험상 500개 이상부터 체감 품질이 확 올라갔다. 100개로 했을 때는 학습 데이터와 비슷한 질문만 잘 답하고 나머지는 다 틀렸다.
데이터 품질 체크리스트 — 이건 내가 삽질하면서 정리한 거다:
- 답변에 모순이 없는지 (같은 주제에 대해 다른 답변이 있으면 모델이 혼란)
- 시스템 프롬프트와 답변 톤이 일관적인지
- 답변 길이가 너무 들쭉날쭉하지 않은지
- JSON 등 구조화된 출력을 원하면 모든 데이터가 같은 포맷인지
RAG: 검색 품질 개선
RAG에서 답변 품질 = 검색 품질이다. 검색이 엉뚱한 문서를 가져오면 아무리 좋은 모델도 소용없다.
내가 써본 검색 개선 방법 중 가장 효과 좋았던 건 하이브리드 검색이다. 벡터 검색(의미 기반)과 BM25(키워드 기반)를 같이 쓰는 거다.
from langchain.retrievers import EnsembleRetriever
from langchain_community.retrievers import BM25Retriever
# BM25 키워드 검색
bm25_retriever = BM25Retriever.from_documents(chunks)
bm25_retriever.k = 4
# 벡터 검색
vector_retriever = vectorstore.as_retriever(search_kwargs={"k": 4})
# 앙상블: 키워드 + 의미 검색 조합
ensemble_retriever = EnsembleRetriever(
retrievers=[bm25_retriever, vector_retriever],
weights=[0.4, 0.6]
)
docs = ensemble_retriever.invoke("퇴직금 중간정산 요건")
for doc in docs:
print(doc.page_content[:100])
weights를 [0.4, 0.6]으로 준 건 법률 문서 특성상 정확한 용어 매칭(BM25)이 꽤 중요했기 때문이다. 도메인마다 이 비율을 실험해봐야 한다. 나는 0.3/0.7부터 0.5/0.5까지 다 돌려봤는데, 법률은 0.4/0.6이 제일 나았다. 다른 도메인은 다를 수 있다.
아 그리고 이건 주의해야 하는데, BM25Retriever는 메모리에 인덱스를 올리기 때문에 문서가 수십만 개면 서버 메모리를 잡아먹는다. 그 규모면 Elasticsearch를 붙이는 게 맞다.
실전 적용 가이드
파인튜닝 vs RAG 선택 판단 플로우
글을 쓰다 보니 판단 기준이 여러 개라 헷갈릴 수 있어서, 내가 실제로 쓰는 의사결정 흐름을 정리한다.
graph TD
A[도메인 특화 AI 필요] --> B{데이터가 자주 바뀌나?}
B -->|월 1회 이상| C{출처 표시 필요?}
C -->|Yes| D[RAG]
C -->|No| E{특수 출력 포맷?}
E -->|Yes| F[하이브리드]
E -->|No| D
B -->|거의 안 바뀜| G{학습 데이터 500개 이상?}
G -->|Yes| H{예산 여유?}
H -->|Yes| I[파인튜닝]
H -->|No| D
G -->|No| D
개인적으로 이게 제일 깔끔하다. 복잡한 매트릭스보다 이진 분기로 따라가는 게 실수가 적다. 실제로 사내에서 새 봇 요청이 들어올 때마다 이 흐름대로 판단한다. 지금까지 5개 프로젝트에 적용했는데, 나중에 "다른 방법으로 할걸" 후회한 적은 아직 없다.
한 가지 덧붙이면, 위 흐름에서 "학습 데이터 500개"가 안 되는데 파인튜닝을 하고 싶다면 — 하지 마라. 과적합 확률이 너무 높다. 차라리 프롬프트 엔지니어링을 잘 하는 게 낫다. few-shot 예제를 시스템 프롬프트에 5~10개 넣는 것만으로도 웬만한 도메인 톤은 잡힌다.

프레임워크와 도구 비교
직접 써본 도구 위주로 정리한다. 안 써본 건 안 넣었다.
파인튜닝 도구:
- OpenAI Fine-tuning API: 가장 간단. 웹 UI에서도 되고 API로도 된다.
gpt-4o-mini파인튜닝이 가성비가 제일 좋았다. - Hugging Face + PEFT/LoRA: 오픈소스 모델 쓸 때.
Llama 3이나Mistral파인튜닝할 때 LoRA 쓰면 GPU 메모리를 많이 아낀다. 근데 A100 한 장은 있어야 편하다. T4로 하면 배치 사이즈 줄여야 하고 시간이 오래 걸린다. Hugging Face PEFT 공식 문서에 LoRA 설정 예제가 잘 정리돼 있으니 처음이라면 여기서 시작하는 게 좋다. - Unsloth: LoRA 파인튜닝 속도를 끌어올려주는 라이브러리. 체감상 Hugging Face 기본 대비 2배 정도 빠르다고들 하는데, 내가
Llama 3 8B로 돌렸을 때도 비슷하게 느꼈다. Unsloth GitHub에서 확인할 수 있다.
RAG 도구:
- LangChain: 생태계가 넓어서 뭐든 된다. 근데 버전 업데이트가 너무 잦아서 3개월 전 코드가 deprecated 되는 경우가 있다. 이건 좀 짜증난다.
- LlamaIndex: RAG에 특화되어 있어서 LangChain보다 RAG 파이프라인이 깔끔하다. 문서 파싱 기능이 특히 좋다.
- 벡터 DB: Chroma(로컬, 프로토타입용), Pinecone(관리형, 프로덕션용), Weaviate(하이브리드 검색 내장). 나는 프로토타입은 Chroma, 프로덕션은 Pinecone 쓴다.
이것 외에 최근 뜨는 게 GraphRAG다. 문서를 그래프 구조로 만들어서 검색하는 건데, 아직 못 써봤다. Microsoft에서 밀고 있는 것 같은데, 복잡한 관계형 질문에 강하다고 한다. 이건 나중에 직접 써보고 글을 쓸 생각이다.
마치며: 이런 상황이라면 이렇게 시작하면 된다
파인튜닝 vs RAG 선택 기준이 어느 정도 잡혔으면, 다음은 평가 체계를 만드는 거다. RAGAS 같은 프레임워크로 답변 품질을 자동 측정하는 파이프라인을 구축하면, 청크 사이즈나 검색 파라미터를 바꿀 때마다 수동으로 확인 안 해도 된다. 실제로 나도 다음 달에 이걸 도입하려고 준비 중이다.
그리고 프롬프트 엔지니어링도 깊게 파볼 만하다. 파인튜닝이나 RAG 이전에 시스템 프롬프트만 잘 짜도 해결되는 문제가 의외로 많다. Claude나 GPT-4o급 모델이면 few-shot 프롬프트로 도메인 톤을 꽤 잡을 수 있다. 비용도 안 들고.
마지막으로 에이전트 아키텍처. 단순 Q&A를 넘어서 모델이 직접 DB를 조회하거나 API를 호출하게 만들면, RAG만으로는 못 하는 것들이 가능해진다. LangChain의 Agents 문서를 보면 기본 구조를 잡는 데 도움이 된다. 나는 법률 봇에 판례 검색 API를 에이전트로 연결하는 걸 고민 중이다.
지금 바로 시작하려면 이렇게 하면 된다. 데이터가 자주 바뀌는 도메인이라면 LangChain RAG 공식 튜토리얼로 기본 파이프라인부터 잡아라. 이 글의 RetrievalQA 예제 코드를 Chroma + OpenAI 조합으로 돌려보는 데 한 시간이면 된다. 파인튜닝을 해보고 싶다면 200개 고품질 데이터를 먼저 확보한 뒤 OpenAI 파인튜닝 API로 소규모 실험부터 시작해라. 100개 미만으로 파인튜닝하거나, 청크 전략 없이 RAG를 붙이는 건 시간 낭비다 — 경험에서 하는 말이다.
관련 글
- 바이브코딩 워크플로우 7단계: Claude Code+Cursor로 SaaS MVP 4주 만에 런칭 – 회사에서 사이드 프로젝트로 SaaS MVP를 만들었다. Claude Code와 Cursor를 조합한 바이브코딩 워크플로우 7단계를 거쳐 4…