송영숙 /ML research · 정책총괄
- RAG을 사용하는 이유와 RAG의 태생적 한계
- RAG(Retrieval-Augmented Generation) 과정
- 0. Colab 환경설정
- 1. PDF에서 텍스트 추출
- 2. 텍스트를 Chunking 단위로 나누기
- 3. Chunk 임베딩 & 벡터스토어 구축
- 4. 질의(Query) 시, 관련 문서 Chunk 검색
- 5. LLM(예: OpenAI GPT)에게 답변 생성시키기
- 정리
- 전체 코드
이번 아티클에서는 쉽게 개념과 코드를 익히는 연재 2편으로 기계학습과 딥러닝, 그리고 언어 모델에 대한 개관에 이어 RAG(Retrieval-Augmented Generation)를 다룹니다. RAG는 질의응답 시스템 구축과 문서 요약, 정보 추출, 지식베이스 검색에 활용되고 있는 주요 자연어처리 기술의 하나입니다.
RAG 기술이 이렇게 각광받게 된 이유는 여러가지가 있지만 그 중에 하나가 우리가 자주 사용하는 초거대 언어 모델이 최근의 지식을 반영하고 있지 못하다는 한계 때문입니다. 가령, GPT-4 Turbo는 2023년 4월까지의 정보를 학습한 것으로 알려져 있습니다. 따라서 최근의 지식이나 학습하지 못한 특정 지식을 입력하기 위한 튜닝 방법 중에 하나로 RAG가 쓰이고 있습니다. 가령 GPT가 GPT가 머릿속에 엄청 많은 지식이 들어 있지만 더 이상 최근 나온 새로 출간된 책을 받지 않고 있는 도서관 같다고 할 때 여기에 원하는 신간을 골라서 입력할 수 있는 기능이 RAG라고 할 수 있겠습니다.
RAG(Retrieval-Augmented Generation)에서 신간을 골라서 입력하는 방법은 이름에서 알 수 있는 것처럼 검색 모듈과 생성 모델을 결합해서 제공하는 것입니다. 따라서 의료나 세무, 최근에 변경된 법률과 같이 특정한 정보를 입력할 수 있습니다. 따라서 RAG에서 중요한 것은 어떤 방식으로 문서를 검색할지(Retriever), 검색된 결과를 어떻게 활용해 문맥을 형성할지(프롬프트 엔지니어링), 그리고 최종 생성 결과에 대한 근거를 어떻게 설명할지(Explainability) 에 대한 설계라고 할 수 있습니다.
RAG을 사용하는 이유와 RAG의 태생적 한계
RAG(Retrieval-Augmented Generation)을 사용하는 주된 이유는 다음과 같습니다.
- 내가 가지고 있는 문서 처리
- 정확도 및 신뢰도 향상
- 모델 업데이트 비용 절감
- 세분화된 제어
장점 : 일반적인 언어 모델(LLM)은 사전에 학습된 파라미터에만 의존하기 때문에, 학습 이후에 새로 생긴 정보나 특정 도메인/분야의 상세 지식에 대해서는 정확한 답변을 주기 어려울 수 있습니다. RAG는 검색 단계에서 최신 혹은 특정 도메인 문서를 참조함으로써, 모델이 자체적으로 갖고 있지 않은 지식에도 답할 수 있다는 장점이 있습니다.
한계 : 서로 다른 가치관이 반영되어 있는 문서와 같이 문서가 포함되어 있는 정보에 따라 품질의 차이가 있을 수 있고 대규모 문서의 경우 상당한 규모의 컴퓨팅 리소스를 필요로 할 수 있습니다.
장점 : LLM은 확률적으로 답변을 생성하기 때문에, 실제로는 없는 내용을 “할루시네이션(hallucination)” 형태로 만들어낼 가능성이 있습니다. RAG는 검색으로 얻은 실제 문서 내용을 답변 생성 과정에 포함시키면서, 정확도 및 신뢰도를 높일 수 있습니다.
한계 : RAG 역시 검색된 문서와 질문 간의 의미적 연관성을 정확하게 파악하지 못할 경우, 관련성이 낮은 정보를 기반으로 답변을 생성할 수 있습니다. 따라서 아직, 정확도를 높이기 위한 실험적인 연구들이 진행되고 있습니다. 이에 대해서는 다음 아티클에서 좀 더 다루도록 하겠습니다.
장점 : 새로운 정보를 반영하기 위해 모델을 재학습하는 파인튜닝에 비해 RAG에서는 벡터 DB(예: FAISS) 등의 검색 인덱스에 새로운 문서를 추가해주는 것만으로 최신 정보를 얻어올 수 있기 때문에, 모델 자체를 계속 업데이트할 필요가 줄어들고 그만큼 경제적이라고 할 수 있습니다.
한계 : 벡터 DB의 크기가 증가할수록 검색 시간과 저장 공간이 증가하며, 이는 시스템의 응답 속도에 영향을 미칠 수 있습니다.
문서의 양이 매우 많아질 경우, 효율적인 인덱스 관리와 업데이트가 필요하고 그만큼 비용 부담이 증가합니다.
장점 : RAG 파이프라인에서 검색할 문서나 데이터베이스를 교체하거나 추가·삭제하는 것만으로, 특정 도메인 지식을 더 강조하거나 빼는 등 응답 범위를 세밀하게 제어하기가 쉽습니다.
한계 : 좁은 범위의 문서만을 참조하게 하고 일반적인 추론 능력을 발휘하지 못하게 하는 것이 모든 사용자 질문에 유용한 것은 아닐 수 있습니다.
정리하면, RAG는 LLM의 언어 생성 능력과 외부 지식 검색(Retrieval) 기능을 결합함으로써 정확도를 높이고, 최신 상태의 정보를 업데이트하기 수월하다는 측면에서 많이 사용되고 있습니다. 하지만 아직 완성된 기술은 아니기 때문에 한계의 극복 여부에 따라 발전 가능성이 높은 기술이라고 할 수 있겠습니다.
RAG(Retrieval-Augmented Generation) 과정
RAG(Retrieval-Augmented Generation) 과정을 두 가지 주요 단계로 나누어 설명드리겠습니다.
- 문서 수집 및 저장 단계
- PDF 문서를 텍스트로 변환합니다
- 변환된 텍스트를 의미 있는 단위로 분할(Chunking)합니다
- SentenceTransformer를 사용하여 각 텍스트 조각을 벡터로 변환(임베딩)합니다
- 생성된 벡터들을 FAISS를 사용해 효율적으로 저장하고 검색할 수 있게 인덱싱합니다
벡터 DB에는 FAISS 외에도 여러 선택지들이 있습니다. FAISS부터 순서대로 특징을 살펴보면 다음과 같습니다.
○ FAISS: Facebook AI Research에서 개발한 라이브러리로, 평가 코드와 매개변수 조정을 지원합니다. 가장 가까운 이웃부터 k번째로 가까운 이웃까지 검색할 수 있으며, 이는 실습에서 자세히 다룰 예정입니다.
○ Milvus: 분산 아키텍처를 지원하며, 유사성 검색과 벡터 검색 및 분석을 위한 다양한 API를 제공합니다.
○ Pinecone: 클라우드에서 사용하기 쉬운 벡터 데이터베이스로,완전 관리형 서비스이기 때문에 모니터링 기능 등을 제공합니다. 실시간 데이터 업데이트, 추가, 수정, 삭제 작업을 지원합니다.
○ Weaviate: 시맨틱 검색에 최적화된 벡터 데이터베이스로, GraphQL API를 제공하며 스키마 기반 구조를 갖추고 있습니다.
○ Qdrant: Rust로 작성된 고성능 벡터 데이터베이스로, 무료로 사용 가능한 영구 클러스터를 지원합니다.
○ Chroma: 가볍고 로컬에서 사용 가능한 임베딩용 오픈소스 데이터베이스로, LangChain과 LlamaIndex를 지원합니다.
○ pgvector: PostgreSQL의 확장 기능으로, 관계형 데이터베이스 기능과 벡터 검색을 함께 사용할 수 있습니다.
○ Elasticsearch with Vector Data Plugin: 분산 검색과 분석을 위한 플러그인을 제공합니다.
○ Couchbase: 사용자별 접근 권한 설정이 가능하며, 다국어와 SQL++ 쿼리를 지원합니다.
위에서 나열된 벡터 DB의 선택지를 벡터 전용 데이터 베이스(좌상)인가 아니면 벡터 검색 지원 데이터 베이스(우상)인가에 따라 나누고 이를 오픈소스로 쓸 수 있는가(좌하), 상업적 사용이 가능한가(우하)로 나누면 다음 이미지와 같습니다.
- 질의 처리 단계 (QueryProcessing)
- 사용자가 질문을 입력합니다
- 입력된 질문을 벡터로 변환(임베딩)합니다
- 벡터 DB(여기서는 FAISS)를 사용해 질문 벡터와 가장 유사한 문서 벡터들을 검색합니다
- 검색된 관련 정보들을 추출합니다
- OpenAI GPT 등의 LLM을 사용해 추출된 정보를 바탕으로 최종 답변을 생성합니다
이를 도식화하면 다음 그림과 같습니다.
전체 과정을 코드와 함께 좀 더 구체적으로 살펴 보겠습니다. 이번 아티클에서는 RAG의 개념을 이해하기 위한 단순한 형태의 RAG를 다룹니다.
0. Colab 환경설정
- 런타임 유형을 “GPU”로 설정(필요 시).
- 필요한 패키지를 설치합니다.
- PyPDF2: PDF에서 텍스트 추출
- transformers / sentence_transformers: 텍스트 임베딩(embedding) 및 모델 로드
- faiss: 벡터 검색 라이브러리
- openai: OpenAI GPT API를 사용하기 위함
!pip install PyPDF2 # PDF 읽기, 쓰기, 병합, 분할, 텍스트 추출 등 PDF 파일 처리를 위한 라이브러리
!pip install transformers # 다양한 사전학습 모델 제공
!pip install sentence_transformers # BERT, RoBERTa 등의 모델 지원
!pip install faiss-cpu # or faiss-gpu (GPU가용 시) # 벡터 유사도 검색 라이브러리
!pip install openai # GPT 모델 등 활용 가능하도록 하는 패키
1. PDF에서 텍스트 추출
자신의 컴퓨터에 있는 pdf를 업로드하여 실습에 사용하는 것으로 가정하고 파일 경로를 설정하였습니다.
from google.colab import files
import PyPDF2 # PDF 파일을 다루기 위한 라이브러리
import re # 정규표현식을 사용하기 위한 라이브러리
# 1) Colab에서 파일 업로드
uploaded = files.upload()
# 2) 업로드된 파일 이름 가져오기
pdf_filename = list(uploaded.keys())[0]
print("업로드된 파일 이름:", pdf_filename)
# 3) PDF 파일을 사용할 이름으로 저장
pdf_path = pdf_filename # 원하는 경우 다른 이름으로 바꿀 수 있음
with open(pdf_path, 'wb') as f:
f.write(uploaded[pdf_filename])
print("파일이 성공적으로 업로드 & 저장되었습니다:", pdf_path)
# 4) PDF -> 텍스트 변환 함수
def pdf_to_text(pdf_path): # PDF 텍스트 추출 함수 정의
text = "" # 추출된 텍스트를 저장할 변수
with open(pdf_path, 'rb') as f: # PDF를 바이너리 읽기 모드('rb')로 열기
# 페이지별 텍스트 추출 및 처리
reader = PyPDF2.PdfReader(f) # PDF 리더 객체 생성
for page_num in range(len(reader.pages)): # 모든 페이지를 순회
page = reader.pages[page_num] # 현재 페이지 가져오기
page_text = page.extract_text() # 페이지에서 텍스트 추출
if page_text: # 텍스트가 있는 경우에만 처리
# 연속된 공백문자들(\s+)을 하나의 공백으로 치환
page_text = re.sub(r'\s+', ' ', page_text)
text += page_text + "\n" # 처리된 텍스트를 누적하고 줄바꿈 추가
return text
document_text = pdf_to_text(pdf_path) # 함수 실행
print("총 텍스트 길이:", len(document_text)) # 추출된 전체 텍스트의 길이 출력
# 필요 시 일부 내용 출력
print("앞부분 미리보기:\n", document_text[:500])
출력
파일이 성공적으로 업로드 & 저장되었습니다: 상호 참조 데이터 세트를 활용한 GPT-4 (2).pdf
총 텍스트 길이: 9661
앞부분 미리보기:
제36회 한글 및 한국어 정보처리 학술대회 논문집 (2024년) 1.서론 본 연구에서는 GPT-4를 사용하여 한국어 상호 참조 성능을 평가하는 것으로 목표로 한다. 이를 위해 국립국어원(2021)의 상호 참조 해결 말뭉치 2020[4]에 추가 주석을 실시하고, 질문 유사성에 따른 답변의 상관 관계를 파악할 것이다. 또한 동일한 질문
- pdf_to_text 함수는 PDF 파일에서 모든 페이지의 텍스트를 순서대로 합칩니다.
- 필요에 따라
re.sub()
부분에서 전처리를 추가(예: 숫자, 특수문자 처리 등)할 수 있습니다.
2. 텍스트를 Chunking 단위로 나누기
이 아트클에서는 추가 작업 없이 텍스트를 일정 길이로 분할한 후 이 Chunk(조각)들을 그대로 저장/검색하는 방법을 사용하겠습니다.
Chunk 크기는 보통 문장 단위 또는 토큰(문자 수) 단위로 나누는데, 아래 예시에선 간단히 토큰(문자 수) 단위로 나누겠습니다. 매개변수는 text, chunk_size, overlap 을 사용합니다.
text
: 분할할 텍스트chunk_size
: 매개변수로 각 청크의 크기를 지정(기본값: 500자)할 수 있습니다.overlap
: 매개변수로 청크 간 겹치는 부분을 설정 (기본값: 50자)하여 문맥 단절을 방지합니다.
def chunk_text(text, chunk_size=500, overlap=50):
"""
text를 chunk_size만큼 잘라낸 리스트를 반환.
overlap: 이전 chunk와의 겹침을 통해 문맥 유실을 일부 방지.
"""
chunks = [] # 청크들을 저장할 리스트
start = 0 # 시작 위치
while start < len(text): # 텍스트 끝까지 반복
end = start + chunk_size # 청크의 끝 위치 계산
chunk = text[start:end] # 텍스트 조각 추출
chunks.append(chunk.strip()) # 앞뒤 공백을 제거하고 리스트에 추가
start += (chunk_size - overlap) # 다음 시작 위치 계산 (겹침 고려)
return chunks
# 함수 실행 및 결과 확인
chunks = chunk_text(document_text, chunk_size=500, overlap=50)
print("총 생성된 청크 수:", len(chunks))
print("예시 청크:", chunks[0][:300], "...")
출력
총 생성된 청크 수: 22
예시 청크: 제36회 한글 및 한국어 정보처리 학술대회 논문집 (2024년) 1.서론 본 연구에서는 GPT-4를 사용하여 한국어 상호 참조 성능을 평가하는 것으로 목표로 한다.
- chunk_size와 overlap는 실험을 통해 적절한 값을 찾는 것이 좋습니다(예: chunk_size=500~1500 정도).
- 이 아티클에서는 단순하게 chunk로 문장을 나누었지만, 문장 경계가 잘릴 수도 있으므로, 문장 단위로 나누는 방법도 있습니다.
3. Chunk 임베딩 & 벡터스토어 구축
문서 청크를 임베딩하고, 검색을 빠르게 수행하기 위해 FAISS를 사용합니다.
여기서는 Hugging Face의 sentence-transformers
를 사용해 문장을 벡터화하겠습니다.
# 필요한 라이브러리 임포트
from sentence_transformers import SentenceTransformer # 문장을 벡터로 변환하는 라이브러리
import faiss # Facebook AI가 개발한 효율적인 벡터 검색 라이브러리
import numpy as np # 수치 연산을 위한 라이브러리
# 1) 임베딩 모델 로드 (경량화된 다국어 문장 임베딩 모델(all-MiniLM-L6-v2) 사용, 원하는 모델로 교체 가능)
embedder = SentenceTransformer('sentence-transformers/all-MiniLM-L6-v2')
# 2) encode()로 각 청크를 벡터화
chunk_embeddings = embedder.encode(chunks, convert_to_numpy=True) # 결과를 numpy 배열로 반환
# 3) FAISS index 생성
dimension = chunk_embeddings.shape[1] # 벡터의 차원 수 확인
faiss_index = faiss.IndexFlatL2(dimension) # 유클리드 거리(L2 거리 기반)를 사용하는 기본 인덱스, dimension: 벡터의 차원 수 (이 경우 384)
faiss_index.add(chunk_embeddings) # add(): 벡터들을 인덱스에 추가
print("FAISS Index size:", faiss_index.ntotal) # ntotal: 인덱스에 저장된 총 벡터의 수 출력
출력
FAISS Index size: 22
이제 faiss_index
에 모든 청크의 임베딩이 저장되었으므로 사용자가 어떤 쿼리를 입력하면, 임베딩 후 FAISS를 통해 유사도(또는 거리)가 가장 높은 N개의 청크를 찾을 수 있습니다.
임베딩 모델은 sentence-transformers/all-MiniLM-L6-v2를 사용했지만 다음과 같이 한국어를 지원하는 모델들로 변경하면서 성능을 확인해 볼 수 있을 것입니다.
- sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2
- jhgan/ko-sroberta-multitask
- snunlp/KR-SBERT-V40K-klueNLI-augSTS
4. 질의(Query) 시, 관련 문서 Chunk 검색
사용자의 query(질문 텍스트)를 입력하면, 임베딩 모델을 통해 질문을 벡터로 변환한 후 FAISS에서 유사도가 가장 높은 상위 k개를 검색합니다. 즉, 벡터 공간에서 가장 가까운 문서 조각들 검색하고 찾은 조각들을 원본 텍스트로 변환하여 반환하는 것입니다.
def retrieve_relevant_chunks(query, k=3): # k: 반환할 관련 청크의 개수 (기본값: 3)
# 1) 쿼리 임베딩
query_emb = embedder.encode([query], convert_to_numpy=True)
# 2) FAISS로 유사 청크 검색
distances, indices = faiss_index.search(query_emb, k)
# 3) 유사도가 높은 상위 k개 청크 추출
relevant_chunks = [chunks[i] for i in indices[0]] # indices[0]: 첫 번째(유일한) 쿼리의 결과 인덱스들
return relevant_chunks
# 예시 쿼리
test_query = "이 문서에서 소개된 핵심 주제는 무엇인가요?"
retrieved = retrieve_relevant_chunks(test_query, k=3)
for i, c in enumerate(retrieved):
print(f"Chunk #{i}:\n{c}\n---\n")
출력
Chunk #0:
번 질문인 ‘주어진 [TEXT]에서 [MENTION2]은 [MENTION1]로 교체 가능성 (can be replaced by)이 있는가?’와 같이 명확한 용어를 사용하는 것이 다른 질문과의 상관 관계가 적고 고유한 특성이 있는 질문으로 파악했다고 할 수 있다. 4.3.일치도에 영향을미친요인분석 일치도에 영향을 미치는 요인으로는 교착어라는 한국어의 특성을 고려하여 조사를 동반하는가 여부와 명사구 사이에 몇 음절이 오는가에 따라 분석했다. 즉, 텍스트에서 명사구 사이의 거리가 일치도에 영향을 미치는지를 파악하였다. 먼저 조사의 경우는 격조사와 보조사 그리고 조사가 없는 경우로 나누었다. 조사 유무에 따른 결과는 다음 <표 4>와 같다. <표 4> 조사 유무에 따른 Yes/No’로 답한 횟수‘Yes’로 답한횟수행레이블 합계 :개수 합 0격조사 511없음 61격조사 39없음 6 2격조사 2 11 보조사 1 없음 8 3격조사 45없음 1 4격조사 5 16 보조사 2 없음 9 5격조사 53
---
Chunk #1:
주어진 [TEXT]에서 [MENTION2]은 [MENTION1]을 나타낸다고 할 수 있는가? 4. 주어진 [TEXT]에서 [MENTION2]은 [MENTION1]로 교체 가능성 (can be replaced by)이 있는가? 5. 주어진 [TEXT]에서 [MENTION2]은 [MENTION1]와 같은 의미로 해석되는가?1 위의 질문들을 통해 GPT-4의 질문 유형에 대한 민감성을 판단할 수 있을 것이라고 판단했다. 두 번째로 같은 질문을 다섯 번 반복하여 답변 일관성을 확인했다. 마지막으로 예시가 없이 질문만을 제시(0-shot)한 경우와 5개의 예시를 주고 응답(5-shot)하게 한 경우의 성능 평가 결과로 구분하여 실험했다. 4.평가 4.1.일치도평가 실험은 다음 <표 2>와 같이 각각의 번호는 해당 문장에 5가지 질문을 하고 GPT-4가 ‘Yes’라고 응답한 횟수를 세는 방식으로 이루어졌다. <표 2> 프롬프트 후 생성 출력 예시 표 번호질문1질문2질문3질문4질문5‘Yes’로
---
Chunk #2:
을 확인할 수 있었다. 추가 주석을 통해 조사 및 멘션 사이에 오는 음절들이 많을수록 성능 평가에 영향을 미치는지를 살폈는데 평균 56.10음절인 문장 길이에서는 조사의 유무 및 음절 사이의 거리가 성능과 상관 관계에 있다고 판단하기는 어려웠다.상호 참조 해결은 자연어 이해의 핵심 과제 중 하나로, 텍스트의 의미를 정확히 파악하는 데 필수적이며 멀티턴 대화(Multi-turn Conversation)의 정확성을 판가름하는 기준이 된다는 점에서 정량적으로 초거대 언어 모델의 다면적 성능을 평가한 본 연구의 의의가 있다고 하겠다. 주제어: 맨션, 상호 참조, GPT-4, 언어 모델, 성능 평가 - 335 -
제36회 한글 및 한국어 정보처리 학술대회 논문집 (2024년) 검수하였다. 작업 후에는 멘션들 사이에 조사의 유무 및 띄어쓰기를 포함한 멘션 쌍 사이의 음절 개수 등을 측정하였다. 최종 구축된 데이터 세트의 예시는 다음 <표 1>과 같다. <표 1> 데이터 주석 예시
---
distances
: query와 각 chunk 간의 거리(작을수록 가깝습니다.)indices
: 가까운 chunk들의 인덱스- 필요한 경우 distance 점수도 확인하여 필터링할 수 있습니다.
5. LLM(예: OpenAI GPT)에게 답변 생성시키기
OpenAI GPT 모델을 사용한다고 가정하겠습니다.
나이브 RAG에서는 검색된 청크들을 단순 연결(Concatenate)하여 Prompt로 넣는 방식을 자주 씁니다.
실제 구현 시에는 Prompt가 너무 길어지지 않도록 주의해야 합니다.
import openai
# OpenAI API Key 설정
openai.api_key = "sk로 시작하는 본인의 API Key를 입력"
def generate_answer_with_rag(query, k=3, model="gpt-3.5-turbo"):
# 1) 검색된 청크 획득
relevant_chunks = retrieve_relevant_chunks(query, k=k)
context_text = "\n".join(relevant_chunks)
# 2) 프롬프트 구성 (naive: 단순 concatenation)
system_prompt = (
"You are a helpful assistant. "
"Use the following context to answer the question. "
"If the context does not contain the answer, say you don't know."
)
user_prompt = f"Context:\n{context_text}\n\nQuestion: {query}"
# 3) ChatCompletion API 호출
response = openai.ChatCompletion.create(
model=model,
messages=[
{"role": "system", "content": system_prompt},
{"role": "user", "content": user_prompt},
],
max_tokens=512,
temperature=0.3
)
return response["choices"][0]["message"]["content"]
# 실제 답변 예시
final_answer = generate_answer_with_rag("이 문서에서 소개된 핵심 주제는 무엇인가요?", k=3)
print(final_answer)
출력
이 문서에서 소개된 핵심 주제는 상호 참조 해결과 관련하여 GPT-4와 같은 초거대 언어 모델의 성능 평가입니다. 특히, 질문 유형에 대한 민감성, 답변 일관성, 그리고 조사 및 명사구 사이의 음절 수가 성능에 미치는 영향을 분석하고 있습니다.
- System Prompt에서 “답이 없으면 모른다고 말하라”는 식으로 지시할 수도 있습니다.
- max_tokens와 temperature 등의 파라미터는 적절히 조정할 수 있습니다.
- 실제론 Chunk가 길고 여러 개이면, Prompt Token Limit을 넘지 않도록 2~3개의 Chunk만 선택하거나, 요약(summarization)을 거친 뒤 연결하는 방법을 사용하기도 합니다.
RAG 기반 질의응답을 받을 수 있도록 전문의가 5년치 상담 데이터를 입력한다면 고객들은 의사를 방문하기 전에 자신의 증상을 입력하는 사전 문진 단계를 집에서도 입력할 수 있고 의사는 문잔표와 이전 데이터를 바탕으로 비슷한 사례에 대한 상담 경험을 요약적이고 체계적으로 제공받을 수 있게 됩니다. 또한 즉시 의문에 대한 추가 답변을 받을 수 있고, 그 답변은 그들의 특정 질문에 맞춤화되어 있을 것입니다. 의료 뿐만이 아니라 금융에서도 비슷하게 분석가들이 시장 보고서, 뉴스 기사, 기업 공시 자료 같은 데이터로부터 통찰을 얻을 수 있을 것입니다. 즉, RAG는 AI의 잠재력과 산업에서의 실질적이고 구체적인 필요 사이의 간극을 메우는 역할을 한다고 할 수 있습니다.
정리
- PDF 텍스트 추출:
PyPDF2
등으로 PDF에서 텍스트를 가져옵니다. - Chunking: 텍스트를 일정 분량으로 나누어 잘게 저장합니다.
- 임베딩 & 벡터 DB 구축:
sentence-transformers
+FAISS
등을 사용해 각 청크를 벡터화하여 검색이 빠른 인덱스를 만듭니다. - 질의 입력 -> 유사도 기반 검색: 사용자의 쿼리를 동일 모델로 임베딩 후, 벡터 DB에서 관련 청크 상위 k개를 찾습니다.
- 답변 생성(LLM): 검색된 청크를 단순 연결(naive RAG)해 “프롬프트”를 구성하고, OpenAI GPT 등에 넣어 답변을 생성합니다.
위 코드는 가장 단순한 RAG 개념을 시연하기 위한 것이므로, 실제 서비스를 만들 때는 다음과 같은 과정을 추가로 고려할 수 있습니다.
- 청크 최적화를 위해 chunk_size와 overlap 파라미터 조정, 문장 단위 분할, 중복 제거 등의 방법을 고려해야 합니다.
- 청크 처리시에 잘린 문장 처리, 컨텍스트 정리 등의 Prompt 최적화가 필요할 수 있습니다.
- LLM Response Handling(초과 토큰, 예외처리, 비용 관리 등)같은 부분들을 세심하게 관리해야 합니다.
- RAG 시스템의 성능 향상을 위해서는 평가 방법론을 구체화하는 것이 필수적이지만 본 아티클에서는 다루지 못했습니다.
- 대규모 문서 처리시의 병렬화, 에러 처리, 초과 토큰에 대한 대응, 민감 정보 및 데이터 프라이버시 등에 대한 것도 반드시 신경써야할 부분이라고 할 수 있습니다.
전체 코드
https://colab.research.google.com/drive/1Mb8BPpOiQ33EBVtG2rf6jcx5S7LwUVa_?usp=sharing