vectra-client 1.0.6 → 1.0.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +8 -5
- data/README.md +13 -0
- data/docs/_layouts/default.html +1 -0
- data/docs/_layouts/home.html +45 -0
- data/docs/_layouts/page.html +1 -0
- data/docs/api/cheatsheet.md +387 -0
- data/docs/api/methods.md +632 -0
- data/docs/api/overview.md +1 -1
- data/docs/assets/style.css +50 -1
- data/docs/examples/index.md +0 -1
- data/docs/guides/faq.md +215 -0
- data/docs/guides/recipes.md +612 -0
- data/docs/guides/testing.md +211 -0
- data/docs/providers/selection.md +215 -0
- data/lib/vectra/active_record.rb +52 -1
- data/lib/vectra/batch.rb +69 -0
- data/lib/vectra/cache.rb +49 -0
- data/lib/vectra/client.rb +144 -12
- data/lib/vectra/version.rb +1 -1
- metadata +7 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: '05778206f66d4e2ead830f5b7c5885f6336b46d36af696f3df5a56eb26163c5f'
|
|
4
|
+
data.tar.gz: 1ae5814cd10df006ecf5568f34e457400b072ecc8a06dbf85a4e4f5b94120fb9
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d25889a4626c9caa9edf7ccc285c849799de87cf38a8a7a6f56a49edb46da592b28bc0232960e2612fd0d9199e990c21bd5fe6b88f15351d8ff38c73bd7afc84
|
|
7
|
+
data.tar.gz: d4ef355089814ed4caf8c61e216b9554f23035df12be72a65c778b3230bd2a32f16ecf4c50de3bc33e40dc323f1aee83d7a891ad962bec105585c8bc6b30902b
|
data/CHANGELOG.md
CHANGED
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [v1.0.8](https://github.com/stokry/vectra/tree/v1.0.8) (2026-01-14)
|
|
4
|
+
|
|
5
|
+
[Full Changelog](https://github.com/stokry/vectra/compare/v1.0.7...v1.0.8)
|
|
6
|
+
|
|
7
|
+
## [v1.0.7](https://github.com/stokry/vectra/tree/v1.0.7) (2026-01-14)
|
|
8
|
+
|
|
9
|
+
[Full Changelog](https://github.com/stokry/vectra/compare/v1.0.6...v1.0.7)
|
|
10
|
+
|
|
3
11
|
## [v1.0.6](https://github.com/stokry/vectra/tree/v1.0.6) (2026-01-13)
|
|
4
12
|
|
|
5
13
|
[Full Changelog](https://github.com/stokry/vectra/compare/v1.0.5...v1.0.6)
|
|
6
14
|
|
|
7
|
-
### Improvements
|
|
8
|
-
- Refined test suite and documentation
|
|
9
|
-
- Performance optimizations
|
|
10
|
-
- Enhanced stability and reliability
|
|
11
|
-
|
|
12
15
|
## [v1.0.5](https://github.com/stokry/vectra/tree/v1.0.5) (2026-01-13)
|
|
13
16
|
|
|
14
17
|
[Full Changelog](https://github.com/stokry/vectra/compare/v1.0.4...v1.0.5)
|
data/README.md
CHANGED
|
@@ -229,6 +229,19 @@ For a complete step-by-step guide including:
|
|
|
229
229
|
|
|
230
230
|
👉 **[Read the complete Rails Integration Guide](https://vectra-docs.netlify.app/guides/rails-integration/)**
|
|
231
231
|
|
|
232
|
+
### Recipes & Patterns
|
|
233
|
+
|
|
234
|
+
Real-world patterns for common use cases:
|
|
235
|
+
|
|
236
|
+
- **E-Commerce Search** - Semantic product search with filters
|
|
237
|
+
- **Blog Hybrid Search** - Combine semantic + keyword matching
|
|
238
|
+
- **Multi-Tenant SaaS** - Namespace isolation per tenant
|
|
239
|
+
- **RAG Chatbot** - Context retrieval for LLMs
|
|
240
|
+
- **Zero-Downtime Migration** - Switch providers safely
|
|
241
|
+
- **Recommendation Engine** - Find similar items
|
|
242
|
+
|
|
243
|
+
👉 **[Browse all Recipes & Patterns](https://vectra-docs.netlify.app/guides/recipes/)**
|
|
244
|
+
|
|
232
245
|
## Production Patterns
|
|
233
246
|
|
|
234
247
|
Vectra includes 7 production-ready patterns out of the box:
|
data/docs/_layouts/default.html
CHANGED
|
@@ -40,6 +40,7 @@
|
|
|
40
40
|
</button>
|
|
41
41
|
<ul class="tma-nav__menu" id="nav-menu">
|
|
42
42
|
<li><a href="{{ site.baseurl }}/guides/getting-started" class="tma-nav__link">Getting Started</a></li>
|
|
43
|
+
<li><a href="{{ site.baseurl }}/guides/recipes" class="tma-nav__link">Recipes</a></li>
|
|
43
44
|
<li><a href="{{ site.baseurl }}/providers" class="tma-nav__link">Providers</a></li>
|
|
44
45
|
<li><a href="{{ site.baseurl }}/api/overview" class="tma-nav__link">API</a></li>
|
|
45
46
|
<li><a href="{{ site.baseurl }}/examples" class="tma-nav__link">Examples</a></li>
|
data/docs/_layouts/home.html
CHANGED
|
@@ -40,6 +40,7 @@
|
|
|
40
40
|
</button>
|
|
41
41
|
<ul class="tma-nav__menu" id="nav-menu">
|
|
42
42
|
<li><a href="{{ site.baseurl }}/guides/getting-started" class="tma-nav__link">Getting Started</a></li>
|
|
43
|
+
<li><a href="{{ site.baseurl }}/guides/recipes" class="tma-nav__link">Recipes</a></li>
|
|
43
44
|
<li><a href="{{ site.baseurl }}/providers" class="tma-nav__link">Providers</a></li>
|
|
44
45
|
<li><a href="{{ site.baseurl }}/api/overview" class="tma-nav__link">API</a></li>
|
|
45
46
|
<li><a href="{{ site.baseurl }}/examples" class="tma-nav__link">Examples</a></li>
|
|
@@ -568,6 +569,50 @@
|
|
|
568
569
|
</div>
|
|
569
570
|
</section>
|
|
570
571
|
|
|
572
|
+
<!-- Recipes Section -->
|
|
573
|
+
<section class="tma-recipes">
|
|
574
|
+
<div class="tma-section-header">
|
|
575
|
+
<h2 class="tma-section-header__title">Recipes & Patterns</h2>
|
|
576
|
+
<p class="tma-section-header__subtitle">Real-world patterns for common use cases</p>
|
|
577
|
+
</div>
|
|
578
|
+
|
|
579
|
+
<div class="tma-recipes__grid">
|
|
580
|
+
<a href="{{ site.baseurl }}/guides/recipes/#e-commerce-semantic-product-search-with-filters" class="tma-recipe-card">
|
|
581
|
+
<h3 class="tma-recipe-card__title">🛒 E-Commerce Search</h3>
|
|
582
|
+
<p class="tma-recipe-card__description">Semantic product search with filters for category, price, and availability</p>
|
|
583
|
+
</a>
|
|
584
|
+
|
|
585
|
+
<a href="{{ site.baseurl }}/guides/recipes/#blog-hybrid-search-semantic--keyword" class="tma-recipe-card">
|
|
586
|
+
<h3 class="tma-recipe-card__title">📝 Blog Hybrid Search</h3>
|
|
587
|
+
<p class="tma-recipe-card__description">Combine semantic understanding with exact keyword matching</p>
|
|
588
|
+
</a>
|
|
589
|
+
|
|
590
|
+
<a href="{{ site.baseurl }}/guides/recipes/#multi-tenant-saas-namespace-isolation" class="tma-recipe-card">
|
|
591
|
+
<h3 class="tma-recipe-card__title">🏢 Multi-Tenant SaaS</h3>
|
|
592
|
+
<p class="tma-recipe-card__description">Namespace isolation per tenant with a single index</p>
|
|
593
|
+
</a>
|
|
594
|
+
|
|
595
|
+
<a href="{{ site.baseurl }}/guides/recipes/#rag-chatbot-context-retrieval" class="tma-recipe-card">
|
|
596
|
+
<h3 class="tma-recipe-card__title">🤖 RAG Chatbot</h3>
|
|
597
|
+
<p class="tma-recipe-card__description">Retrieve relevant context chunks for LLM integration</p>
|
|
598
|
+
</a>
|
|
599
|
+
|
|
600
|
+
<a href="{{ site.baseurl }}/guides/recipes/#zero-downtime-provider-migration" class="tma-recipe-card">
|
|
601
|
+
<h3 class="tma-recipe-card__title">🔄 Provider Migration</h3>
|
|
602
|
+
<p class="tma-recipe-card__description">Zero-downtime migration between providers</p>
|
|
603
|
+
</a>
|
|
604
|
+
|
|
605
|
+
<a href="{{ site.baseurl }}/guides/recipes/#recommendation-engine-similar-items" class="tma-recipe-card">
|
|
606
|
+
<h3 class="tma-recipe-card__title">💡 Recommendations</h3>
|
|
607
|
+
<p class="tma-recipe-card__description">Find similar items based on user behavior</p>
|
|
608
|
+
</a>
|
|
609
|
+
</div>
|
|
610
|
+
|
|
611
|
+
<div style="text-align: center; margin-top: var(--tma-spacing-lg);">
|
|
612
|
+
<a href="{{ site.baseurl }}/guides/recipes" class="tma-btn tma-btn--secondary">View All Recipes →</a>
|
|
613
|
+
</div>
|
|
614
|
+
</section>
|
|
615
|
+
|
|
571
616
|
<!-- Quick Start Code Section -->
|
|
572
617
|
<section class="tma-code-section">
|
|
573
618
|
<div class="tma-section-header">
|
data/docs/_layouts/page.html
CHANGED
|
@@ -40,6 +40,7 @@
|
|
|
40
40
|
</button>
|
|
41
41
|
<ul class="tma-nav__menu" id="nav-menu">
|
|
42
42
|
<li><a href="{{ site.baseurl }}/guides/getting-started" class="tma-nav__link">Getting Started</a></li>
|
|
43
|
+
<li><a href="{{ site.baseurl }}/guides/recipes" class="tma-nav__link">Recipes</a></li>
|
|
43
44
|
<li><a href="{{ site.baseurl }}/providers" class="tma-nav__link">Providers</a></li>
|
|
44
45
|
<li><a href="{{ site.baseurl }}/api/overview" class="tma-nav__link">API</a></li>
|
|
45
46
|
<li><a href="{{ site.baseurl }}/examples" class="tma-nav__link">Examples</a></li>
|
|
@@ -0,0 +1,387 @@
|
|
|
1
|
+
---
|
|
2
|
+
layout: page
|
|
3
|
+
title: API Cheatsheet
|
|
4
|
+
permalink: /api/cheatsheet/
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# API Cheatsheet
|
|
8
|
+
|
|
9
|
+
Quick reference for the most important Vectra APIs.
|
|
10
|
+
|
|
11
|
+
> For full details, see the [API Overview](/api/overview/) and provider guides.
|
|
12
|
+
|
|
13
|
+
## Core Client
|
|
14
|
+
|
|
15
|
+
### Initialize Client
|
|
16
|
+
|
|
17
|
+
```ruby
|
|
18
|
+
require 'vectra'
|
|
19
|
+
|
|
20
|
+
client = Vectra::Client.new(
|
|
21
|
+
provider: :qdrant, # :pinecone, :qdrant, :weaviate, :pgvector, :memory
|
|
22
|
+
api_key: ENV['QDRANT_API_KEY'],
|
|
23
|
+
host: 'http://localhost:6333',
|
|
24
|
+
environment: 'us-west-4' # For Pinecone
|
|
25
|
+
)
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Or use shortcuts:
|
|
29
|
+
|
|
30
|
+
```ruby
|
|
31
|
+
client = Vectra.qdrant(host: 'http://localhost:6333')
|
|
32
|
+
client = Vectra.pinecone(api_key: ENV['PINECONE_API_KEY'], environment: 'us-west-4')
|
|
33
|
+
client = Vectra.pgvector(connection_url: ENV['DATABASE_URL'])
|
|
34
|
+
client = Vectra.memory # In-memory (testing only)
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
You can also set a **default index and namespace**:
|
|
38
|
+
|
|
39
|
+
```ruby
|
|
40
|
+
client = Vectra::Client.new(
|
|
41
|
+
provider: :qdrant,
|
|
42
|
+
host: 'http://localhost:6333',
|
|
43
|
+
index: 'products',
|
|
44
|
+
namespace: 'tenant-1'
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
# Now index and namespace can be omitted
|
|
48
|
+
client.upsert(vectors: [...])
|
|
49
|
+
client.query(vector: query_embedding, top_k: 10)
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### Upsert
|
|
53
|
+
|
|
54
|
+
```ruby
|
|
55
|
+
client.upsert(
|
|
56
|
+
index: 'documents',
|
|
57
|
+
vectors: [
|
|
58
|
+
{
|
|
59
|
+
id: 'doc-1',
|
|
60
|
+
values: embedding_array,
|
|
61
|
+
metadata: { title: 'Hello World', category: 'docs' }
|
|
62
|
+
}
|
|
63
|
+
],
|
|
64
|
+
namespace: 'default' # optional
|
|
65
|
+
)
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### Query (similarity search)
|
|
69
|
+
|
|
70
|
+
```ruby
|
|
71
|
+
results = client.query(
|
|
72
|
+
index: 'documents',
|
|
73
|
+
vector: query_embedding,
|
|
74
|
+
top_k: 10,
|
|
75
|
+
filter: { category: 'docs' },
|
|
76
|
+
namespace: 'default',
|
|
77
|
+
include_values: false,
|
|
78
|
+
include_metadata: true
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
results.each do |match|
|
|
82
|
+
puts "#{match.id} (score=#{match.score.round(3)}): #{match.metadata['title']}"
|
|
83
|
+
end
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### Hybrid Search (semantic + keyword)
|
|
87
|
+
|
|
88
|
+
```ruby
|
|
89
|
+
results = client.hybrid_search(
|
|
90
|
+
index: 'documents',
|
|
91
|
+
vector: query_embedding,
|
|
92
|
+
text: 'ruby vector search',
|
|
93
|
+
alpha: 0.7, # 70% semantic, 30% keyword
|
|
94
|
+
top_k: 10,
|
|
95
|
+
filter: { category: 'blog' }
|
|
96
|
+
)
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Supported providers: Qdrant ✅, Weaviate ✅, pgvector ✅, Pinecone ⚠️
|
|
100
|
+
|
|
101
|
+
### Fetch
|
|
102
|
+
|
|
103
|
+
```ruby
|
|
104
|
+
vectors = client.fetch(
|
|
105
|
+
index: 'documents',
|
|
106
|
+
ids: ['doc-1', 'doc-2'],
|
|
107
|
+
namespace: 'default'
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
vectors['doc-1'].values # => [0.1, 0.2, ...]
|
|
111
|
+
vectors['doc-1'].metadata # => { 'title' => 'Hello World' }
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### Update
|
|
115
|
+
|
|
116
|
+
```ruby
|
|
117
|
+
client.update(
|
|
118
|
+
index: 'documents',
|
|
119
|
+
id: 'doc-1',
|
|
120
|
+
metadata: { category: 'guides' }
|
|
121
|
+
)
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### Delete
|
|
125
|
+
|
|
126
|
+
```ruby
|
|
127
|
+
# By IDs
|
|
128
|
+
client.delete(index: 'documents', ids: ['doc-1', 'doc-2'])
|
|
129
|
+
|
|
130
|
+
# By filter
|
|
131
|
+
client.delete(index: 'documents', filter: { category: 'old' })
|
|
132
|
+
|
|
133
|
+
# Delete all in namespace
|
|
134
|
+
client.delete(index: 'documents', delete_all: true)
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
### Index Management
|
|
138
|
+
|
|
139
|
+
```ruby
|
|
140
|
+
# Create index
|
|
141
|
+
client.create_index(name: 'documents', dimension: 384, metric: 'cosine')
|
|
142
|
+
|
|
143
|
+
# List indexes
|
|
144
|
+
indexes = client.list_indexes
|
|
145
|
+
# => [{ name: 'documents', dimension: 384, ... }]
|
|
146
|
+
|
|
147
|
+
# Describe index
|
|
148
|
+
info = client.describe_index(index: 'documents')
|
|
149
|
+
# => { name: 'documents', dimension: 384, metric: 'cosine', status: 'ready' }
|
|
150
|
+
|
|
151
|
+
# Get stats
|
|
152
|
+
stats = client.stats(index: 'documents')
|
|
153
|
+
# => { total_vector_count: 1000, dimension: 384, namespaces: { ... } }
|
|
154
|
+
|
|
155
|
+
# List namespaces
|
|
156
|
+
namespaces = client.list_namespaces(index: 'documents')
|
|
157
|
+
# => ['tenant-1', 'tenant-2']
|
|
158
|
+
|
|
159
|
+
# Delete index
|
|
160
|
+
client.delete_index(name: 'old-index')
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
**Note:** `create_index` and `delete_index` are supported by Pinecone, Qdrant, and pgvector. Memory and Weaviate providers don't support these operations.
|
|
164
|
+
|
|
165
|
+
---
|
|
166
|
+
|
|
167
|
+
## Health & Monitoring
|
|
168
|
+
|
|
169
|
+
### Health Check
|
|
170
|
+
|
|
171
|
+
```ruby
|
|
172
|
+
if client.healthy?
|
|
173
|
+
puts 'Vectra provider is healthy'
|
|
174
|
+
else
|
|
175
|
+
puts 'Vectra provider is NOT healthy'
|
|
176
|
+
end
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
### Ping (with latency)
|
|
180
|
+
|
|
181
|
+
```ruby
|
|
182
|
+
status = client.ping
|
|
183
|
+
# => { healthy: true, provider: :qdrant, latency_ms: 23.4 }
|
|
184
|
+
|
|
185
|
+
puts "Provider: #{status[:provider]}, latency: #{status[:latency_ms]}ms"
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
### Rails Health Endpoint (Example)
|
|
189
|
+
|
|
190
|
+
```ruby
|
|
191
|
+
# config/routes.rb
|
|
192
|
+
Rails.application.routes.draw do
|
|
193
|
+
get '/health/vectra', to: 'health#vectra'
|
|
194
|
+
end
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
```ruby
|
|
198
|
+
# app/controllers/health_controller.rb
|
|
199
|
+
class HealthController < ApplicationController
|
|
200
|
+
def vectra
|
|
201
|
+
client = Vectra::Client.new
|
|
202
|
+
|
|
203
|
+
status = client.ping
|
|
204
|
+
if status[:healthy]
|
|
205
|
+
render json: { status: 'ok', provider: status[:provider], latency_ms: status[:latency_ms] }
|
|
206
|
+
else
|
|
207
|
+
render json: { status: 'unhealthy' }, status: :service_unavailable
|
|
208
|
+
end
|
|
209
|
+
rescue => e
|
|
210
|
+
render json: { status: 'error', error: e.message }, status: :service_unavailable
|
|
211
|
+
end
|
|
212
|
+
end
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
Use this endpoint for Kubernetes / load balancer health checks.
|
|
216
|
+
|
|
217
|
+
---
|
|
218
|
+
|
|
219
|
+
## Vector Helpers
|
|
220
|
+
|
|
221
|
+
### Normalize Embeddings
|
|
222
|
+
|
|
223
|
+
```ruby
|
|
224
|
+
embedding = openai_response['data'][0]['embedding']
|
|
225
|
+
normalized = Vectra::Vector.normalize(embedding, type: :l2) # or :l1
|
|
226
|
+
|
|
227
|
+
client.upsert(
|
|
228
|
+
index: 'documents',
|
|
229
|
+
vectors: [
|
|
230
|
+
{ id: 'doc-1', values: normalized, metadata: { title: 'Hello' } }
|
|
231
|
+
]
|
|
232
|
+
)
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
### In-Place Normalization
|
|
236
|
+
|
|
237
|
+
```ruby
|
|
238
|
+
vector = Vectra::Vector.new(id: 'doc-1', values: embedding)
|
|
239
|
+
vector.normalize! # Mutates values
|
|
240
|
+
client.upsert(index: 'documents', vectors: [vector])
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
### Embedding Cache Helper
|
|
244
|
+
|
|
245
|
+
```ruby
|
|
246
|
+
cache = Vectra::Cache.new(ttl: 600, max_size: 1000)
|
|
247
|
+
|
|
248
|
+
embedding = Vectra::Embeddings.fetch(
|
|
249
|
+
cache: cache,
|
|
250
|
+
model_name: "Product",
|
|
251
|
+
id: product.id,
|
|
252
|
+
input: product.description,
|
|
253
|
+
field: :description
|
|
254
|
+
) do
|
|
255
|
+
EmbeddingService.generate(product.description)
|
|
256
|
+
end
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
---
|
|
260
|
+
|
|
261
|
+
## Batch Operations
|
|
262
|
+
|
|
263
|
+
### Simple Batch Upsert
|
|
264
|
+
|
|
265
|
+
```ruby
|
|
266
|
+
vectors = products.map do |product|
|
|
267
|
+
{
|
|
268
|
+
id: product.id.to_s,
|
|
269
|
+
values: product.embedding,
|
|
270
|
+
metadata: { name: product.name, price: product.price }
|
|
271
|
+
}
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
client.upsert(index: 'products', vectors: vectors)
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
### Batch with Progress Callback
|
|
278
|
+
|
|
279
|
+
```ruby
|
|
280
|
+
Vectra::Batch.upsert(
|
|
281
|
+
client: client,
|
|
282
|
+
index: 'products',
|
|
283
|
+
vectors: vectors,
|
|
284
|
+
batch_size: 100,
|
|
285
|
+
on_progress: ->(batch_index, total_batches, batch_count) do
|
|
286
|
+
puts "Batch #{batch_index + 1}/#{total_batches} (#{batch_count} vectors)"
|
|
287
|
+
end
|
|
288
|
+
)
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
### Batch Query (Multiple Vectors)
|
|
292
|
+
|
|
293
|
+
```ruby
|
|
294
|
+
batch = Vectra::Batch.new(client, concurrency: 4)
|
|
295
|
+
|
|
296
|
+
# Find similar items for multiple products at once
|
|
297
|
+
product_embeddings = products.map(&:embedding)
|
|
298
|
+
results = batch.query_async(
|
|
299
|
+
index: 'products',
|
|
300
|
+
vectors: product_embeddings,
|
|
301
|
+
top_k: 5
|
|
302
|
+
)
|
|
303
|
+
|
|
304
|
+
# Each result corresponds to one product
|
|
305
|
+
results.each_with_index do |result, i|
|
|
306
|
+
puts "Similar to product #{i}: #{result.ids}"
|
|
307
|
+
end
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
---
|
|
311
|
+
|
|
312
|
+
## ActiveRecord + has_vector DSL
|
|
313
|
+
|
|
314
|
+
### Basic Setup
|
|
315
|
+
|
|
316
|
+
```ruby
|
|
317
|
+
class Document < ApplicationRecord
|
|
318
|
+
include Vectra::ActiveRecord
|
|
319
|
+
|
|
320
|
+
has_vector :embedding,
|
|
321
|
+
provider: :qdrant,
|
|
322
|
+
index: 'documents',
|
|
323
|
+
dimension: 1536,
|
|
324
|
+
metadata_fields: [:title, :category]
|
|
325
|
+
end
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
### Auto-Index on Save
|
|
329
|
+
|
|
330
|
+
```ruby
|
|
331
|
+
doc = Document.create!(
|
|
332
|
+
title: 'Hello World',
|
|
333
|
+
category: 'docs',
|
|
334
|
+
embedding: EmbeddingService.generate('Hello World')
|
|
335
|
+
)
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
### Vector Search from Model
|
|
339
|
+
|
|
340
|
+
```ruby
|
|
341
|
+
results = Document.vector_search(
|
|
342
|
+
embedding: EmbeddingService.generate('vector search in ruby'),
|
|
343
|
+
limit: 10,
|
|
344
|
+
filter: { category: 'docs' }
|
|
345
|
+
)
|
|
346
|
+
|
|
347
|
+
results.each do |doc|
|
|
348
|
+
puts doc.title
|
|
349
|
+
end
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
### Reindex All Records
|
|
353
|
+
|
|
354
|
+
```ruby
|
|
355
|
+
# Reindex all documents that already have embeddings
|
|
356
|
+
processed = Document.reindex_vectors(
|
|
357
|
+
scope: Document.where.not(embedding: nil),
|
|
358
|
+
batch_size: 500
|
|
359
|
+
)
|
|
360
|
+
|
|
361
|
+
puts "Reindexed #{processed} documents"
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
---
|
|
365
|
+
|
|
366
|
+
## Error Handling
|
|
367
|
+
|
|
368
|
+
```ruby
|
|
369
|
+
begin
|
|
370
|
+
client.query(index: 'missing', vector: [0.1, 0.2, 0.3])
|
|
371
|
+
rescue Vectra::NotFoundError => e
|
|
372
|
+
Rails.logger.warn("Index not found: #{e.message}")
|
|
373
|
+
rescue Vectra::RateLimitError => e
|
|
374
|
+
Rails.logger.error("Rate limited: #{e.message}")
|
|
375
|
+
rescue Vectra::Error => e
|
|
376
|
+
Rails.logger.error("Vectra error: #{e.message}")
|
|
377
|
+
end
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
---
|
|
381
|
+
|
|
382
|
+
## See Also
|
|
383
|
+
|
|
384
|
+
- [API Overview](/api/overview/)
|
|
385
|
+
- [Recipes & Patterns](/guides/recipes/)
|
|
386
|
+
- [Rails Integration Guide](/guides/rails-integration/)
|
|
387
|
+
- [Memory Provider (Testing)](/providers/memory/)
|