vectra-client 1.0.7 → 1.1.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 316a75b282cab4d293dfdb3ab9f7b2220a58078008a6887e7e47e7caf6172211
4
- data.tar.gz: d4a9c10c5e6862194e7fb028c254b19d63b0d2fb6fa5ef5f45b59b6a9da1a317
3
+ metadata.gz: 70730299dab8475b05688f7017dccd1965b87a49589c96be7633a356af8d57f2
4
+ data.tar.gz: 14999ebde62586578b444ba45d396cb38f1a4dd5dcbf2fd126e042ebb1c6fae5
5
5
  SHA512:
6
- metadata.gz: ca06a9e2a961f6130aaa06cf42caafd900ef9fb38dc902e42c3bccb649962abe3d3cd64a50af84605df273682013f0ae90476c5096f4a3efee1ac67d3d1dc672
7
- data.tar.gz: e1fb369da70479f0b3505be4c15d1b6ee7089d0f6c14bb6b58d1ae02c15b9cbb92bfb448e88697726a769d01d5ac7b9569f6085c04ee420e39e55b2ee754013c
6
+ metadata.gz: 69c8fa722ee4abfe3ddf6b19f8bd12f46d09edefee50612021142c3951600666ed854018e8a5c1bc33249895fec42ae18ab1d821beffa989d2700da99c28bcf4
7
+ data.tar.gz: f27a0df4bbcf618659297b1376ebe977588d32d5dfef76fe7c8f5f38c8780b37e055a0e8d91f6bc3ae8eb7cad88606c760d4ed5882c61756ac1495dedfc339a5
data/CHANGELOG.md CHANGED
@@ -1,5 +1,9 @@
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
+
3
7
  ## [v1.0.7](https://github.com/stokry/vectra/tree/v1.0.7) (2026-01-14)
4
8
 
