LLM 엔지니어링11장 · 폐쇄망 문서 파이프라인과 폐쇄형 RAG10 / 23← 이전목차다음 →MetaContents

11장. 폐쇄망 문서 파이프라인과 폐쇄형 RAG

벡터DB 고르기

반찬은 밀폐 용기에 담을 수도 있고, 집 냉장고에 정리할 수도 있고, 가게라면 공동 물류 창고를 빌릴 수도 있습니다. 담을 양과 쓰는 사람 수에 따라 고릅니다. 벡터DB 도 라이브러리, 내 프로그램 안에서 도는 내장형 DB, 따로 띄우는 서버형 DB 로 나뉩니다.

벡터만 다루는 라이브러리 FAISS, 파일로 저장하는 내장형 Chroma 와 Qdrant 로컬, 여럿이 쓰는 서버형 Qdrant 서버와 Milvus 와 pgvector 를 견준 세 카드

왼쪽 라이브러리(FAISS)는 벡터와 번호만 다루고 원문과 라벨은 우리가 따로 들고 있습니다. 가운데 내장형 DB(Chroma, Qdrant 의 로컬 모드)는 원문과 메타데이터까지 파일로 저장하고 서버가 필요 없습니다. 오른쪽 서버형 DB 는 따로 띄운 서버에 여러 프로그램이 접속해 씁니다. 여기서 밀폐 용기가 라이브러리, 집 냉장고가 내장형, 공동 물류 창고가 서버형입니다.

견주는 기준

기준FAISSChromaQdrant(로컬 모드)서버형(Qdrant 서버, Milvus, pgvector)
서버없음없음(서버로도 띄울 수 있다)없음있다. 컨테이너나 DB 로 띄운다
원문과 메타데이터우리가 따로함께 저장함께 저장(payload)함께 저장
메타데이터로 거르기번호 목록으로만where 조건Filter 조건조건, SQL(pgvector)
지우기와 고치기번호를 붙여 넣었을 때만id 와 조건으로id 와 조건으로id 와 조건으로
큰 규모(수천만 개 이상)빠른 인덱스가 많다작은~중간 규모작은 규모(서버로 옮겨 키운다)여러 대로 나눠 키운다
폐쇄망 반입휠 하나휠 여럿휠 여럿컨테이너 이미지나 DB 패키지

이 장에서는 서버 없이 도는 셋(FAISS, Chroma, Qdrant 로컬 모드)을 실습 4에서 같은 조각으로 견줍니다. 서버형은 Docker 나 DB 서버가 있어야 해서 이 PC 에서 돌리지 않았습니다.

Qdrant 의 생김새

Qdrant 는 QdrantClient(":memory:") 나 QdrantClient(path="폴더") 로 부르면 서버 없이 돕니다. 점(point)마다 id, 벡터, payload(메타데이터)를 넣습니다.

Python
from qdrant_client import QdrantClient, models

qc = QdrantClient(":memory:")                       # 서버 없이 메모리 안에서 돈다
qc.create_collection("demo", vectors_config=models.VectorParams(size=3, distance=models.Distance.COSINE))
qc.upsert("demo", points=[
    models.PointStruct(id=1, vector=[1, 0, 0], payload={"제목": "휴가 규정", "등급": 1}),
    models.PointStruct(id=2, vector=[0.9, 0.1, 0], payload={"제목": "인사평가 기준", "등급": 3}),
])
# 등급 1 이하만 보며 [1, 0, 0] 과 가까운 것을 찾는다
hits = qc.query_points("demo", query=[1, 0, 0], limit=2,
                       query_filter=models.Filter(must=[models.FieldCondition(key="등급", range=models.Range(lte=1))])).points
print([(h.id, h.payload["제목"], round(h.score, 3)) for h in hits])
qc.close()

코드에 나오는 함수

