RAG는 LLM 애플리케이션 개발에서 가장 중요한 개념 중 하나입니다. 단어 그대로 해석하면 검색-증강 생성이라는 의미를 가지고 있습니다.
Retrieval은 데이터를 가져오는 것을 의미합니다. 좀 더 구체적으로는 컴퓨터 시스템에 저장된 자료를 취득하는 것을 뜻합니다.
언어모델이 가지고 있지 않은 정보를 가져오는 과정이 바로 Retrieval입니다. 언어모델은 답변 생성에는 능숙하지만, 답변 생성을 위한 모든 정보를 가지고 있지는 않습니다. 예를 들어 보안이 걸려 있는 사내 자료나 최신 정보는 언어모델이 알 수 없습니다.
Augmented는 AR/VR에 사용되는 것과 같은 단어로, 마치 사실인 것처럼 만든다는 의미입니다. 포켓몬고를 생각하면 이해가 쉽습니다. 실제로는 존재하지 않는 포켓몬이 마치 현실에 있는 것처럼 보이는 것처럼요!
RAG에서는 Retrieval된 데이터를 LLM에게 주면서, **"마치 네가 이 정보를 아는 것처럼 답변을 생성해라"**라고 지시하는 것입니다.
말 그대로 생성입니다. Augmented된 Context(문맥)를 기반으로 LLM에게 답변을 생성하게 하는 과정입니다.
RAG의 의미를 이해했다면, 이제 개발자로서 어떤 고민을 해야 할까요?
답변을 생성하는 것은 LLM의 역할입니다. 따라서 개발자인 우리는 데이터를 잘 가져와서 LLM에게 잘 전달해야 합니다.
일단 잘 저장해야 합니다! 이것이 가장 중요합니다. 아무리 뛰어난 검색 알고리즘이 있어도 데이터가 제대로 저장되어 있지 않으면 소용없습니다.
프롬프트를 잘 활용해야 합니다. 프롬프트는 LLM과 소통할 수 있는 유일한 방법이기 때문입니다. 또한 문맥을 어떻게 제공할 것인가도 매우 중요합니다.
데이터를 잘 가져오더라도 제대로 전달하지 못하면 LLM이 올바른 답변을 생성할 수 없습니다.
RAG를 구성하면 답변을 생성할 때 필요한 데이터를 가져온다고 했습니다. 그렇다면 답변을 생성할 때 필요한 데이터는 무엇일까요?
바로 사용자가 원하는 정보일 가능성이 매우 높습니다! 즉, 사용자의 질문과 관련 있는 데이터일 확률이 높습니다.
관련이 있다는 것을 어떻게 판단할까요? 그 판단 기준은 바로 Vector를 활용합니다. 단어 또는 문장의 유사도를 파악해서 관련성을 측정하는 것입니다.
Embedding 모델을 활용해서 vector를 생성합니다. Embedding 모델은 문장에서 비슷한 단어가 자주 붙어있는 패턴을 학습합니다.
예시를 들어보겠습니다:
이 두 문장에서 "왕자의"라는 단어 앞에 등장하는 "왕"과 "여왕"은 유사할 가능성이 높다는 것을 모델이 학습합니다.
💡 참고 자료: https://projector.tensorflow.org 에서 Embedding Projector를 확인해보세요. 단어 하나하나가 Embedding 모델을 활용해서 Vector화되는 것을 시각적으로 확인할 수 있습니다!
Vector Database는 다음과 같은 역할을 합니다:
1. Vector 저장
2. 유사도 검색 수행
예를 들어 소득세법을 RAG의 knowledge base로 활용한다고 가정해봅시다. 문서 전체를 활용하면 속도도 느리고 토큰 수 초과로 답변 생성이 안 될 수 있습니다. 따라서 문서를 chunking, 즉 나눠서 저장해야 합니다.
정리하면:
텍스트를 수학적으로 변환한 상태입니다. 숫자들의 배열로 표현됩니다.
텍스트를 Vector로 변환하는 방법입니다.
Embedding을 통해 생성된 Vector를 저장하는 데이터베이스입니다. LangChain에서 사용하는 Vector Store라는 용어와 같은 개념입니다.
두 개의 Vector가 얼마나 가까운지를 계산하는 방법에는 여러 가지가 있습니다:
1. Euclidean Distance (유클리드 거리)
두 Vector 사이의 직선 거리를 재는 방법입니다
가장 직관적인 거리 측정 방식입니다

2. Cosine Similarity (코사인 유사도)
원점으로부터 두 Vector에 선을 긋고 각도의 차이를 계산하는 방법입니다
벡터의 크기보다는 방향성을 중요시합니다

3. Dot Product (내적)

