작년 11월, 우리 팀에서 고객 지원 챗봇을 만들기로 했다. 제품 문서가 수백 페이지, 자주 바뀌는 정책 문서가 따로 있고, 거기에 우리만의 독특한 답변 톤 — 너무 딱딱하지 않으면서도 전문적인 — 을 유지해야 했다. 당연히 첫 회의에서 나온 질문이 “RAG 쓸까요, 아니면 파인튜닝할까요?”였다.
솔직히 그 질문에 자신 있게 답 못 했다. 이론적으로는 알고 있었는데, 실제로 둘 다 프로덕션에서 운영해본 경험이 없었거든. 그래서 결정하기 전에 2주를 갈아 넣어서 직접 테스트했다. 이 글은 그 경험을 정리한 것이다.
RAG가 실제로 어떻게 작동하고, 어디서 빛나는가
RAG(Retrieval-Augmented Generation)의 핵심 아이디어는 단순하다. 모델 자체를 건드리지 않고, 질문이 들어올 때마다 관련 문서를 검색해서 컨텍스트로 붙여주는 것이다. 모델은 그 문서를 보고 답변을 생성한다.
아키텍처를 단순화하면 이렇다:
from langchain_openai import ChatOpenAI, OpenAIEmbeddings
from langchain_community.vectorstores import Chroma
from langchain.chains import RetrievalQA
# 문서를 벡터로 변환해서 저장 (이건 최초 1회만)
embeddings = OpenAIEmbeddings(model="text-embedding-3-small")
vectorstore = Chroma.from_documents(
documents=chunked_docs, # 미리 청킹된 문서들
embedding=embeddings,
persist_directory="./chroma_db"
)
# 질문이 들어올 때마다 실행되는 파이프라인
llm = ChatOpenAI(model="gpt-4o", temperature=0.2)
qa_chain = RetrievalQA.from_chain_type(
llm=llm,
chain_type="stuff", # 검색된 문서를 그냥 다 때려 넣는 방식
retriever=vectorstore.as_retriever(
search_kwargs={"k": 4} # 관련 문서 4개 가져오기
),
return_source_documents=True # 어느 문서에서 답했는지 추적용
)
result = qa_chain.invoke({"query": "환불 정책이 어떻게 되나요?"})
# result["source_documents"]로 근거 문서 확인 가능
테스트하면서 RAG가 압도적으로 좋았던 상황이 있다. 데이터가 자주 바뀔 때다. 우리 정책 문서는 분기마다 업데이트됐는데, RAG는 새 문서를 벡터 DB에 다시 인덱싱하면 끝이다. 파인튜닝이었다면 매 분기마다 재학습 비용이 나갔을 거다.
또 환각(hallucination) 추적이 중요할 때. 답변에 return_source_documents=True를 붙이면 어느 문단에서 답을 가져왔는지 볼 수 있다. 고객 지원 맥락에서 이건 꽤 중요하다 — 답변이 틀렸을 때 어디서 잘못된 건지 바로 디버깅할 수 있으니까.
한 가지 놀라웠던 점: 청킹(chunking) 전략이 생각보다 훨씬 중요했다. 처음에 문서를 그냥 512 토큰으로 자르니까 맥락이 잘려서 엉뚱한 답변이 나왔다. 문단 단위로 자르고 overlap을 100 토큰 정도 줬더니 확연히 나아졌다. 이건 뒤에서 더 얘기하겠다.
RAG를 쓰면 좋은 상황 요약:
– 지식이 자주 업데이트됨
– 사실 기반 답변이 필요하고, 출처를 추적해야 함
– 데이터가 기밀이라 외부 서비스에 학습시키기 곤란함
– 빠르게 프로토타이핑해야 함 (수일 내 배포하기” rel=”nofollow sponsored” target=”_blank”>배포 가능)
파인튜닝이 진짜 효과적인 경우
파인튜닝은 결이 다르다. 모델의 가중치 자체를 수정해서, 특정 태스크나 스타일에 맞게 모델 자체를 변형시키는 거다. 컨텍스트에 문서를 넣어주는 게 아니라, 모델 자체가 그 지식이나 행동 방식을 “체화”하게 만드는 것에 가깝다.
내가 파인튜닝이 명확하게 승리한다고 느낀 케이스는 세 가지다.
1. 일관된 출력 형식이 필요할 때. 우리 팀이 별도로 진행했던 프로젝트 — JSON 스키마를 항상 특정 형태로 뱉어야 하는 코드 생성 도구 — 에서는 파인튜닝이 압도적이었다. 프롬프트 엔지니어링으로 100번 중 95번 맞추던 걸 파인튜닝 후에는 99번 이상으로 올렸다. 5%가 별거 아닌 것 같아도, 프로덕션에서는 그게 꽤 많은 에러다.
2. 특정 도메인의 언어 패턴이나 톤을 학습시킬 때. 법률 문서 요약, 의료 리포트 작성 같은 케이스. 이건 RAG로는 한계가 있다 — 컨텍스트에 예시를 넣어줄 수 있지만, 그 스타일이 모델에 깊게 녹아드는 건 다르다.
3. 모델의 기본 행동을 바꾸고 싶을 때. 특정 질문 유형을 무조건 거절하게 만들거나, 응답 길이를 항상 짧게 유지하거나. 이런 건 시스템 프롬프트로도 어느 정도 되지만, 신뢰도가 파인튜닝만 못하다.
다만 파인튜닝에 대해 자주 보이는 오해가 있다. 파인튜닝이 새로운 사실적 지식을 심어준다는 착각. 나도 처음에 이렇게 생각했다. “우리 회사 내부 용어랑 제품 지식을 학습시키면 알아서 답하겠지?” — 아니다. 파인튜닝된 모델도 학습 데이터에 없는 최신 정보는 모른다. 그리고 사실 기반 지식을 파인튜닝으로 심으려다가 환각이 더 심해지는 경우도 있다. 이 점은 뒤에서 내 실수 사례로 다시 나온다.
비용 얘기를 안 할 수 없다. GPT-4o 기준으로 파인튜닝 학습 비용은 현재 1M 토큰당 $25 정도다 (2026년 3월 현재). 데이터셋 준비하고, 학습하고, 평가하고, 배포까지 — 처음 세팅하는 데 생각보다 시간이 많이 들어간다. 소규모 팀이라면 이 비용이 부담스러울 수 있다.
내가 실제로 저질렀던 실수들
여기서부터가 사실 이 글의 핵심이다.
실수 1: 파인튜닝으로 지식을 심으려 했다.
처음에 고객 지원 봇을 만들 때, “우리 제품 기능을 파인튜닝으로 학습시키면 RAG 없이도 되지 않을까?”라고 생각했다. 결론부터 말하면 완전히 틀렸다.
제품 기능 설명 100개를 QA 형태로 만들어서 파인튜닝했다. 테스트에서는 잘 됐다. 그런데 프로덕션에서 두 달 뒤에 제품 스펙이 바뀌었다. 모델은 여전히 예전 정보를 자신 있게 답했다. 그리고 더 나쁜 건, 비슷한 질문을 살짝 다르게 물으면 학습 데이터에 없는 내용을 지어내기 시작했다. 신뢰도 면에서 파인튜닝 전보다 오히려 나빠진 부분이 있었다.
실수 2: 청킹을 너무 대충 했다.
RAG를 처음 세팅할 때 문서를 단순하게 1000 토큰 단위로 잘랐다. 한 청크가 이렇게 생겼다:
...정책은 구매일로부터 30일 이내에 한해 적용됩니다.
## 국제 배송 정책
국제 배송의 경우 배송 기간은 국가에 따라...
보이는 것처럼 환불 정책 문단 마지막과 국제 배송 정책 시작이 한 청크로 묶였다. “환불 어떻게 하나요?” 질문에 이 청크가 검색되면, 모델은 국제 배송 내용도 섞어서 답하거나 혼란스러워했다.
해결책은 문서 구조(헤더 기준)로 청킹하고, 각 청크에 메타데이터로 섹션 정보를 붙이는 거였다. RecursiveCharacterTextSplitter에서 separators=["\n## ", "\n### ", "\n\n", "\n"] 식으로 헤더 우선 분할하도록 바꾸고 나서 retrieval 정확도가 눈에 띄게 올라갔다.
실수 3: 파인튜닝 데이터 품질을 과소평가했다.
파인튜닝할 때 데이터 100개 정도면 충분하다는 글을 봤다. 사실이긴 한데, 그 100개의 질이 관건이다. 처음에 빠르게 만들었던 QA 데이터에 일관성 없는 톤, 약간씩 다른 포맷이 섞여 있었다. 결과 모델이 답변 톤이 들쭉날쭉했다. 데이터를 깔끔하게 정제하고 50개로 줄였더니 오히려 결과가 더 일관됐다. 더 많은 데이터가 항상 좋은 게 아니더라.
그래서 어떻게 결정하면 되나
내가 지금 쓰는 결정 기준이다. 팀에서 이 질문을 받으면 아래 순서로 물어본다.
“지식이 자주 바뀌는가?” — 예스면 RAG. 파인튜닝으로 동적 지식을 다루는 건 유지보수 악몽이다.
“팩트 추적이 중요한가?” — 출처를 보여줘야 하거나 환각 디버깅이 필요하면 RAG. 파인튜닝된 모델은 어디서 그 답이 나왔는지 설명하기 어렵다.
“모델의 행동 방식이나 출력 형식을 바꿔야 하는가?” — 예스면 파인튜닝. 프롬프트만으로 해결하려다 지치는 케이스가 이거다.
“둘 다 필요한가?” — 이건 실제로 꽤 흔한 케이스다. 지식은 RAG로, 톤과 형식은 파인튜닝으로. 다만 복잡도가 올라가므로 정말 필요한지 먼저 검증하고 들어가야 한다.
하나 더 — RAG를 먼저 충분히 최적화했는가를 확인해야 한다. 청킹 전략, 임베딩 모델 선택, 리트리버 파라미터, 리랭킹(reranking) 적용 여부. 이걸 제대로 튜닝하지 않고 “RAG가 답 질이 나빠서 파인튜닝으로 가야겠다”는 결론을 내리는 경우를 종종 봤다. 100% 확신할 순 없지만, 대부분의 경우 RAG 최적화로도 충분히 커버된다고 생각한다.
내 결론: 대부분의 경우 RAG로 시작해라
솔직하게 말하겠다. 내 경험상 RAG가 먼저다.
스타트업이나 소규모 팀이라면 파인튜닝의 운영 비용 — 학습 데이터 관리, 재학습 주기, 버전 관리 — 이 생각보다 크다. RAG는 벡터 DB 유지 비용이 있지만, 지식 업데이트가 훨씬 유연하고 디버깅이 쉽다.
파인튜닝은 RAG로는 도저히 안 되는 게 명확하게 있을 때 — 출력 형식이 너무 중요하거나, 모델의 기본 행동을 바꿔야 할 때 — 진지하게 고려한다.
그리고 둘을 같이 쓰는 건, 각자 충분히 운영해보고 한계가 명확할 때 합치는 게 맞다. 처음부터 복잡한 아키텍처로 시작하면 문제가 생겼을 때 어디가 원인인지 찾기 어렵다.
우리 팀 결론은 RAG로 갔고, 배포한 지 4개월 됐다. 지금까지 크게 후회하지 않는다. 분기마다 문서 업데이트할 때 재인덱싱하는 것 외에 별다른 유지보수가 없었다 — 이게 생각보다 큰 장점이다.
파인튜닝이 필요할 것 같다고 느끼기 시작하는 순간이 오면, 그때 다시 판단해도 늦지 않다.