자바 개발자의 AI 입문기 (3편) - RAG 기초, 문서를 벡터로 저장하기
TL;DR
- RAG: AI가 모르는 정보를 검색해서 답변에 활용하는 기술
- Embedding: 텍스트를 숫자 배열(벡터)로 변환하는 것
- Vector DB: 벡터를 저장하고 유사도로 검색하는 데이터베이스
- 핵심 흐름: 문서 → 청킹 → 임베딩 → 저장 → 검색
- 한계: ChromaDB는 로컬 전용, 대용량/멀티 인스턴스 환경에서는 Pinecone 등 클라우드 Vector DB 필요
이전 편 요약
2편에서는 다음을 다뤘습니다:
- LangChain 프롬프트 템플릿
- 체이닝으로 AI 호출 연결하기
- StrOutputParser로 출력 정리
이번 편부터 RAG를 다룹니다. AI 개발에서 가장 실용적인 기술이에요.
RAG가 뭔가요?
RAG = Retrieval-Augmented Generation (검색 증강 생성)
한 줄 요약: “AI가 모르는 걸 검색해서 알려주는 기술”
GPT-4든 Claude든, AI는 학습된 데이터까지만 알아요. 우리 회사 문서? 최신 뉴스? 내부 위키? 모릅니다.
그래서 이런 흐름이 필요해요:
사용자 질문
↓
관련 문서 검색 (Retrieval)
↓
검색 결과 + 질문을 AI에게 전달
↓
AI가 답변 생성 (Generation)“검색(Retrieval)으로 생성(Generation)을 강화(Augmented)한다” = RAG
왜 RAG가 중요한가
ChatGPT에게 “우리 회사 휴가 정책 알려줘”라고 하면?
죄송합니다. 귀사의 휴가 정책에 대한 정보가 없습니다.
일반적으로 연차 휴가는...이러면 쓸모없죠.
RAG를 쓰면:
[검색된 문서: 휴가정책.pdf]
"연차는 입사 1년 후 15일 부여, 3년차부터 20일..."
→ AI 답변:
"귀사의 휴가 정책에 따르면, 연차는 입사 1년 후 15일이 부여되고,
3년차부터는 20일로 늘어납니다."우리 데이터 기반으로 답변할 수 있게 됩니다.
Embedding - 텍스트를 숫자로 바꾸기
RAG의 핵심은 “유사한 문서를 검색”하는 거예요.
근데 컴퓨터가 “유사하다”를 어떻게 판단할까요?
텍스트를 숫자 배열(벡터)로 바꾸면 됩니다. 이게 Embedding이에요.
"자바 개발자" → [0.12, -0.34, 0.56, 0.78, ...]
"스프링 백엔드" → [0.15, -0.31, 0.52, 0.81, ...]
"맛있는 피자" → [-0.87, 0.22, -0.11, 0.03, ...]비슷한 의미의 텍스트는 비슷한 벡터가 됩니다.
- “자바 개발자” ↔ “스프링 백엔드”: 벡터가 비슷 (유사도 높음)
- “자바 개발자” ↔ “맛있는 피자”: 벡터가 다름 (유사도 낮음)
유사도 계산
두 벡터의 유사도는 “코사인 유사도”로 계산합니다.
코사인 유사도 = 두 벡터의 각도
- 1에 가까움 = 비슷함
- 0에 가까움 = 관련 없음
- -1에 가까움 = 반대자바 개발자라면 이미 익숙한 개념이에요. Lucene이나 Elasticsearch에서 쓰는 TF-IDF 유사도와 비슷한 거예요.
Embedding 실습
OpenAI의 Embedding API를 써봅시다.
from langchain_openai import OpenAIEmbeddings
from dotenv import load_dotenv
load_dotenv()
# Embedding 모델 초기화
embeddings = OpenAIEmbeddings(model="text-embedding-3-small")
# 텍스트를 벡터로 변환
text = "자바 백엔드 개발자"
vector = embeddings.embed_query(text)
print(f"텍스트: {text}")
print(f"벡터 길이: {len(vector)}")
print(f"벡터 앞 5개: {vector[:5]}")결과:
텍스트: 자바 백엔드 개발자
벡터 길이: 1536
벡터 앞 5개: [0.0234, -0.0156, 0.0412, ...]1536차원의 숫자 배열이 됐어요. 이 벡터로 유사도를 계산합니다.
Vector DB - 벡터를 저장하고 검색하는 DB
벡터를 어디에 저장할까요?
일반 RDB에 저장해도 되지만, 유사도 검색이 느려요. 그래서 Vector DB를 씁니다.
주요 Vector DB:
| DB | 특징 | 사용 사례 |
|---|---|---|
| ChromaDB | 로컬 설치, 무료, 간단 | 토이프로젝트, 프로토타입 |
| Pinecone | 클라우드 서비스, 확장성 | 프로덕션, 대용량 |
| Supabase pgvector | PostgreSQL 확장 | 기존 Postgres 사용 시 |
| Weaviate | 오픈소스, 풍부한 기능 | 복잡한 검색 요구 |
이번 편에서는 ChromaDB를 씁니다. 로컬에서 바로 돌릴 수 있어서 학습하기 좋아요.
ChromaDB 설치
pip install chromadb실습: 문서를 벡터로 저장하기
전체 흐름을 한 번에 해봅시다.
1단계: 문서 준비
간단한 회사 정책 문서를 만들어요. 실제로는 PDF, 위키, 노션 등에서 가져오겠죠.
# 샘플 문서 (실제로는 파일에서 로드)
documents = [
"연차 휴가는 입사 1년 후 15일이 부여됩니다. 3년차부터는 20일로 늘어납니다.",
"병가는 연간 10일까지 사용 가능합니다. 3일 이상 사용 시 진단서가 필요합니다.",
"재택근무는 주 2회까지 가능합니다. 팀장 승인이 필요합니다.",
"야근 수당은 오후 9시 이후 근무 시 지급됩니다. 시간당 기본급의 1.5배입니다.",
"점심시간은 12시부터 1시까지입니다. 유연하게 조정 가능합니다.",
]2단계: 벡터 변환 및 저장
from langchain_openai import OpenAIEmbeddings
from langchain_community.vectorstores import Chroma
from dotenv import load_dotenv
load_dotenv()
# Embedding 모델
embeddings = OpenAIEmbeddings(model="text-embedding-3-small")
# 문서를 벡터로 변환하고 ChromaDB에 저장
vectorstore = Chroma.from_texts(
texts=documents,
embedding=embeddings,
persist_directory="./chroma_db" # 로컬 저장 경로
)
print(f"저장된 문서 수: {vectorstore._collection.count()}")끝이에요. 문서가 벡터로 변환되어 ChromaDB에 저장됐습니다.
3단계: 유사도 검색
이제 질문과 관련된 문서를 검색해봅시다.
# 질문과 유사한 문서 검색
query = "휴가는 며칠이나 쓸 수 있나요?"
results = vectorstore.similarity_search(query, k=2) # 상위 2개
print(f"질문: {query}\n")
print("검색된 문서:")
for i, doc in enumerate(results, 1):
print(f"{i}. {doc.page_content}")결과:
질문: 휴가는 며칠이나 쓸 수 있나요?
검색된 문서:
1. 연차 휴가는 입사 1년 후 15일이 부여됩니다. 3년차부터는 20일로 늘어납니다.
2. 병가는 연간 10일까지 사용 가능합니다. 3일 이상 사용 시 진단서가 필요합니다.“휴가”라는 단어가 직접 들어있지 않아도, 의미적으로 관련된 문서를 찾아냈어요.
전체 코드 정리
from langchain_openai import OpenAIEmbeddings
from langchain_community.vectorstores import Chroma
from dotenv import load_dotenv
load_dotenv()
# 샘플 문서
documents = [
"연차 휴가는 입사 1년 후 15일이 부여됩니다. 3년차부터는 20일로 늘어납니다.",
"병가는 연간 10일까지 사용 가능합니다. 3일 이상 사용 시 진단서가 필요합니다.",
"재택근무는 주 2회까지 가능합니다. 팀장 승인이 필요합니다.",
"야근 수당은 오후 9시 이후 근무 시 지급됩니다. 시간당 기본급의 1.5배입니다.",
"점심시간은 12시부터 1시까지입니다. 유연하게 조정 가능합니다.",
]
# Embedding + Vector DB
embeddings = OpenAIEmbeddings(model="text-embedding-3-small")
vectorstore = Chroma.from_texts(
texts=documents,
embedding=embeddings,
persist_directory="./chroma_db"
)
def search_documents(query: str, k: int = 2) -> list[str]:
"""질문과 관련된 문서 검색"""
results = vectorstore.similarity_search(query, k=k)
return [doc.page_content for doc in results]
# 테스트
queries = [
"휴가는 며칠이나 쓸 수 있나요?",
"재택근무 하려면 어떻게 해야 하나요?",
"야근하면 수당 나오나요?"
]
for query in queries:
print(f"Q: {query}")
results = search_documents(query)
print(f"A: {results[0]}\n")자바 개발자를 위한 비유
| RAG 개념 | 자바 비유 |
|---|---|
| Embedding | hashCode() + 의미 보존 |
| Vector DB | Redis + 유사도 검색 기능 |
| similarity_search | Elasticsearch의 match 쿼리 |
| 청킹 (Chunking) | 페이징 처리 |
특히 Elasticsearch 써보셨다면 익숙할 거예요. “정확한 매칭”이 아니라 “유사도 기반 검색”을 하는 거니까요.
정리 - 이번 편에서 배운 것
- RAG: AI가 모르는 정보를 검색해서 답변에 활용
- Embedding: 텍스트 → 벡터 변환
- Vector DB: 벡터 저장 + 유사도 검색
- ChromaDB: 로컬에서 쓰기 좋은 Vector DB
아직 “검색까지”만 했어요. 검색 결과를 AI에게 전달해서 답변을 생성하는 건 다음 편에서 다룹니다.
다음 편 예고
다음 편에서는 RAG 파이프라인을 완성합니다.
- 검색된 문서를 AI에게 전달
- 문서 기반 답변 생성
- Knowledge Base 검색 챗봇 완성
3편에서 저장한 문서를 활용해서, 진짜 쓸모있는 챗봇을 만들어볼 거예요.
(4편) RAG 실전으로 계속
실습 체크리스트
따라하고 나서 아래 항목들을 점검해보세요:
-
chromadb설치 확인 (pip install chromadb) - 임베딩 벡터 길이 1536 확인 (
text-embedding-3-small모델 기준) - ChromaDB 로컬 저장 경로 (
./chroma_db) 폴더 생성 확인 - 저장된 문서 수
_collection.count()숫자 확인 - 의미적으로 유사한 질문으로 관련 문서 상위 2개 검색 결과 확인
참고:
OpenAI Embeddings: https://platform.openai.com/docs/guides/embeddings
ChromaDB 문서: https://docs.trychroma.com
LangChain Vector Stores: https://python.langchain.com/docs/integrations/vectorstores
