Migrating from qdrant-ruby to vectra-client
This guide helps you migrate from the qdrant-ruby gem to vectra-client.
Why Migrate?
- Unified API: Same interface across Pinecone, Qdrant, Weaviate, pgvector
- Production-ready: Middleware, retry logic, circuit breakers built-in
- Better testing: In-memory provider for fast tests
- ActiveRecord integration:
has_vectorDSL for Rails apps
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:
points→vectorsidcan be string (Qdrant supports both, Vectra uses strings for consistency)vector→valuespayload→metadata
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
Hybrid Search
Qdrant supports hybrid search (BM25 + vector). Vectra exposes this:
results = client.hybrid_search(
index: 'my-collection',
vector: embedding,
text: 'ruby programming',
alpha: 0.7
)
Text Search
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
- Update
Gemfile(removeqdrant-ruby, addvectra-client) - Update client initialization (
url→host) - Replace
client.collections.get('name')with direct method calls - Update
points→vectors,payload→metadata - Convert ID types to strings if needed
- Simplify filter syntax where possible
- Update query result iteration (use
match.idinstead ofresult['id']) - Update fetch result access (use
.valuesinstead of['vector']) - Test all operations (upsert, query, fetch, delete)
- Test hybrid search and text search if used