실제로 코드를 통해 단어들의 유사도를 계산해보겠습니다.
from openai import OpenAI
client = OpenAI()
import numpy as np
def cosine_similarity(vec1, vec2):
"""
Calculate the cosine similarity between two vectors.
Parameters:
vec1 (numpy array): First vector
vec2 (numpy array): Second vector
Returns:
float: Cosine similarity between vec1 and vec2
"""
dot_product = np.dot(vec1, vec2)
norm_vec1 = np.linalg.norm(vec1)
norm_vec2 = np.linalg.norm(vec2)
if norm_vec1 == 0 or norm_vec2 == 0:
return 0.0
return dot_product / (norm_vec1 * norm_vec2)
# King의 embedding 생성
king_embedding_response = client.embeddings.create(
input = "King",
model = "text-embedding-3-large"
)
king_vector = np.array(king_embedding_response.data[0].embedding)
# Queen의 embedding 생성
queen_embedding_response = client.embeddings.create(
input = "Queen",
model = "text-embedding-3-large"
)
queen_vector = np.array(queen_embedding_response.data[0].embedding)
# King과 Queen의 유사도 검색
king_queen_similarity = cosine_similarity(king_vector, queen_vector)
print(king_queen_similarity)
# 0.552343432# Slave의 embedding 생성
slave_embedding_response = client.embeddings.create(
input = "Slave",
model = "text-embedding-3-large"
)
slave_vector = np.array(slave_embedding_response.data[0].embedding)
# King과 Slave의 유사도 검색
king_slave_similarity = cosine_similarity(king_vector, slave_vector)
print(king_slave_similarity)
# 0.2302444결과를 보면 King과 Queen의 유사도(0.55)가 King과 Slave의 유사도(0.23)보다 훨씬 높은 것을 확인할 수 있습니다! 🎉
본격적인 실습을 위해 Python 가상환경을 설정해보겠습니다.
# pyenv 설치
brew install pyenv
echo 'export PYENV_ROOT="$HOME/.pyenv"' >> ~/.zshrc
echo '[[ -d $PYENV_ROOT/bin ]] && export PATH="$PYENV_ROOT/bin:$PATH"' >> ~/.zshrc
echo 'eval "$(pyenv init - zsh)"' >> ~/.zshrc
exec "$SHELL"
# pyenv-virtualenv 설치
brew install pyenv-virtualenv
# .zshrc에 설정 추가
echo 'eval "$(pyenv virtualenv-init -)"' >> ~/.zshrc
exec "$SHELL"
# 프로젝트 폴더 생성
mkdir llm-application
cd llm-application
# 3.14버전으로 가상환경 생성
pyenv virtualenv 3.14.0 llm-application
# 프로젝트 폴더에서 가상환경 활성화
pyenv local llm-application💡 팁: pyenv local 명령어를 사용하면 별도 activate 명령어를 입력할 필요 없이 해당 폴더에 진입할 때마다 자동으로 가상환경이 활성화됩니다!
프로젝트 폴더에서 .ipynb 확장자로 파일을 하나 생성하면 노트북이 생성됩니다.
필요한 패키지를 설치할 때는 명령어 앞에 %를 붙이면 터미널 없이도 노트북에서 바로 실행할 수 있습니다.
%pip install langchain-openai python-dotenv주피터 노트북을 실행하려면 ipykernel 패키지가 필요합니다. 커널 선택에서 원하는 커널을 선택하고 설치하세요.
OpenAI에서 API KEY를 발급받은 후 .env 파일에 저장합니다:
OPENAI_API_KEY=your-api-key-here그런 다음 환경변수를 불러옵니다
from dotenv import load_dotenv
load_dotenv()import os
from langchain_openai import ChatOpenAI
api_key = os.getenv("OPENAI_API_KEY")
llm = ChatOpenAI(api_key=api_key)
llm.invoke("대한민국의 수도는 어디인가요?")실행 결과:
AIMessage(content='대한민국의 수도는 서울입니다.')content만 보고 싶다면 다음과 같이 변수에 할당하여 .content로 텍스트만 추출할 수 있습니다
ai_message = llm.invoke("대한민국의 수도는 어디인가요?")
print(ai_message.content)최초 OpenAI API KEY를 발급받고 난 후 RateLimitError가 발생하는 경우가 있습니다.
You exceeded your current quota, please check your plan and billing details이 에러는 quota(쿼터)를 초과했다는 메시지입니다.
OpenAI는 최초 리밋을 5달러로 설정해둡니다. 이는 무료로 5달러를 제공하는 것이 아니라 최대 5달러까지만 사용할 수 있다는 의미입니다.

다음 단계를 따라 quota를 늘릴 수 있습니다:
💰 참고: 실제로 사용한 만큼만 과금되므로, 충전 금액이 바로 소진되는 것은 아닙니다.
보안 문제로 인해 외부 API를 연동할 수 없거나 충전을 하고 싶지 않다면, Ollama가 훌륭한 대체제가 될 수 있습니다!
Ollama는 로컬에서 동작하기 때문에 별도 API_KEY가 필요 없습니다.
모델 다운로드는 이전 포스트의 LangChain 기초 편을 참고하세요. 사용 방법은 다음과 같습니다
from langchain_community.chat_models import ChatOllama
llm = ChatOllama(model="llama3")
ai_message = llm.invoke("대한민국의 수도는 어디인가요?")
print(ai_message.content)⏱️ 주의: Ollama는 로컬에서 실행되기 때문에 OpenAI API보다 처리 시간이 상당히 오래 걸릴 수 있습니다. 하드웨어 성능에 따라 답변 생성 시간이 달라집니다.