Skip to content

자바 개발자의 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 pgvectorPostgreSQL 확장기존 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 개념자바 비유
EmbeddinghashCode() + 의미 보존
Vector DBRedis + 유사도 검색 기능
similarity_searchElasticsearch의 match 쿼리
청킹 (Chunking)페이징 처리

특히 Elasticsearch 써보셨다면 익숙할 거예요. “정확한 매칭”이 아니라 “유사도 기반 검색”을 하는 거니까요.





정리 - 이번 편에서 배운 것

  1. RAG: AI가 모르는 정보를 검색해서 답변에 활용
  2. Embedding: 텍스트 → 벡터 변환
  3. Vector DB: 벡터 저장 + 유사도 검색
  4. 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




읽어주셔서 감사합니다.🖐


Ramsbaby
Written byRamsbaby
이 블로그는 직접 개발/운영하는 블로그이므로 당신을 불쾌하게 만드는 불필요한 광고가 없습니다.

#My Github#소개 페이지#Blog OpenSource Github#Blog OpenSource Demo Site