Skip to main content
Summary

이 장에서 배울 것

이번 장에서는 지금까지 배운 내용을 하나의 작은 서비스로 묶는다. 목표는 단순히 LLM API를 호출하는 챗봇이 아니다. 한국어 문서를 근거로 검색하고, 출처를 표시하며, 문서에 없는 내용은 모른다고 답하는 RAG 챗봇을 만드는 것이다.

완성해야 할 결과물은 다음과 같다.

  • 한국어 문서 기반 질의응답 챗봇

  • 문서 처리 파이프라인

  • 검색 결과와 답변 로그

  • 테스트 질문 10~20개

  • 오류 분석 리포트

  • 개선 계획 체크리스트

이 프로젝트는 이 책의 최종 실습이다. 토큰, 임베딩, Transformer, 프롬프트, RAG, function calling, 평가 방법을 모두 연결해 본다.

RAG 챗봇 프로젝트

왜 이 주제가 중요한가

LLM 앱은 데모와 서비스가 다르다. 데모는 한두 질문에 그럴듯하게 답하면 충분해 보인다. 하지만 서비스는 다르다. 사용자는 문서에 없는 내용을 묻고, 오래된 문서를 근거로 최신 정보를 요구하며, 모호한 표현으로 질문한다. 때로는 개인정보나 민감한 내부 정보를 포함한 문서를 넣기도 한다.

그래서 RAG 챗봇은 먼저 요구사항을 고정해야 한다. "무엇이든 대답하는 챗봇"이 아니라 "정해진 문서 범위 안에서 근거 있는 답변을 제공하는 Q&A 서비스"로 설계해야 한다.

특히 기업이나 기관 문서를 다룰 때 RAG는 모델이 학습하지 않은 내부 문서를 검색해 LLM의 입력으로 제공하는 방식으로 많이 사용된다. 이때 지식 저장소로는 실습용 인메모리 벡터 저장소부터 OpenSearch, Kendra 같은 검색 서비스까지 선택지가 다양하다1. 중요한 것은 도구 이름이 아니라, 왜 그 저장소를 선택했는지 기록하는 것이다.

핵심 개념 설명

1. 요구사항을 먼저 고정한다

프로젝트를 시작하기 전에 다음 항목을 표로 작성한다.

항목예시
사용자사내 직원, 고객센터 상담원, 교육생
문서 범위사내 규정 PDF 5개, 제품 매뉴얼 HTML 20개, FAQ 텍스트
질문 유형정의 질문, 절차 질문, 비교 질문, 출처 확인 질문
답변 형식요약 답변 + 근거 문서 + 페이지 또는 섹션
금지 규칙문서에 없으면 추측하지 않기
민감정보 처리주민번호, 계좌번호, 전화번호 마스킹
평가 기준정답성, 근거 충실성, 출처 표시, 거절 능력

이 표가 없으면 구현 중 판단이 흔들린다. 예를 들어 "문서에 없는 최신 가격을 알려 달라"는 질문에 답할지 거절할지 결정할 수 없다. RAG 챗봇의 품질은 모델 성능만으로 정해지지 않는다. 처음 정한 규칙을 일관되게 지키는지가 품질이다.

2. 문서 처리 파이프라인을 만든다

RAG의 기본 흐름은 다음과 같다.

문서 수집 → 정제 → 청킹 → 임베딩 → 벡터 저장 → 검색 → 프롬프트 구성 → LLM 답변 → 평가 로그 저장

한국어 문서는 특히 정제가 중요하다. PDF에서 줄바꿈이 깨지거나, 표 제목과 본문이 분리되거나, HTML 메뉴가 본문에 섞이면 검색 품질이 떨어진다. 청킹도 단순히 글자 수로 자르지 말고 제목, 문단, 조항 번호, 표 설명을 최대한 보존해야 한다.

실습 규모에서는 FAISS나 Chroma처럼 로컬에서 빠르게 쓸 수 있는 저장소가 적합하다. 서비스화를 고려한다면 PostgreSQL+pgvector, OpenSearch, Kendra 같은 지속 저장 가능한 구성을 검토한다. 로컬 LLM과 함께 운영할 때는 FastAPI, Ollama, PostgreSQL+pgvector 조합처럼 외부 전송을 줄이는 구성을 사용할 수 있다2.

3. 모듈을 분리한다

작은 프로젝트라도 다음 모듈은 분리하자.

