오늘도 RAG에 대한 자료를 찾아보던 중, KT DS의 김성우 기술혁신단장님께서 공유하신 RAG 관련 자료를 발견했습니다.
KT 사내 직원 RAG 교안이라는 말에 어떤 내용일지 궁금하여, 이번 포스팅에서는 해당 자료를 자세하게 공부하며 내용을 정리해보았으니 함께 살펴보시죠.
본 블로그 포스팅은 "KT DS의 김성우 기술혁신단장님께서 공유해주신 RAG 관련 자료를 바탕으로 작성된 자료입니다.
- 별도의 Reference를 달지 않은 사진 자료는 해당 강의 자료 내용을 발췌한 자료임을 미리 밝힙니다.
판별형 AI
생성형 AI
하이프 사이클(Hype Cycle)
신기술의 대중적 열광 및 채택 단계를 설명하는 가트너의 모델.
주요 단계:
현재 생성형 AI는 과도한 기대의 정점에서 환멸 단계로 진입 중임.
LLM(Large Language Models)이란?
LLM(Large Language Models)의 특징
LLM 한계 극복 전략에서 다루는 주요 요소들은 각각 장단점이 있으며, 특정 상황에 적합하게 활용될 수 있습니다.
프롬프트 엔지니어링
정의
: 프롬프트 엔지니어링은 LLM의 성능을 개선하기 위해 입력 텍스트를 구조화하거나 재구성하는 과정을 말합니다. 이는 소프트웨어적 접근 방식으로, 모델의 가중치를 변경하지 않으면서도 성능을 크게 향상시킬 수 있는 방법입니다.
예시
: Standard Prompting(기본 질의)와 Chain-of-Thought Prompting(사고의 흐름)을 비교해보면, 후자는 복잡한 문제를 단계별로 해결하도록 유도하여 정확한 답변을 도출합니다.
한계
: 도메인 지식이나 복잡한 데이터를 처리하는 데 한계가 있으며, 잘 설계된 프롬프트 없이 결과가 일관적이지 않을 수 있습니다.
RAG 개념
정의
: RAG는 외부 지식베이스(Vector Database)와 LLM을 결합하여 더 강력한 답변을 제공합니다. 검색된 정보를 기반으로 응답을 생성함으로써 최신 데이터 반영이 가능하며, LLM이 자체적으로 처리할 수 없는 지식의 공백을 메웁니다.
장점
:
단점
:
Fine-Tuning
정의
: Fine-Tuning(미세 조정)은 사전 학습된 대규모 언어 모델(LLM)을 특정 도메인이나 작업에 맞게 다시 학습시키는 과정입니다.
예시
: 의료 분야에서 특화된 진단 데이터를 사용하여 Fine-Tuning을 진행하면, 모델이 더 전문적이고 신뢰할 수 있는 의료 관련 답변을 생성할 수 있습니다.
장점
:
단점
:
높은 학습 비용: Fine-Tuning은 모델 전체의 가중치를 학습시키기 때문에, GPU/TPU와 같은 대규모 계산 자원이 필요하며, 학습 시간도 많이 소요됩니다.
유연성 부족: Fine-Tuning된 모델은 특정 데이터나 도메인에 지나치게 최적화되는 경향이 있어, 새로운 데이터나 빠르게 변하는 환경(예: 실시간 뉴스)에 적응하기 어렵습니다.
과적합 위험: 학습 데이터에 지나치게 의존할 경우, Fine-Tuning된 모델이 일반화 능력을 잃어 과적합 문제를 일으킬 수 있습니다.
💌 RAG vs Fine-Tuning
비교 항목 | RAG | Fine-Tuning |
---|---|---|
특징 | 외부 데이터(예: 문서, 데이터베이스)를 검색하여 LLM이 활용하도록 설계. | 모델 자체를 특정 작업에 맞게 조정하여 도메인에 특화된 성능 제공. |
비용 | 비교적 낮음. 검색 인프라 구축에 따라 초기 비용은 발생하나, 모델 재학습 비용 없음. | 데이터 준비 및 모델 재학습에 상당한 비용과 시간이 소요됨. |
적용 가능성 | 자주 업데이트되는 동적 데이터나 실시간 정보에 적합. | 상대적으로 정적이고 구조화된 데이터 환경에서 적합. |
데이터 관리 | 데이터베이스 업데이트를 통해 쉽게 지식 확장 가능. | 데이터셋 생성 및 레이블링이 필요하며, 데이터 업데이트 시 재학습 필요. |
모델 수정 필요성 | 모델 가중치를 수정하지 않음. 외부 데이터베이스를 통한 지식 확장에 의존. | 모델 가중치를 직접 수정하여 특정 작업이나 도메인에 최적화된 모델 생성. |
성능 최적화 | 검색 단계의 품질에 따라 결과가 달라짐. 검색 시스템 개선으로 성능 향상 가능. | 모델 성능은 데이터 품질과 학습 설정에 따라 결정되며, 도메인 특화된 우수한 성능 제공. |
지연 시간 | 검색 및 생성 단계를 거쳐야 하므로 응답 시간이 길어질 수 있음. | 사전 학습된 모델로 바로 응답 가능하여 응답 속도가 빠름. |
유지 관리 | 데이터베이스 업데이트만으로 유지 보수 가능. | 데이터셋의 업데이트 및 모델 재학습이 필요하여 유지 관리 비용이 높음. |
윤리적 고려사항 | 외부 데이터 활용으로 인해 개인 정보 보호 및 데이터 소스 신뢰성이 중요. | 민감한 데이터 포함 시, 모델이 이를 학습하면서 개인정보 문제를 야기할 가능성 존재. |
환각(hallucination) | 검색된 정보에 의존하기 때문에 환각 가능성이 낮음. | 특정 도메인에서 학습되지 않은 경우, 부정확하거나 환각된 정보를 생성할 가능성 존재. |
도메인 적합성 | 다중 도메인 또는 새로운 데이터 환경에 적합. | 특정 도메인에 최적화된 데이터셋으로 높은 전문성 제공. |
구현 난이도 | 검색 시스템과의 통합이 요구되므로 초기 구축이 다소 복잡할 수 있음. | 모델 학습 및 데이터셋 준비 과정이 복잡하며, 계산 리소스 요구량이 높음. |
투명성(Explainability) | 검색된 데이터와 답변을 연결 가능하므로 결과의 추적 및 해석이 용이. | 모델이 내린 결론을 설명하기 어렵고, 블랙박스 특성을 가짐. |
활용 사례 | FAQ 시스템, 최신 뉴스 응답, 고객 지원 등 실시간 정보 제공 서비스에 적합. | 의료, 법률, 과학 연구 등 고도로 특화된 전문 분야에 적합. |
효율적인 Fine-Tuning의 대안
PEFT 개념
정의
: PEFT(Parameter-Efficient Fine-Tuning)
는 기존 Fine-Tuning의 한계를 극복하기 위해 제안된 방법으로, 모델 전체를 조정하지 않고 일부 파라미터만 업데이트하여 학습하는 기술입니다.PEFT 특징 : PEFT의 주요 특징은 다음과 같습니다:
기존 모델 유지
: 사전 학습된 모델의 대부분의 파라미터는 고정된 상태로 유지됩니다.
새로운 파라미터 추가
: 모델에 적은 수의 새로운 학습 가능한 파라미터를 추가합니다.
효율적인 학습
: 추가된 파라미터만 학습하여 계산 비용과 저장 공간을 크게 줄입니다.
성능 유지
: 전체 미세 조정과 비슷한 성능을 달성하면서도 자원 사용을 최소화합니다.
다양한 구현 방법: PEFT는 다음과 같은 방법으로 구현됩니다:
LoRA
(Low-Rank Adaptation):
저랭크 행렬로 분해
하여 학습 가능한 작은 매개변수를 추가.Adapter
:
Feedforward Layer
또는 Bottleneck Layer
로 구성됩니다.Prefix Tuning
:
장점
:
단점
:
💌 Traditional Fine-Tuning vs. Parameter-Efficient Fine-Tuning
복잡성과 비용
" 그리고 "성능
" 측면에서 LLM 접근 방식을 비교하는 지표를 제공합니다.항목 | Fine-Tuning | PEFT |
---|---|---|
학습 파라미터 수 | 모델의 모든 파라미터 업데이트 | 일부 파라미터만 업데이트 |
계산 비용 | 높음 | 낮음 |
유연성 | 특정 도메인에 한정 | 다양한 작업에 빠르게 적용 가능 |
학습 시간 | 오래 걸림 | 비교적 빠름 |
적용 가능한 작업 | 복잡한 도메인 작업 | 적은 데이터로 빠르게 적용해야 하는 작업 |
위 장표를 살펴보면 아래와 같이 해석해볼 수 있습니다.
Complexity of Implementation (구현 복잡성)
Cost of Implementation & Maintenance (구현 및 유지 비용)
위 장표를 살펴보면 아래와 같이 해석해볼 수 있습니다.
Avoiding Hallucinations (할루시네이션(환각) 최소화)
Domain-Specific Terminology (도메인 특화 용어)
Up-to-date Response (최신 응답 제공)
Transparency & Interpretability (투명성 및 해석 가능성)
Agentic Workflow
AI Agentic Workflow는 복잡한 작업을 여러 개의 특화된 에이전트로 분할하여 처리하는 AI 시스템 구조를 말합니다.
이는 단일 AI 모델이 모든 작업을 처리하는 기존 방식과 달리, 각 단계를 전문화된 에이전트가 담당함으로써 더욱 효율적이고 정확한 결과를 도출할 수 있는 접근 방식입니다.
Stanford 앤드류 응 교수는 AI Agentic Workflow
를 다음과 같이 정의하고 있습니다:
LLM 에이전트 패턴
LLM 기반 에이전트의 다양한 설계 방식은 특정 문제를 해결하기 위한 최적의 워크플로우를 제공합니다.
주요 디자인 패턴:
리플렉션(Reflection)
: AI가 자체적으로 작업을 검토하고 개선 방안을 마련합니다.도구 사용(Tool Use)
: AI가 웹 검색, 코드 실행 등의 도구를 활용하여 정보를 수집하고 데이터를 처리합니다.계획(Planning)
: AI가 목표 달성을 위한 다단계 계획을 수립하고 실행합니다.다중 에이전트 협업(Multi-Agent Collaboration)
: 여러 AI 에이전트가 협력하여 작업을 수행합니다.주요 디자인 패턴 상세:
(1) Reflection Pattern
(2) Tool Use Pattern
(3) Planning Pattern
(4) Multi-Agent Collaboration
설치 명령어
pip install langchain\
langchain-community\
langchain-openai\
langchain-huggingface\
langchain-cohere -qU
pip install pypdf faiss-cpu rank-bm25 ragas deepeval -qU
langchain
: LLM 애플리케이션 개발을 위한 프레임워크langchain-community
: 서드파티 통합을 위한 LangChain 패키지langchain-openai
: OpenAI와 LangChain 통합을 위한 패키지langchain-huggingface
: Hugging Face와 LangChain 통합을 위한 패키지langchain-cohere
: Cohere와 LangChain 통합을 위한 패키지pypdf
: PDF 파일을 처리하기 위한 순수 Python 라이브러리faiss-cpu
: 고밀도 벡터의 효율적인 유사성 검색 및 클러스터링을 위한 라이브러리rank-bm25
: BM25 랭킹 함수를 구현한 패키지ragas
: RAG(Retrieval-Augmented Generation) 시스템을 평가하기 위한 프레임워크deepeval
: RAG(Retrieval-Augmented Generation) 파이프라인 평가를 위한 프레임워크pip 명령어의 -qU 옵션은 다음과 같은 의미를 가집니다:
-q
: quiet 모드로, 설치 과정의 출력을 최소화합니다.-U
: --upgrade의 축약형으로, 이미 설치된 패키지가 있다면 최신 버전으로 업그레이드합니다.따라서 -qU 옵션을 사용하면 패키지를 조용히 설치하거나 업그레이드하게 됩니다.
작업 목적: 다양한 형태의 문서를 벡터 데이터베이스에 저장하기 위해 불러옵니다.
코드 예시
입력 예시 : 아래 코드는 PDF를 로딩하는 코드 snippet입니다.
from langchain.document_loaders import PyPDFLoader
# PDF 파일 로딩
loader = PyPDFLoader("sample.pdf")
docs = loader.load()
출력 예시: 로드된 문서의 페이지 수와 메타데이터를 확인.
print(len(docs)) # 문서의 개수
print(docs[0].metadata) # 메타데이터 정보
청킹(Chunking) 방식
큰 Chunk
=> 많은 정보, 많은 노이즈작은 Chunk
=> 작은 정보, 작은 노이즈재귀적 청킹(Recursive Chunking) 방식
텍스트를 계층적으로 분할하여 정보의 맥락과 의미를 보존하는 과정.
단순 청킹 방식에서는 텍스트를 일정 크기로 나누지만, 재귀적 청킹은 텍스트 구조를 이해하여 상위-하위 관계를 유지하며 분할함.
RecursiveCharacterTextSplitter
는 텍스트를 지정된 크기의 청크로 분할하는 LangChain의 유틸리티 클래스입니다.
이를 활용하여 텍스트를 분할한 후 최종 chunk를 정의하는 과정은 다음과 같습니다:
분할 과정:
병합 과정:
최종 chunk 정의:
주요 매개변수
chunk_size
: 각 청크의 최대 문자 수 (여기서는 100)chunk_overlap
: 연속된 청크 간 겹치는 문자 수 (여기서는 20)separators
: 텍스트를 분할할 때 사용할 구분자 목록Separators의 작동 방식
separators 리스트 ["\n\n", "\n", ".", " "]는 우선순위 순서대로 적용됩니다:
\n\n
": 먼저 빈 줄로 구분된 단락을 분리합니다.\n
": 그 다음 줄바꿈으로 구분된 줄을 분리합니다..
": 그 다음 마침표로 구분된 문장을 분리합니다.
": 마지막으로 공백으로 구분된 단어를 분리합니다.코드 예시
입력
from langchain.text_splitter import RecursiveCharacterTextSplitter
# 입력 텍스트
text = """
This is a sample document. It contains multiple paragraphs.
Each paragraph contains multiple sentences.
This is the second sentence in the second paragraph.
Finally, this is the last paragraph.
"""
# RecursiveCharacterTextSplitter 설정
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=100, # 청크 크기 (최대 100자)
chunk_overlap=20, # 청크 간 겹치는 부분 (20자)
separators=["\n\n", "\n", ".", " "] # 분할 기준
)
# 텍스트 분할
chunks = text_splitter.split_text(text)
# 분할된 청크 출력
for i, chunk in enumerate(chunks):
print(f"Chunk {i + 1}:\n{chunk}\n")
출력
Chunk 1:
This is a sample document. It contains multiple paragraphs.
Chunk 2:
Each paragraph contains multiple sentences.
This is the second sentence in the second paragraph.
Chunk 3:
Finally, this is the last paragraph.
설명
첫 번째 청크:
두 번째 청크:
세 번째 청크:
chunk_overlap이 20으로 설정되었지만, 이 예시에서는 각 청크가 100자 미만이어서 겹치는 부분이 생기지 않았습니다.
📋 참고 (Chunking 기법)
텍스트
, 이미지
, 음성
등의 데이터를 고차원의 벡터 공간에 표현하는 기술입니다. 벡터 데이터베이스(Vector DB)
에서 유사도 기반 검색을 가능하게 하는 핵심 과정입니다.Embedding의 역할:
의미적 유사성 포착
: 유사한 의미를 가진 데이터는 벡터 공간에서 서로 가깝게 위치합니다.차원 축소
: 고차원의 복잡한 데이터를 저차원으로 표현하여 효율적인 처리가 가능합니다.유사도 기반 검색
: 벡터 간 거리나 각도를 계산하여 유사한 항목을 빠르게 찾을 수 있습니다.Embedding 예시
이 예시에서는 HuggingFace
와 OpenAI의 임베딩
모델을 사용하는 방법을 보여줍니다.
OpenAI의 임베딩 모델은 강력한 성능을 제공하지만 API 키가 필요하고 사용량에 따른 비용이 발생합니다.
# OpenAI API 사용
from langchain.embeddings import OpenAIEmbeddings
import os
# OpenAI API 키 설정
os.environ["OPENAI_API_KEY"] = "your-api-key-here"
# 임베딩 모델 설정
embedding_model = OpenAIEmbeddings()
embeddings = [embedding_model.embed_query(chunk) for chunk in chunks]
반면 HuggingFace 모델은 무료로 사용할 수 있고 로컬에서 실행 가능하지만, 일반적으로 OpenAI 모델에 비해 성능이 떨어질 수 있습니다.
# HuggingFace 모델 사용:
from langchain_huggingface import HuggingFaceEmbeddings
model_name = "BAAI/bge-m3"
model_kwargs = {"device": "cuda"}
encode_kwargs = {"normalize_embeddings": True}
hf_embeddings = HuggingFaceEmbeddings(
model_name=model_name, model_kwargs=model_kwargs, encode_kwargs=encode_kwargs
)
Embedding 활용 예시
import numpy as np
# 임베딩 차원 확인
print(f"임베딩 차원: {len(embeddings[0])}")
# 임베딩 벡터 간 유사도 계산 (코사인 유사도)
def cosine_similarity(v1, v2):
return np.dot(v1, v2) / (np.linalg.norm(v1) * np.linalg.norm(v2))
similarity = cosine_similarity(embeddings[0], embeddings[1])
print(f"첫 번째와 두 번째 청크의 유사도: {similarity}")
임베딩 과정을 통해 생성된 벡터는 벡터 데이터베이스(Vector DB)에 저장됩니다.
Vector DB는 검색 속도를 높이고, 대규모 데이터셋에서도 빠르게 검색 결과를 반환할 수 있습니다.
벡터 DB 특징
인덱싱(Indexing)
: 효율적인 검색을 위해 벡터를 인덱싱하여 관리합니다.클라우드 네이티브
: Milvus, Pinecone, Weaviate 등 벡터 DB 플랫폼이 클라우드 환경에서 빌리언 스케일 데이터를 다룰 수 있습니다.코드 예시
from langchain.vectorstores import FAISS
# 1. 벡터 DB 생성
vector_db = FAISS.from_documents(documents = split_documents,
embedding = embedding_model)
# 2. 로컬에 저장
vectorstore.save_local('./db/faiss')
# 3. 로컬에서 로드
vectorstore = FAISS.load_local(
'./db/faiss',
embedding_model,
allow_dangerous_deserialization=True
)
📁 (참고) 다양한 유형의 Vector DB
- 아래 각 카테고리 별로 몇가지 사례들을 조사해서 설명을 추가해 두었습니다.
🗃️ 벡터 데이터베이스
- 주로 고차원 벡터 데이터의 저장과 검색에 특화된 데이터베이스로,
검색 효율성
과확장성
을 제공합니다.
- Milvus
- https://milvus.io/
- 다양한 인덱스 타입 지원과 확장 가능한 아키텍처가 특징
- 하드웨어 가속화된 연산 지원과 실시간 데이터 처리 기능 제공
- 10개 이상의 인덱스 타입을 지원하며 GPU 기반 인덱싱도 가능
🗃️ 벡터 라이브러리
- 벡터 데이터베이스에서 사용되는
벡터 임베딩 처리를 위한 라이브러리
.
- Faiss
- https://github.com/facebookresearch/faiss
- Facebook AI(현 Meta)가 개발한 벡터 유사도 검색 라이브러리
- C++로 작성된 코어 라이브러리와 Python 래퍼 제공
- k-평균 클러스터링, 근접 그래프 기반 방식 등 다양한 인덱싱 방법 지원
🗃️ Vector-capable SQL 데이터베이스
- 기존 SQL DB에 벡터 처리 기능을 추가한 시스템.
- Timescale
- https://www.timescale.com/ai
- PostgreSQL 기반의 벡터 검색 기능 제공
- 시계열 데이터에 최적화된 벡터 검색 지원
- DiskANN 알고리즘 기반의 빠른 유사도 검색 제공
🗃️ Vector-capable NoSQL 데이터베이스
- 비정형 데이터를 위한 NoSQL 데이터베이스로 벡터 기능을 추가 제공.
- Cassandra
- LLamaIndex - Cassandra Index Demo
- 5.0 버전부터 벡터 검색 기능 추가
- Storage-Attached Indexing(SAI)를 통한 효율적인 벡터 인덱싱 제공
- 분산 아키텍처를 활용한 대규모 벡터 데이터 처리 가능
(참고) SQL 데이터베이스
vs NoSQL 데이터베이스
SQL 데이터베이스
는 전통적인 관계형 데이터베이스(Relational Database Management Systems, RDBMS)로, 데이터가 테이블 형태로 구조화되어 있습니다.
NoSQL 데이터베이스
는 비관계형 데이터베이스로, 정형 데이터뿐만 아니라 비정형(Unstructured) 데이터와 반정형(Semi-structured) 데이터 처리를 위해 설계되었습니다.
SQL vs NoSQL: 주요 차이점 정리
특징 | SQL | NoSQL |
---|---|---|
데이터 구조 | 고정된 스키마 | 스키마가 유연하거나 없음 |
데이터 관계 | 관계형 데이터 지원 | 비관계형 데이터 처리 |
확장성 | 수직적 확장(더 강력한 서버) | 수평적 확장(노드 추가) |
쿼리 언어 | 표준 SQL 사용 | 다양한 API 및 자체 쿼리 언어 사용 |
트랜잭션 지원 | ACID 속성 보장 | BASE 속성(최종 일관성) 제공 |
처리 데이터 유형 | 정형 데이터 | 비정형 및 반정형 데이터 |
적합한 사용 사례 | 금융, ERP, CRM 등 관계형 데이터 | 소셜 미디어, IoT, 로그 분석 |
🗃️ Text Search Databases
- 텍스트 기반 데이터 검색을 처리하며, 벡터 처리 기능도 점진적으로 도입.
- Elasticsearch
- LLamaIndex - Elasticsearch Index Demo
- Apache Lucene 기반의 오픈소스 전문 검색 엔진
- 실시간 데이터 분석과 시각화 기능 제공
- 구조화/비구조화 데이터에 대한 강력한 전문 검색 기능 제공
검색기(Retriever)는 RAG 시스템에서 사용자의 질문에 대해 적절한 정보를 제공하기 위해 벡터 데이터베이스를 활용해 문서를 검색하는 핵심 모듈입니다.
검색기 생성
과 검색 방식
에 대한 구체적인 설명입니다.코드 예시
retriever = vector_db.as_retriever(search_type="similarity",
search_kwargs={"k": 5})
주요 매개변수
search_type
:
"similarity"
: 유사도 기반 검색."mmr"
: MMR(Maximal Marginal Relevance) 방식."similarity_score_threshold"
: 유사도 점수 임계값을 설정하여 필터링.search_kwargs
:
k
: 반환할 문서 수.fetch_k
: MMR 검색에서 초기 탐색 문서 수.lambda_mult
: MMR 결과에서 유사성과 다양성 간의 균형 조절.검색기는 사용자의 질의(Query)를 벡터로 변환하고, 이를 데이터베이스 내 벡터들과 비교하여 가장 관련성 높은 문서를 반환합니다.
(1) 유사도 검색(Similarity Search)
retriever = vector_db.as_retriever(search_type="similarity",
search_kwargs={"k": 5})
(2) MMR (Maximal Marginal Relevance)
lambda_mult
매개변수를 통해 유사성과 다양성의 가중치를 조정.retriever = vector_db.as_retriever(search_type="mmr",
search_kwargs={"k": 5,
"fetch_k": 10,
"lambda_mult": 0.6}
)
(3) 유사도 점수 임계값 검색(Similarity Score Threshold)
retriever = vector_db.as_retriever(
search_type="similarity_score_threshold",
search_kwargs={"score_threshold": 0.8}
)
앞 장 2.2 검색기
에서 만든 검색기는 아래와 같이 동작을 하게 됩니다.
🤔 하지만 검색기만으로는 최종적인 답변을 생성할 수 없으며, 검색된 문서를 기반으로 질문에 대한 답변을 생성하는 과정이 추가적으로 필요합니다.
=> 이 역할을 수행하는 것이 바로 생성기(Generator)입니다.
- 검색기는 관련 문서를 잘 찾아내지만, 검색된 문서를 그대로 사용자에게 제공하기에는 다음과 같은
한계
가 있습니다:
- 문서가 너무 길거나 질문과 관련 없는 세부 정보 포함 가능.
- 질문의 맥락에 맞는 요약이 필요.
- 사용자에게 최적화된 답변이 요구됨.
- 따라서 생성기는 검색된 문서를 사용해 사용자 질문에 맞는 간결하고 정확한 응답을 생성하는 역할을 합니다.
검색기와 생성기의 연결
검색기와 생성기는 다음과 같은 흐름으로 동작합니다:
질의(Query) → 검색기
:
검색 결과 → 생성기
:
LLM → 응답 생성
:
생성기의 핵심은 검색된 문서를 질문과 함께 언어 모델에 전달하는 방식입니다. 이를 위해 프롬프트를 설계하여 LLM이 환각(Hallucination)
을 줄이고 명확한 답변을 생성
하도록 유도합니다.
프롬프트 예시
prompt = PromptTemplate.from_template(
"""
당신은 질문 답변 작업을 위한 어시스턴트입니다.
제공된 컨텍스트를 활용하여 질문에 답변해 주세요.
답을 모르는 경우에는 모른다고 말씀해 주세요.
한국어로 답변해 주세요.
#질문: {question}
#컨텍스트: {context}
#답변:
"""
)
이 프롬프트는 RAG(Retrieval-Augmented Generation) 시스템에서 사용되는 기본 템플릿으로, 주어진 질문에 대해 검색된 컨텍스트를 바탕으로 한국어로 답변을 생성하도록 설계되어 있습니다.
사용자의 질문(question
)과 검색기를 통해 얻은 관련 문맥(context
)이 각각 {question
}과 {context
} 위치에 삽입됩니다.
question
: 사용자의 질문.context
: 검색기를 통해 반환된 관련 문서.생성기는 LLM을 활용하여 검색된 문서와 질문을 바탕으로 답변을 만듭니다. OpenAI의 GPT-4, Hugging Face 모델 등 다양한 언어 모델을 사용할 수 있습니다.
from langchain.chat_models import ChatOpenAI
llm = ChatOpenAI(model_name="gpt-4", temperature=0.5, max_tokens=1000)
temperature
: Temperature는 LLM의 출력 다양성과 창의성을 조절하는 핵심 파라미터입니다. 낮은 Temperature은 사실적이고 간결한 응답을, 높은 Temperature은 토큰의 무작위성이 증가하여 보다 다양하고 창조적인 결과를 촉진합니다.
max_tokens
: Max tokens는 모델이 생성할 수 있는 출력 텍스트의 최대 길이를 제한합니다. 이때, 프롬프트의 토큰 수와 max_tokens의 합이 모델의 컨텍스트 길이를 초과할 수 없습니다.
(추가)
Top-P
(sampling)는 모델이 다음 토큰(단어)을 선택할 때 누적 확률을 기준으로 선택 범위를 제한하는 파라미터입니다.
- 확률이 높은 토큰부터 누적 확률을 계산하여 설정된 P값에 도달할 때까지의 토큰들만 선택 대상으로 고려합니다.
- 예를 들어 Top-P가 0.9라면, 확률이 높은 순서대로 토큰들을 더해가다가 합이 90%가 되는 지점까지의 토큰들만 고려합니다.
(Tips) Top-P & Temperature와의 관계
- Temperature와 Top-P는 모두 텍스트 생성의 다양성을 제어하는 파라미터이지만:
- Temperature는 전체 확률 분포를 조정하여 다양성을 제어합니다.
- Top-P는 선택 가능한 토큰의 범위를 제한하여 다양성을 제어합니다.
- 위 그림에서 파라미터 설정 시, 둘 중 하나만 변경하라고 권장하는 이유는:
- 두 파라미터가 비슷한 역할을 하기 때문에 동시에 조정하면 의도치 않은 결과가 나올 수 있습니다
- Temperature와 함께 사용할 경우 Top-P는 높은 Temperature에서 완전히 이상한 토큰이 선택되는 것을 방지하는 역할을 합니다.
(예시) Top-P 값에 따른 "강아지는 어떤 동물인가요?"에 대한 답변 예시를 살펴보겠습니다.
- 낮은 Top-P (0.3)
모델은 가장 확률이 높은 몇 가지 선택지만 고려하여 답변:
"강아지는 개과에 속하는 포유류입니다. 사람과 함께 사는 반려동물입니다."- 높은 Top-P (0.9)
더 많은 선택지를 고려하여 다양한 표현 가능:
"강아지는 개과에 속하는 포유류이면서, 충실한 반려동물입니다. 놀라운 후각과 청각을 가졌으며, 다양한 크기와 품종이 있고, 인간과 특별한 유대관계를 형성할 수 있는 감정이 풍부한 동물입니다."
(예시) Temperature 값에 따른 "강아지는 어떤 동물인가요?"에 대한 답변 예시를 살펴보겠습니다
- 낮은 Temperature (0.2)
더 사실적이고 일관된 답변:
"강아지는 개과에 속하는 반려동물입니다. 충실하고 사람과 잘 어울리며 보호자에게 충성심이 강합니다."- 높은 Temperature (0.8)
더 창의적이고 다양한 답변:
"강아지는 우리의 삶을 행복으로 물들이는 마법 같은 존재예요! 때로는 장난꾸러기 광대처럼, 때로는 든든한 수호천사처럼 우리 곁을 지켜주죠."
LangChain을 활용하면 검색된 문서
와 질문을 결합
해 자연스러운 흐름으로 생성기와 연결할 수 있습니다. 이를 체이닝(Chaining)이라고 하며, 검색과 생성 단계를 하나의 파이프라인으로 구성합니다.
LCEL(LangChain Expression Language)
을 사용하여 Chain을 연결하면 복잡한 워크플로우를 구현하거나 여러 단계의 논리적 흐름을 만들 수 있습니다.
(참고) LCEL(LangChain Expression Language)
LangChain Expression Language(LCEL)은 LangChain 프레임워크 내에서 다양한 컴포넌트(Ex. 프롬프트 템플릿, 언어 모델, 출력 파서 등)를 선언적 방식으로 결합하여 체인을 구성할 수 있게 해주는 도구입니다.
- 이를 통해 복잡한 LLM(Large Language Model) 애플리케이션을 간결하고 효율적으로 구축할 수 있습니다.
- 주요 특징:
선언적 구문
: 복잡한 로직을 간단하고 읽기 쉬운 방식으로 표현할 수 있습니다.모듈성
: 다양한 컴포넌트를 쉽게 조합하고 재사용할 수 있습니다.유연성
: 다양한 유형의 LLM 애플리케이션을 구축할 수 있습니다.확장성
: 사용자 정의 컴포넌트를 쉽게 통합할 수 있습니다.최적화
: 실행 시 자동으로 최적화를 수행합니다.- 주요 구성 요소:
Runnable
: 모든 LCEL 컴포넌트의 기본 클래스입니다.Chain
: 여러 Runnable을 순차적으로 실행합니다.RunnableMap
: 여러 Runnable을 병렬로 실행합니다.RunnableSequence
: Runnable의 시퀀스를 정의합니다.RunnableLambda
: 사용자 정의 함수를 Runnable로 래핑합니다
코드 예시 1
from langchain_core.runnables import RunnablePassthrough
from langchain_core.output_parsers import StrOutputParser
# 검색 문서 형식화 함수
def format_docs(docs):
return "\n\n".join(doc.page_content for doc in docs)
chain = {
"question": RunnablePassthrough(),
"context": faiss_retriever | format_docs
} | prompt | llm | StrOutputParser()
주요 구성요소 설명:
RunnablePassThrough()
:
retriever | format_docs
:
prompt | llm | StrOutputParser()
:
위에처럼 chain을 정의하고 아래 이미지처럼 실행할 수 있습니다.
코드 예시 2
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.runnables import RunnableParallel, RunnablePassthrough
from langchain_core.output_parsers import StrOutputParser
# 첫 번째 체인: 한국어 질문을 영어로 번역 (KOR2ENG)
prompt1 = ChatPromptTemplate.from_template(
"[{korean_input}] translate the question into English.
Don't say anything else, just translate it."
)
chain1 = (
prompt1
| llm
| StrOutputParser()
)
# 두 번째 체인: 번역된 질문에 대한 대답 생성
prompt2 = ChatPromptTemplate.from_messages([
{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": "{input}"}
])
chain2 = (
{"input": chain1} # chain1의 출력이 chain2의 input으로 전달됨
| prompt2
| llm
| StrOutputParser()
)
# 체인 실행
result = chain2.invoke({"korean_input": "대통령제에 대해 설명해줘"})
print(result)
주요 구성요소 설명:
chain1
: 한국어 -> 영어 번역 (KOR2ENG)
korean_input
)을 영어로 번역.chain2
: 영어 질문 -> 답변 생성
chain1
의 출력(번역된 영어 질문)을 입력받아 답변 생성.체인 연결
:
chain1
의 출력은 자동으로 chain2
의 입력(input)으로 전달됩니다.체인 흐름
chain2.invoke({"korean_input": "대통령제에 대해 설명해줘"})
을 실행했을 때 어떻게 호출이 될까요?chain2.invoke()
호출입력
: {"korean_input": "대통령제에 대해 설명해줘"}
korean_input
은 chain1
으로 전달됩니다.chain1
실행: 번역 작업prompt1
에서 프롬프트 템플릿이 생성됩니다.
[대통령제에 대해 설명해줘] translate the question into English.
Don't say anything else, just translate it.
번역된 영어 질문
을 생성합니다.
"Explain about the presidential system."
번역 결과
를 chain2
로 전달chain1
의 출력인 번역된 질문 "Explain about the presidential system."이 chain2
의 입력으로 전달됩니다.chain2
실행: 대답 생성prompt2의 템플릿이 생성됩니다:
SYSTEM: You are a helpful assistant.
USER: Explain about the presidential system.
LLM이 이 프롬프트를 처리하여 대답을 생성합니다:
"A presidential system is a form of government where the president is the head of state and government, often elected by the people."
(정리) 입력 데이터 흐름:
korean_input
→chain1
→chain2
.
코드 예시 3
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.runnables import RunnableParallel
# 두 개의 체인 생성
joke_chain = (
ChatPromptTemplate.from_template("{topic}에 관한 짧은 농담을 말해줘")
| llm
| StrOutputParser()
)
poem_chain = (
ChatPromptTemplate.from_template("{topic}에 관한 5줄 현대시를 써줘")
| llm
| StrOutputParser()
)
# 병렬 실행
parallel_chain = RunnableParallel({"joke": joke_chain, "poem": poem_chain})
# 실행
response = parallel_chain.invoke({"topic": "AI"})
print(response["joke"].content)
print(response["poem"].content)
주요 구성요소 설명:
RunnableParallel
:
joke_chain
과 poem_chain
이 독립적으로 실행되며, 동일한 입력 변수(topic
)를 공유합니다.joke_chain
:
poem_chain
:
joke_chain
과 동일한 구조로 작동하지만, 응답의 내용은 시(poem)로 설정.결과 병합
:
RunnableParallel
은 두 체인의 결과를 병합하여 "joke"와 "poem" 키에 저장된 응답을 반환합니다.Advanced RAG는 Naive RAG의 기본 구조를 기반으로 하면서도, 검색 품질 향상
과 답변 생성 최적화
를 위해 추가적인 전략과 과정을 포함한 고도화된 RAG 구조입니다.
각각의 개선사항에 대해서 아래에서 살펴보도록 하겠습니다.
지식화 증강은 기존의 검색 과정을 단순히 검색 결과를 반환하는 것에서 나아가, 검색된 정보를 더욱 구조적이고 풍부하게 만들어 검색 품질을 향상시키는 과정입니다.
이를 위해 주요 기술
과 접근 방식
을 다음과 같이 활용합니다:
Document Level
구조적 파싱 (Structural Parsing)
멀티모달 처리 (Multi-Modal Processing)
메타데이터 증강 (Metadata Enrichment)
Model Level
2.1. HyDE (Hypothetical Document Embeddings)
from langchain.prompts.chat import SystemMessagePromptTemplate
def generate_hypothetical_document(query):
template =
"Imagine you are an expert writing a detailed explanation on the topic: '{query}'.
Your response should be comprehensive and include all key points from top search results."
system_message_prompt = SystemMessagePromptTemplate.from_template(template=template)
chat_prompt = ChatPromptTemplate.from_messages([system_message_prompt])
messages = chat_prompt.format_prompt(query=query).to_messages()
response = llm(messages=messages)
return response.content
(참고) Langchain 함수를 이용한 Hyde 구현
HypotheticalDocumentEmbedder.from_llm
메서드를 사용하여, LLM과 기본 임베딩(Base Embedding)을 결합한 HyDE 모델을 생성합니다.llm
: 언어 모델 (예: OpenAI의 GPT 모델)Embeddings
: 기본 임베딩 모델prompt_key
: 프롬프트 템플릿을 선택하는 키prompt_key='web_search'
는 가상 문서를 생성할 때 활용되는 프롬프트 템플릿을 지정합니다. 이 프롬프트 템플릿은 생성된 가상 문서의 내용과 형식을 결정하는 데 중요한 역할을 합니다.prompt_key='web_search'
프롬프트는 웹 검색과 관련된 정보를 효과적으로 생성하도록 설계되어, 사용자 질문에 대한 가상의 웹 검색 결과나 관련 정보를 생성하는 데 최적화되어 있습니다.
web_search_template = """
Please write a passage to answer the question
Question: {QUESTION}
Passage:"""
custom_prompt
매개변수를 통해 직접 프롬프트 템플릿을 제공할 수 있습니다.
custom_prompt = PromptTemplate(
input_variables=["question"],
template="최신 기술 동향에 대해 상세히 설명해주세요: {question}"
)
FAISS.from_documents
는 Facebook AI가 개발한 FAISS(Facebook AI Similarity Search) 라이브러리를 사용하여 문서를 벡터 데이터베이스로 변환하는 메서드입니다.documents
: split_documents를 통해 분할된 문서들embedding
: hyde_embedding을 사용하여 문서를 벡터화hyde_retriever.as_retriever
를 통해 MMR(Maximal Marginal Relevance) 방식으로 검색을 수행합니다.search_type="mmr"
: search_kwargs
:2.2. Multi-Query
개념: Multi-Query는 사용자가 입력한 단일 질문(쿼리)
을 여러 개의 하위 질문으로 변환하여 검색 과정의 품질을 극대화하는 접근 방식입니다.
다양한 관점
(정책
, 경제 상황
, 글로벌 동향
등)에서 관련 문서를 검색하고, 이를 결합하여 종합적인 답변을 제공합니다.코드 예시
from langchain.retrievers.multi_query import MultiQueryRetriever
from langchain.llms import ChatOpenAI
from langchain.prompts.chat import ChatPromptTemplate
from langchain.schema import RunnablePassthrough
from langchain.chains import Chain
# 언어 모델 및 기본 검색기 설정
llm = ChatOpenAI(temperature=0.5) # 질문 확장을 위한 LLM 설정
retriever = faiss_retriever # 벡터 데이터베이스 기반 검색기
# Multi-Query Retriever 생성
multi_query_retriever = MultiQueryRetriever.from_llm(
retriever=retriever, # FAISS 기반 기본 검색기
llm=llm # 질문 확장을 위한 LLM
)
# LCEL 스타일 Chain 구성
prompt_template = """
You are an assistant answering user questions based on the following retrieved context.
Context:
{context}
Question:
{question}
Provide a concise and structured response in bullet points.
"""
prompt = ChatPromptTemplate.from_template(prompt_template)
# LCEL Chain 설정
chain = (
{
"question": RunnablePassthrough(), # 사용자 입력 질문
"context": multi_query_retriever | format_docs, # 검색 결과를 정리 및 형식화
}
| prompt # 프롬프트 생성
| llm # LLM을 사용한 응답 생성
| StrOutputParser() # 결과 형식화
)
# LCEL Chain 실행
query = "2025년 한국 금리 전망은?"
response = chain.invoke(query)
# 결과 출력
print("Generated Response:")
print(response)
(참고)
format_docs
?
format_docs
는 검색된 문서를 특정 형식으로 정리하거나, LLM에 전달하기 적합한 텍스트로 변환하는 역할을 하는 함수 또는 프로세스를 나타냅니다. 이를 통해 검색된 문서가 LLM에서 더 쉽게 처리될 수 있도록 구성됩니다.
- 위 코드에서는
format_docs
는MultiQueryRetriever
와 LLM 간의 중간 단계를 담당하며, 검색 결과를 정리한 뒤 프롬프트에 삽입될 수 있도록 준비합니다.- 아래는 format_docs가 어떻게 구현될 수 있는지 보여주는 예시입니다.
def format_docs(docs): """ 검색된 문서를 정리하고 LLM에 적합한 포맷으로 변환하는 함수. """ formatted = [] for doc in docs: # 문서 내용을 요약하거나 정리 content = doc.page_content metadata = doc.metadata # 예: 제목과 주요 내용만 추출 formatted_content = f"Title: {metadata.get('title', 'Untitled')}\nContent: {content[:500]}..." formatted.append(formatted_content) return "\n\n".join(formatted)
핵심 특징
1) 질문 확장
원 질문 (Original Question, 1개):
"2025년 한국 금리 전망은?"
생성된 하위 질문(Generated Question, 3개):
"2025년 한국 금리에 영향을 미치는 주요 요인은 무엇인가?"
"글로벌 경제 상황이 2025년 한국 금리에 미칠 영향은?"
"한국은행의 정책이 2025년 금리 변화에 어떻게 작용할까?"
2) 결과 통합
생성된 하위 질문들(Generated Question, 3개):
"2025년 한국 금리에 영향을 미치는 주요 요인은?"
"글로벌 경제 상황이 한국 금리에 미칠 영향은?"
"한국은행의 정책이 2025년 금리 변화에 어떻게 작용할까?"
검색 결과(Retrieved Documents, 3개):
문서 1: 한국은행 발표 자료, 금리 정책 보고서.
문서 2: 글로벌 경제 보고서에서 발췌된 금리 관련 정보.
문서 3: 금리 변화와 시장 반응에 대한 전문가 인터뷰.
장점:
Ensemble Retrieval은 여러 검색기(Retriever)
를 결합하여 각각의 장점을 조합하고 검색 성능을 극대화하는 RAG(Retrieval-Augmented Generation) 기법입니다.
Dense Retrieval(고밀도 검색)
와 Sparse Retrieval(희소 검색)
를 적절히 결합하여 다양한 검색 시나리오에서 뛰어난 결과를 제공합니다.1. Sparse Retrieval
Sparse Retrieval
은 전통적인 키워드 기반 검색 방식으로, 단어의 존재 여부와 빈도를 기반으로 검색합니다. 문서를 희소 벡터(sparse vector) 형태로 표현하며, 각 단어는 독립적인 차원을 가집니다(TF-IDF, BM25 등). 주로 전통적인 정보 검색(Information Retriever) 모델에 사용됩니다.장점
단점
활용 예시
Sparse Retrieval
에서 가장 대표적인 알고리즘으로, 뉴스 기사나 검색 엔진과 같은 키워드 중심 검색에 자주 사용됩니다.2. Dense Retrieval
Dense Retrieval
은 신경망 기반 모델(BERT, RoBERTa 등)을 활용하여 문맥적 의미를 포함하는 밀집 벡터(dense vector)를 생성합니다. 문서와 질문을 고차원 공간에서 의미적으로 유사한 벡터로 변환하여 검색합니다. 각 차원이 단어 간 관계를 포함하므로 복잡한 문맥을 이해할 수 있습니다.장점
단점
활용 예시
Dense Retrieval
은 학술 논문 검색, 자연어 기반 질의응답 시스템, 그리고 의미론적 문서 검색과 같은 복잡한 검색 작업에 사용됩니다.Sparse vs Dense Retrieval: 비교와 조합
특징 | Sparse Retrieval | Dense Retrieval |
---|---|---|
알고리즘 | BM25, TF-IDF 등 전통적 IR 모델 | BERT, RoBERTa 등 신경망 기반 모델 |
연산 효율성 | 계산 복잡도 낮음 | 계산 복잡도 높음 |
메모리 사용 | 메모리 요구량 낮음 | 메모리 요구량 높음 |
검색 성능 | 단순 단어 매칭에서 높은 정확도 | 문맥적 의미 이해에서 높은 정확도 |
훈련 필요성 | 사전 구축된 모델 사용, 추가 훈련 불필요 | 대규모 데이터 기반 학습 필요 |
주요 장점 | 빠른 검색 속도, 적은 메모리 사용 | 문맥 이해 및 유사어 처리 가능 |
주요 단점 | 문맥적 의미 이해 부족 | 높은 계산 비용 및 메모리 요구 |
Sparse Retrieval
과 Dense Retrieval
은 각각의 장점과 단점을 보완하기 위해 자주 함께 사용됩니다.
Ensemble Retrieval 구현 예시:
from langchain_community.retrievers import BM25Retriever, EnsembleRetriever
from langchain_openai import OpenAIEmbeddings
from langchain_community.vectorstores import FAISS
# Embeddings 초기화
embeddings = OpenAIEmbeddings()
# Sparse 검색기 (BM25) 초기화
bm25_retriever = BM25Retriever.from_documents(
split_documents,
k=20 # 검색할 문서 수
)
# Dense 검색기 (FAISS) 초기화
faiss_retriever = FAISS.load_local(
"dense_index",
embeddings,
allow_dangerous_deserialization=True
).as_retriever()
# Ensemble 검색기 생성
ensemble_retriever = EnsembleRetriever(
retrievers=[bm25_retriever, faiss_retriever],
weights=[0.4, 0.6]
)
chain = (
{
"question": RunnablePassthrough(), # 사용자 입력 질문
"context": ensemble_retriever | format_docs, # 검색 결과를 포맷팅
}
| prompt # 프롬프트 적용
| llm # LLM을 사용해 응답 생성
| StrOutputParser() # 출력 형식 정리
)
출력 예시
Ensemble Retrieval 검색 과정
입력된 질문: "2025년 한국 금리 전망은?"
사용자가 입력한 이 질문은 RunnablePassthrough
를 통해 체인의 첫 단계로 전달되었습니다.
두 검색기(Sparse와 Dense)를 결합하여 검색을 수행합니다.
Sparse Retrieval(BM25 등)
: 단어 빈도 기반으로 관련 문서를 빠르게 검색.Dense Retrieval(FAISS 등)
: 문맥적 의미를 고려하여 관련 문서를 검색.이 두 검색기의 결과가 가중치를 기반으로 통합됩니다.
해석
weights=[0.4, 0.6]
의 의미, 각 검색기는 독립적으로 문서를 검색하고 순위를 매김.Re-Ranking(리랭킹)은 초기 검색 단계에서 수집된 후보 문서(또는 청크)의 순위를 재조정하는 기술입니다.
(앞에서 Ensemble Retrieval 이후 일종의 재순위화(Re-Ranking) 작업을 수행했다고 봐도 됩니다.)
작동 원리
Re-Ranking의 작동 방식은 다음과 같습니다:
1. 초기 검색 결과 수집:
Re-Ranker로 순위 재조정:
상위 결과 반환:
코드 예시
from langchain.retrievers import ContextualCompressionRetriever
from langchain_cohere import CohereRerank
# Cohere Reranker 설정
COHERE_API_KEY = "your_cohere_api_key"
reranker = CohereRerank(model="rerank-multilingual-v3.0", top_n=3)
# 리랭킹을 적용한 검색기 생성
reranking_retriever = ContextualCompressionRetriever(
base_compressor=reranker,
base_retriever=faiss_retriever # 초기 검색 단계에서 사용한 Retriever
)
# 질의에 따른 검색 실행
test_question = "2025년 한국 금리 전망은?"
retrieved_chunks = reranking_retriever.invoke(test_question)
# 최종 결과 확인
print(len(retrieved_chunks)) # 출력: 3
for chunk in retrieved_chunks:
print(chunk.page_content)
출력 결과 해석
retrieved_chunks
의 길이는 3
으로, 초기 검색 결과 30개
중 가장 관련성이 높은 3개
의 문서만 최종적으로 반환되었습니다."2025년 한국 금리 전망은?"
과 관련성이 높은 내용을 포함하며, LLM이 활용 가능한 형태로 준비되었습니다. 이처럼 Re-Ranking은 초기 검색 결과를 최적화하여 사용자 질문에 가장 적합한 정보를 제공합니다.
Compression은 검색된 문서들의 길이를 줄이거나 요약(Summarization)하여 관련성이 높은 정보만 유지하는 과정입니다. 이는 LLM의 토큰 제한 문제를 극복하고 효율적인 문서 처리를 가능하게 합니다.
작동 원리
ContextualCompressionRetriever
는 base_compressor
와 base_retriever
를 결합하여 동작합니다.base_compressor
로 LLM을 사용하여 문서를 압축하거나 필요한 정보를 요약합니다.코드 구현
from langchain.retrievers.document_compressors import LLMChainExtractor
compressor_from_llm = LLMChainExtractor.from_llm(llm)
compression_retriever = ContextualCompressionRetriever(
base_compressor=compressor_from_llm,
base_retriever=faiss_retriever
)
위 코드를 통해 Compression 과정이 수행되며, 핵심적인 정보만 남기게 됩니다.
이 접근 방식은 여러 검색 기법과 순위 조정(Re-Ranking)을 결합하여 검색 품질을 극대화하는 고급 RAG 기법입니다. 위 내용과 좀 유사한 부분들이 있긴 하지만 Multi-query Ensemble과 Re-Ranking을 같이 쓸 수 있다는 부분이 있다는 것을 강조하기 위해 별도로 챕터로 추가하신 것 같군요 🤔
구성 요소
Multi-query Retrieval
Multi-query Retrieval
은 하나의 질문을 다양한 관점으로 확장하여 검색 범위를 넓힙니다.Ensemble Retrieval
Ensemble Retrieval
은 Sparse Retriever(BM25)와 Dense Retriever를 조합합니다.Re-Ranking
Re-Ranking
은 Ensemble 검색 결과를 Re-Ranker로 다시 정렬합니다.작동 원리
코드 구현
from langchain.retrievers.contextual_compression import ContextualCompressionRetriever
from langchain.retrievers import MultiQueryRetriever
from langchain.retrievers import EnsembleRetriever
from langchain.retrievers.document_compressors import CrossEncoderReranker
from langchain_community.cross_encoders import HuggingFaceCrossEncoder
# Define MultiQuery_Ensemble_reranking_retriever
MultiQuery_Ensemble_reranking_retriever = ContextualCompressionRetriever(
base_compressor=reranker,
base_retriever=MultiQuery_Ensemble_retriever
)
프로세스 증강(Process Augmentation)은 단일 검색 및 생성 단계를 넘어 다단계 검색
, 점진적 추론
, 적응적 검색
방식을 도입하여 복잡한 문제를 해결하기 위한 접근법입니다. 이는 RAG(Retrieval-Augmented Generation)에서 정보를 보다 정교하고 효과적으로 사용할 수 있도록 설계되었습니다.
기존의 단일 검색과 응답 생성 프로세스
는 제한된 정보만 제공하거나, 복잡한 문제를 충분히 해결하지 못할 수 있습니다. 프로세스 증강
은 다음과 같은 여러 단계를 통해 이를 보완합니다.
Iterative Search(반복적 검색)
: 이전 결과를 기반으로 검색을 반복하여 더욱 풍부한 정보를 수집.Recursive Search(재귀적 검색)
: 문제를 세부적인 하위 문제로 분해하고 단계적으로 해결.Adaptive Search(적응적 검색)
: 검색과 생성 과정에서 상황에 따라 유연하게 처리.프로세스 증강 유형 (위 그림 참고)
1) Iterative Search (반복적 검색)
특징
:종료 조건
:활용 사례
:2) Recursive Search (재귀적 검색)
특징
:종료 조건
:활용 사례
:3) Adaptive Search (적응적 검색)
특징
:종료 조건
:활용 사례
:RAG 시스템 구현 예시
RAG 평가의 중요성
RAG 시스템의 품질을 평가하는 것은 모델 성능을 개선하고 신뢰도를 확보하기 위해 필수적입니다.
주요 평가 대상
검색 품질
: 검색 결과의 정확성 및 관련성.생성 품질
: LLM이 생성한 답변의 품질.사용자 경험
: 실무에서의 응답 신뢰성과 일관성.측정 코드 예시
측정 출력 예시
Quantitative (계량적):
Reliable (신뢰성):
Accurate (정확성):
Human Evaluation
평가 방식
: 사람이 직접 평가하여 생성된 텍스트의 유창성, 관련성, 논리적 일관성, 사실적 정확성을 판단합니다.장점
: 실제 사용자 관점에서의 직관적인 평가 가능.단점
: 비용과 시간이 많이 들며, 평가자의 주관이 결과에 영향을 줄 수 있습니다.Statistical Evaluation
대표 메트릭
: BLEU, ROUGE, METEOR, Levenshtein Distance.장점
: 자동화된 평가로 빠르고 비용 효율적.단점
: N-gram 기반 평가로 문맥 및 의미를 충분히 반영하지 못할 가능성.LLM-based Evaluation
평가 방식의 핵심 기술적 접근법
과 사용된 모델 또는 알고리즘의 종류
에 따라 Natural Language Process Model, Embedding Model, Large Language Model로 메트릭을 구분할 수 있음.
Natural Language Process Model
: 논리적 관계와 문맥적 유사성 평가에 중점을 둡니다.
Embedding Model
: 텍스트의 벡터 표현을 활용한 의미적 유사성 평가에 초점을 맞춥니다.
Large Language Model
: 대규모 언어 모델을 활용한 다각적이고 고차원적인 평가가 가능합니다.
장점
: LLM을 활용하여 고차원적인 평가 가능.
단점
: 평가 모델의 편향성에 따라 결과가 왜곡될 가능성.
(참고) 분류에 따라서는 아래와 같이도 정의될 수도 있음.
- Statistical Scorers는 통계적 평가 방식을 사용하는 메트릭들로, 주로 N-gram과 같은 언어 통계적 특징에 기반하여 텍스트 간의 유사도를 계산합니다.
- Model-Based Scorers는 사전 훈련된 모델을 활용하여 텍스트 간의 의미적 유사성을 평가하거나, 모델이 생성한 응답 자체를 평가합니다.
- 교집합(Statistical ∩ Model-Based)은 양쪽 특성을 모두 가지고 있다고 해석할 수 있습니다.
구분 | Statistical Scorers | Model-Based Scorers | 교집합 |
---|---|---|---|
평가 방식 | 단순 N-gram 일치, 통계적 거리 계산 | 사전 훈련된 NLP 모델 활용 | 통계적 계산 + 임베딩 기반 의미 평가 |
장점 | 빠르고 비용 효율적, 자동화 가능 | 문맥적 의미와 구조적 평가 가능 | 효율성과 정확성을 균형 있게 결합 |
단점 | 문맥 반영 부족, 의미적 연결성 미흡 | 계산 비용 높음, 편향 가능성 | 계산 비용과 도메인 의존성 일부 존재 |
대표 메트릭 | BLEU, ROUGE, METEOR, Levenshtein Distance | BERTScore, GPTScore, SelfCheckGPT, Prometheus | BERTScore, MoverScore |
모델 출력의 형태와 평가 목표 정의: 정답이 명확한지, 아니면 유사성이나 품질 평가인지.
자동화의 가능성 및 비용 효율성 고려: Human Evaluation을 포함할지, LLM 기반으로 대체할지.
상황에 맞는 메트릭 선택: Accuracy, Similarity, Creativity 등.
아래는 LLM(Large Language Model)을 평가하는 LLM 평가 메트릭에 대해서 강의 내용과 해당 내용을 보충할만한 자료를 추가적으로 첨부한 내용입니다.
기준 텍스트
와 생성 텍스트
)가 논리적으로 얼마나 관련이 있는지를 평가합니다.특징:
장점:
단점:
기준 텍스트
와 생성 텍스트
간의 의미적 유사성을 평가합니다. 특징:
장점:
단점:
생성된 텍스트
와 기준 텍스트
의 의미적 유사성을 벡터 공간에서 측정합니다. 특징:
장점:
단점:
특징:
장점:
단점:
특징:
장점:
단점:
생성된 텍스트
와 기준 텍스트
간의 사실적 일관성을 평가합니다.(참고) 해당 논문은 Question-Answer Generation Task Pipeline을 다루긴 했지만, 정확하게 Scoring에 대해서 다루고 있지 않습니다.
특징:
장점:
단점:
특징:
장점:
단점:
특징:
장점:
단점:
특징:
장점:
단점:
아래는 RAG(Retrieval-Augmented Generation)를 평가하는 RAG 평가 메트릭에 대해서 강의 내용과 해당 내용을 보충할만한 자료를 추가적으로 첨부한 내용입니다.
RAG 시스템은 검색 단계
와 생성(답변) 단계
의 평가가 모두 중요합니다.
1. Input
2. RAG Pipeline
RAG 파이프라인은 크게 두 단계로 구성됩니다:
Retriever (검색기)
:💬 검색 평가
목적
: 검색된 컨텍스트가 사용자의 질문에 얼마나 관련성이 있는지를 평가합니다.
- 검색된 컨텍스트(
[C] Retrieval Context
)가 질문([Q] Question
)과 얼마나 관련이 있는지 확인합니다.평가 메트릭
:
- Contextual Recall: 검색된 문서가 질문과 관련된 내용을 얼마나 많이 포함하는지.
- Contextual Precision: 검색된 문서의 비율 중 관련된 내용이 얼마나 되는지.
- Contextual Relevancy: 검색된 문서가 질문의 의미와 얼마나 유사한지.
Generator (생성기)
:💬 생성 평가
목적
: 생성된 답변이 검색된 문서 및 질문에 얼마나 잘 부합하는지를 평가합니다.
- 생성된 답변(
[A] Actual Output
)이 검색된 컨텍스트([C] Retrieval Context
), 질문([Q] Question
), Ground Truth([G] Ground Truth
)와 얼마나 일치하는지 평가합니다.평가 메트릭
:
- Answer Relevancy: 생성된 답변이 질문과 얼마나 관련 있는지.
- Faithfulness: 생성된 답변이 검색된 컨텍스트에 얼마나 충실한지.
3. LLM Output
RAG 평가에서 LLM Evaluation Metric을 사용하여 질문, 검색된 컨텍스트, 생성된 답변, Ground Truth(Golden Data)를 기반으로 점수를 산출하는 과정을 보여줍니다.
1. 입력/평가 요소
RAG 평가에 필요한 주요 입력 값(arguments from LLM application)들은 다음과 같습니다:
2. LLM Evaluation Metric
Scorer (점수 산출기):
Thresholding (임계값 평가):
Score와 Reason(Optional):
1. Ground Truth란?
정의: Ground Truth는 사용자의 질문에 적합한 이상적인 답변이나 문서의 청크를 말합니다.
특징:
🏆 Golden Data ?
- Ground Truth는 학습 및 평가 기준이 되는 데이터라면, Golden Data는 그 데이터를 기반으로 구체적인 평가를 위한 샘플링된 데이터를 의미합니다.
- Golden Data는 Ground Truth 데이터를 특정 목적에 맞게 선별하여 구성합니다.
그렇다면 이러한 Ground_truth는 어떻게 말들 수 있을까요?
2. Ground Truth 생성 과정
아래 이미지에서 제시된 내용은 Ground Truth를 생성하는 일반적인 과정입니다.
각 단계는 다음과 같습니다:
(1) 문서 파싱 및 전처리
(2) 질문 생성
(3) 답변 생성
(4) 결과 검토
강의 자료에서는 대표적인 평가 프레임워크(Evaluation Framework)로 Ragas와 DeepEval, 그리고 Pheonix를 예로 듭니다.
세 프레임워크 모두 대형 언어 모델(LLM) 애플리케이션의 성능을 평가하고 개선하는 데 사용되는 도구입니다.
Ragas
Ragas는 문서에서 청크를 생성하고 질문-답변 쌍을 자동으로 생성하는 도구입니다.
Ragas의 주요 특징은 다음과 같습니다:
자동화된 데이터 생성
: 문서로부터 청크를 추출하고, 이를 바탕으로 질문-답변 쌍을 자동으로 생성합니다.비용 효율성
: 데이터 생성 과정을 자동화함으로써 수동으로 데이터를 생성하는 것보다 비용을 크게 절감할 수 있습니다.정량적 평가
: 생성된 데이터를 사용하여 LLM 애플리케이션의 성능을 정량적으로 평가할 수 있습니다.DeepEval
DeepEval은 LLM을 위한 유닛 테스트 도구로, Ground Truth 생성 및 평가를 지원합니다.
DeepEval의 주요 특징은 다음과 같습니다:
LLM 기반 평가
: LLM을 사용하여 Ground Truth를 생성하고 평가합니다.파이썬 친화적 접근
: 파이썬으로 단위 테스트를 작성하는 것처럼 간단하게 LLM 애플리케이션용 테스트를 작성할 수 있습니다.다양한 메트릭 제공
: 관련성, 사실의 일관성, 독성, 편향성 등 다양한 메트릭을 사용하여 LLM 응답을 평가합니다.CI/CD 통합
: 테스트를 제품 개발 파이프라인에 쉽게 추가할 수 있어, 지속적인 통합과 배포가 가능합니다.Web UI 제공
: 테스트, 구현, 비교를 위한 웹 인터페이스를 제공합니다.Phoenix
Phoenix는 Arize AI에서 개발한 오픈소스 LLM 관찰성 및 평가 플랫폼으로, LLM 애플리케이션의 개발, 디버깅, 평가를 위한 종합적인 도구를 제공합니다.
Phoenix의 주요 특징은 다음과 같습니다:
트레이싱
: LLM 작업의 실행 메트릭을 추적하고 모델 동작을 분석합니다. 지연 시간, 토큰 사용량 등의 성능 메트릭을 모니터링하며, 개발자가 특정 스팬을 자세히 살펴보고 관련 로그와 메타데이터에 접근할 수 있습니다.평가
: 사전 테스트된 평가 템플릿을 제공하여 모델 품질을 평가합니다. 사용자 정의 메트릭 정의, 사용자 피드백 수집, 자동 평가를 위한 별도 LLM 활용 등을 지원합니다.실험 및 디버깅
: 프로토타이핑 및 디버깅 단계에서 코드 계측을 통해 상세한 실행 흐름 정보를 제공하고, 프롬프트 및 모델 성능을 측정하기 위한 실험 기능을 제공합니다.검색 및 추출(RAG) 분석
: 검색 프로세스를 시각화하고 청크 생성, 컨텍스트 추출, 프롬프트 템플릿 등을 최적화할 수 있습니다.프롬프트 템플릿 디버깅
: LLM 호출에서 사용된 프롬프트 템플릿과 최종 생성된 프롬프트를 추적합니다.다양한 환경 지원
: 주피터 노트북, 로컬 환경, 컨테이너, 클라우드 등 다양한 환경에서 실행 가능합니다.프레임워크 및 모델 통합
: LlamaIndex, LangChain 등의 프레임워크와 OpenAI, Amazon Bedrock 등 다양한 LLM 모델을 지원합니다.(참고) Retrieval-Augmented Generation for Large Language Models: A Survey
- 링크 : https://arxiv.org/pdf/2312.10997v1
- 본 논문에서는
RAGAS
와ARES
라는 두 가지 주요 평가 프레임워크가 소개합니다.- 아래 내용은 논문 내용을 기준으로 작성되었습니다.
- RAGAS:
- 이 프레임워크는 검색 시스템이 관련성과 중요성을 가진 문단을 식별하는 능력, LLM이 이러한 문단을 얼마나 충실히 사용하는지, 생성의 질을 평가합니다.
- RAGAS는 간단한 수동 프롬프트를 사용하여 세 가지 품질 측면(답변의 신뢰성, 답변의 관련성, 문맥의 관련성)을 평가합니다.
- 모든 프롬프트는 OpenAI API를 통해 gpt-3.5-turbo-16k 모델로 평가됩니다.
- ARES:
- ARES는 RAG 시스템의 성능을 자동으로 평가하기 위해 Context Relevance, Answer Faithfulness, Answer Relevance의 세 가지 측면을 목표로 합니다.
- RAGAS와 유사한 평가 지표를 사용하지만, 더 새로운 설정에 대한 적응성이 떨어지는 단점이 있습니다.
- ARES는 소량의 수동 주석 데이터와 합성 데이터를 사용하여 평가 비용을 줄이며, Predictive-Driven Reasoning (PDR)을 사용해 평가의 정확도를 높입니다.
RAGAs를 활용하여 RAG 평가를 위한 Ground Truth를 생성하는 방법은 다음과 같은 프로세스를 따릅니다.
RAGAs 설정 및 활용
필요한 라이브러리 불러오기:
from ragas.llms import LangchainLLMWrapper
from ragas.embeddings import LangchainEmbeddingsWrapper
from ragas.testset import TestsetGenerator
모델 및 임베딩 초기화:
llm = LangchainLLMWrapper("openai-gpt") # LLM 모델 초기화
embeddings = LangchainEmbeddingsWrapper("openai-embedding") # 임베딩 모델 초기화
테스트셋 생성기 정의:
generator = TestsetGenerator(
llm=llm,
embedding_model=embeddings
)
Ground Truth 생성:
dataset = generator.generate_with_langchain_docs(
split_documents=split_documents, # 사전 분리된 문서
testset_size=10 # 생성할 질문-답변 쌍 개수
)
데이터셋 확인:
print(dataset.to_pandas()) # Pandas 데이터프레임 형식으로 확인
위 코드를 수행하면, 아래와 같이 질문과, GT가 생성되는 것을 확인할 수 있습니다.
RAG Triad는 RAG(Retrieval-Augmented Generation) 시스템의 성능을 평가하기 위한 세 가지 핵심 평가 지표 프레임워크입니다.
이는 ‘질의 (Query)’, ‘컨텍스트 (Context)’, ‘응답 (Response)’의 세 요소가 ‘적절한 (Relevance)’ 내용을 담고 있는지 평가하고자 합니다.
이 프레임워크는 검색과 생성의 균형을 평가하고 잠재적인 실패 모드를 식별하는 데 도움을 줍니다.
답변 관련성 평가
편향성(bias) 및 독성(toxicity) 평가
Bias (편향성)
: 출력에 성별, 인종, 정치적 편향성이 포함되었는지 평가.Toxicity (독성)
: 출력이 모욕적이거나 유해한 언어를 포함하는지 평가.대화 완전성 및 관련성 평가
Conversation Completeness (대화 완전성)
: 대화 전반에서 사용자의 요구를 충족하는지 평가.Conversation Relevancy (대화 관련성)
: 대화 맥락에서 지속적으로 관련성 있는 응답을 생성하는지 평가.DeepEval의 Test Dataset 생성
코드 실습
from deepeval.dataset import EvaluationDataset
from deepeval.synthesizer import Synthesizer
# Synthesizer 초기화
synthesizer = Synthesizer(
model="gpt-4o-mini", # 기본 GPT 모델 설정
critic_model="gpt-4o-mini", # 평가 기준(Critic) 모델 설정
embedder=embeddings, # 문서 임베딩 모델
context_quality_threshold=0.6, # 컨텍스트 품질 임계값 설정
context_similarity_threshold=0.7, # 컨텍스트 유사도 기준
context_max_retries=4, # 컨텍스트 생성 재시도 횟수
synthetic_input_quality_threshold=0.8, # 합성 입력 품질 기준
synthetic_input_max_retries=5 # 합성 입력 재시도 횟수
)
Synthesizer
객체는 문서를 기반으로 합성 질문-답변 쌍을 생성하기 위한 핵심 역할을 수행합니다.model
과 critic_model
: 생성과 평가에 사용할 GPT 모델.embedder
: 문서를 임베딩하여 유사도 계산에 사용하는 모델.context_quality_threshold
: 컨텍스트의 최소 품질 기준 (0.6).context_similarity_threshold
: 코사인 유사도를 기반으로 컨텍스트의 유사도를 평가 (기본값: 0.7).context_max_retries
: 기준 미달 시 컨텍스트 생성 재시도 횟수 (4회).synthetic_input_quality_threshold
: 합성 입력의 품질 기준 (0.8).synthetic_input_max_retries
: 합성 입력 생성 재시도 횟수 (5회).
# Test Dataset 생성
dataset = EvaluationDataset()
# Prompt Customization
dataset.generate_goldens_from_docs(
synthesizer=synthesizer,
document_paths=['/content/drive/MyDrive/RAG docs/경기_차별화_전망.pdf'], # 문서 경로 설정
scenario="A typical users asking question to obtain information using plain Korean instructions",
task="Answering to user questions by sending queries to the database and returning the results to the user.",
input_format="Korean instructions for retrieving information from a database",
expected_output_format="Korean informations based on the given input"
)
Test Dataset 생성
generate_goldens_from_docs
메서드를 통해 Test Dataset 생성.document_paths
: 문서 경로 리스트.scenario
: 사용 사례 설명 (예: 사용자 요청 시 데이터베이스에서 정보를 반환하는 시나리오).task
: 작업의 목표 설명 (예: 질의응답).input_format
: 입력 데이터의 형식 (한국어 질의 형식).expected_output_format
: 출력 데이터의 형식 (한국어 정보).Prompt Customization
마지막 단계에서는 생성된 데이터를 Markdown 형식으로 출력하여 확인합니다:
display(Markdown(f"**Input**: {dataset.goldens[2].input}"))
display(Markdown(f"**Context**: {dataset.goldens[2].context}"))
display(Markdown(f"**Expected_output**: {dataset.goldens[2].expected_output}"))
결과:
문맥적 관련성 (Contextual Relevance)
from deepeval.metrics import ContextualRelevancyMetric
# 문맥적 관련성 평가 메트릭 초기화
metric = ContextualRelevancyMetric(
threshold=0.7, # 임계값: 0.7 이상이면 Pass
model="gpt-4o-mini", # 평가에 사용할 모델
include_reason=True # 점수에 대한 이유를 출력
)
# 평가 구성요소 생성
question = dataset.goldens[2].input # 입력 질문
actual_output = chain.invoke(question) # 실제 출력
retrieval_context = dataset.goldens[2].context # 검색된 컨텍스트
# 테스트 케이스 생성
test_case = LLMTestCase(
input=question,
actual_output=actual_output,
retrieval_context=retrieval_context
)
# 문맥적 관련성 평가 수행
metric.measure(test_case)
평가 결과 해석
충실도 (Faithfulness) 평가
from deepeval.metrics import FaithfulnessMetric
# 충실도 평가 메트릭 초기화
metric = FaithfulnessMetric(
threshold=0.7, # 임계값: 0.7 이상이면 Pass
model="gpt-4o-mini", # 평가에 사용할 모델
include_reason=True # 점수에 대한 이유를 출력
)
# 평가 구성요소 생성
question = dataset.goldens[2].input # 입력 질문
actual_output = chain.invoke(question) # 실제 출력
retrieval_context = dataset.goldens[2].context # 검색된 컨텍스트
# 테스트 케이스 생성
test_case = LLMTestCase(
input=question,
actual_output=actual_output,
retrieval_context=retrieval_context
)
# 충실도 평가 수행
metric.measure(test_case)
아래는 3가지 품질 평가 Metric을 한번에 코드로 묶어서 작성한 코드입니다:
from deepeval.metrics import ContextualPrecisionMetric, ContextualRecallMetric
from deepeval.metrics.ragas import RagasMetric
# 문맥적 정밀성 메트릭 정의
contextual_precision_metric = ContextualPrecisionMetric(
threshold=0.7, # 평가 통과 기준
model="gpt-4o-mini", # 사용 모델
include_reason=True # 점수 계산 이유 포함 여부
)
# 문맥적 재현성 메트릭 정의
contextual_recall_metric = ContextualRecallMetric(
threshold=0.7, # 평가 통과 기준
model="gpt-4o-mini", # 사용 모델
include_reason=True # 점수 계산 이유 포함 여부
)
# RAGAS 메트릭 정의
ragas_metric = RagasMetric(
threshold=0.5, # 평가 통과 기준
model="gpt-4o-mini" # 사용 모델
)
# 평가 구성 요소 생성
question = dataset.golden_data[2].input # 질문 데이터
actual_output = chain.invoke(question) # 모델 출력
retrieval_context = dataset.golden_data[2].context # 검색된 컨텍스트
expected_output = dataset.golden_data[2].expected_output # 기대 출력 (Ground Truth)
# 테스트 케이스 생성 (Type3는 expected_output 포함)
test_case_type3 = LLMTestCase(
input=question,
actual_output=actual_output,
retrieval_context=retrieval_context,
expected_output=expected_output
)
# 메트릭 평가
contextual_precision_metric.measure(test_case_type3)
contextual_recall_metric.measure(test_case_type3)
ragas_metric.measure(test_case_type3)
각각의 결과 해석 및 설명은 아래에서 보실 수 있습니다.
문맥적 정밀성 (Contextual Precision)
문맥적 재현성 (Contextual Recall)
RAGAS 메트릭 (RAGAS Metric)
이상으로 강의 자료에 있는 모든 내용을 정리해보았는데요.
자료를 토대로 조사하면서 작성한 블로그이므로, 실제 강연 내용과는 상이할 수 있습니다 💌
읽어주셔서 감사합니다.