vectra-client 0.2.1 → 0.3.0
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/.rubocop.yml +77 -37
- data/CHANGELOG.md +49 -6
- data/README.md +52 -393
- data/docs/Gemfile +9 -0
- data/docs/_config.yml +37 -0
- data/docs/_layouts/default.html +14 -0
- data/docs/_layouts/home.html +187 -0
- data/docs/_layouts/page.html +82 -0
- data/docs/_site/api/overview/index.html +145 -0
- data/docs/_site/assets/main.css +649 -0
- data/docs/_site/assets/main.css.map +1 -0
- data/docs/_site/assets/minima-social-icons.svg +33 -0
- data/docs/_site/assets/style.css +295 -0
- data/docs/_site/community/contributing/index.html +110 -0
- data/docs/_site/examples/basic-usage/index.html +117 -0
- data/docs/_site/examples/index.html +58 -0
- data/docs/_site/feed.xml +1 -0
- data/docs/_site/guides/getting-started/index.html +106 -0
- data/docs/_site/guides/installation/index.html +82 -0
- data/docs/_site/index.html +92 -0
- data/docs/_site/providers/index.html +119 -0
- data/docs/_site/providers/pgvector/index.html +155 -0
- data/docs/_site/providers/pinecone/index.html +121 -0
- data/docs/_site/providers/qdrant/index.html +124 -0
- data/docs/_site/providers/weaviate/index.html +123 -0
- data/docs/_site/robots.txt +1 -0
- data/docs/_site/sitemap.xml +39 -0
- data/docs/api/overview.md +126 -0
- data/docs/assets/style.css +927 -0
- data/docs/community/contributing.md +89 -0
- data/docs/examples/basic-usage.md +102 -0
- data/docs/examples/index.md +54 -0
- data/docs/guides/getting-started.md +90 -0
- data/docs/guides/installation.md +67 -0
- data/docs/guides/performance.md +200 -0
- data/docs/index.md +37 -0
- data/docs/providers/index.md +81 -0
- data/docs/providers/pgvector.md +95 -0
- data/docs/providers/pinecone.md +72 -0
- data/docs/providers/qdrant.md +73 -0
- data/docs/providers/weaviate.md +72 -0
- data/lib/vectra/batch.rb +148 -0
- data/lib/vectra/cache.rb +261 -0
- data/lib/vectra/configuration.rb +6 -1
- data/lib/vectra/pool.rb +256 -0
- data/lib/vectra/streaming.rb +153 -0
- data/lib/vectra/version.rb +1 -1
- data/lib/vectra.rb +4 -0
- data/netlify.toml +12 -0
- metadata +58 -5
- data/IMPLEMENTATION_GUIDE.md +0 -686
- data/NEW_FEATURES_v0.2.0.md +0 -459
- data/RELEASE_CHECKLIST_v0.2.0.md +0 -383
- data/USAGE_EXAMPLES.md +0 -787
data/IMPLEMENTATION_GUIDE.md
DELETED
|
@@ -1,686 +0,0 @@
|
|
|
1
|
-
# 🚀 VECTRA IMPLEMENTATION GUIDE
|
|
2
|
-
|
|
3
|
-
Step-by-step guide to implementing new features in your Vectra gem.
|
|
4
|
-
|
|
5
|
-
## Table of Contents
|
|
6
|
-
|
|
7
|
-
1. [Instrumentation & Monitoring](#1-instrumentation--monitoring)
|
|
8
|
-
2. [Rails Generator](#2-rails-generator)
|
|
9
|
-
3. [ActiveRecord Integration](#3-activerecord-integration)
|
|
10
|
-
4. [Retry Logic for pgvector](#4-retry-logic-for-pgvector)
|
|
11
|
-
5. [Performance Benchmarks](#5-performance-benchmarks)
|
|
12
|
-
6. [Testing New Features](#6-testing-new-features)
|
|
13
|
-
|
|
14
|
-
---
|
|
15
|
-
|
|
16
|
-
## 1. Instrumentation & Monitoring
|
|
17
|
-
|
|
18
|
-
### What was added:
|
|
19
|
-
|
|
20
|
-
- **Core:** `lib/vectra/instrumentation.rb` - Event system for tracking operations
|
|
21
|
-
- **New Relic:** `lib/vectra/instrumentation/new_relic.rb`
|
|
22
|
-
- **Datadog:** `lib/vectra/instrumentation/datadog.rb`
|
|
23
|
-
|
|
24
|
-
### How to use:
|
|
25
|
-
|
|
26
|
-
#### Enable instrumentation in configuration:
|
|
27
|
-
|
|
28
|
-
```ruby
|
|
29
|
-
# config/initializers/vectra.rb
|
|
30
|
-
Vectra.configure do |config|
|
|
31
|
-
config.instrumentation = true
|
|
32
|
-
end
|
|
33
|
-
```
|
|
34
|
-
|
|
35
|
-
#### New Relic:
|
|
36
|
-
|
|
37
|
-
```ruby
|
|
38
|
-
require 'vectra/instrumentation/new_relic'
|
|
39
|
-
Vectra::Instrumentation::NewRelic.setup!
|
|
40
|
-
```
|
|
41
|
-
|
|
42
|
-
Automatically tracks:
|
|
43
|
-
- `Custom/Vectra/pgvector/query/duration`
|
|
44
|
-
- `Custom/Vectra/pgvector/upsert/success`
|
|
45
|
-
- etc.
|
|
46
|
-
|
|
47
|
-
#### Datadog:
|
|
48
|
-
|
|
49
|
-
```ruby
|
|
50
|
-
require 'vectra/instrumentation/datadog'
|
|
51
|
-
Vectra::Instrumentation::Datadog.setup!(
|
|
52
|
-
host: 'localhost',
|
|
53
|
-
port: 8125
|
|
54
|
-
)
|
|
55
|
-
```
|
|
56
|
-
|
|
57
|
-
Sends metrics:
|
|
58
|
-
- `vectra.operation.duration` (timing)
|
|
59
|
-
- `vectra.operation.count` (counter)
|
|
60
|
-
- `vectra.operation.error` (counter)
|
|
61
|
-
|
|
62
|
-
#### Custom instrumentation:
|
|
63
|
-
|
|
64
|
-
```ruby
|
|
65
|
-
Vectra.on_operation do |event|
|
|
66
|
-
puts "#{event.operation} took #{event.duration}ms"
|
|
67
|
-
|
|
68
|
-
if event.failure?
|
|
69
|
-
Sentry.capture_exception(event.error)
|
|
70
|
-
end
|
|
71
|
-
|
|
72
|
-
# event.operation => :query, :upsert, etc.
|
|
73
|
-
# event.provider => :pgvector, :pinecone
|
|
74
|
-
# event.index => 'documents'
|
|
75
|
-
# event.duration => 123.45 (ms)
|
|
76
|
-
# event.metadata => { result_count: 10, vector_count: 1 }
|
|
77
|
-
# event.error => Exception or nil
|
|
78
|
-
end
|
|
79
|
-
```
|
|
80
|
-
|
|
81
|
-
### Integration into providers:
|
|
82
|
-
|
|
83
|
-
To add instrumentation to a provider operation, wrap it:
|
|
84
|
-
|
|
85
|
-
```ruby
|
|
86
|
-
# In provider method:
|
|
87
|
-
def upsert(index:, vectors:, namespace: nil)
|
|
88
|
-
Instrumentation.instrument(
|
|
89
|
-
operation: :upsert,
|
|
90
|
-
provider: provider_name,
|
|
91
|
-
index: index,
|
|
92
|
-
metadata: { vector_count: vectors.size }
|
|
93
|
-
) do
|
|
94
|
-
# Actual upsert logic
|
|
95
|
-
perform_upsert(index, vectors, namespace)
|
|
96
|
-
end
|
|
97
|
-
end
|
|
98
|
-
```
|
|
99
|
-
|
|
100
|
-
---
|
|
101
|
-
|
|
102
|
-
## 2. Rails Generator
|
|
103
|
-
|
|
104
|
-
### What was added:
|
|
105
|
-
|
|
106
|
-
- **Generator:** `lib/generators/vectra/install_generator.rb`
|
|
107
|
-
- **Templates:**
|
|
108
|
-
- `lib/generators/vectra/templates/vectra.rb` - Initializer template
|
|
109
|
-
- `lib/generators/vectra/templates/enable_pgvector_extension.rb` - Migration template
|
|
110
|
-
|
|
111
|
-
### How to use:
|
|
112
|
-
|
|
113
|
-
```bash
|
|
114
|
-
# Basic install
|
|
115
|
-
rails generate vectra:install
|
|
116
|
-
|
|
117
|
-
# With options
|
|
118
|
-
rails generate vectra:install --provider=pgvector --instrumentation=true
|
|
119
|
-
rails generate vectra:install --provider=pinecone --api-key=xxx
|
|
120
|
-
```
|
|
121
|
-
|
|
122
|
-
### What it does:
|
|
123
|
-
|
|
124
|
-
1. Creates `config/initializers/vectra.rb`
|
|
125
|
-
2. For pgvector: Creates migration to enable `vector` extension
|
|
126
|
-
3. Shows setup instructions based on provider
|
|
127
|
-
|
|
128
|
-
### Customization:
|
|
129
|
-
|
|
130
|
-
To add a new provider to generator:
|
|
131
|
-
|
|
132
|
-
1. Add to `templates/vectra.rb`:
|
|
133
|
-
|
|
134
|
-
```erb
|
|
135
|
-
<%- elsif options[:provider] == 'my_provider' -%>
|
|
136
|
-
# My Provider credentials
|
|
137
|
-
config.api_key = Rails.application.credentials.dig(:my_provider, :api_key)
|
|
138
|
-
config.host = Rails.application.credentials.dig(:my_provider, :host)
|
|
139
|
-
<%- end -%>
|
|
140
|
-
```
|
|
141
|
-
|
|
142
|
-
2. Add instructions in `install_generator.rb`:
|
|
143
|
-
|
|
144
|
-
```ruby
|
|
145
|
-
def show_my_provider_instructions
|
|
146
|
-
say " 2. Add to credentials:", :yellow
|
|
147
|
-
say " my_provider:", :cyan
|
|
148
|
-
say " api_key: your_api_key_here", :cyan
|
|
149
|
-
end
|
|
150
|
-
```
|
|
151
|
-
|
|
152
|
-
---
|
|
153
|
-
|
|
154
|
-
## 3. ActiveRecord Integration
|
|
155
|
-
|
|
156
|
-
### What was added:
|
|
157
|
-
|
|
158
|
-
- **Module:** `lib/vectra/active_record.rb`
|
|
159
|
-
|
|
160
|
-
### How to use:
|
|
161
|
-
|
|
162
|
-
#### 1. Include in model:
|
|
163
|
-
|
|
164
|
-
```ruby
|
|
165
|
-
class Document < ApplicationRecord
|
|
166
|
-
include Vectra::ActiveRecord
|
|
167
|
-
|
|
168
|
-
has_vector :embedding,
|
|
169
|
-
dimension: 384,
|
|
170
|
-
provider: :pgvector,
|
|
171
|
-
index: 'documents',
|
|
172
|
-
auto_index: true, # Auto-sync on save
|
|
173
|
-
metadata_fields: [:title, :category, :status]
|
|
174
|
-
end
|
|
175
|
-
```
|
|
176
|
-
|
|
177
|
-
#### 2. Generate embeddings:
|
|
178
|
-
|
|
179
|
-
```ruby
|
|
180
|
-
class Document < ApplicationRecord
|
|
181
|
-
include Vectra::ActiveRecord
|
|
182
|
-
|
|
183
|
-
has_vector :embedding, dimension: 384
|
|
184
|
-
|
|
185
|
-
before_validation :generate_embedding, if: :content_changed?
|
|
186
|
-
|
|
187
|
-
private
|
|
188
|
-
|
|
189
|
-
def generate_embedding
|
|
190
|
-
self.embedding = EmbeddingService.generate(content)
|
|
191
|
-
end
|
|
192
|
-
end
|
|
193
|
-
```
|
|
194
|
-
|
|
195
|
-
#### 3. Search:
|
|
196
|
-
|
|
197
|
-
```ruby
|
|
198
|
-
# Vector search (loads AR objects)
|
|
199
|
-
query_vector = EmbeddingService.generate('search query')
|
|
200
|
-
results = Document.vector_search(query_vector, limit: 10)
|
|
201
|
-
|
|
202
|
-
# With filters
|
|
203
|
-
results = Document.vector_search(
|
|
204
|
-
query_vector,
|
|
205
|
-
limit: 10,
|
|
206
|
-
filter: { status: 'published' },
|
|
207
|
-
score_threshold: 0.8
|
|
208
|
-
)
|
|
209
|
-
|
|
210
|
-
# Each result has .vector_score
|
|
211
|
-
results.each do |doc|
|
|
212
|
-
puts "#{doc.title} - Score: #{doc.vector_score}"
|
|
213
|
-
end
|
|
214
|
-
|
|
215
|
-
# Find similar to specific document
|
|
216
|
-
similar_docs = doc.similar(limit: 5, filter: { category: doc.category })
|
|
217
|
-
|
|
218
|
-
# Similar without loading AR objects (just vector results)
|
|
219
|
-
results = Document.vector_search(query_vector, limit: 10, load_records: false)
|
|
220
|
-
# Returns Vectra::QueryResult instead
|
|
221
|
-
```
|
|
222
|
-
|
|
223
|
-
#### 4. Manual control:
|
|
224
|
-
|
|
225
|
-
```ruby
|
|
226
|
-
# Disable auto-index:
|
|
227
|
-
has_vector :embedding, dimension: 384, auto_index: false
|
|
228
|
-
|
|
229
|
-
# Manual operations:
|
|
230
|
-
doc.index_vector! # Index this record
|
|
231
|
-
doc.delete_vector! # Remove from index
|
|
232
|
-
```
|
|
233
|
-
|
|
234
|
-
### How it works:
|
|
235
|
-
|
|
236
|
-
1. **Callbacks:** Hooks into `after_save` and `after_destroy`
|
|
237
|
-
2. **Vector ID:** Generates ID as `{table_name}_{record_id}`
|
|
238
|
-
3. **Metadata:** Extracts specified fields into vector metadata
|
|
239
|
-
4. **Search:** Queries Vectra, then loads AR records by ID
|
|
240
|
-
|
|
241
|
-
### Advanced usage:
|
|
242
|
-
|
|
243
|
-
#### Custom index name:
|
|
244
|
-
|
|
245
|
-
```ruby
|
|
246
|
-
has_vector :embedding, dimension: 384, index: "custom_index_#{Rails.env}"
|
|
247
|
-
```
|
|
248
|
-
|
|
249
|
-
#### Multiple vector attributes:
|
|
250
|
-
|
|
251
|
-
```ruby
|
|
252
|
-
class Product < ApplicationRecord
|
|
253
|
-
include Vectra::ActiveRecord
|
|
254
|
-
|
|
255
|
-
has_vector :title_embedding, dimension: 384, index: 'products_title'
|
|
256
|
-
has_vector :desc_embedding, dimension: 384, index: 'products_desc'
|
|
257
|
-
end
|
|
258
|
-
|
|
259
|
-
# Search either:
|
|
260
|
-
Product.vector_search(query_vector, limit: 10) # Uses first defined
|
|
261
|
-
```
|
|
262
|
-
|
|
263
|
-
#### Background indexing:
|
|
264
|
-
|
|
265
|
-
```ruby
|
|
266
|
-
has_vector :embedding, dimension: 384, auto_index: false
|
|
267
|
-
|
|
268
|
-
after_commit :index_async, on: [:create, :update]
|
|
269
|
-
|
|
270
|
-
def index_async
|
|
271
|
-
IndexVectorJob.perform_later(self.class.name, id)
|
|
272
|
-
end
|
|
273
|
-
|
|
274
|
-
# Job:
|
|
275
|
-
class IndexVectorJob < ApplicationJob
|
|
276
|
-
def perform(model_class, record_id)
|
|
277
|
-
model = model_class.constantize.find(record_id)
|
|
278
|
-
model.index_vector!
|
|
279
|
-
end
|
|
280
|
-
end
|
|
281
|
-
```
|
|
282
|
-
|
|
283
|
-
---
|
|
284
|
-
|
|
285
|
-
## 4. Retry Logic for pgvector
|
|
286
|
-
|
|
287
|
-
### What was added:
|
|
288
|
-
|
|
289
|
-
- **Module:** `lib/vectra/retry.rb`
|
|
290
|
-
|
|
291
|
-
### How to use:
|
|
292
|
-
|
|
293
|
-
#### In pgvector provider:
|
|
294
|
-
|
|
295
|
-
```ruby
|
|
296
|
-
# lib/vectra/providers/pgvector/connection.rb
|
|
297
|
-
module Vectra
|
|
298
|
-
module Providers
|
|
299
|
-
class Pgvector < Base
|
|
300
|
-
include Retry # Add this
|
|
301
|
-
|
|
302
|
-
module Connection
|
|
303
|
-
def execute(sql, params = [])
|
|
304
|
-
# Wrap with retry
|
|
305
|
-
with_retry(max_attempts: 3) do
|
|
306
|
-
connection.exec_params(sql, params)
|
|
307
|
-
end
|
|
308
|
-
rescue PG::Error => e
|
|
309
|
-
handle_pg_error(e)
|
|
310
|
-
end
|
|
311
|
-
end
|
|
312
|
-
end
|
|
313
|
-
end
|
|
314
|
-
end
|
|
315
|
-
```
|
|
316
|
-
|
|
317
|
-
#### Custom retry config:
|
|
318
|
-
|
|
319
|
-
```ruby
|
|
320
|
-
with_retry(
|
|
321
|
-
max_attempts: 5,
|
|
322
|
-
base_delay: 1.0, # Start with 1s
|
|
323
|
-
max_delay: 30.0, # Cap at 30s
|
|
324
|
-
backoff_factor: 2, # 1s, 2s, 4s, 8s, 16s, 30s
|
|
325
|
-
jitter: true # Add ±25% randomness
|
|
326
|
-
) do
|
|
327
|
-
perform_operation
|
|
328
|
-
end
|
|
329
|
-
```
|
|
330
|
-
|
|
331
|
-
### Retryable errors:
|
|
332
|
-
|
|
333
|
-
Automatically retries:
|
|
334
|
-
- `PG::ConnectionBad`
|
|
335
|
-
- `PG::UnableToSend`
|
|
336
|
-
- `PG::TooManyConnections`
|
|
337
|
-
- `PG::SerializationFailure`
|
|
338
|
-
- `PG::DeadlockDetected`
|
|
339
|
-
- `ConnectionPool::TimeoutError`
|
|
340
|
-
- Any error with "timeout" or "connection" in message
|
|
341
|
-
|
|
342
|
-
### How it works:
|
|
343
|
-
|
|
344
|
-
1. **Exponential backoff:** `delay = base_delay * (2 ^ attempt)`
|
|
345
|
-
2. **Jitter:** Adds randomness to prevent thundering herd
|
|
346
|
-
3. **Max delay:** Caps at 30s by default
|
|
347
|
-
4. **Logging:** Logs each retry attempt with delay
|
|
348
|
-
|
|
349
|
-
---
|
|
350
|
-
|
|
351
|
-
## 5. Performance Benchmarks
|
|
352
|
-
|
|
353
|
-
### What was added:
|
|
354
|
-
|
|
355
|
-
- `benchmarks/batch_operations_benchmark.rb`
|
|
356
|
-
- `benchmarks/connection_pooling_benchmark.rb`
|
|
357
|
-
|
|
358
|
-
### How to run:
|
|
359
|
-
|
|
360
|
-
```bash
|
|
361
|
-
# Batch operations
|
|
362
|
-
DATABASE_URL=postgres://localhost/vectra_benchmark \
|
|
363
|
-
ruby benchmarks/batch_operations_benchmark.rb
|
|
364
|
-
|
|
365
|
-
# Connection pooling
|
|
366
|
-
DATABASE_URL=postgres://localhost/vectra_benchmark \
|
|
367
|
-
ruby benchmarks/connection_pooling_benchmark.rb
|
|
368
|
-
```
|
|
369
|
-
|
|
370
|
-
### What they measure:
|
|
371
|
-
|
|
372
|
-
#### Batch operations:
|
|
373
|
-
- **Vector counts:** 100, 500, 1K, 5K, 10K
|
|
374
|
-
- **Batch sizes:** 50, 100, 250, 500
|
|
375
|
-
- **Metrics:** avg time, vectors/sec, batches count
|
|
376
|
-
|
|
377
|
-
Example output:
|
|
378
|
-
```
|
|
379
|
-
1000 vectors:
|
|
380
|
-
Batch size 50: 2.134s avg (468 vectors/sec, 20 batches)
|
|
381
|
-
Batch size 100: 1.892s avg (529 vectors/sec, 10 batches)
|
|
382
|
-
Batch size 250: 1.645s avg (608 vectors/sec, 4 batches)
|
|
383
|
-
Batch size 500: 1.523s avg (657 vectors/sec, 2 batches)
|
|
384
|
-
```
|
|
385
|
-
|
|
386
|
-
#### Connection pooling:
|
|
387
|
-
- **Pool sizes:** 5, 10, 20
|
|
388
|
-
- **Thread counts:** 1, 2, 5, 10, 20
|
|
389
|
-
- **Metrics:** total time, ops/sec, pool availability
|
|
390
|
-
|
|
391
|
-
Example output:
|
|
392
|
-
```
|
|
393
|
-
Pool Size: 10
|
|
394
|
-
1 threads: 5.21s total (9.6 ops/sec) Pool: 9/10 available
|
|
395
|
-
5 threads: 5.34s total (46.8 ops/sec) Pool: 5/10 available
|
|
396
|
-
10 threads: 5.67s total (88.2 ops/sec) Pool: 0/10 available
|
|
397
|
-
```
|
|
398
|
-
|
|
399
|
-
### Creating custom benchmarks:
|
|
400
|
-
|
|
401
|
-
```ruby
|
|
402
|
-
# benchmarks/my_custom_benchmark.rb
|
|
403
|
-
require 'bundler/setup'
|
|
404
|
-
require 'vectra'
|
|
405
|
-
require 'benchmark'
|
|
406
|
-
|
|
407
|
-
client = Vectra.pgvector(connection_url: ENV['DATABASE_URL'])
|
|
408
|
-
|
|
409
|
-
# Setup
|
|
410
|
-
client.provider.create_index(name: 'test', dimension: 384)
|
|
411
|
-
|
|
412
|
-
# Benchmark
|
|
413
|
-
time = Benchmark.realtime do
|
|
414
|
-
# Your code here
|
|
415
|
-
end
|
|
416
|
-
|
|
417
|
-
puts "Took: #{time.round(2)}s"
|
|
418
|
-
|
|
419
|
-
# Cleanup
|
|
420
|
-
client.provider.delete_index(name: 'test')
|
|
421
|
-
```
|
|
422
|
-
|
|
423
|
-
---
|
|
424
|
-
|
|
425
|
-
## 6. Testing New Features
|
|
426
|
-
|
|
427
|
-
### Instrumentation tests:
|
|
428
|
-
|
|
429
|
-
```ruby
|
|
430
|
-
# spec/vectra/instrumentation_spec.rb
|
|
431
|
-
RSpec.describe Vectra::Instrumentation do
|
|
432
|
-
before { described_class.clear_handlers! }
|
|
433
|
-
|
|
434
|
-
it "calls handlers on operations" do
|
|
435
|
-
events = []
|
|
436
|
-
Vectra.on_operation { |event| events << event }
|
|
437
|
-
|
|
438
|
-
Vectra::Instrumentation.instrument(
|
|
439
|
-
operation: :test,
|
|
440
|
-
provider: :pgvector,
|
|
441
|
-
index: 'test_index'
|
|
442
|
-
) do
|
|
443
|
-
sleep 0.1
|
|
444
|
-
end
|
|
445
|
-
|
|
446
|
-
expect(events.size).to eq(1)
|
|
447
|
-
expect(events.first.operation).to eq(:test)
|
|
448
|
-
expect(events.first.duration).to be >= 100
|
|
449
|
-
expect(events.first.success?).to be true
|
|
450
|
-
end
|
|
451
|
-
|
|
452
|
-
it "captures errors" do
|
|
453
|
-
events = []
|
|
454
|
-
Vectra.on_operation { |event| events << event }
|
|
455
|
-
|
|
456
|
-
expect {
|
|
457
|
-
Vectra::Instrumentation.instrument(
|
|
458
|
-
operation: :test,
|
|
459
|
-
provider: :pgvector,
|
|
460
|
-
index: 'test'
|
|
461
|
-
) do
|
|
462
|
-
raise StandardError, "Test error"
|
|
463
|
-
end
|
|
464
|
-
}.to raise_error(StandardError)
|
|
465
|
-
|
|
466
|
-
expect(events.first.failure?).to be true
|
|
467
|
-
expect(events.first.error.message).to eq("Test error")
|
|
468
|
-
end
|
|
469
|
-
end
|
|
470
|
-
```
|
|
471
|
-
|
|
472
|
-
### ActiveRecord integration tests:
|
|
473
|
-
|
|
474
|
-
```ruby
|
|
475
|
-
# spec/vectra/active_record_spec.rb
|
|
476
|
-
RSpec.describe Vectra::ActiveRecord do
|
|
477
|
-
before do
|
|
478
|
-
# Create test model
|
|
479
|
-
stub_const('TestDocument', Class.new(ApplicationRecord) do
|
|
480
|
-
self.table_name = 'documents'
|
|
481
|
-
include Vectra::ActiveRecord
|
|
482
|
-
|
|
483
|
-
has_vector :embedding,
|
|
484
|
-
dimension: 384,
|
|
485
|
-
index: 'test_docs',
|
|
486
|
-
metadata_fields: [:title, :category]
|
|
487
|
-
end)
|
|
488
|
-
end
|
|
489
|
-
|
|
490
|
-
it "auto-indexes on create" do
|
|
491
|
-
expect_any_instance_of(Vectra::Client).to receive(:upsert)
|
|
492
|
-
|
|
493
|
-
TestDocument.create!(
|
|
494
|
-
title: 'Test',
|
|
495
|
-
embedding: Array.new(384) { rand }
|
|
496
|
-
)
|
|
497
|
-
end
|
|
498
|
-
|
|
499
|
-
it "searches vectors" do
|
|
500
|
-
query_vector = Array.new(384) { rand }
|
|
501
|
-
|
|
502
|
-
allow_any_instance_of(Vectra::Client).to receive(:query).and_return(
|
|
503
|
-
Vectra::QueryResult.from_response(matches: [
|
|
504
|
-
{ id: 'test_docs_1', score: 0.95, metadata: {} }
|
|
505
|
-
])
|
|
506
|
-
)
|
|
507
|
-
|
|
508
|
-
results = TestDocument.vector_search(query_vector, limit: 10)
|
|
509
|
-
expect(results.size).to be > 0
|
|
510
|
-
end
|
|
511
|
-
end
|
|
512
|
-
```
|
|
513
|
-
|
|
514
|
-
### Retry logic tests:
|
|
515
|
-
|
|
516
|
-
```ruby
|
|
517
|
-
# spec/vectra/retry_spec.rb
|
|
518
|
-
RSpec.describe Vectra::Retry do
|
|
519
|
-
let(:config) { double(max_retries: 3, retry_delay: 0.01, logger: nil) }
|
|
520
|
-
let(:test_class) do
|
|
521
|
-
Class.new do
|
|
522
|
-
include Vectra::Retry
|
|
523
|
-
attr_accessor :config
|
|
524
|
-
end
|
|
525
|
-
end
|
|
526
|
-
|
|
527
|
-
subject { test_class.new.tap { |obj| obj.config = config } }
|
|
528
|
-
|
|
529
|
-
it "retries on connection errors" do
|
|
530
|
-
attempts = 0
|
|
531
|
-
allow(subject).to receive(:sleep)
|
|
532
|
-
|
|
533
|
-
result = subject.with_retry do
|
|
534
|
-
attempts += 1
|
|
535
|
-
raise PG::ConnectionBad, "Connection failed" if attempts < 3
|
|
536
|
-
"success"
|
|
537
|
-
end
|
|
538
|
-
|
|
539
|
-
expect(attempts).to eq(3)
|
|
540
|
-
expect(result).to eq("success")
|
|
541
|
-
end
|
|
542
|
-
|
|
543
|
-
it "stops after max attempts" do
|
|
544
|
-
attempts = 0
|
|
545
|
-
allow(subject).to receive(:sleep)
|
|
546
|
-
|
|
547
|
-
expect {
|
|
548
|
-
subject.with_retry(max_attempts: 3) do
|
|
549
|
-
attempts += 1
|
|
550
|
-
raise PG::ConnectionBad, "Connection failed"
|
|
551
|
-
end
|
|
552
|
-
}.to raise_error(PG::ConnectionBad)
|
|
553
|
-
|
|
554
|
-
expect(attempts).to eq(3)
|
|
555
|
-
end
|
|
556
|
-
|
|
557
|
-
it "doesn't retry non-retryable errors" do
|
|
558
|
-
attempts = 0
|
|
559
|
-
|
|
560
|
-
expect {
|
|
561
|
-
subject.with_retry do
|
|
562
|
-
attempts += 1
|
|
563
|
-
raise ArgumentError, "Invalid argument"
|
|
564
|
-
end
|
|
565
|
-
}.to raise_error(ArgumentError)
|
|
566
|
-
|
|
567
|
-
expect(attempts).to eq(1)
|
|
568
|
-
end
|
|
569
|
-
end
|
|
570
|
-
```
|
|
571
|
-
|
|
572
|
-
---
|
|
573
|
-
|
|
574
|
-
## Rollout Checklist
|
|
575
|
-
|
|
576
|
-
### Phase 1: Core Features (v0.2.0)
|
|
577
|
-
- [x] Instrumentation hooks
|
|
578
|
-
- [x] Rails generator
|
|
579
|
-
- [x] ActiveRecord integration
|
|
580
|
-
- [x] Retry logic
|
|
581
|
-
- [x] Performance benchmarks
|
|
582
|
-
- [ ] Update README with new features
|
|
583
|
-
- [ ] Write migration guide
|
|
584
|
-
- [ ] Add YARD docs to new classes
|
|
585
|
-
- [ ] Update CHANGELOG
|
|
586
|
-
|
|
587
|
-
### Phase 2: Testing (before v0.2.0 release)
|
|
588
|
-
- [ ] Unit tests for instrumentation (>90% coverage)
|
|
589
|
-
- [ ] Integration tests for ActiveRecord
|
|
590
|
-
- [ ] Retry logic edge cases
|
|
591
|
-
- [ ] Generator tests
|
|
592
|
-
- [ ] Update CI to run benchmarks
|
|
593
|
-
|
|
594
|
-
### Phase 3: Documentation
|
|
595
|
-
- [ ] Usage examples for each feature
|
|
596
|
-
- [ ] API documentation (YARD)
|
|
597
|
-
- [ ] Blog post announcing features
|
|
598
|
-
- [ ] Update RubyGems description
|
|
599
|
-
|
|
600
|
-
### Phase 4: Release
|
|
601
|
-
- [ ] Bump version to 0.2.0
|
|
602
|
-
- [ ] Update CHANGELOG
|
|
603
|
-
- [ ] Create GitHub release
|
|
604
|
-
- [ ] Publish to RubyGems
|
|
605
|
-
- [ ] Announce on Ruby communities
|
|
606
|
-
|
|
607
|
-
---
|
|
608
|
-
|
|
609
|
-
## Recommended Next Steps
|
|
610
|
-
|
|
611
|
-
1. **Implement instrumentation in Client methods**
|
|
612
|
-
- Wrap upsert, query, fetch, etc. with `Instrumentation.instrument`
|
|
613
|
-
|
|
614
|
-
2. **Add retry logic to pgvector Connection module**
|
|
615
|
-
- Include `Retry` module
|
|
616
|
-
- Wrap `execute` method
|
|
617
|
-
|
|
618
|
-
3. **Test ActiveRecord integration thoroughly**
|
|
619
|
-
- Create a demo Rails app
|
|
620
|
-
- Test with real database
|
|
621
|
-
- Document edge cases
|
|
622
|
-
|
|
623
|
-
4. **Run benchmarks on real hardware**
|
|
624
|
-
- Different PostgreSQL versions
|
|
625
|
-
- Different data sizes
|
|
626
|
-
- Document optimal settings
|
|
627
|
-
|
|
628
|
-
5. **Write comprehensive tests**
|
|
629
|
-
- Aim for 90%+ coverage
|
|
630
|
-
- Include integration tests
|
|
631
|
-
- Test error scenarios
|
|
632
|
-
|
|
633
|
-
6. **Update documentation**
|
|
634
|
-
- README with new features
|
|
635
|
-
- CHANGELOG entries
|
|
636
|
-
- API docs (YARD)
|
|
637
|
-
|
|
638
|
-
---
|
|
639
|
-
|
|
640
|
-
## Questions & Troubleshooting
|
|
641
|
-
|
|
642
|
-
### Q: Instrumentation not working?
|
|
643
|
-
|
|
644
|
-
**Check:**
|
|
645
|
-
- `config.instrumentation = true` is set
|
|
646
|
-
- Handler is registered before operations run
|
|
647
|
-
- No errors in handler (check logs)
|
|
648
|
-
|
|
649
|
-
### Q: ActiveRecord auto-index not firing?
|
|
650
|
-
|
|
651
|
-
**Check:**
|
|
652
|
-
- `auto_index: true` is set
|
|
653
|
-
- Embedding attribute actually changed (`saved_change_to_embedding?`)
|
|
654
|
-
- No errors in callback (check logs)
|
|
655
|
-
|
|
656
|
-
### Q: Retry not working?
|
|
657
|
-
|
|
658
|
-
**Check:**
|
|
659
|
-
- Error is retryable (check `RETRYABLE_PG_ERRORS`)
|
|
660
|
-
- Not exceeded `max_attempts`
|
|
661
|
-
- `config.max_retries` is > 0
|
|
662
|
-
|
|
663
|
-
### Q: Poor benchmark results?
|
|
664
|
-
|
|
665
|
-
**Check:**
|
|
666
|
-
- PostgreSQL is tuned (`work_mem`, `shared_buffers`)
|
|
667
|
-
- Index exists (`CREATE INDEX USING ivfflat`)
|
|
668
|
-
- `batch_size` matches your workload
|
|
669
|
-
- `pool_size` matches concurrent connections
|
|
670
|
-
|
|
671
|
-
---
|
|
672
|
-
|
|
673
|
-
## Contributing
|
|
674
|
-
|
|
675
|
-
When implementing new features:
|
|
676
|
-
|
|
677
|
-
1. Follow existing code style (RuboCop passing)
|
|
678
|
-
2. Add comprehensive tests (>90% coverage)
|
|
679
|
-
3. Update documentation (README, YARD, CHANGELOG)
|
|
680
|
-
4. Add usage examples
|
|
681
|
-
5. Run benchmarks if performance-related
|
|
682
|
-
6. Update this guide with implementation details
|
|
683
|
-
|
|
684
|
-
---
|
|
685
|
-
|
|
686
|
-
Happy coding! 🚀
|