5
9
  [Full Changelog](https://github.com/stokry/vectra/compare/v1.0.6...v1.0.7)
data/README.md CHANGED
@@ -242,6 +242,59 @@ Real-world patterns for common use cases:
242
242
 
243
243
  👉 **[Browse all Recipes & Patterns](https://vectra-docs.netlify.app/guides/recipes/)**
244
244
 
245
+ ## Middleware System
246
+
247
+ Vectra features a powerful **Rack-style middleware system** that allows you to extend functionality without modifying core code.
248
+
249
+ ### Quick Start
250
+
251
+ ```ruby
252
+ # Global middleware (applies to all clients)
253
+ Vectra::Client.use Vectra::Middleware::Logging
254
+ Vectra::Client.use Vectra::Middleware::Retry, max_attempts: 5
255
+ Vectra::Client.use Vectra::Middleware::CostTracker
256
+
257
+ # Per-client middleware
258
+ client = Vectra::Client.new(
259
+ provider: :qdrant,
260
+ middleware: [
261
+ Vectra::Middleware::PIIRedaction,
262
+ Vectra::Middleware::Instrumentation
263
+ ]
264
+ )
265
+ ```
266
+
267
+ ### Built-in Middleware
268
+
269
+ - **Logging** - Request/response logging with timing
270
+ - **Retry** - Automatic retries with exponential backoff
271
+ - **Instrumentation** - Metrics and monitoring integration
272
+ - **PIIRedaction** - Automatic PII (email, phone, SSN) redaction
273
+ - **CostTracker** - Track API usage costs per operation
274
+
275
+ ### Custom Middleware
276
+
277
+ ```ruby
278
+ class MyAuditMiddleware < Vectra::Middleware::Base
279
+ def before(request)
280
+ # Called before the operation
281
+ AuditLog.create!(action: request.operation, user: Current.user)
282
+ end
283
+
284
+ def after(request, response)
285
+ # Called after successful operation
286
+ puts "Duration: #{response.metadata[:duration_ms]}ms"
287
+ end
288
+
289
+ def on_error(request, error)
290
+ # Called when an error occurs
291
+ ErrorTracker.notify(error, context: { operation: request.operation })
292
+ end
293
+ end
294
+
295
+ Vectra::Client.use MyAuditMiddleware
296
+ ```
297
+
245
298
  ## Production Patterns
246
299
 
247
300
  Vectra includes 7 production-ready patterns out of the box:
@@ -55,7 +55,7 @@
55
55
  <!-- Hero Section -->
56
56
  <section class="tma-hero">
57
57
  <div class="tma-hero__container">
58
- <span class="tma-hero__badge">v1.0.1 — Hybrid Search & Rails Generator</span>
58
+ <span class="tma-hero__badge">v1.1.0 — Hybrid Search, Rails Generator & Middleware</span>
59
59
  <h1 class="tma-hero__title">
60
60
  Vector Databases,<br>
61
61
  <span class="tma-hero__title-gradient">Unified for Ruby.</span>
@@ -34,6 +34,21 @@ client = Vectra.pgvector(connection_url: ENV['DATABASE_URL'])
34
34
  client = Vectra.memory # In-memory (testing only)
35
35
  ```
36
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
+
37
52
  ### Upsert
38
53
 
39
54
  ```ruby
@@ -225,6 +240,22 @@ vector.normalize! # Mutates values
225
240
  client.upsert(index: 'documents', vectors: [vector])
226
241
  ```
227
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
+
228
259
  ---
229
260
 
230
261
  ## Batch Operations
@@ -318,6 +349,18 @@ results.each do |doc|
318
349
  end
319
350
  ```
320
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
+
321
364
  ---
322
365
 
323
366
  ## Error Handling
data/docs/api/methods.md CHANGED
@@ -43,7 +43,7 @@ client = Vectra::Client.new(
43
43
  Upsert vectors into an index. If a vector with the same ID exists, it will be updated.
44
44
 
45
45
  **Parameters:**
46
- - `index` (String) - Index/collection name
46
+ - `index` (String) - Index/collection name (uses client's default index when omitted)
47
47
  - `vectors` (Array<Hash, Vector>) - Array of vector hashes or Vector objects
48
48
  - `namespace` (String, optional) - Namespace
49
49
 
@@ -77,7 +77,7 @@ result = client.upsert(
77
77
  Search for similar vectors using cosine similarity.
78
78
 
79
79
  **Parameters:**
80
- - `index` (String) - Index/collection name
80
+ - `index` (String) - Index/collection name (uses client's default index when omitted)
81
81
  - `vector` (Array<Float>) - Query vector
82
82
  - `top_k` (Integer) - Number of results (default: 10)
83
83
  - `namespace` (String, optional) - Namespace
@@ -152,7 +152,7 @@ results = client.hybrid_search(
152
152
  Fetch vectors by their IDs.
153
153
 
154
154
  **Parameters:**
155
- - `index` (String) - Index/collection name
155
+ - `index` (String) - Index/collection name (uses client's default index when omitted)
156
156
  - `ids` (Array<String>) - Array of vector IDs
157
157
  - `namespace` (String, optional) - Namespace
158
158
 
@@ -176,7 +176,7 @@ vectors['doc-1'].metadata # => { 'title' => 'Hello' }
176
176
  Update a vector's metadata or values.
177
177
 
178
178
  **Parameters:**
179
- - `index` (String) - Index/collection name
179
+ - `index` (String) - Index/collection name (uses client's default index when omitted)
180
180
  - `id` (String) - Vector ID
181
181
  - `metadata` (Hash, optional) - New metadata (merged with existing)
182
182
  - `values` (Array<Float>, optional) - New vector values
@@ -202,7 +202,7 @@ client.update(
202
202
  Delete vectors.
203
203
 
204
204
  **Parameters:**
205
- - `index` (String) - Index/collection name
205
+ - `index` (String) - Index/collection name (uses client's default index when omitted)
206
206
  - `ids` (Array<String>, optional) - Vector IDs to delete
207
207
  - `namespace` (String, optional) - Namespace
208
208
  - `filter` (Hash, optional) - Delete by metadata filter
@@ -231,7 +231,7 @@ client.delete(index: 'documents', delete_all: true)
231
231
  Get index statistics.
232
232
 
233
233
  **Parameters:**
234
- - `index` (String) - Index/collection name
234
+ - `index` (String) - Index/collection name (uses client's default index when omitted)
235
235
  - `namespace` (String, optional) - Namespace
236
236
 
237
237
  **Returns:** `Hash` with statistics:
@@ -571,6 +571,30 @@ end
571
571
 
572
572
  ---
573
573
 
574
+ ### `Model.reindex_vectors(scope: all, batch_size: 1000, on_progress: nil)`
575
+
576
+ Reindex all records for a model into the configured vector index.
577
+
578
+ **Parameters:**
579
+ - `scope` (ActiveRecord::Relation) - Records to reindex (default: `Model.all`)
580
+ - `batch_size` (Integer) - Number of records per batch (default: 1000)
581
+ - `on_progress` (Proc, optional) - Progress callback, receives a hash with `:processed` and `:total`
582
+
583
+ **Returns:** `Integer` - Number of records processed
584
+
585
+ **Example:**
586
+ ```ruby
587
+ # Reindex all products with embeddings
588
+ processed = Product.reindex_vectors(
589
+ scope: Product.where.not(embedding: nil),
590
+ batch_size: 500
591
+ )
592
+
593
+ puts "Reindexed #{processed} products"
594
+ ```
595
+
596
+ ---
597
+
574
598
  ## Error Handling
575
599
 
576
600
  Vectra defines specific error types:
data/docs/api/overview.md CHANGED
@@ -216,3 +216,9 @@ end
216
216
  ```
217
217
 
218
218
  See [Complete API Methods Reference]({{ site.baseurl }}/api/methods/) for detailed method documentation.
219
+
220
+ ## Next Steps
221
+
222
+ - [Providers Guide]({{ site.baseurl }}/providers/)
223
+ - [Rails Integration Guide]({{ site.baseurl }}/guides/rails-integration/)
224
+ - [Middleware System]({{ site.baseurl }}/guides/middleware/)
@@ -0,0 +1,324 @@
1
+ ---
2
+ layout: page
3
+ title: Middleware System
4
+ permalink: /guides/middleware/
5
+ ---
6
+
7
+ # Middleware System
8
+
9
+ Vectra includes a **Rack-style middleware stack** that lets you extend the client
10
+ without forking the gem or patching providers.
11
+
12
+ You can:
13
+
14
+ - Add **logging, metrics, retries, PII redaction, cost tracking**.
15
+ - Inject **custom behaviour** before/after every operation.
16
+ - Enable features **globally** or **per client**, same kao Faraday/Sidekiq.
17
+
18
+ ---
19
+
20
+ ## Core Concepts
21
+
22
+ ### Request / Response
23
+
24
+ - **`Vectra::Middleware::Request`**
25
+ - `operation` – npr. `:upsert`, `:query`, `:fetch`, `:delete`, `:stats`, `:hybrid_search`
26
+ - `index` – ime indeksa (može biti `nil` ako koristiš default)
27
+ - `namespace` – namespace (može biti `nil`)
28
+ - `params` – originalni keyword parametri koje je `Vectra::Client` proslijedio
29
+ - `provider` – ime providera (`:pinecone`, `:qdrant`, `:pgvector`, `:memory`, …)
30
+ - helperi: `write_operation?`, `read_operation?`
31
+
32
+ - **`Vectra::Middleware::Response`**
33
+ - `result` – što god provider vrati (npr. hash, `QueryResult`, itd.)
34
+ - `error` – iznimka ako je došlo do greške
35
+ - `metadata` – slobodan hash za dodatne informacije (trajanje, cost, retry_count…)
36
+ - helperi: `success?`, `failure?`, `raise_if_error!`, `value!`
37
+
38
+ ### Base i Stack
39
+
40
+ - **`Vectra::Middleware::Base`**
41
+ - Hookovi koje možeš override-ati:
42
+ - `before(request)` – prije poziva providera / sljedećeg middlewarea
43
+ - `after(request, response)` – nakon uspješnog poziva
44
+ - `on_error(request, error)` – kad dođe do iznimke (error se zatim re-raise-a)
45
+
46
+ - **`Vectra::Middleware::Stack`**
47
+ - Gradi chain oko konkretnog providera:
48
+ ```ruby
49
+ stack = Vectra::Middleware::Stack.new(provider, [Logging.new, Retry.new])
50
+ result = stack.call(:upsert, index: "docs", vectors: [...], provider: :qdrant)
51
+ ```
52
+ - `Stack` interno:
53
+ - kreira `Request`,
54
+ - kroz sve middlewares propagira isti `Request`,
55
+ - na kraju zove `provider.public_send(request.operation, **provider_params)`,
56
+ - vraća `Response` (s `result` ili `error`).
57
+
58
+ ---
59
+
60
+ ## Enabling Middleware
61
+
62
+ ### Global Middleware
63
+
64
+ Primjenjuje se na **sve** `Vectra::Client` instance.
65
+
66
+ ```ruby
67
+ require "vectra"
68
+
69
+ # Global logging + retry + cost tracking
70
+ Vectra::Client.use Vectra::Middleware::Logging
71
+ Vectra::Client.use Vectra::Middleware::Retry, max_attempts: 5
72
+ Vectra::Client.use Vectra::Middleware::CostTracker
73
+ ```
74
+
75
+ Sve sljedeće `Vectra::Client.new(...)` instance će koristiti ovaj globalni stack.
76
+
77
+ ### Per‑Client Middleware
78
+
79
+ Dodatni ili prilagođeni middleware samo za jedan klijent:
80
+
81
+ ```ruby
82
+ client = Vectra::Client.new(
83
+ provider: :qdrant,
84
+ index: "products",
85
+ middleware: [
86
+ Vectra::Middleware::PIIRedaction,
87
+ Vectra::Middleware::Instrumentation
88
+ ]
89
+ )
90
+ ```
91
+
92
+ - Per‑client middleware se izvodi **nakon** globalnog, u istom chainu.
93
+ - Redoslijed u arrayu definira redoslijed ekzekucije (zadnji je najunutarnji, tik do providera).
94
+
95
+ ---
96
+
97
+ ## Koje operacije prolaze kroz stack?
98
+
99
+ Sve standardne operacije `Vectra::Client`a koriste middleware stack:
100
+
101
+ - `upsert`
102
+ - `query`
103
+ - `fetch`
104
+ - `update`
105
+ - `delete`
106
+ - `list_indexes`
107
+ - `describe_index`
108
+ - `stats`
109
+ - `hybrid_search`
110
+
111
+ To znači da middleware može:
112
+
113
+ - logirati / instrumentirati **sve pozive** prema provideru,
114
+ - raditi **PII redakciju** na `upsert` zahtjevima,
115
+ - brojati i retry-ati i **read** i **write** operacije,
116
+ - računati trošak po operaciji (npr. za billing / budžete).
117
+
118
+ ---
119
+
120
+ ## Built‑in Middleware
121
+
122
+ ### Logging (`Vectra::Middleware::Logging`)
123
+
124
+ **Što radi:**
125
+ - logira početak i kraj svake operacije (`operation`, `index`, `namespace`),
126
+ - mjeri trajanje i sprema ga u `response.metadata[:duration_ms]`.
127
+
128
+ **Konfiguracija:**
129
+
130
+ ```ruby
131
+ # Globalno
132
+ Vectra::Client.use Vectra::Middleware::Logging
133
+
134
+ # S custom loggerom
135
+ logger = Logger.new($stdout)
136
+ Vectra::Client.use Vectra::Middleware::Logging, logger: logger
137
+ ```
138
+
139
+ **Tipična upotreba:** debugiranje, audit logovi, korelacija s HTTP logovima.
140
+
141
+ ---
142
+
143
+ ### Retry (`Vectra::Middleware::Retry`)
144
+
145
+ **Što radi:**
146
+ - automatski retry-a transient greške:
147
+ - `Vectra::RateLimitError`
148
+ - `Vectra::ConnectionError`
149
+ - `Vectra::TimeoutError`
150
+ - `Vectra::ServerError`
151
+ - koristi exponential ili linear backoff,
152
+ - upisuje broj retry-a u `response.metadata[:retry_count]`.
153
+
154
+ **Konfiguracija:**
155
+
156
+ ```ruby
157
+ # 3 pokušaja, exponential backoff (default)
158
+ Vectra::Client.use Vectra::Middleware::Retry
159
+
160
+ # 5 pokušaja, linearni backoff
161
+ Vectra::Client.use Vectra::Middleware::Retry,
162
+ max_attempts: 5,
163
+ backoff: :linear
164
+
165
+ # Fiksni delay 1.0s
166
+ Vectra::Client.use Vectra::Middleware::Retry,
167
+ max_attempts: 3,
168
+ backoff: 1.0
169
+ ```
170
+
171
+ **Tipična upotreba:** zaštita od povremenih mrežnih problema i rate‑limit grešaka.
172
+
173
+ ---
174
+
175
+ ### Instrumentation (`Vectra::Middleware::Instrumentation`)
176
+
177
+ **Što radi:**
178
+ - emitira događaje preko postojećeg `Vectra::Instrumentation` sustava,
179
+ - prikuplja trajanje, status, error class, dodatni `metadata`.
180
+
181
+ **Primjer:**
182
+
183
+ ```ruby
184
+ Vectra::Client.use Vectra::Middleware::Instrumentation
185
+
186
+ Vectra.on_operation do |event|
187
+ # event[:operation], event[:provider], event[:duration_ms], event[:success], ...
188
+ StatsD.timing("vectra.#{event[:operation]}", event[:duration_ms])
189
+ end
190
+ ```
191
+
192
+ **Tipična upotreba:** integracija s Prometheus/Grafana, Datadog, New Relic…
193
+
194
+ ---
195
+
196
+ ### PII Redaction (`Vectra::Middleware::PIIRedaction`)
197
+
198
+ **Što radi:**
199
+ - prije `upsert` operacija prolazi kroz `vectors[:metadata]`,
200
+ - prepoznaje PII pattern-e (email, phone, SSN, credit card) i zamjenjuje ih placeholderom
201
+ npr. `[REDACTED_EMAIL]`, `[REDACTED_PHONE]`, itd.
202
+
203
+ **Primjer:**
204
+
205
+ ```ruby
206
+ Vectra::Client.use Vectra::Middleware::PIIRedaction
207
+
208
+ client.upsert(
209
+ index: "sensitive",
210
+ vectors: [
211
+ {
212
+ id: "user-1",
213
+ values: [0.1, 0.2, 0.3],
214
+ metadata: {
215
+ email: "user@example.com",
216
+ phone: "555-1234",
217
+ note: "Contact at user@example.com"
218
+ }
219
+ }
220
+ ]
221
+ )
222
+ ```
223
+
224
+ Nakon `upsert`‑a, provider će vidjeti već **redaktirani** metadata.
225
+
226
+ **Custom patterni:**
227
+
228
+ ```ruby
229
+ patterns = {
230
+ credit_card: /\b\d{4}[-\s]?\d{4}[-\s]?\d{4}[-\s]?\d{4}\b/,
231
+ api_key: /sk-[a-zA-Z0-9]{32}/
232
+ }
233
+
234
+ Vectra::Client.use Vectra::Middleware::PIIRedaction, patterns: patterns
235
+ ```
236
+
237
+ **Tipična upotreba:** GDPR, SOC2, PCI‑DSS okruženja gdje je zabranjen PII u vektor bazi.
238
+
239
+ ---
240
+
241
+ ### CostTracker (`Vectra::Middleware::CostTracker`)
242
+
243
+ **Što radi:**
244
+ - procjenjuje trošak po operaciji na temelju providera i tipa operacije (`read` / `write`),
245
+ - upisuje trošak u `response.metadata[:cost_usd]`,
246
+ - opcionalno zove `on_cost` callback za real‑time praćenje.
247
+
248
+ **Primjer:**
249
+
250
+ ```ruby
251
+ Vectra::Client.use Vectra::Middleware::CostTracker,
252
+ on_cost: ->(event) {
253
+ puts "💰 Cost: $#{event[:cost_usd].round(6)} for #{event[:operation]} (#{event[:provider]})"
254
+ }
255
+ ```
256
+
257
+ **Custom pricing:**
258
+
259
+ ```ruby
260
+ pricing = {
261
+ pinecone: { read: 0.0001, write: 0.0002 },
262
+ qdrant: { read: 0.00005, write: 0.0001 }
263
+ }
264
+
265
+ Vectra::Client.use Vectra::Middleware::CostTracker, pricing: pricing
266
+ ```
267
+
268
+ **Tipična upotreba:** unutarnji billing, budget guardrails, cost dashboards.
269
+
270
+ ---
271
+
272
+ ## Custom Middleware
273
+
274
+ Najjednostavniji način je naslijediti `Vectra::Middleware::Base` i override-ati hookove:
275
+
276
+ ```ruby
277
+ class MyAuditMiddleware < Vectra::Middleware::Base
278
+ def before(request)
279
+ AuditLog.create!(
280
+ operation: request.operation,
281
+ index: request.index,
282
+ namespace: request.namespace,
283
+ provider: request.provider
284
+ )
285
+ end
286
+
287
+ def after(_request, response)
288
+ puts "Duration: #{response.metadata[:duration_ms]}ms"
289
+ end
290
+
291
+ def on_error(request, error)
292
+ ErrorTracker.notify(error, context: { operation: request.operation })
293
+ end
294
+ end
295
+
296
+ Vectra::Client.use MyAuditMiddleware
297
+ ```
298
+
299
+ **Savjeti:**
300
+
301
+ - Ne mijenjaj strukturu `request.params` na način koji provider ne očekuje.
302
+ - Svoje pomoćne podatke stavljaj u `response.metadata` ili `request.metadata`.
303
+ - Ako hvataš greške u `on_error`, **nemoj ih gutati** – middleware stack će ih ponovo baciti.
304
+
305
+ ---
306
+
307
+ ## Primjer: `examples/middleware_demo.rb`
308
+
309
+ U repozitoriju imaš kompletan demo:
310
+
311
+ - konfigurira globalni stack (`Logging`, `Retry`, `CostTracker`),
312
+ - pokazuje per‑client PII redaction,
313
+ - definira custom `TimingMiddleware`,
314
+ - demonstrira kako izgleda output u konzoli.
315
+
316
+ Pokretanje:
317
+
318
+ ```bash
319
+ bundle exec ruby examples/middleware_demo.rb
320
+ ```
321
+
322
+ Ovaj demo je dobar “živi” primjer kako kombinirati više middleware-a u praksi.
323
+
324
+
@@ -0,0 +1,103 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # Middleware System Demo
5
+ #
6
+ # This script demonstrates the new middleware system in Vectra.
7
+ # Run with: ruby examples/middleware_demo.rb
8
+
9
+ require_relative "../lib/vectra"
10
+
11
+ # Configure global middleware
12
+ puts "🎯 Configuring global middleware..."
13
+ Vectra::Client.use Vectra::Middleware::Logging
14
+ Vectra::Client.use Vectra::Middleware::Retry, max_attempts: 3
15
+ Vectra::Client.use Vectra::Middleware::CostTracker, on_cost: ->(event) {
16
+ puts "💰 Cost: $#{event[:cost_usd].round(6)} for #{event[:operation]}"
17
+ }
18
+
19
+ # Create client
20
+ puts "\n📦 Creating client with Memory provider..."
21
+ client = Vectra::Client.new(
22
+ provider: :memory,
23
+ index: "demo"
24
+ )
25
+
26
+ # Example 1: Upsert with middleware
27
+ puts "\n🔄 Example 1: Upsert with middleware stack"
28
+ puts "=" * 50
29
+ client.upsert(
30
+ index: "demo",
31
+ vectors: [
32
+ { id: "doc-1", values: [0.1, 0.2, 0.3], metadata: { title: "Ruby" } },
33
+ { id: "doc-2", values: [0.4, 0.5, 0.6], metadata: { title: "Python" } }
34
+ ]
35
+ )
36
+
37
+ # Example 2: Query with middleware
38
+ puts "\n🔍 Example 2: Query with middleware stack"
39
+ puts "=" * 50
40
+ results = client.query(
41
+ index: "demo",
42
+ vector: [0.1, 0.2, 0.3],
43
+ top_k: 2
44
+ )
45
+ puts "Found #{results.size} results"
46
+
47
+ # Example 3: Per-client middleware
48
+ puts "\n🎨 Example 3: Per-client middleware (PII Redaction)"
49
+ puts "=" * 50
50
+ pii_client = Vectra::Client.new(
51
+ provider: :memory,
52
+ index: "sensitive",
53
+ middleware: [Vectra::Middleware::PIIRedaction]
54
+ )
55
+
56
+ pii_client.upsert(
57
+ index: "sensitive",
58
+ vectors: [
59
+ {
60
+ id: "user-1",
61
+ values: [0.1, 0.2, 0.3],
62
+ metadata: {
63
+ email: "user@example.com",
64
+ phone: "555-1234",
65
+ note: "Contact at user@example.com"
66
+ }
67
+ }
68
+ ]
69
+ )
70
+
71
+ # Fetch to see redacted data
72
+ fetched = pii_client.fetch(index: "sensitive", ids: ["user-1"])
73
+ puts "Original email: user@example.com"
74
+ puts "Redacted: #{fetched["user-1"].metadata[:email]}"
75
+ puts "Redacted note: #{fetched["user-1"].metadata[:note]}"
76
+
77
+ # Example 4: Custom middleware
78
+ puts "\n🛠️ Example 4: Custom middleware"
79
+ puts "=" * 50
80
+
81
+ class TimingMiddleware < Vectra::Middleware::Base
82
+ def before(request)
83
+ puts "⏱️ Starting #{request.operation}..."
84
+ end
85
+
86
+ def after(request, response)
87
+ duration = response.metadata[:duration_ms] || 0
88
+ puts "✅ Completed in #{duration.round(2)}ms"
89
+ end
90
+ end
91
+
92
+ custom_client = Vectra::Client.new(
93
+ provider: :memory,
94
+ index: "custom",
95
+ middleware: [TimingMiddleware]
96
+ )
97
+
98
+ custom_client.upsert(
99
+ index: "custom",
100
+ vectors: [{ id: "test", values: [1, 2, 3] }]
101
+ )
102
+
103
+ puts "\n✨ Demo complete!"