코드쉬운 뜻
QdrantClient(":memory:")Qdrant 를 서버 없이 메모리 안에서 띄운다. 프로그램이 끝나면 내용이 사라진다
create_collection("demo", vectors_config=…)컬렉션(collection, 모음)을 만든다. 표 하나에 해당한다
models.VectorParams(size=3, distance=…)벡터의 설정이다. size 는 벡터의 길이(차원), distance 는 가까움을 재는 법이다
models.Distance.COSINE코사인(cosine) 유사도로 잰다. 두 벡터의 방향이 같을수록 1 에 가깝다
upsert("demo", points=[…])update(고치기)와 insert(넣기)를 합친 말이다. 같은 id 가 있으면 고치고 없으면 넣는다
models.PointStruct(id=…, vector=…, payload=…)점(point) 하나다. payload 는 "실은 짐"이라는 뜻으로, 벡터에 딸린 메타데이터다
query_points("demo", query=…, limit=2, query_filter=…)질문 벡터와 가까운 점을 최대 limit 개 찾는다. query_filter 로 조건을 건다
models.Filter(must=[…])must(반드시)에 적은 조건을 모두 지키는 점만 남긴다
models.FieldCondition(key="등급", range=models.Range(lte=1))"등급" 칸이 범위 안인지 본다. lte 는 less than or equal(작거나 같다)이다
.points, h.id, h.payload, h.score찾은 점들의 목록과, 점마다의 id, 메타데이터, 점수다
qc.close()쓴 뒤 클라이언트를 닫아 자원을 돌려준다

이 코드가 뜻하는 것

집 냉장고에 반찬통을 넣는 일로 읽어 봅니다. 반찬통마다 "휴가 규정, 누구나", "인사평가 기준, 어른만" 같은 라벨을 붙여 넣습니다. 아이가 "이것과 비슷한 반찬 두 개 줘"라고 하면, 먼저 "누구나" 라벨이 붙은 통만 고르고 그 가운데 맛이 비슷한 것을 꺼냅니다. 여기서 반찬통이 점, 라벨이 payload, 맛이 벡터, "누구나만"이 Filter 입니다.

줄하는 일쉽게 말하면
qc = QdrantClient(":memory:")서버 없는 Qdrant 를 띄운다냉장고 전원을 켠다
qc.create_collection(...)길이 3, 코사인으로 재는 모음을 만든다반찬 칸을 하나 비운다
qc.upsert(...)점 두 개를 라벨과 함께 넣는다라벨 붙인 반찬통 두 개를 넣는다
qc.query_points(..., query_filter=...)등급 1 이하만 보며 가까운 것을 찾는다"누구나" 통 가운데 비슷한 맛
print([...])찾은 점의 id, 제목, 점수를 찍는다꺼낸 반찬통
qc.close()클라이언트를 닫는다냉장고 문을 닫는다

교안을 만든 PC 에서는 [(1, '휴가 규정', 1.0)] 하나만 나왔습니다. limit=2 로 두 개를 달라고 했지만, 둘째 점은 등급이 3 이라 조건에서 빠졌습니다. 거르기가 검색 안에서 먼저 일어나서, 조건에 맞는 점이 모자라면 limit 보다 적게 돌아온다는 뜻입니다.

작게 줄여서 눈으로 보기

조건이 없었다면 둘째 점은 얼마나 가까웠을지, 코사인 유사도를 NumPy 로 직접 재 봅니다.

Python
import numpy as np

q = np.array([1, 0, 0])
for name, v in [("휴가 규정", [1, 0, 0]), ("인사평가 기준", [0.9, 0.1, 0])]:
    v = np.array(v)
    cos = q @ v / (np.linalg.norm(q) * np.linalg.norm(v))
    print(name, round(float(cos), 3))
출력
휴가 규정 1.0
인사평가 기준 0.994
결과읽는 법
휴가 규정 1.0질문과 방향이 똑같다. Qdrant 가 준 점수와 같다
인사평가 기준 0.994거의 같은 방향이라 조건이 없었다면 둘째로 나왔을 것이다

np.linalg.norm 은 linear algebra(선형대수)의 노름(norm), 곧 벡터의 길이를 잽니다. 두 벡터의 곱을 두 길이의 곱으로 나누면 코사인 유사도입니다. 0.994 처럼 아주 가까운 비밀 문서도 등급 조건 하나로 결과에서 빠졌습니다. 권한은 점수가 아니라 조건으로 지킨다는 뜻입니다.

주의할 점

  • 처음에는 작게 시작합니다. 문서 수만 개까지는 내장형으로 충분하고, 여러 서비스가 함께 쓰거나 수백만 개를 넘을 때 서버형으로 옮깁니다. 같은 임베딩을 다시 넣으면 되므로 옮기는 일은 어렵지 않습니다.
  • 서버형 DB 는 폐쇄망에 컨테이너 이미지와 운영 방법(백업, 권한, 감시)까지 들여와야 합니다. 들여올 것과 지킬 것이 늘어납니다.
← 이전 절11장 · 조각과 메타데이터다음 절 →11장 · 인덱스의 속도와 정확도