Qdrant와 Docling(및 Ollama) 직접 체험

발행: (2025년 12월 14일 오전 04:25 GMT+9)
5 min read
원문: Dev.to

Source: Dev.to

처음으로 Qdrant 사용하기!

Qdrant는 오픈‑소스이며 고성능 벡터 데이터베이스이자 유사도‑검색 엔진입니다. 고차원 벡터를 저장·인덱싱·검색하며, 이는 Retrieval‑Augmented Generation (RAG) 시스템, 추천 엔진, 이미지 인식 등 현대 AI 애플리케이션에 필수적입니다. Rust로 구현돼 속도와 메모리 효율이 뛰어나며, 클라우드와 온‑프레미스 모두에 배포할 수 있습니다.

다양한 RAG 솔루션을 평가한 뒤, Qdrant의 고급 메타데이터 필터링 및 하이브리드‑검색 기능을 테스트하고 싶었습니다. Docling 문서에 있는 실용적인 RAG 파이프라인 예제를 활용해 로컬 인스턴스를 띄우고 직접 성능을 측정했습니다.

Docling이란?

Docling은 여러 형식의 문서를 처리·파싱하는 작업을 단순화해 주는 오픈‑소스 라이브러리이며, 고급 PDF 이해 기능도 포함합니다. 주요 특징은 다음과 같습니다:

  • PDF, DOCX, PPTX, XLSX, HTML, WAV, MP3, VTT, 이미지(PNG, TIFF, JPEG, …) 등 다양한 포맷 파싱
  • 고급 PDF 이해: 페이지 레이아웃, 읽기 순서, 표, 코드, 수식, 이미지 분류 등
  • 통합 DoclingDocument 표현
  • Markdown, HTML, DocTags, 손실 없는 JSON 으로 내보내기
  • 민감 데이터와 에어갭 환경을 위한 로컬 실행
  • LangChain, LlamaIndex, Crew AI, Haystack 와 플러그‑앤‑플레이 연동
  • 스캔된 PDF와 이미지에 대한 광범위한 OCR 지원
  • 여러 Visual Language Models (GraniteDocling) 지원
  • 자동 음성 인식(ASR) 모델을 활용한 오디오 지원
  • 모든 에이전트와 연결 가능한 MCP 서버
  • 간단한 CLI

Qdrant 로컬 배포

빠른 시작 페이지는:

다음 Docker(또는 Podman) 명령으로 몇 분 안에 Qdrant를 설정할 수 있습니다:

docker pull qdrant/qdrant
mkdir -p qdrant_storage

docker run -p 6333:6333 -p 6334:6334 \
    -v "$(pwd)/qdrant_storage:/qdrant/storage:z" \
    qdrant/qdrant

컨테이너가 시작되면 다음과 같은 메시지가 표시됩니다:

      _                 _
  __ _| |_ __ __ _ _ __ | |_  
 / _` | __/ _` | '__/ _` | __|
