RAG 지식베이스 구축 실전: 국산 오픈 모델과 로컬 배포 완전 가이드
RAG 지식베이스 구축 실전
RAG(검색 증강 생성)는 국산 오픈 모델의 가장 가치 있는 응용 중 하나입니다. 내 문서·코드베이스·사내 지식을 모델에 넘겨, 범용 학습 데이터의 "추측"이 아니라 내 데이터에 기반해 답하게 합니다. 이 글은 국산 오픈 모델로 RAG 지식베이스를 구축하는 전체 흐름을 다룹니다.
30초 요약
RAG = 문서 분할 → Embedding으로 벡터화 → 벡터 DB 저장 → 질문 시 유사 청크 검색 → 프롬프트에 넣어 LLM이 답변. 로컬 구성은 Ollama Embedding + llama.cpp 모델 + Chroma, 클라우드 구성은 EXAIV OpenAI 호환 엔드포인트 + 임의 벡터 DB. 핵심 코드는 LangChain으로 30줄 남짓입니다.
RAG의 4단계
RAG는 "문서를 모델에 밀어 넣는 것"이 아닙니다. 파이프라인입니다.
| 단계 | 하는 일 | 핵심 판단 |
|---|---|---|
| 1. 문서 분할 | 문서를 검색 가능한 조각으로 | 청크 크기, 오버랩 |
| 2. Embedding | 조각을 벡터로 | Embedding 모델 선정 |
| 3. 벡터 검색 | 질문에서 관련 조각 찾기 | 벡터 DB, 유사도 임계값 |
| 4. 답변 생성 | 검색 조각을 프롬프트에 | LLM 선정, 프롬프트 설계 |
Embedding과 LLM은 따로 고른다
Embedding 모델은 "텍스트 의미를 이해해 벡터로 변환"하고, LLM은 "검색된 조각으로 답변을 생성"합니다. 두 개의 독립 모델이라 가벼운 Embedding + 강한 LLM 조합이 가능합니다. "한 모델로 전부"보다 유연하고 리소스 효율도 좋습니다.
Embedding과 벡터 DB 선정
Embedding 선정 기준은 다국어 지원(한국어·중국어 시나리오에서 필수), 벡터 차원(저장·검색 속도 영향), 모델 크기(인코딩 속도)입니다. 한국어·중국어가 포함되면 반드시 해당 언어 학습 데이터를 가진 모델인지 확인하세요.
| 벡터 DB | 특징 | 적합한 곳 |
|---|---|---|
| Chroma | 경량, 순 Python, 배포 불필요 | 개인 프로젝트, 시제품 |
| Qdrant | 고성능, 필터 지원 | 운영 환경 |
| Milvus | 분산, 대규모 | 엔터프라이즈 |
| FAISS | Meta제, 순 계산 라이브러리 | 앱 내장 |
개인·소규모 팀은 Chroma로 시작하는 게 가장 간편합니다.
완전 구현: LangChain 30줄
로컬 모델 기반 최소 RAG 파이프라인:
# pip install langchain langchain-community chromadb
from langchain_community.embeddings import OllamaEmbeddings
from langchain_community.vectorstores import Chroma
from langchain_community.llms import Ollama
from langchain.text_splitter import RecursiveCharacterTextSplitter
# 1. 문서 분할
splitter = RecursiveCharacterTextSplitter(chunk_size=500, chunk_overlap=50)
docs = splitter.split_documents(your_documents)
# 2. Embedding + 벡터 DB 저장
embeddings = OllamaEmbeddings(base_url="http://127.0.0.1:11434", model="nomic-embed-text")
vectorstore = Chroma.from_documents(docs, embeddings)
# 3. 검색 + 생성
llm = Ollama(base_url="http://127.0.0.1:11434", model="your-model")
query = "당신의 질문"
results = vectorstore.similarity_search(query, k=3)
context = "\n\n".join([r.page_content for r in results])
prompt = f"다음 내용을 바탕으로 답하세요.\n\n{context}\n\n질문: {query}"
print(llm.invoke(prompt))
클라우드 API 전환은 3줄뿐
Ollama LLM을 EXAIV OpenAI 호환 엔드포인트로 교체, Embedding은 로컬 유지(또는 변경), 벡터 DB는 그대로 — LangChain 추상층 덕분에 "로컬 모델"과 "클라우드 API"를 교체할 수 있습니다. 이것이 OpenAI 호환 인터페이스의 가치입니다.
클라우드 구성: EXAIV 엔드포인트
로컬 모델을 관리하고 싶지 않다면 생성 단계를 EXAIV 호환 엔드포인트에 맡깁니다:
from openai import OpenAI
client = OpenAI(api_key="YOUR_API_KEY", base_url="https://exaiv.com/api/ai/v1")
resp = client.chat.completions.create(
model="kimi-k3", # /models 실시간 ID 기준
messages=[
{"role": "system", "content": f"다음 내용을 바탕으로 답하세요. 내용이 부족하면 명시하세요.\n\n{context}"},
{"role": "user", "content": query},
],
)
print(resp.choices[0].message.content)
청크 분할 전략
| 전략 | chunk_size | 적합한 곳 |
|---|---|---|
| 잘게 | 200-300자 | FAQ, 짧은 문서, 정밀 일치 |
| 표준 | 400-600자 | 범용 문서 |
| 굵게 | 800-1200자 | 긴 문서, 문맥 밀도가 높은 |
작을수록 좋은 게 아닙니다
조각이 너무 작으면 문맥이 끊겨(하나의 논점이 반으로 갈라짐) 검색된 조각만으로는 답할 수 없습니다. 너무 크면 의미가 희석되어 검색 정밀도가 떨어집니다. 500자부터 시작해 검색 결과를 보며 조정하세요 — RAG 튜닝에서 가장 시간을 들일 가치가 있는 파라미터입니다.
고급 기법
기본 파이프라인이 동작한 뒤 검색 품질을 올리는 방법:
1. 재랭킹. 벡터 DB가 돌려준 후보 조각을 cross-encoder 등으로 재채점·재정렬합니다. 벡터 유사도는 높은데 실제 관련성은 낮은 케이스를 잡아냅니다.
2. 쿼리 확장. 검색 전 LLM이 사용자 질문을 여러 변형으로 바꿔 모두 검색합니다. 질문과 문서의 어휘가 어긋날 때 재현율이 올라갑니다.
3. 메타데이터 필터. 출처·날짜·문서 종류를 각 조각에 붙여 벡터 검색 전에 좁힙니다.
4. 하이브리드 검색. 벡터 유사 검색과 키워드(BM25) 검색을 병행하고 가중치로 통합합니다.
어느 쪽도 LLM 변경이 필요 없고, 프롬프트에 넘기기 전 검색층을 개선합니다.
운영 배포 주의점
영속화: Chroma 기본값은 인메모리라 재시작 시 사라집니다. 시제품을 넘어서면 영속 백엔드로 전환하세요.
증분 업데이트: 문서가 바뀌면 변경된 조각만 재인코딩합니다. 출처 문서와 대응을 추적할 수 있는 설계로 만드세요.
평가: 질문 + 기대 출처 조각 + 기대 답변으로 구성된 작은 평가 세트를 만들어 파이프라인 변경마다 실행하세요. 평가가 없으면 검색 품질 저하가 조용히 진행됩니다.
비용 관리: 클라우드 모드에서는 검색 컨텍스트와 생성 양쪽이 토큰을 씁니다. 컨텍스트 크기를 모니터링하고 검색 조각을 줄여 비용을 제어하세요.
FAQ
RAG와 문서를 그대로 프롬프트에 넣는 것의 차이는?
Embedding 모델은 무엇을 쓰나요?
검색 조각이 엉뚱할 때는?
LangChain과 LlamaIndex 중 무엇?
가격은 어디서 보나요?
관련 글
- LangChain과 EXAIV — LangChain 완전 연동
- 로컬 배포 완전 플레이북 — 배포 흐름
- Qwen 모델 선택 가이드 — 모델 선정
- 중국 오픈 웨이트 vs 미국 플래그십 — 로컬과 클라우드 전체 그림
- 요금 — 클라우드 실시간 가격