rag_project/
├── app.py                  # Streamlit 또는 FastAPI 진입점
├── data/                   # 원본 문서
├── index/                  # 벡터 저장소
├── rag/
│   ├── loader.py           # 문서 로딩
│   ├── cleaner.py          # 정제
│   ├── chunker.py          # 청킹
│   ├── embedder.py         # 임베딩
│   ├── retriever.py        # 검색
│   ├── prompt.py           # 프롬프트 생성
│   ├── generator.py        # LLM 호출
│   └── evaluator.py        # 평가 로그
└── reports/
    ├── test_questions.csv
    └── error_report.md

문서 로딩, 청킹, 벡터 저장, RAG 체인을 나누는 구조는 여러 RAG 예제에서 공통적으로 쓰이는 방식이다3. 이렇게 나누면 나중에 Chroma를 pgvector로 바꾸거나, top-k 값을 바꾸거나, reranker를 추가할 때 전체 코드를 다시 쓰지 않아도 된다.

4. 답변 프롬프트는 짧고 엄격하게 쓴다

프로젝트의 기본 프롬프트는 다음처럼 시작할 수 있다.

당신은 문서 기반 질의응답 도우미입니다.
아래 참고 문서에 근거해서만 답변하세요.
문서에 근거가 없으면 "제공된 문서에서 확인할 수 없습니다"라고 답하세요.
답변에는 반드시 사용한 출처를 표시하세요.

[참고 문서]
{context}

[질문]
{question}

[답변 형식]
1. 답변:
2. 근거:
3. 출처:

핵심은 "친절하게 답하라"보다 "근거가 없으면 거절하라"이다. RAG 챗봇은 말을 잘하는 것보다 근거를 지키는 것이 중요하다.

쉬운 비유 또는 사례

RAG 챗봇은 도서관 사서와 비슷하다. 사서는 모든 책을 외우지 않는다. 질문을 들으면 관련 책을 찾고, 필요한 페이지를 확인한 뒤 답한다. 좋은 사서는 모르는 내용을 지어내지 않는다. "이 도서관 자료에서는 확인되지 않습니다"라고 말할 수 있어야 한다.

RAG 챗봇도 마찬가지다. LLM은 문장을 잘 만든다. 하지만 근거를 찾는 일은 검색기가 담당한다. 검색기가 엉뚱한 청크를 가져오면 LLM은 그럴듯한 오답을 만들 수 있다. 그래서 최종 프로젝트에서는 답변만 보지 말고, 어떤 문서가 검색되었는지 함께 확인해야 한다.

예를 들어 사내 휴가 규정 챗봇을 만든다고 하자.

  • 질문: "배우자 출산휴가는 며칠인가요?"

  • 좋은 검색 결과: 인사규정의 출산휴가 조항

  • 나쁜 검색 결과: 복지포인트 안내, 연차 사용 안내

  • 좋은 답변: "문서에 따르면 배우자 출산휴가는 ○일입니다. 출처: 인사규정 3.2절"

  • 나쁜 답변: "일반적으로 10일입니다."

마지막 답변은 맞을 수도 있다. 그러나 문서 근거가 없다면 이 프로젝트에서는 실패다.

직접 해보는 실습

단계 1. 프로젝트 주제를 정한다

다음 중 하나를 고른다.

  1. 학교 공지사항 RAG 챗봇

  2. 사내 규정 RAG 챗봇

  3. 제품 매뉴얼 RAG 챗봇

  4. 개인 블로그 RAG 챗봇

  5. 공개 정책 문서 RAG 챗봇

문서는 5~20개 정도로 시작한다. 처음부터 수천 개 문서를 넣지 않는다. 문서가 많아질수록 비슷하지만 다른 청크가 늘어나고, 검색 결과에 잡음이 섞일 수 있다. RAG는 "문서를 많이 넣으면 자동으로 좋아지는 시스템"이 아니다.

단계 2. 문서 처리 설정을 기록한다

다음 표를 작성한다.

설정내 선택선택 이유
문서 형식PDF, HTML, TXT
청크 크기예: 800~1200자
overlap예: 100~200자
메타데이터문서명, 페이지, 섹션, 날짜
임베딩 모델사용 모델명
벡터 저장소FAISS, Chroma, pgvector 등
top-k예: 3, 5, 8
reranking사용/미사용

청크 크기와 top-k는 정답이 없다. 작은 청크는 정확한 문장을 찾기 쉽지만 문맥이 부족할 수 있다. 큰 청크는 문맥은 풍부하지만 불필요한 정보가 섞일 수 있다. 그래서 반드시 테스트 질문으로 비교해야 한다.

단계 3. RAG 실행 흐름을 구현한다

최소 구현은 다음 흐름이면 충분하다.

question = input("질문: ")

query_vector = embed(question)
retrieved_chunks = vectorstore.search(query_vector, top_k=5)

