vectra-client 0.2.1 → 0.2.2

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.
Files changed (46) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +140 -334
  3. data/docs/Gemfile +10 -0
  4. data/docs/_config.yml +20 -0
  5. data/docs/_layouts/default.html +14 -0
  6. data/docs/_layouts/home.html +33 -0
  7. data/docs/_layouts/page.html +20 -0
  8. data/docs/_site/api/overview/index.html +145 -0
  9. data/docs/_site/assets/main.css +649 -0
  10. data/docs/_site/assets/main.css.map +1 -0
  11. data/docs/_site/assets/minima-social-icons.svg +33 -0
  12. data/docs/_site/assets/style.css +295 -0
  13. data/docs/_site/community/contributing/index.html +110 -0
  14. data/docs/_site/examples/basic-usage/index.html +117 -0
  15. data/docs/_site/examples/index.html +58 -0
  16. data/docs/_site/feed.xml +1 -0
  17. data/docs/_site/guides/getting-started/index.html +106 -0
  18. data/docs/_site/guides/installation/index.html +82 -0
  19. data/docs/_site/index.html +92 -0
  20. data/docs/_site/providers/index.html +119 -0
  21. data/docs/_site/providers/pgvector/index.html +155 -0
  22. data/docs/_site/providers/pinecone/index.html +121 -0
  23. data/docs/_site/providers/qdrant/index.html +124 -0
  24. data/docs/_site/providers/weaviate/index.html +123 -0
  25. data/docs/_site/robots.txt +1 -0
  26. data/docs/_site/sitemap.xml +39 -0
  27. data/docs/api/overview.md +126 -0
  28. data/docs/assets/style.css +295 -0
  29. data/docs/community/contributing.md +89 -0
  30. data/docs/examples/basic-usage.md +102 -0
  31. data/docs/examples/index.md +32 -0
  32. data/docs/guides/getting-started.md +90 -0
  33. data/docs/guides/installation.md +67 -0
  34. data/docs/index.md +53 -0
  35. data/docs/providers/index.md +62 -0
  36. data/docs/providers/pgvector.md +95 -0
  37. data/docs/providers/pinecone.md +72 -0
  38. data/docs/providers/qdrant.md +73 -0
  39. data/docs/providers/weaviate.md +72 -0
  40. data/lib/vectra/version.rb +1 -1
  41. data/netlify.toml +12 -0
  42. metadata +39 -5
  43. data/IMPLEMENTATION_GUIDE.md +0 -686
  44. data/NEW_FEATURES_v0.2.0.md +0 -459
  45. data/RELEASE_CHECKLIST_v0.2.0.md +0 -383
  46. data/USAGE_EXAMPLES.md +0 -787
@@ -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! 🚀