13장. 임베딩과 벡터 DB
벡터 DB와 Chroma — 벡터를 저장하고 꺼내는 곳
한 줄 요약
벡터 DB(vector database) 는 벡터를 저장해 두고, 질문 벡터와 가까운 것을 찾아 주는 저장소입니다. 우리는 폴더 하나에 저장되는 Chroma를 씁니다. 청크를 벡터로 바꿔 넣는 일을 색인(indexing) 이라고 합니다.
1. 왜 따로 저장소가 필요한가
원리만 보면 저장소 없이도 검색할 수 있습니다. 질문을 벡터로 바꾸고, 청크 벡터 전부와 유사도를 계산해 높은 것부터 고르면 됩니다. 그런데 문제가 둘 있습니다.
| 문제 | 설명 |
|---|---|
| 매번 다시 임베딩할 수 없다 | 청크를 벡터로 바꾸려면 임베딩 API를 호출해야 합니다. 프로그램을 켤 때마다 모든 청크를 다시 보내는 것은 시간과 비용의 낭비입니다 |
| 전부 비교하면 느려진다 | 청크 12개는 전부 비교해도 됩니다. 수십만 개가 되면 전부 비교하는 방식은 쓸 수 없습니다 |
벡터 DB는 이 두 가지를 맡습니다. 벡터를 파일로 남겨 두고, 전부 비교하지 않고도 가까운 것을 빨리 찾는 색인 구조를 만들어 둡니다.
2. 두 시점 — 색인은 미리, 검색은 그때그때
12장에서 본 RAG의 두 시점이 여기서 구체적인 모습이 됩니다.
| 시점 | 언제 | 하는 일 | 임베딩 API 호출 |
|---|---|---|---|
| 색인 | 미리, 문서가 바뀔 때만 | 청크 12개 → 벡터 12개 → chroma_db/에 저장 |
청크 수만큼 |
| 검색 | 질문이 올 때마다 | 질문 1개 → 벡터 1개 → 가까운 청크 찾기 | 질문 하나만큼 |
이번 장의 파일은 색인을 하고, 잘 들어갔는지 검색으로 확인합니다. 14장부터의 파일은 색인을 하지 않고 저장된 폴더를 불러오기만 합니다.
13장을 실행해야
chroma_db/폴더가 생깁니다. 14장 이후의 파일은 모두 이 폴더가 있어야 제대로 동작합니다.
3. Chroma
벡터 DB는 여러 가지가 있습니다. 서버를 따로 띄우는 것도 있고, 기존 데이터베이스에 기능을 덧붙이는 것도 있습니다. 우리는 Chroma를 씁니다.
| Chroma의 성질 | 우리에게 좋은 점 |
|---|---|
| 서버가 필요 없다 | 설치 외에 준비할 것이 없습니다 |
| 폴더 하나에 저장된다 | 무엇이 어디 저장됐는지 눈으로 볼 수 있습니다 |
| 본문과 메타데이터를 함께 저장한다 | 12장에서 붙인 출처(문서 이름, 조항)가 검색 결과에 그대로 따라옵니다 |
원리는 어느 벡터 DB든 같습니다. "벡터를 넣는다, 가까운 것을 찾는다" 두 가지입니다.
Chroma 안에서 벡터 묶음 하나를 컬렉션(collection) 이라고 부릅니다. 데이터베이스의 표 하나에 해당합니다. 우리 컬렉션의 이름은 haru_policy입니다.
4. 색인하는 코드
docs = [Document(page_content=c["text"], metadata=c["metadata"], id=c["id"])
for c in chunks]
vectorstore = Chroma.from_documents(
documents=docs,
embedding=embeddings,
collection_name=COLLECTION,
persist_directory=CHROMA_DIR,
)
Document는 LangChain이 문서 한 조각을 담는 표준 그릇입니다. 세 가지를 담습니다.
| 칸 | 넣는 것 | 12장 청크의 어느 값 |
|---|---|---|
page_content |
본문. 이것이 벡터로 바뀝니다 | text |
metadata |
본문에 딸린 정보. 벡터로 바뀌지 않고 그대로 저장됩니다 | metadata |
id |
이 조각의 고유한 이름 | id (policy-000 ~ policy-011) |
Chroma.from_documents(...) 한 줄이 세 가지 일을 합니다.
- 본문 12개를 임베딩 모델로 보내 벡터 12개를 받습니다.
- 벡터, 본문, 메타데이터, id를 컬렉션에 넣습니다.
persist_directory폴더에 파일로 저장합니다.
5. 다시 실행하면 중복해서 들어가는가
같은 파일을 두 번 실행하면 청크가 24개가 될까요? 되지 않습니다. 12개 그대로입니다.
이유는 Document에 넣은 id입니다. Chroma는 이미 있는 id가 다시 들어오면 새로 추가하지 않고 덮어씁니다. 처음 실행한 뒤와 한 번 더 실행한 뒤에 컬렉션의 개수를 세어 보면 두 번 모두 12개입니다.
| 상황 | 결과 |
|---|---|
id를 넣고 다시 실행 |
같은 id를 덮어씁니다. 개수가 늘지 않습니다 |
id를 넣지 않고 다시 실행 |
실행할 때마다 새 id가 만들어져 같은 내용이 쌓입니다. 그래서 우리는 id를 넣습니다 |
알아 둘 점이 두 가지 더 있습니다.
- 다시 실행해도 임베딩 API는 다시 호출됩니다. 덮어쓸 벡터를 만들어야 하기 때문입니다. 중복은 생기지 않지만 호출은 공짜가 아닙니다.
- 덮어쓰기는 같은 id에만 일어납니다. 문서가 줄어 청크가 12개에서 10개가 되면, 예전의
policy-010,policy-011은 지워지지 않고 남습니다. 문서를 바꿨을 때는chroma_db/폴더를 지우고 다시 실행하는 것이 가장 확실합니다.
핵심 정리
- 벡터 DB는 벡터를 저장하고, 가까운 벡터를 찾아 줍니다.
- 색인은 미리 한 번, 검색은 질문마다 합니다. 색인은 문서가 바뀔 때만 다시 합니다.
- Chroma는 서버 없이 폴더 하나(
chroma_db/)에 저장됩니다. Document에는 본문, 메타데이터, id를 담습니다. 벡터가 되는 것은 본문뿐입니다.id를 넣었기 때문에 다시 실행해도 중복되지 않습니다. 다만 임베딩 API는 다시 호출됩니다.