[코드 예제 완벽 가이드] RAG 기반 챗봇 구축: 우리 회사 지식으로 무장하는 AI 만들기
"이 AI, 우리 회사 내부 규정은 알고 있나요?"
최근 몇 년 사이, LLM(Large Language Model)은 비약적인 발전을 거듭하며 우리 업무의 많은 영역을 자동화하고 있습니다. GPT-4나 Claude 같은 모델을 API로 호출하는 것만으로도 놀라운 수준의 결과물을 얻을 수 있게 되었죠.
하지만 현업 개발자나 PM들이 가장 먼저 부딪히는 벽이 있습니다. 바로 '신뢰성' 문제입니다.
기성 LLM은 방대한 일반 지식으로 훈련되었기 때문에, 최신 정보나 특정 기업의 독점적인 내부 규정, 혹은 방금 작성한 프로젝트 문서를 알지 못합니다. 설령 답변을 하더라도, 마치 자신 있게 틀린 정보를 말하는 듯한 환각(Hallucination) 현상을 보일 위험이 매우 높습니다.
이 문제를 해결하고, "우리 회사 자료로만 답변하는, 믿을 수 있는 AI"를 만드는 것이 현재 IT 업계의 가장 뜨거운 화두입니다. 그 해답이 바로 RAG (Retrieval-Augmented Generation) 패턴입니다.
이 글에서는 RAG가 무엇인지 개념적으로만 아는 수준을 넘어, 실제로 작동하는 지식 기반 챗봇을 구축하는 전체 로드맵과 핵심 코드 개념을, 개발자 관점에서 깊이 있게 파헤쳐 보겠습니다.
💡 1. 서론: 왜 '우리 회사 자료'로 학습한 AI가 필요한가?
LLM은 강력한 '추론 엔진'이지만, 그 자체로 '지식 저장소'는 아닙니다. 마치 최고의 논리력을 가진 신입사원에게 최신 프로젝트 매뉴얼을 주지 않고 "이 프로젝트 어떻게 진행해야 해?"라고 묻는 것과 같습니다. 논리적으로는 훌륭하지만, 근거가 부족합니다.
RAG는 이 간극을 메워주는 '지식 연결 장치'입니다.
RAG는 LLM에게 답변을 생성하기 직전에, 사용자의 질문과 가장 관련성이 높은 외부 지식(우리가 가진 PDF, Notion 페이지, Wiki 등)을 **검색(Retrieval)**하여, 그 지식을 **참고 자료(Context)**로 제공한 뒤, 이 자료를 바탕으로 답변을 **생성(Generation)**하게 만드는 아키텍처입니다.
핵심: RAG는 LLM 자체를 재학습(Fine-tuning)시키는 것이 아니라, '답변의 근거 자료'를 실시간으로 주입하여 신뢰성을 극대화하는 방식입니다.
⚙️ 2. RAG의 작동 원리 완벽 해부: 3단계 프로세스 이해하기
RAG가 어떻게 작동하는지 이해하려면, 이 과정을 세 단계로 나누어 이해하는 것이 가장 효과적입니다. 이 3단계 흐름을 반드시 기억해 주세요.
📚 Step 1: Indexing (색인화) - 지식을 AI가 읽을 수 있게 준비하기
우리가 가진 비정형 데이터(PDF, DOCX, HTML 등)는 컴퓨터가 바로 이해할 수 있는 형태가 아닙니다. 따라서 이 데이터를 AI가 처리할 수 있는 '숫자 형태'로 변환하고, 효율적으로 검색할 수 있도록 데이터베이스에 저장하는 과정이 필요합니다.
- Chunking (분할): 긴 문서를 의미 있는 작은 조각(Chunk)으로 나눕니다. (이 부분이 매우 중요합니다!)
- Embedding (임베딩): 각 텍스트 조각(Chunk)을 임베딩 모델(예: OpenAI
text-embedding-ada-002)을 이용해 고차원 벡터(숫자의 배열)로 변환합니다. 이 벡터는 텍스트의 '의미'를 수학적으로 표현한 것입니다. - Vector DB 저장: 변환된 벡터와 원본 텍스트 조각을 **벡터 데이터베이스(Vector DB)**에 저장합니다.
🔍 Step 2: Retrieval (검색) - 질문과 가장 가까운 지식 찾기
사용자가 질문을 던지면, 이 질문 역시 임베딩 과정을 거쳐 하나의 '질문 벡터'가 됩니다.
이 질문 벡터를 가지고 벡터 DB에 질의합니다. 벡터 DB는 저장된 수많은 벡터들 중에서, 질문 벡터와 '수학적으로 가장 가까운' (즉, 의미적으로 가장 유사한) K개의 지식 조각(Context)을 찾아 반환합니다. 이것이 '유사도 검색'의 원리입니다.
✍️ Step 3: Generation (생성) - 최종 답변 만들기
마지막 단계입니다. LLM에게 세 가지 정보를 함께 전달합니다.
- 질문 (Query): 사용자가 처음에 물어본 질문.
- 검색된 Context: Step 2에서 찾아낸, 가장 관련성 높은 지식 조각들.
- 명령어 (Prompt): "너는 전문가야. 아래 [Context] 자료만을 근거로 [Query]에 답변해 줘. 모르면 모른다고 말해."와 같은 명확한 지침.
LLM은 이 모든 정보를 종합하여, 환각 없이 근거에 기반한 최종 답변을 생성합니다.
💻 3. 실전 구축 가이드: 개발자가 알아야 할 기술 스택과 구현 순서
이론을 알았다면, 이제 코드로 구현할 차례입니다. RAG 파이프라인을 구축하는 데 필요한 핵심 스택과 흐름을 살펴보겠습니다.
🛠️ 필수 기술 스택 비교
| 기술 요소 | 역할 | 대표 라이브러리/서비스 | 특징 및 선택 가이드 |
|---|---|---|---|
| 오케스트레이션 | 전체 파이프라인 흐름 제어 | LangChain, LlamaIndex | 초보자에게는 LangChain이 자료가 많아 접근성이 좋습니다. |
| 임베딩 모델 | 텍스트 $\rightarrow$ 벡터 변환 | OpenAI Embeddings, Hugging Face Sentence Transformers | 성능과 비용을 고려하여 선택합니다. |
| 벡터 DB | 벡터 저장 및 유사도 검색 | ChromaDB, Pinecone, Weaviate | ChromaDB는 로컬 테스트에 가장 쉽고 빠릅니다. Pinecone은 대규모 상용 서비스에 적합합니다. |
| LLM | 최종 답변 생성 | OpenAI GPT, Anthropic Claude | 답변의 톤과 복잡도에 따라 선택합니다. |
🚀 핵심 구현 과정 (코드 중심)
실제 코드를 작성한다고 가정하고, 가장 핵심적인 두 가지 단계를 보여드리겠습니다. (실제 환경에서는 pip install 및 API 키 설정이 선행되어야 합니다.)
1. 문서 로드 및 벡터 DB 저장 (Indexing 시뮬레이션)
먼저, 로드한 문서를 청킹하고 벡터 DB에 저장하는 과정입니다.
# 1. 문서 로드 (PDF, TXT 등)
from langchain_community.document_loaders import PyPDFLoader
loader = PyPDFLoader("company_manual.pdf")
documents = loader.load()
# 2. 텍스트 청킹 (Chunking)
from langchain.text_splitter import RecursiveCharacterTextSplitter
text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=200)
chunks = text_splitter.split_documents(documents)
# 3. 임베딩 및 벡터 저장 (실제로는 OpenAI나 HuggingFace 모델 사용)
# vectorstore = Chroma.from_documents(embeddings, chunks, persist_directory="./chroma_db")
print(f"총 {len(chunks)}개의 청크로 분할 완료.")2. 질의응답 실행 (Retrieval & Generation)
사용자가 질문을 하면, 이 질문을 벡터화하여 DB에서 가장 유사한 문서를 검색(Retrieval)하고, 이 문서를 바탕으로 LLM이 답변을 생성(Generation)합니다.
# 1. 검색기 초기화 (Vector Store에서 유사 문서 검색)
# retriever = vectorstore.as_retriever(search_kwargs={"k": 3}) # 상위 3개 검색
# 2. 검색 실행 (가상의 검색 결과)
retrieved_docs = [
"검색된 문서 1: 2024년 휴가 정책에 따르면, 근속 3년차부터 연차 휴가 15일이 부여됩니다.",
"검색된 문서 2: 재택근무는 팀장 승인 하에 주 2회까지 가능하며, 사용 기록을 남겨야 합니다.",
"검색된 문서 3: 모든 보고서는 반드시 사내 공유 드라이브에 최종본을 업로드해야 합니다."
]
# 3. 프롬프트 구성 및 LLM 호출
prompt_context = "\n---\n".join(retrieved_docs)
final_prompt = f"""
다음 [참고 자료]를 바탕으로 [질문]에 답변해 주세요.
만약 참고 자료에 답변 근거가 없다면, '제공된 자료를 바탕으로 답변할 수 없습니다.'라고 답변하세요.
[참고 자료]:
{prompt_context}
[질문]:
근속 3년차 직원이 휴가 사용 시 유의할 점과 재택근무 규정을 종합적으로 알려주세요.
"""
# LLM_Response = call_llm(final_prompt)
print("\n--- 최종 답변 ---")
print("근속 3년차 직원은 연차 휴가 15일을 사용할 수 있으며, 재택근무는 팀장 승인 하에 주 2회까지 가능합니다. 모든 사용 기록은 반드시 남겨야 합니다.")💡 핵심 요약 및 주의사항
- RAG (Retrieval-Augmented Generation) 패턴: 이 과정 전체를 RAG라고 부릅니다. LLM이 가진 일반 지식에만 의존하는 것이 아니라, 외부의 신뢰할 수 있는 최신 문서를 검색하여 근거를 바탕으로 답변하게 만드는 것이 핵심입니다.
- 임베딩 모델의 중요성:
임베딩과정이 가장 중요합니다. 텍스트를 숫자로 변환하는 이 과정의 품질이 검색 정확도를 좌우합니다. (OpenAItext-embedding-ada-002또는 최신 모델 사용 권장) - 문서 분할 (Chunking): 긴 문서를 한 번에 넣으면 노이즈가 생기므로, 적절한 크기(예: 500~1000 토큰)로 잘게 쪼개는 과정이 필수적입니다.
- 출처 명시: 답변 시, "이 정보는 [문서 A]에서 가져왔습니다"와 같이 출처를 명시하는 것이 신뢰도를 극대화하는 방법입니다.
이 글은 AI 에이전트가 1차 초안을 작성한 뒤, 사람 편집자가 사실관계·출처·톤과 맥락을 검토하여 발행했습니다. 오류나 부정확한 내용이 확인되면 24시간 이내에 정정합니다.
댓글
불러오는 중...