자바 개발자의 AI 입문기 (3편) - RAG 기초, 문서를 벡터로 저장하기
시리즈: 자바 개발자의 AI 입문기
3 / 5편
- 11편 - Python 환경세팅과 첫 API 호출
- 22편 - LangChain 기초
- 33편 - RAG 기초지금 읽는 중
- 44편 - RAG 실전
- 55편 - LangGraph
목차
TL;DR
- RAG: AI가 모르는 정보를 검색해서 답변에 활용하는 기술
- Embedding: 텍스트를 숫자 배열(벡터)로 변환하는 것
- Vector DB: 벡터를 저장하고 유사도로 검색하는 데이터베이스
- 핵심 흐름: 문서 → 청킹 → 임베딩 → 저장 → 검색
- 한계: 이 글은 Chroma 로컬 예제. Chroma도 client/server와 관리형 배포를 지원하며 운영 선택은 확장·권한·백업·비용 요건에 따라 달라짐
이전 편 요약
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 | 특징 | 사용 사례 |
|---|---|---|
| Chroma | 로컬·client/server·관리형 배포 선택 | 필요한 격리·백업·확장 요건을 검증해 선택 |
| Pinecone | 클라우드 서비스, 확장성 | 프로덕션, 대용량 |
| Supabase pgvector | PostgreSQL 확장 | 기존 Postgres 사용 시 |
| Weaviate | 오픈소스, 풍부한 기능 | 복잡한 검색 요구 |
이번 편에서는 ChromaDB를 씁니다. 로컬에서 바로 돌릴 수 있어서 학습하기 좋습니다.
모델과 저장소의 운영 경계
Chroma 아키텍처 문서처럼 배포 형태와 검색 엔진 선택을 구분해야 합니다. 임베딩은 의미적 특징을 표현하는 벡터이며, 충돌 방지나 무결성 검증용 해시를 대신하지 않습니다. 모델·차원·거리 함수를 바꾸면 기존 벡터와 새 벡터를 그대로 섞지 말고 재임베딩과 인덱스 전환을 계획해야 합니다. 권한 필터, 삭제 전파, 관련 없는 질문에서의 실패도 평가해야 합니다. 아래 설치 코드는 작성 당시의 학습 예제이며 현재 패키지 조합의 재실행 검증을 의미하지 않습니다.
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
