티스토리 뷰
임베딩과 벡터스토어를 처음 이해하는 글 — RAG를 배우기 전에 꼭 알아야 할 검색의 언어
여기서부터 많은 분이 갑자기 어려워합니다.
문서 하나 읽고 답하게 만드는 것까지는 따라왔는데,
이제 문서가 10개, 100개, 1000개가 되면 어떻게 해야 하냐는 거죠.
그 순간부터 나오는 단어가 있습니다.
- 임베딩
- 벡터
- 벡터스토어
- 유사도 검색
- semantic search
처음 보면 좀 부담스럽습니다.
저도 처음엔 그랬어요.
괜히 수학 같고, 뭔가 AI 엔진 속으로 들어가는 느낌이었거든요.
근데 막상 핵심은 생각보다 단순합니다.
임베딩은 “문장의 의미를 숫자로 바꾼 것”이고,
벡터스토어는 “그 숫자들을 저장해두고 비슷한 의미를 찾는 저장소”입니다.
LangChain 문서는 임베딩 모델이 텍스트를 고정 길이 숫자 벡터로 바꾸고, 이 벡터가 텍스트의 의미를 담기 때문에 정확히 같은 단어가 아니어도 의미가 비슷하면 가까운 위치에 놓이게 된다고 설명합니다. 또 벡터스토어는 add_documents, delete, similarity_search 같은 공통 인터페이스를 제공한다고 안내해요. (LangChain Docs)
왜 임베딩이 필요한가
이전 글에서는 작은 문서라서 chunk를 전부 프롬프트에 넣어도 됐습니다.
그런데 문서가 많아지면 그 방식은 바로 무너져요.
예를 들어 회사 위키 문서가 500개라고 해보죠.
질문 하나 들어올 때마다 그걸 다 넣을 수는 없습니다.
그래서 필요한 게 검색입니다.
근데 여기서 문제가 하나 생깁니다.
보통 키워드 검색은 같은 단어가 있는지를 많이 봅니다.
그런데 사람 질문은 꼭 문서와 같은 표현을 쓰지 않아요.
예를 들면:
- 질문: “LangChain에서 출력 형식 강제하는 법”
- 문서: “structured output을 이용한 schema-based response handling”
단어는 다르지만 의미는 꽤 가깝죠.
이럴 때 semantic search, 즉 의미 기반 검색이 필요합니다.
OpenAI의 임베딩 모델 문서도 임베딩은 텍스트 관련도를 측정하는 데 쓰이고, 검색·클러스터링·추천·분류 같은 작업에 유용하다고 설명합니다. (OpenAI 개발자)
오늘 글에서 얻어갈 것
이번 글은 딱 여기까지 잡으면 성공입니다.
- 임베딩이 왜 필요한지
- 벡터스토어가 무슨 역할을 하는지
- LangChain에서 가장 작은 의미 검색 예제를 어떻게 만드는지
- 왜 이게 다음 글의 RAG로 바로 이어지는지
아직은 답변 생성까지 완전한 RAG 체인을 만들지 않겠습니다.
오늘은 **“질문과 비슷한 문서 조각을 잘 찾는 것”**에 집중할 거예요.
LangChain의 knowledge-base 튜토리얼도 document loader, embeddings, vector store abstraction을 익히는 것이 retrieval 기반 워크플로의 핵심이라고 설명합니다. (LangChain Docs)
1. 임베딩을 가장 쉽게 이해하는 비유
저는 처음 설명할 때 이렇게 말하는 편입니다.
문장을 그대로 컴퓨터에 저장하면, 컴퓨터는 그 문장의 뜻을 잘 모릅니다.
그냥 문자열일 뿐이죠.
그래서 임베딩 모델은 문장을 숫자 배열로 바꿉니다.
예를 들면 이런 느낌이에요.
- “LangChain으로 챗봇 만들기” → [0.12, -0.44, 0.98, ...]
- “LangChain 기반 대화형 AI 구현” → [0.11, -0.40, 1.01, ...]
숫자 자체는 사람이 해석하기 어렵지만,
중요한 건 의미가 비슷한 텍스트는 가까운 벡터가 되도록 만들어진다는 점입니다.
LangChain 문서도 비슷한 아이디어의 텍스트들이 벡터 공간에서 가까워지기 때문에 exact keyword가 달라도 의미 기반 비교가 가능하다고 설명합니다. (LangChain Docs)
이게 핵심이에요.
임베딩은
문장을 보기 좋게 만드는 게 아니라,
비슷한 의미끼리 가깝게 비교할 수 있게 만드는 표현 방식입니다.
2. 벡터스토어는 정확히 뭘 하는가
이제 숫자로 바꿨으면 저장해야겠죠.
그 저장소가 벡터스토어입니다.
벡터스토어는 보통 이런 일을 합니다.
- 문서 chunk를 저장한다
- 각 chunk의 임베딩 벡터를 저장한다
- 사용자의 질문도 임베딩으로 바꾼다
- 질문 벡터와 가까운 문서 벡터를 찾는다
LangChain은 이걸 공통 인터페이스로 감싸서, 벡터스토어 구현이 달라도 비슷한 방식으로 다루게 해줍니다. 공식 문서에는 벡터스토어 인터페이스로 add_documents, delete, similarity_search가 제시됩니다. (LangChain Docs)
즉, 벡터스토어는 단순 DB가 아니라
**“의미가 비슷한 텍스트를 찾기 위한 저장소”**에 가깝습니다.
3. 오늘은 가장 작은 예제로 간다
처음부터 FAISS, Qdrant, Pinecone, Elasticsearch 같은 걸 넣으면 머리가 복잡해집니다.
물론 실제 서비스에선 그런 선택이 필요할 수 있어요.
하지만 지금은 원리를 이해하는 게 먼저입니다.
그래서 오늘은 LangChain 문서 예시처럼 InMemoryVectorStore를 쓰겠습니다.
OpenAIEmbeddings 통합 문서는 InMemoryVectorStore.from_texts(...) 또는 from_documents(...)와 함께 OpenAIEmbeddings를 사용하는 예시를 보여줍니다. LangGraph의 agentic RAG 문서도 in-memory vector store와 OpenAI embeddings 조합을 예제로 사용합니다. (LangChain Docs)
장점은 단순합니다.
- 설치와 이해가 쉽다
- 개념 학습에 좋다
- 다음 글의 RAG 예제로 자연스럽게 이어진다
단점도 분명합니다.
- 메모리 기반이라 서버 재시작하면 사라진다
- 운영용 대규모 저장소는 아니다
근데 지금은 딱 좋습니다.
4. 바로 실행해볼 코드
설치
pip install -U langchain-openai langchain langchain-community
LangChain OpenAI 통합은 langchain-openai 패키지로 제공되고, OpenAI 임베딩 문서도 OpenAIEmbeddings 사용을 안내합니다. (LangChain Docs)
코드
import os
from langchain_core.documents import Document
from langchain_core.vectorstores import InMemoryVectorStore
from langchain_openai import OpenAIEmbeddings
def main() -> None:
if not os.environ.get("OPENAI_API_KEY"):
raise ValueError("OPENAI_API_KEY 환경변수가 설정되어 있지 않습니다.")
documents = [
Document(
page_content="LangChain은 프롬프트, 모델, 툴, 리트리벌을 연결해 LLM 애플리케이션을 구조적으로 개발하게 돕는다.",
metadata={"source": "note-1"},
),
Document(
page_content="Structured Output은 모델 응답을 JSON이나 Pydantic 스키마처럼 예측 가능한 형식으로 다루는 데 유용하다.",
metadata={"source": "note-2"},
),
Document(
page_content="RAG는 외부 문서를 검색해서 질문과 관련 있는 문맥을 모델에 함께 넣어 답변 품질을 높이는 방식이다.",
metadata={"source": "note-3"},
),
Document(
page_content="PromptTemplate은 프롬프트를 문자열이 아니라 재사용 가능한 입력 구조로 관리하게 도와준다.",
metadata={"source": "note-4"},
),
]
embeddings = OpenAIEmbeddings(model="text-embedding-3-small")
vectorstore = InMemoryVectorStore.from_documents(
documents=documents,
embedding=embeddings,
)
query = "LangChain에서 문서 검색으로 답하게 만드는 방식이 뭐야?"
results = vectorstore.similarity_search(query, k=2)
print("=== query ===")
print(query)
print()
print("=== top results ===")
for idx, doc in enumerate(results, start=1):
print(f"[{idx}] source={doc.metadata.get('source')}")
print(doc.page_content)
print()
if __name__ == "__main__":
main()
이 코드는 아주 작은 semantic search 엔진입니다.
- 문서 4개를 준비하고
- 각 문서를 임베딩으로 바꾸고
- 벡터스토어에 저장하고
- 질문과 의미가 가까운 문서 2개를 찾습니다
OpenAI 임베딩 문서의 예시도 InMemoryVectorStore와 OpenAIEmbeddings를 함께 사용해 텍스트를 저장하고 retriever로 비슷한 텍스트를 찾는 흐름을 보여줍니다. (LangChain Docs)
5. 이 코드가 실제로 하는 일
이걸 단계별로 뜯어보면 훨씬 덜 어렵습니다.
Document
문서 조각 하나입니다.
실제 텍스트와 metadata를 같이 들고 갑니다.
이 metadata는 나중에 출처 표시나 필터링에 꽤 중요해집니다.
OpenAIEmbeddings
문서를 숫자 벡터로 바꾸는 모델입니다.
여기서는 text-embedding-3-small을 썼습니다. OpenAI 문서는 이 모델이 검색, 추천, 클러스터링 등에 유용한 임베딩 모델이라고 설명합니다. (OpenAI 개발자)
InMemoryVectorStore.from_documents(...)
문서들을 임베딩해서 메모리 안의 벡터스토어에 저장합니다.
similarity_search(query, k=2)
질문도 임베딩으로 바꾸고, 가장 가까운 문서 2개를 찾습니다.
LangChain 벡터스토어 인터페이스의 핵심 메서드 중 하나가 바로 similarity_search입니다. (LangChain Docs)
즉, 이 코드는 결국 이렇게 말할 수 있어요.
“질문이 들어오면, 문장 뜻이 비슷한 문서 조각을 찾아줘.”
이게 바로 RAG의 절반입니다.
6. keyword search랑 뭐가 다른가
이 차이를 이해하는 순간, 임베딩이 왜 필요한지 확 와닿습니다.
예를 들어 문서에는 “retrieval augmented generation”이라고 적혀 있는데,
사용자는 “문서 검색으로 답변 보강하는 방식”이라고 물을 수 있어요.
키워드 검색은 exact word가 안 맞으면 놓치기 쉽습니다.
반면 임베딩 기반 검색은 뜻이 비슷한가를 보려 하기 때문에 이런 표현 차이에 더 강합니다. LangChain 임베딩 문서도 semantic meaning을 담은 벡터라서 exact words보다 의미 기반 검색이 가능하다고 설명합니다. (LangChain Docs)
물론 keyword search가 쓸모없다는 건 아닙니다.
실제로는 하이브리드 검색이 더 좋은 경우도 많습니다.
하지만 지금 단계에서는 semantic search의 직관을 먼저 잡는 게 중요해요.
7. 왜 문서를 chunk로 나눠서 저장해야 하나
이전 글과 연결되는 부분입니다.
문서 전체를 한 덩어리로 임베딩하면 이런 문제가 생깁니다.
- 질문과 직접 관련 없는 내용까지 한 벡터에 섞인다
- 검색 정확도가 애매해진다
- 나중에 정확한 근거를 보여주기 어렵다
그래서 보통은 문서를 적당한 크기로 쪼개서 저장합니다.
LangChain knowledge-base 튜토리얼도 문서를 split한 뒤 그 조각들을 vector store에 넣는 흐름을 보여줍니다. (LangChain Docs)
즉, 저장 단위는 “파일 전체”보다
질문에 답할 수 있는 의미 있는 조각이 더 좋습니다.
이게 나중에 RAG 품질을 크게 좌우합니다.
8. retriever라는 말은 또 뭐냐
여기서 또 새 용어가 하나 나옵니다.
- vector store
- retriever
처음엔 둘이 비슷해 보여요.
간단히 말하면:
- vector store: 데이터를 저장하고 유사도 검색하는 저장소
- retriever: 그 저장소를 “문서 찾아오는 도구”처럼 쓰게 해주는 추상화
OpenAI 임베딩 문서 예시도 벡터스토어를 만든 뒤 as_retriever()로 retriever를 만들어 invoke("What is LangChain?")처럼 사용하는 흐름을 보여줍니다. (LangChain Docs)
예를 들면 이렇게 쓸 수 있습니다.
retriever = vectorstore.as_retriever(search_kwargs={"k": 2})
docs = retriever.invoke("RAG가 뭐야?")
이 방식이 좋은 이유는,
나중에 vector store 구현체가 바뀌어도 retriever 관점의 사용법을 꽤 일정하게 가져갈 수 있기 때문입니다.
9. retriever까지 포함한 실행 예제
아래 코드는 위 예제를 retriever 스타일로 바꾼 버전입니다.
import os
from langchain_core.documents import Document
from langchain_core.vectorstores import InMemoryVectorStore
from langchain_openai import OpenAIEmbeddings
def main() -> None:
if not os.environ.get("OPENAI_API_KEY"):
raise ValueError("OPENAI_API_KEY 환경변수가 설정되어 있지 않습니다.")
documents = [
Document(page_content="LangChain은 LLM 앱 개발을 위한 프레임워크다.", metadata={"source": "a"}),
Document(page_content="RAG는 외부 문서를 검색해서 답변에 문맥을 더하는 기법이다.", metadata={"source": "b"}),
Document(page_content="벡터스토어는 의미가 비슷한 텍스트를 빠르게 찾기 위한 저장소다.", metadata={"source": "c"}),
]
embeddings = OpenAIEmbeddings(model="text-embedding-3-small")
vectorstore = InMemoryVectorStore.from_documents(documents, embedding=embeddings)
retriever = vectorstore.as_retriever(search_kwargs={"k": 2})
results = retriever.invoke("문서 검색 기반 답변 기법을 설명해줘.")
for doc in results:
print(doc.metadata, "->", doc.page_content)
if __name__ == "__main__":
main()
이제 느낌이 보이죠.
질문이 들어오면 retriever가 관련 문서를 찾아오고,
다음 글에서는 그 문서를 모델 프롬프트에 넣어서 답하게 만들 겁니다.
그게 바로 우리가 흔히 말하는 RAG 흐름입니다.
10. 자주 하는 오해
이 부분은 꼭 짚고 가고 싶습니다.
오해 1. 임베딩을 하면 모델이 문서를 “학습”한다
아닙니다.
임베딩은 문서를 숫자 표현으로 바꿔 저장해두는 거지, 모델을 재학습시키는 게 아닙니다.
오해 2. 벡터스토어가 있으면 곧바로 RAG다
반은 맞고 반은 아닙니다.
벡터스토어는 retrieval 단계입니다.
RAG는 보통 retrieval + generation까지 이어져야 합니다.
오해 3. 임베딩만 쓰면 검색이 항상 완벽하다
아닙니다.
chunk 품질, 문서 전처리, 질문 표현, k값, 메타데이터, 하이브리드 검색 여부까지 다 영향을 줍니다.
LangChain 문서도 retrieval 워크플로 전체를 document loaders, splitters, embeddings, vector stores, retrievers의 조합으로 설명하지, 임베딩 하나로 해결된다고 말하지는 않습니다. (LangChain Docs)
11. 지금 단계에서 제일 좋은 연습
이건 꼭 해보면 좋습니다.
직접 짧은 문서 10개 정도를 만들어보세요.
예를 들면:
- LangChain PromptTemplate 메모
- Structured Output 메모
- RAG 메모
- Agent 메모
- Vector Store 메모
그리고 질문을 조금씩 바꿔가며 semantic search 결과를 확인해보세요.
예를 들어:
- “문서 검색으로 답변 보강하는 방식”
- “외부 지식 기반 답변”
- “RAG 비슷한 개념”
이런 식으로 exact keyword를 안 써도
관련 문서가 잘 올라오는지 보는 겁니다.
이 연습을 해보면 semantic search가 머리보다 손에서 먼저 이해됩니다.
12. 오늘 글에서 꼭 가져가야 할 한 문장
오늘 내용을 한 줄로 줄이면 이겁니다.
임베딩은 문장의 뜻을 비교 가능하게 바꾸는 것이고, 벡터스토어는 그 뜻들을 저장해두고 비슷한 문서를 찾아주는 저장소다.
이 감각만 잡히면 다음 글이 훨씬 쉬워집니다.
왜냐면 다음은 사실 이것뿐이거든요.
- 질문과 비슷한 문서를 찾는다
- 그 문서를 모델에 넣는다
- 그 문서를 근거로 답하게 만든다
네, 이제 거의 RAG입니다.
마무리
처음 임베딩을 볼 때는 되게 복잡해 보입니다.
숫자 벡터니 유사도니 나오니까 괜히 멀게 느껴져요.
근데 막상 서비스 만드는 입장에서 보면,
결국 되게 현실적인 기술이에요.
“질문이 들어왔을 때,
정확히 같은 단어는 아니어도
뜻이 비슷한 문서를 찾아오고 싶다.”
이 요구를 해결하는 게 임베딩과 벡터스토어입니다.
조금 덜 멋있게 말하면,
RAG의 출발점은 AI 마법이 아니라
문서를 좀 더 똑똑하게 찾는 검색 기술에 가깝습니다.
이걸 이해하고 나면,
다음 단계는 생각보다 자연스럽습니다.
다음 글 예고
다음 글에서는
첫 번째 RAG 만들기 — Retriever로 찾고, 문서를 넣고, 근거 기반으로 답하게 하기
로 이어가겠습니다.
이제부터는 진짜로 “문서 검색 + 답변 생성”이 합쳐집니다.
많은 사람이 말하는 RAG의 가장 기본형을 직접 만들어보게 될 거예요.
출처
- LangChain Embedding model integrations: 임베딩은 텍스트를 의미를 담은 고정 길이 벡터로 바꾸고, 의미 기반 비교와 검색을 가능하게 한다고 설명합니다. (LangChain Docs)
- LangChain Vector store integrations: 벡터스토어는 add_documents, delete, similarity_search 같은 공통 인터페이스를 제공한다고 설명합니다. (LangChain Docs)
- LangChain OpenAIEmbeddings integration: OpenAIEmbeddings와 InMemoryVectorStore.from_texts/from_documents, as_retriever() 예시를 제공합니다. (LangChain Docs)
- LangChain knowledge-base tutorial: document loader, embeddings, vector store abstraction이 retrieval 기반 지식 베이스 구축의 핵심이라고 설명합니다. (LangChain Docs)
- LangGraph agentic RAG 문서: InMemoryVectorStore와 OpenAIEmbeddings를 이용한 retriever 구성 예시를 제공합니다. (LangChain Docs)
- OpenAI text-embedding-3-small 문서: 임베딩은 관련도 측정에 쓰이며 검색, 클러스터링, 추천, 분류에 유용하다고 설명합니다. (OpenAI 개발자)
LangChain, Embeddings, Vector Store, OpenAIEmbeddings, Semantic Search, RAG입문, 생성형AI, LLM개발, 벡터검색, 주니어개발자
'study > langchain' 카테고리의 다른 글
| LangChain으로 문서 하나 읽고 답하는 AI 만들기 — RAG 전에 꼭 알아야 할 가장 작은 문서 Q&A (0) | 2026.03.31 |
|---|---|
| LangChain 대화 히스토리 다루기 — 긴 대화를 자르고, 요약하고, 비용을 관리하는 방법 (0) | 2026.03.30 |
| LangChain 대화형 챗봇 만들기 — 메시지 히스토리와 문맥 유지를 처음부터 이해하는 방법 (0) | 2026.03.26 |
| LangChain 체인 연결하기 — Prompt | Model | Parser를 부품처럼 조립하는 법 (0) | 2026.03.23 |
| LangChain 출력 제어하기 — Structured Output으로 JSON 응답을 안정적으로 받는 방법 (0) | 2026.03.20 |
- Total
- Today
- Yesterday
- NestJS
- rag
- Docker
- ai철학
- fastapi
- Redis
- JAX
- seo 최적화 10개
- SEO최적화
- CI/CD
- JWT
- kotlin
- nextJS
- Prisma
- flax
- Express
- DevOps
- REACT
- llm
- 딥러닝
- 백엔드개발
- node.js
- 압박면접
- PostgreSQL
- 개발블로그
- 쿠버네티스
- 웹개발
- LangChain
- Next.js
- Python
| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | |||
| 5 | 6 | 7 | 8 | 9 | 10 | 11 |
| 12 | 13 | 14 | 15 | 16 | 17 | 18 |
| 19 | 20 | 21 | 22 | 23 | 24 | 25 |
| 26 | 27 | 28 | 29 | 30 |