| (_| | || (_| | | | (_| | |_
 \__,_|\__\__,_|_|  \__,_|\__|

Version: 1.16.2, build: d2834de0
Access web UI at http://localhost:6333/dashboard

REST API: http://localhost:6333
GRPC API: http://localhost:6334

파이썬 샘플 애플리케이션

아래 코드는 Qdrant의 기본 작업을 보여 줍니다: 컬렉션 생성, 포인트 업서트, 일반 및 필터링된 최근접 이웃 검색.

# qdrant_app.py
from qdrant_client import QdrantClient
from qdrant_client.models import (
    Distance,
    VectorParams,
    PointStruct,
    Filter,
    FieldCondition,
    MatchValue,
    UpdateStatus,
)
import time

# 1. Client initialization
print("1. Initializing Qdrant client...")
client = QdrantClient(url="http://localhost:6333")

# 2. Collection setup
COLLECTION_NAME = "city_vectors"
VECTOR_SIZE = 4
DISTANCE_METRIC = Distance.DOT

# Ensure a clean start
try:
    client.delete_collection(collection_name=COLLECTION_NAME)
    print(f"   Collection '{COLLECTION_NAME}' deleted (if it existed).")
except Exception:
    pass

print(f"   Creating collection '{COLLECTION_NAME}'...")
client.create_collection(
    collection_name=COLLECTION_NAME,
    vectors_config=VectorParams(size=VECTOR_SIZE, distance=DISTANCE_METRIC),
)
print("   Collection created successfully.")

# 3. Upsert points
print("\n2. Upserting data points...")
points_to_insert = [
    PointStruct(id=1, vector=[0.05, 0.61, 0.76, 0.74], payload={"city": "Berlin", "population": 3.7}),
    PointStruct(id=2, vector=[0.19, 0.81, 0.75, 0.11], payload={"city": "London", "population": 8.9}),
    PointStruct(id=3, vector=[0.36, 0.55, 0.47, 0.94], payload={"city": "Moscow", "population": 12.6}),
    PointStruct(id=4, vector=[0.18, 0.01, 0.85, 0.80], payload={"city": "New York", "population": 8.4}),
    PointStruct(id=5, vector=[0.24, 0.18, 0.22, 0.44], payload={"city": "Beijing", "population": 21.5}),
    PointStruct(id=6, vector=[0.35, 0.08, 0.11, 0.44], payload={"city": "Mumbai", "population": 20.4}),
]

operation_info = client.upsert(
    collection_name=COLLECTION_NAME,
    wait=True,
    points=points_to_insert,
)

print(f"   Upsert operation status: {operation_info.status.name}")
if operation_info.status == UpdateStatus.COMPLETED:
    time.sleep(1)  # ensure index is ready
    print(f"   Successfully inserted {len(points_to_insert)} points.")

# 4. Plain nearest‑neighbors search
QUERY_VECTOR_1 = [0.2, 0.1, 0.9, 0.7]
print(f"\n3. Performing nearest neighbors search for query vector: {QUERY_VECTOR_1}")

search_result_1 = client.query_points(
    collection_name=COLLECTION_NAME,
    query=QUERY_VECTOR_1,
    with_payload=True,
    limit=3,
).points

print("   Top 3 closest points:")
for result in search_result_1:
    print(f"   - Score: {result.score:.4f}, City: {result.payload.get('city')}, Vector ID: {result.id}")

# 5. Filtered nearest‑neighbors search (only London)
QUERY_VECTOR_2 = [0.2, 0.1, 0.9, 0.7]
print(f"\n4. Performing filtered search (city='London') for query vector: {QUERY_VECTOR_2}")

search_result_2 = client.query_points(
    collection_name=COLLECTION_NAME,
    query=QUERY_VECTOR_2,
    query_filter=Filter(
        must=[FieldCondition(key="city", match=MatchValue(value="London"))]
    ),
    with_payload=True,
    limit=3,
).points

print("   Closest point filtered by city='London':")
for result in search_result_2:
    print(f"   - Score: {result.score:.4f}, City: {result.payload.get('city')}, Vector ID: {result.id}")

예제 실행

python qdrant_app.py

예상 출력:

1. Initializing Qdrant client...
   Collection 'city_vectors' deleted (if it existed).
   Creating collection 'city_vectors'...
   Collection created successfully.

2. Upserting data points...
   Upsert operation status: COMPLETED
   Successfully inserted 6 points.

3. Performing nearest neighbors search for query vector: [0.2, 0.1, 0.9, 0.7]
   Top 3 closest points:
   - Score: 1.3620, City: New York, Vector ID: 4
   - Score: 1.2730, City: Berlin, Vector ID: 1
   - Score: 1.2080, City: Moscow, Vector ID: 3

4. Performing filtered search (city='London') for query vector: [0.2, 0.1, 0.9, 0.7]
   Closest point filtered by city='London':
   - Score: 0.8710, City: London, Vector ID: 2
Back to Blog

관련 글

더 보기 »