我对 Qdrant 和 Docling(以及 Ollama)的实战经验

发布: (2025年12月14日 GMT+8 03:25)
5 min read
原文: Dev.to

Source: Dev.to

第一次使用 Qdrant!

Qdrant 是一个开源的、高性能向量数据库和相似度搜索引擎。它存储、索引并搜索高维向量,这对于现代 AI 应用(如检索增强生成(RAG)系统、推荐引擎和图像识别)至关重要。Qdrant 使用 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 支持
  • 支持多种 视觉语言模型(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

示例 Python 应用

下面的代码演示了 Qdrant 的基本操作:创建集合、插入(upsert)点,以及执行普通和过滤的最近邻搜索。

# 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

相关文章

阅读更多 »