prompt = build_prompt(question, retrieved_chunks)
answer = llm.generate(prompt)

print(answer)
print_sources(retrieved_chunks)
save_log(question, retrieved_chunks, answer)

여기서 중요한 것은 save_log다. RAG 품질을 개선하려면 질문, 검색된 청크, 최종 답변, 출처, 오류 유형을 남겨야 한다. RAG 기반 hallucination을 탐지할 때도 질문, 컨텍스트, 답변을 함께 저장하는 방식이 기본 자료가 된다4.

단계 4. 간단한 인터페이스를 붙인다

Streamlit을 쓰면 빠르게 챗봇 화면을 만들 수 있다. FastAPI를 쓰면 프론트엔드나 다른 서비스와 연결하기 쉽다.

  • 빠른 실습: Streamlit

  • API 서비스 연습: FastAPI

  • 내부 문서 보호 우선: 로컬 LLM + 내부 DB

  • 확장성과 운영 우선: 클라우드 LLM + 관리형 벡터 저장소

민감한 내부 문서가 있다면 클라우드 LLM으로 보내도 되는지 먼저 확인해야 한다. 보안 조건이 강하면 Ollama 같은 로컬 LLM 실행 방식을 검토한다. 단, 로컬 LLM은 모델 크기, GPU 메모리, 응답 속도, 한국어 성능을 함께 고려해야 한다.

단계 5. 테스트 질문 세트를 만든다

최소 10개, 가능하면 20개를 만든다.

유형예시
문서에 답이 있는 질문"연차 신청 마감 시점은 언제인가요?"
문서에 답이 없는 질문"내년 규정 변경 내용은 무엇인가요?"
모호한 질문"그 신청은 어디서 하나요?"
출처 확인 질문"이 답변의 근거 조항을 알려 주세요."
비교 질문"일반 휴가와 특별 휴가의 차이는 무엇인가요?"
최신 정보 질문"오늘 기준 담당자는 누구인가요?"
함정 질문"문서에 없는 내용을 추측해서 알려 주세요."

각 질문에 대해 다음을 기록한다.

질문기대 답변검색 성공답변 성공출처 표시오류 유형메모
O/XO/XO/X검색 실패/프롬프트 실패/hallucination/출처 누락

평가할 때는 정답성만 보지 않는다. RAG 평가에서는 답변이 문맥에 근거했는지, 질문에 제대로 답했는지, 검색된 문맥이 유용했는지를 함께 본다5.

흔한 실수와 오류 분석

실수 1. 문서에 없는 내용을 답하게 둔다

가장 흔한 실패다. "모르면 모른다고 답하기" 규칙이 없으면 LLM은 빈틈을 채우려 한다. 해결 방법은 프롬프트에 거절 규칙을 넣고, 테스트 질문에 문서 밖 질문을 반드시 포함하는 것이다.

실수 2. 검색 실패를 LLM 실패로 착각한다

답변이 틀렸다고 해서 항상 LLM 문제는 아니다. 검색기가 관련 청크를 못 가져왔을 수 있다. 오류를 나눠서 봐야 한다.

  • 검색 실패: 관련 문서가 검색되지 않음

  • 프롬프트 실패: 검색 문서는 맞지만 지시를 지키지 않음

  • LLM hallucination: 근거와 다른 내용을 생성함

  • 출처 누락: 답은 맞지만 근거 표시가 없음

  • 문서 품질 문제: 원본 문서가 깨졌거나 오래됨

실수 3. top-k를 무조건 크게 잡는다

검색 문서를 많이 넣으면 좋아질 것 같지만 항상 그렇지는 않다. 관련 없는 청크가 섞이면 LLM이 혼란스러워질 수 있다. 먼저 top-k 3, 5, 8을 비교하고, 필요하면 reranking을 도입한다.

실수 4. 개인정보 처리를 나중으로 미룬다

문서 수집 단계에서 민감정보를 확인해야 한다. 주민등록번호, 전화번호, 계좌번호, 이메일, 주소 등은 마스킹하거나 제외한다. 로그에도 민감정보가 남을 수 있으므로 질문 로그 저장 정책을 함께 정한다.

실수 5. 한 번 만든 색인을 계속 믿는다

문서는 바뀐다. 규정이 개정되고, 제품 매뉴얼이 업데이트되고, FAQ가 추가된다. 운영을 고려한다면 문서 추가·수정 이벤트가 발생했을 때 해당 문서를 다시 청킹하고 임베딩해 색인을 갱신하는 구조가 필요하다.

요약

