vectra-client 0.1.2 → 0.2.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.
@@ -0,0 +1,117 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/setup"
4
+ require "vectra"
5
+ require "benchmark"
6
+
7
+ # Benchmark for batch operations
8
+ #
9
+ # Usage:
10
+ # ruby benchmarks/batch_operations_benchmark.rb
11
+
12
+ puts "=" * 80
13
+ puts "VECTRA BATCH OPERATIONS BENCHMARK"
14
+ puts "=" * 80
15
+ puts
16
+
17
+ # Setup
18
+ DB_URL = ENV.fetch("DATABASE_URL", "postgres://postgres:password@localhost/vectra_benchmark")
19
+ DIMENSION = 384
20
+ ITERATIONS = 5
21
+
22
+ client = Vectra.pgvector(connection_url: DB_URL)
23
+
24
+ # Create test index
25
+ puts "Creating test index..."
26
+ begin
27
+ client.provider.delete_index(name: "benchmark_test")
28
+ rescue Vectra::NotFoundError
29
+ # Index doesn't exist, that's fine
30
+ end
31
+
32
+ client.provider.create_index(
33
+ name: "benchmark_test",
34
+ dimension: DIMENSION,
35
+ metric: "cosine"
36
+ )
37
+
38
+ # Generate test vectors
39
+ def generate_vectors(count, dimension)
40
+ count.times.map do |i|
41
+ {
42
+ id: "vec_#{i}",
43
+ values: Array.new(dimension) { rand },
44
+ metadata: { index: i, category: "cat_#{i % 10}" }
45
+ }
46
+ end
47
+ end
48
+
49
+ puts "\nRunning benchmarks (#{ITERATIONS} iterations each)..."
50
+ puts "-" * 80
51
+
52
+ # Test different vector counts
53
+ [100, 500, 1000, 5000, 10_000].each do |count|
54
+ puts "\n#{count} vectors:"
55
+
56
+ vectors = generate_vectors(count, DIMENSION)
57
+
58
+ # Test different batch sizes
59
+ [50, 100, 250, 500].each do |batch_size|
60
+ next if batch_size > count
61
+
62
+ client.config.batch_size = batch_size
63
+
64
+ times = []
65
+ ITERATIONS.times do
66
+ time = Benchmark.realtime do
67
+ client.upsert(index: "benchmark_test", vectors: vectors)
68
+ end
69
+ times << time
70
+ end
71
+
72
+ avg_time = times.sum / times.size
73
+ vectors_per_sec = count / avg_time
74
+ batches = (count.to_f / batch_size).ceil
75
+
76
+ puts " Batch size #{batch_size.to_s.rjust(3)}: " \
77
+ "#{avg_time.round(3)}s avg " \
78
+ "(#{vectors_per_sec.round(0)} vectors/sec, " \
79
+ "#{batches} batches)"
80
+ end
81
+ end
82
+
83
+ # Query benchmarks
84
+ puts "\n#{"=" * 80}"
85
+ puts "QUERY BENCHMARKS"
86
+ puts "=" * 80
87
+
88
+ query_vector = Array.new(DIMENSION) { rand }
89
+
90
+ puts "\nQuery performance (#{ITERATIONS} iterations):"
91
+ [10, 20, 50, 100].each do |top_k|
92
+ times = []
93
+ ITERATIONS.times do
94
+ time = Benchmark.realtime do
95
+ client.query(
96
+ index: "benchmark_test",
97
+ vector: query_vector,
98
+ top_k: top_k
99
+ )
100
+ end
101
+ times << time
102
+ end
103
+
104
+ avg_time = times.sum / times.size
105
+ queries_per_sec = 1 / avg_time
106
+
107
+ puts " top_k=#{top_k.to_s.rjust(3)}: " \
108
+ "#{(avg_time * 1000).round(1)}ms avg " \
109
+ "(#{queries_per_sec.round(1)} queries/sec)"
110
+ end
111
+
112
+ # Cleanup
113
+ puts "\nCleaning up..."
114
+ client.provider.delete_index(name: "benchmark_test")
115
+ client.provider.shutdown!
116
+
117
+ puts "\n✅ Benchmark complete!"
@@ -0,0 +1,93 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/setup"
4
+ require "vectra"
5
+ require "benchmark"
6
+
7
+ # Benchmark for connection pooling under concurrent load
8
+ #
9
+ # Usage:
10
+ # ruby benchmarks/connection_pooling_benchmark.rb
11
+
12
+ puts "=" * 80
13
+ puts "VECTRA CONNECTION POOLING BENCHMARK"
14
+ puts "=" * 80
15
+ puts
16
+
17
+ DB_URL = ENV.fetch("DATABASE_URL", "postgres://postgres:password@localhost/vectra_benchmark")
18
+ DIMENSION = 384
19
+ THREAD_COUNTS = [1, 2, 5, 10, 20].freeze
20
+ OPERATIONS_PER_THREAD = 50
21
+
22
+ # Test different pool sizes
23
+ [5, 10, 20].each do |pool_size|
24
+ puts "\n#{"=" * 80}"
25
+ puts "Pool Size: #{pool_size}"
26
+ puts "=" * 80
27
+
28
+ client = Vectra.pgvector(
29
+ connection_url: DB_URL,
30
+ pool_size: pool_size,
31
+ pool_timeout: 10
32
+ )
33
+
34
+ # Create test index
35
+ begin
36
+ client.provider.create_index(name: "benchmark_pool", dimension: DIMENSION)
37
+ rescue StandardError
38
+ # Already exists
39
+ end
40
+
41
+ # Pre-populate some data
42
+ vectors = 100.times.map do |i|
43
+ {
44
+ id: "vec_#{i}",
45
+ values: Array.new(DIMENSION) { rand },
46
+ metadata: { index: i }
47
+ }
48
+ end
49
+ client.upsert(index: "benchmark_pool", vectors: vectors)
50
+
51
+ THREAD_COUNTS.each do |thread_count|
52
+ # Skip if threads > pool size (will timeout)
53
+ next if thread_count > pool_size + 5
54
+
55
+ total_time = Benchmark.realtime do
56
+ threads = thread_count.times.map do |_thread_idx|
57
+ Thread.new do
58
+ query_vector = Array.new(DIMENSION) { rand }
59
+
60
+ OPERATIONS_PER_THREAD.times do
61
+ client.query(
62
+ index: "benchmark_pool",
63
+ vector: query_vector,
64
+ top_k: 10
65
+ )
66
+ end
67
+ end
68
+ end
69
+
70
+ threads.each(&:join)
71
+ end
72
+
73
+ total_operations = thread_count * OPERATIONS_PER_THREAD
74
+ ops_per_sec = total_operations / total_time
75
+
76
+ # Get pool stats
77
+ stats = client.provider.pool_stats
78
+
79
+ puts " #{thread_count.to_s.rjust(2)} threads: " \
80
+ "#{total_time.round(2)}s total " \
81
+ "(#{ops_per_sec.round(1)} ops/sec) " \
82
+ "Pool: #{stats[:available]}/#{stats[:size]} available"
83
+ end
84
+
85
+ # Cleanup
86
+ client.provider.shutdown!
87
+ end
88
+
89
+ puts "\n✅ Benchmark complete!"
90
+ puts "\nKey takeaways:"
91
+ puts " • Pool size should match max concurrent threads"
92
+ puts " • More threads than pool size causes waiting/timeouts"
93
+ puts " • Monitor pool_stats in production for optimal sizing"
@@ -0,0 +1,227 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # Demo of Vectra ActiveRecord integration
5
+ #
6
+ # Usage: ruby examples/active_record_demo.rb
7
+
8
+ require "bundler/setup"
9
+ require "active_record"
10
+ require "vectra"
11
+
12
+ puts "=" * 80
13
+ puts "VECTRA ACTIVERECORD INTEGRATION DEMO"
14
+ puts "=" * 80
15
+ puts
16
+
17
+ # Setup database connection
18
+ ActiveRecord::Base.establish_connection(
19
+ adapter: "postgresql",
20
+ database: ENV.fetch("DATABASE_NAME", "vectra_demo"),
21
+ host: ENV.fetch("DATABASE_HOST", "localhost"),
22
+ username: ENV.fetch("DATABASE_USER", "postgres"),
23
+ password: ENV.fetch("DATABASE_PASSWORD", "password")
24
+ )
25
+
26
+ # Configure Vectra
27
+ Vectra.configure do |config|
28
+ config.provider = :pgvector
29
+ config.host = ENV.fetch("DATABASE_URL", "postgres://postgres:password@localhost/vectra_demo")
30
+ end
31
+
32
+ # Create documents table if not exists
33
+ ActiveRecord::Schema.define do
34
+ unless ActiveRecord::Base.connection.table_exists?("documents")
35
+ enable_extension "vector"
36
+
37
+ create_table :documents do |t|
38
+ t.string :title
39
+ t.text :content
40
+ t.string :category
41
+ t.string :status
42
+ t.column :embedding, :vector, limit: 3 # 3-dimensional for demo
43
+ t.timestamps
44
+ end
45
+ end
46
+ end
47
+
48
+ # Define Document model with vector search
49
+ class Document < ActiveRecord::Base
50
+ include Vectra::ActiveRecord
51
+
52
+ has_vector :embedding,
53
+ dimension: 3,
54
+ provider: :pgvector,
55
+ index: "documents",
56
+ auto_index: true,
57
+ metadata_fields: [:title, :category, :status]
58
+
59
+ # Generate embedding before validation
60
+ # In production, use OpenAI/Cohere/etc.
61
+ before_validation :generate_embedding, if: -> { content.present? && embedding.nil? }
62
+
63
+ private
64
+
65
+ def generate_embedding
66
+ # Simple deterministic embedding for demo
67
+ # In production: self.embedding = OpenAI.embed(content)
68
+ hash = content.hash.abs
69
+ self.embedding = [
70
+ (hash % 1000) / 1000.0,
71
+ ((hash / 1000) % 1000) / 1000.0,
72
+ ((hash / 1_000_000) % 1000) / 1000.0
73
+ ]
74
+ end
75
+ end
76
+
77
+ # Create pgvector index
78
+ puts "Creating vector index..."
79
+ begin
80
+ Vectra::Client.new.provider.create_index(
81
+ name: "documents",
82
+ dimension: 3,
83
+ metric: "cosine"
84
+ )
85
+ puts "✅ Index created\n"
86
+ rescue StandardError => e
87
+ puts "⚠️ Index might already exist: #{e.message}\n"
88
+ end
89
+
90
+ puts "\n#{"=" * 80}"
91
+ puts "TESTING ACTIVERECORD INTEGRATION"
92
+ puts "=" * 80
93
+ puts
94
+
95
+ # Clean up existing data
96
+ Document.delete_all
97
+
98
+ # Test 1: Create document (auto-indexes)
99
+ puts "1. Creating documents (auto-indexes on save)...\n"
100
+
101
+ doc1 = Document.create!(
102
+ title: "Getting Started Guide",
103
+ content: "This guide will help you get started with our platform.",
104
+ category: "tutorial",
105
+ status: "published"
106
+ )
107
+ puts " Created: #{doc1.title} (ID: #{doc1.id})"
108
+ puts " Embedding: #{doc1.embedding.map { |v| v.round(3) }}"
109
+ puts " ✅ Automatically indexed in Vectra\n\n"
110
+
111
+ doc2 = Document.create!(
112
+ title: "Advanced Features",
113
+ content: "Learn about advanced features and best practices.",
114
+ category: "tutorial",
115
+ status: "published"
116
+ )
117
+ puts " Created: #{doc2.title} (ID: #{doc2.id})\n\n"
118
+
119
+ doc3 = Document.create!(
120
+ title: "API Reference",
121
+ content: "Complete API documentation for developers.",
122
+ category: "reference",
123
+ status: "published"
124
+ )
125
+ puts " Created: #{doc3.title} (ID: #{doc3.id})\n\n"
126
+
127
+ sleep 0.5
128
+
129
+ # Test 2: Vector search
130
+ puts "2. Vector search (finds similar documents)...\n"
131
+
132
+ query_embedding = [0.5, 0.5, 0.5]
133
+ results = Document.vector_search(query_embedding, limit: 5)
134
+
135
+ puts " Query: #{query_embedding.inspect}"
136
+ puts " Found #{results.size} results:\n\n"
137
+
138
+ results.each_with_index do |doc, idx|
139
+ puts " #{idx + 1}. #{doc.title}"
140
+ puts " Score: #{doc.vector_score.round(3)}"
141
+ puts " Category: #{doc.category}"
142
+ puts
143
+ end
144
+
145
+ # Test 3: Search with filters
146
+ puts "3. Vector search with metadata filter...\n"
147
+
148
+ results = Document.vector_search(
149
+ query_embedding,
150
+ limit: 10,
151
+ filter: { category: "tutorial" }
152
+ )
153
+
154
+ puts " Filter: category='tutorial'"
155
+ puts " Found #{results.size} results:\n"
156
+ results.each { |doc| puts " • #{doc.title}" }
157
+ puts
158
+
159
+ # Test 4: Find similar documents
160
+ puts "4. Find similar to specific document...\n"
161
+
162
+ similar = doc1.similar(limit: 2)
163
+
164
+ puts " Document: '#{doc1.title}'"
165
+ puts " Similar documents:\n"
166
+ similar.each do |doc|
167
+ puts " • #{doc.title} (score: #{doc.vector_score.round(3)})"
168
+ end
169
+ puts
170
+
171
+ # Test 5: Update triggers re-indexing
172
+ puts "5. Update document (triggers re-indexing)...\n"
173
+
174
+ doc1.update!(content: "Updated content about getting started.")
175
+ puts " Updated: #{doc1.title}"
176
+ puts " New embedding: #{doc1.embedding.map { |v| v.round(3) }}"
177
+ puts " ✅ Automatically re-indexed\n\n"
178
+
179
+ # Test 6: Manual index control
180
+ puts "6. Manual index control...\n"
181
+
182
+ doc4 = Document.new(
183
+ title: "Draft Article",
184
+ content: "This is a draft article.",
185
+ category: "blog",
186
+ status: "draft"
187
+ )
188
+ doc4.save!(validate: false) # Skip auto-index
189
+
190
+ puts " Created without auto-index: #{doc4.title}"
191
+ puts " Manually indexing..."
192
+
193
+ doc4.index_vector!
194
+ puts " ✅ Manually indexed\n\n"
195
+
196
+ # Test 7: Delete removes from index
197
+ puts "7. Delete document (removes from index)...\n"
198
+
199
+ doc4.destroy!
200
+ puts " ✅ Deleted and removed from vector index\n\n"
201
+
202
+ # Cleanup
203
+ puts "=" * 80
204
+ puts "SUMMARY"
205
+ puts "=" * 80
206
+ puts
207
+
208
+ puts "ActiveRecord Integration Features:"
209
+ puts " ✅ Automatic indexing on create/update"
210
+ puts " ✅ Automatic removal on delete"
211
+ puts " ✅ Vector search with AR object loading"
212
+ puts " ✅ Metadata filtering"
213
+ puts " ✅ Find similar documents"
214
+ puts " ✅ Manual index control"
215
+ puts " ✅ Custom embedding generation"
216
+ puts
217
+
218
+ puts "Total documents in database: #{Document.count}"
219
+ puts "Total documents in vector index: (same, auto-synced)"
220
+ puts
221
+
222
+ puts "✅ Demo complete!"
223
+ puts "\nNext steps:"
224
+ puts " • Replace embedding generation with real model (OpenAI, Cohere, etc.)"
225
+ puts " • Add background job for async indexing"
226
+ puts " • Use higher dimensions (384, 768, 1536)"
227
+ puts " • Add score threshold filtering"
@@ -0,0 +1,157 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # Demo of Vectra instrumentation features
5
+ #
6
+ # Usage: ruby examples/instrumentation_demo.rb
7
+
8
+ require "bundler/setup"
9
+ require "vectra"
10
+
11
+ puts "=" * 80
12
+ puts "VECTRA INSTRUMENTATION DEMO"
13
+ puts "=" * 80
14
+ puts
15
+
16
+ # Configure Vectra with instrumentation
17
+ Vectra.configure do |config|
18
+ config.provider = :pgvector
19
+ config.host = ENV.fetch("DATABASE_URL", "postgres://postgres:password@localhost/vectra_demo")
20
+ config.instrumentation = true # Enable instrumentation
21
+ config.pool_size = 5
22
+ config.batch_size = 100
23
+ config.max_retries = 3
24
+ config.retry_delay = 0.5
25
+ end
26
+
27
+ # Register custom instrumentation handler
28
+ puts "Registering custom instrumentation handler...\n"
29
+
30
+ Vectra.on_operation do |event|
31
+ status = event.success? ? "✅ SUCCESS" : "❌ ERROR"
32
+ duration_color = event.duration > 100 ? "\e[31m" : "\e[32m" # Red if > 100ms, green otherwise
33
+ reset_color = "\e[0m"
34
+
35
+ puts "#{status} | #{event.operation.to_s.upcase.ljust(10)} | " \
36
+ "#{event.provider}/#{event.index.ljust(15)} | " \
37
+ "#{duration_color}#{event.duration.round(1)}ms#{reset_color}"
38
+
39
+ if event.metadata.any?
40
+ puts " Metadata: #{event.metadata.inspect}"
41
+ end
42
+
43
+ if event.failure?
44
+ puts " Error: #{event.error.class} - #{event.error.message}"
45
+ end
46
+
47
+ puts
48
+ end
49
+
50
+ # Create client
51
+ client = Vectra::Client.new
52
+
53
+ puts "Creating test index...\n"
54
+
55
+ begin
56
+ client.provider.delete_index(name: "demo_index")
57
+ rescue Vectra::NotFoundError
58
+ # Doesn't exist, that's fine
59
+ end
60
+
61
+ client.provider.create_index(name: "demo_index", dimension: 3, metric: "cosine")
62
+ sleep 0.5 # Give it a moment
63
+
64
+ puts "\n#{"=" * 80}"
65
+ puts "TESTING OPERATIONS"
66
+ puts "=" * 80
67
+ puts
68
+
69
+ # Test 1: Upsert
70
+ puts "1. UPSERT (3 vectors):"
71
+ client.upsert(
72
+ index: "demo_index",
73
+ vectors: [
74
+ { id: "vec1", values: [0.1, 0.2, 0.3], metadata: { text: "Hello" } },
75
+ { id: "vec2", values: [0.4, 0.5, 0.6], metadata: { text: "World" } },
76
+ { id: "vec3", values: [0.7, 0.8, 0.9], metadata: { text: "Test" } }
77
+ ]
78
+ )
79
+
80
+ sleep 0.5
81
+
82
+ # Test 2: Query
83
+ puts "2. QUERY (top_k=2):"
84
+ client.query(
85
+ index: "demo_index",
86
+ vector: [0.1, 0.2, 0.3],
87
+ top_k: 2
88
+ )
89
+
90
+ sleep 0.5
91
+
92
+ # Test 3: Fetch
93
+ puts "3. FETCH (2 IDs):"
94
+ client.fetch(
95
+ index: "demo_index",
96
+ ids: ["vec1", "vec2"]
97
+ )
98
+
99
+ sleep 0.5
100
+
101
+ # Test 4: Update
102
+ puts "4. UPDATE (metadata):"
103
+ client.update(
104
+ index: "demo_index",
105
+ id: "vec1",
106
+ metadata: { text: "Updated", processed: true }
107
+ )
108
+
109
+ sleep 0.5
110
+
111
+ # Test 5: Delete
112
+ puts "5. DELETE (1 ID):"
113
+ client.delete(
114
+ index: "demo_index",
115
+ ids: ["vec3"]
116
+ )
117
+
118
+ sleep 0.5
119
+
120
+ # Test 6: Bulk operations
121
+ puts "6. BULK UPSERT (100 vectors):"
122
+ bulk_vectors = 100.times.map do |i|
123
+ { id: "bulk_#{i}", values: [rand, rand, rand], metadata: { index: i } }
124
+ end
125
+
126
+ client.upsert(index: "demo_index", vectors: bulk_vectors)
127
+
128
+ sleep 0.5
129
+
130
+ # Test 7: Large query
131
+ puts "7. LARGE QUERY (top_k=50):"
132
+ client.query(
133
+ index: "demo_index",
134
+ vector: [rand, rand, rand],
135
+ top_k: 50
136
+ )
137
+
138
+ # Cleanup
139
+ puts "\n#{"=" * 80}"
140
+ puts "CLEANUP"
141
+ puts "=" * 80
142
+ puts
143
+
144
+ puts "Deleting test index..."
145
+ client.provider.delete_index(name: "demo_index")
146
+
147
+ puts "\n✅ Demo complete!"
148
+ puts "\nYou can see:"
149
+ puts " • Operation names (UPSERT, QUERY, FETCH, UPDATE, DELETE)"
150
+ puts " • Provider and index"
151
+ puts " • Duration in milliseconds (color-coded)"
152
+ puts " • Metadata (vector counts, filters, etc.)"
153
+ puts " • Success/error status"
154
+ puts "\nThis data can be sent to:"
155
+ puts " • New Relic (require 'vectra/instrumentation/new_relic')"
156
+ puts " • Datadog (require 'vectra/instrumentation/datadog')"
157
+ puts " • Custom monitoring systems"
@@ -0,0 +1,115 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails/generators/base"
4
+
5
+ module Vectra
6
+ module Generators
7
+ # Rails generator for installing Vectra
8
+ #
9
+ # @example
10
+ # rails generate vectra:install
11
+ # rails generate vectra:install --provider=pinecone
12
+ # rails generate vectra:install --provider=pgvector --database-url=postgres://localhost/mydb
13
+ #
14
+ class InstallGenerator < Rails::Generators::Base
15
+ source_root File.expand_path("templates", __dir__)
16
+
17
+ class_option :provider, type: :string, default: "pgvector",
18
+ desc: "Vector database provider (pinecone, pgvector, qdrant, weaviate)"
19
+ class_option :database_url, type: :string, default: nil,
20
+ desc: "PostgreSQL connection URL (for pgvector)"
21
+ class_option :api_key, type: :string, default: nil,
22
+ desc: "API key for the provider"
23
+ class_option :instrumentation, type: :boolean, default: false,
24
+ desc: "Enable instrumentation"
25
+
26
+ def create_initializer_file
27
+ template "vectra.rb", "config/initializers/vectra.rb"
28
+ end
29
+
30
+ def create_migration
31
+ return unless options[:provider] == "pgvector"
32
+
33
+ generate :migration, "EnablePgvectorExtension"
34
+
35
+ migration_template(
36
+ "enable_pgvector_extension.rb",
37
+ "db/migrate/enable_pgvector_extension.rb",
38
+ migration_version: migration_version
39
+ )
40
+ end
41
+
42
+ def show_readme
43
+ say "\n"
44
+ say "Vectra has been installed!", :green
45
+ say "\n"
46
+ say "Next steps:", :yellow
47
+ say " 1. Add your #{options[:provider]} credentials to Rails credentials:"
48
+ say " $ rails credentials:edit", :cyan
49
+ say "\n"
50
+
51
+ case options[:provider]
52
+ when "pinecone"
53
+ show_pinecone_instructions
54
+ when "pgvector"
55
+ show_pgvector_instructions
56
+ when "qdrant"
57
+ show_qdrant_instructions
58
+ when "weaviate"
59
+ show_weaviate_instructions
60
+ end
61
+
62
+ return unless options[:instrumentation]
63
+
64
+ say "\n"
65
+ say " 📊 Instrumentation is enabled!", :green
66
+ say " Add New Relic or Datadog setup to config/initializers/vectra.rb"
67
+ end
68
+
69
+ private
70
+
71
+ def show_pinecone_instructions
72
+ say " 2. Add to credentials:", :yellow
73
+ say " pinecone:", :cyan
74
+ say " api_key: your_api_key_here", :cyan
75
+ say " environment: us-east-1", :cyan
76
+ say "\n"
77
+ say " 3. Create an index in Pinecone dashboard"
78
+ say "\n"
79
+ say " 4. Use in your app:", :yellow
80
+ say " @client = Vectra::Client.new", :cyan
81
+ say " @client.upsert(index: 'my-index', vectors: [...])", :cyan
82
+ end
83
+
84
+ def show_pgvector_instructions
85
+ say " 2. Run migrations:", :yellow
86
+ say " $ rails db:migrate", :cyan
87
+ say "\n"
88
+ say " 3. Create a vector index:", :yellow
89
+ say " $ rails runner 'Vectra::Client.new.provider.create_index(name: \"documents\", dimension: 384)'", :cyan
90
+ say "\n"
91
+ say " 4. Use in your app:", :yellow
92
+ say " @client = Vectra::Client.new", :cyan
93
+ say " @client.upsert(index: 'documents', vectors: [...])", :cyan
94
+ end
95
+
96
+ def show_qdrant_instructions
97
+ say " 2. Add to credentials:", :yellow
98
+ say " qdrant:", :cyan
99
+ say " api_key: your_api_key_here", :cyan
100
+ say " host: https://your-cluster.qdrant.io", :cyan
101
+ end
102
+
103
+ def show_weaviate_instructions
104
+ say " 2. Add to credentials:", :yellow
105
+ say " weaviate:", :cyan
106
+ say " api_key: your_api_key_here", :cyan
107
+ say " host: https://your-cluster.weaviate.io", :cyan
108
+ end
109
+
110
+ def migration_version
111
+ "[#{Rails::VERSION::MAJOR}.#{Rails::VERSION::MINOR}]"
112
+ end
113
+ end
114
+ end
115
+ end