Migrating from qdrant-ruby to vectra-client

This guide helps you migrate from the qdrant-ruby gem to vectra-client.

Why Migrate?

Step 1: Update Gemfile

# Remove
gem 'qdrant-ruby'

# Add
gem 'vectra-client'

Then run:

bundle install

Step 2: Update Client Initialization

Before (qdrant-ruby)

require 'qdrant'

client = Qdrant::Client.new(
  url: 'http://localhost:6333',
  api_key: ENV['QDRANT_API_KEY']  # Optional
)

After (vectra-client)

require 'vectra'

client = Vectra::Client.new(
  provider: :qdrant,
  host: 'http://localhost:6333',
  api_key: ENV['QDRANT_API_KEY']  # Optional for local
)

Step 3: Update Method Calls

Collections → Indexes

Qdrant uses “collections”, but Vectra uses “indexes” for consistency across providers.

Upsert

Before:

collection = client.collections.get('my-collection')
collection.upsert(
  points: [
    { id: 1, vector: [0.1, 0.2, 0.3], payload: { title: 'Hello' } }
  ]
)

After:

client.upsert(
  index: 'my-collection',
  vectors: [
    { id: '1', values: [0.1, 0.2, 0.3], metadata: { title: 'Hello' } }
  ]
)

Key differences:

Query

Before:

collection = client.collections.get('my-collection')
results = collection.search(
  vector: [0.1, 0.2, 0.3],
  limit: 10,
  filter: { must: [{ key: 'category', match: { value: 'docs' } }] }
)

After:

results = client.query(
  index: 'my-collection',
  vector: [0.1, 0.2, 0.3],
  top_k: 10,
  filter: { category: 'docs' }  # Simplified filter syntax
)

Filter differences:

Qdrant uses complex filter syntax:

filter: {
  must: [
    { key: 'category', match: { value: 'docs' } },
    { key: 'price', range: { gte: 10, lte: 100 } }
  ]
}

Vectra uses simplified syntax (automatically converted):

filter: {
  category: 'docs',
  price: { gte: 10, lte: 100 }
}

Fetch

Before:

collection = client.collections.get('my-collection')
points = collection.retrieve(ids: [1, 2])

After:

vectors = client.fetch(
  index: 'my-collection',
  ids: ['1', '2']  # String IDs
)

Delete

Before:

collection = client.collections.get('my-collection')
collection.delete(points: [1, 2])

After:

client.delete(
  index: 'my-collection',
  ids: ['1', '2']
)

Step 4: Collection Management

List Collections

Before:

collections = client.collections.list

After:

indexes = client.list_indexes
# Returns Array<Hash> with collection details

Create Collection

Before:

client.collections.create(
  collection_name: 'my-collection',
  vectors: { size: 1536, distance: 'Cosine' }
)

After:

client.create_index(
  name: 'my-collection',
  dimension: 1536,
  metric: 'cosine'
)

Describe Collection

Before:

info = client.collections.get('my-collection')

After:

info = client.describe_index(index: 'my-collection')

Step 5: Response Format Differences

Query Results

Before:

results.each do |result|
  puts result['id']
  puts result['score']
  puts result['payload']
end

After:

results.each do |match|
  puts match.id        # String
  puts match.score     # Float
  puts match.metadata  # Hash (was 'payload')
end

Fetch Results

Before:

points = collection.retrieve(ids: [1])
points[0]['vector']

After:

vectors = client.fetch(index: 'my-collection', ids: ['1'])
vectors['1'].values  # Vectra::Vector object

Step 6: Filter Syntax Migration

Simple Filters

Before:

filter: {
  must: [{ key: 'category', match: { value: 'docs' } }]
}

After:

filter: { category: 'docs' }

Range Filters

Before:

filter: {
  must: [
    { key: 'price', range: { gte: 10, lte: 100 } }
  ]
}

After:

filter: { price: { gte: 10, lte: 100 } }

Complex Filters

For complex filters (AND/OR/NOT), Vectra converts them automatically. If you need full control, you can still use Qdrant’s native filter format:

# Vectra will pass through complex filters
filter: {
  must: [
    { key: 'category', match: { value: 'docs' } },
    { key: 'price', range: { gte: 10 } }
  ]
}

Step 7: Advanced Features

Qdrant supports hybrid search (BM25 + vector). Vectra exposes this:

results = client.hybrid_search(
  index: 'my-collection',
  vector: embedding,
  text: 'ruby programming',
  alpha: 0.7
)

Qdrant’s BM25 text search:

results = client.text_search(
  index: 'my-collection',
  text: 'iPhone 15 Pro',
  top_k: 10
)

Default Index/Namespace

client = Vectra::Client.new(
  provider: :qdrant,
  host: 'http://localhost:6333',
  index: 'my-collection',
  namespace: 'default'
)

# Omit index/namespace in calls
client.upsert(vectors: [...])

Migration Checklist

Need Help?