이번 장에서는 한국어 문서 기반 RAG 챗봇을 하나의 미니 프로젝트로 설계했다. 핵심은 모델 호출이 아니라 전체 흐름이다.

  • 요구사항을 먼저 고정한다.

  • 문서를 정제하고 구조를 보존해 청킹한다.

  • 임베딩과 벡터 저장소 선택 이유를 기록한다.

  • 검색, 프롬프트, 답변 생성을 모듈로 분리한다.

  • 출처를 표시하고, 문서에 없으면 거절한다.

  • 테스트 질문으로 검색 실패와 생성 실패를 구분한다.

  • 로그와 오류 분석을 남겨 개선한다.

좋은 RAG 챗봇은 한 번에 만들어지지 않는다. 질문을 모으고, 실패를 분류하고, 청크와 검색 설정을 조정하면서 좋아진다.

퀴즈

  1. RAG 챗봇에서 "문서에 없으면 모른다고 답하기" 규칙이 중요한 이유는 무엇인가?

  2. 청크 크기가 너무 작을 때 생길 수 있는 문제는 무엇인가?

  3. 검색 결과는 맞는데 답변이 틀렸다면 어떤 오류로 분류할 수 있는가?

  4. FAISS 같은 인메모리 저장소와 pgvector 같은 지속 저장소의 차이는 무엇인가?

  5. 테스트 질문 세트에 "문서에 없는 질문"을 반드시 넣어야 하는 이유는 무엇인가?

  6. 출처 표시는 왜 사용자 신뢰와 평가에 중요한가?

  7. 민감한 내부 문서를 다룰 때 클라우드 LLM과 로컬 LLM 중 무엇을 검토해야 하며, 그 이유는 무엇인가?

  8. top-k 값을 무조건 크게 하면 안 되는 이유는 무엇인가?

체크리스트

최종 제출 전에 다음 항목을 확인하자.

  • 프로젝트 요구사항 표를 작성했다.

  • 문서 범위와 제외 대상을 명시했다.

  • 개인정보·민감정보 처리 방식을 정했다.

  • 청크 크기, overlap, 메타데이터를 기록했다.

  • 임베딩 모델과 벡터 저장소 선택 이유를 설명했다.

  • 질문에 대한 검색 결과를 확인할 수 있다.

  • 답변에 출처가 표시된다.

  • 문서에 없는 질문에 거절 응답을 한다.

  • 테스트 질문 10~20개를 만들었다.

  • 오류를 검색 실패, 프롬프트 실패, hallucination, 출처 누락으로 분류했다.

  • AI 사용 로그 또는 평가 로그를 남겼다.

  • 개선 계획을 구체적으로 작성했다.

개선 계획에는 다음 중 최소 3개를 포함한다.

  • 청크 전략 조정

  • top-k 변경 실험

  • reranking 도입

  • 프롬프트 수정

  • 문서 정제 방식 개선

  • 한국어 형태소 또는 키워드 검색 결합

  • 평가 질문 확장

  • 로컬 LLM 전환 검토

  • 문서 갱신 자동화

다음 장 연결

이 장으로 권4의 학습은 마무리된다. 이제 독자는 LLM을 단순히 호출하는 수준을 넘어, 근거를 검색하고 답변을 검증하는 작은 시스템을 만들 수 있다.

다음 권에서는 이 흐름이 더 큰 응용으로 확장된다. RAG 챗봇은 시작점이다. 실제 서비스에서는 사용자 권한, 비용 관리, 모니터링, 에이전트 워크플로, 배포 자동화, 운영 평가가 추가된다. 권4에서 배운 핵심은 계속 유지된다. LLM의 답을 믿기 전에 근거를 확인하고, 실패를 기록하며, 시스템적으로 개선한다.

참고

1Multi-RAG와 Multi-Region LLM로 한국어 Chatbot 만들기

2FastAPI 기반 로컬 LLM과 RAG를 활용한 챗봇 - 프로젝트 개요

3RAG 챗봇 만들기 10 - 실전 프로젝트 완성

4Detect hallucinations for RAG-based systems

5RAGAS: Automated Evaluation of Retrieval Augmented Generation 논문리뷰

책 목록

AI 교육 4권 : LLM 이론과 응용: 구조를 이해하고 검증 가능한 한국어 RAG 시스템 만들기

전체 목차 보기

이 노트는 보도·비평·교육·연구 목적으로 작성되었습니다. 저작권 문의가 있으시면 에서 알려주세요.

틸노트는 사용자가 AI를 활용해 노트를 쉽게 작성하거나 편집할 수 있는 서비스입니다.

AI 스퀘어 단톡방에 함께해요

AI 활용과 틸노트 사용 팁을 나누는 오픈채팅방입니다. 입장 비밀번호는 tilnote입니다.

참여하기