Middleware System
Vectra includes a Rack-style middleware stack that lets you extend the client without forking the gem or patching providers.
You can:
- Add logging, metrics, retries, PII redaction, cost tracking.
- Inject custom behaviour before/after every operation.
- Enable features globally or per client, same kao Faraday/Sidekiq.
Core Concepts
Request / Response
Vectra::Middleware::Requestoperation– npr.:upsert,:query,:fetch,:delete,:stats,:hybrid_searchindex– ime indeksa (može bitinilako koristiš default)namespace– namespace (može bitinil)params– originalni keyword parametri koje jeVectra::Clientproslijedioprovider– ime providera (:pinecone,:qdrant,:pgvector,:memory, …)- helperi:
write_operation?,read_operation?
Vectra::Middleware::Responseresult– što god provider vrati (npr. hash,QueryResult, itd.)error– iznimka ako je došlo do greškemetadata– slobodan hash za dodatne informacije (trajanje, cost, retry_count…)- helperi:
success?,failure?,raise_if_error!,value!
Base i Stack
Vectra::Middleware::Base- Hookovi koje možeš override-ati:
before(request)– prije poziva providera / sljedećeg middlewareaafter(request, response)– nakon uspješnog pozivaon_error(request, error)– kad dođe do iznimke (error se zatim re-raise-a)
- Hookovi koje možeš override-ati:
Vectra::Middleware::Stack- Gradi chain oko konkretnog providera:
stack = Vectra::Middleware::Stack.new(provider, [Logging.new, Retry.new]) result = stack.call(:upsert, index: "docs", vectors: [...], provider: :qdrant) Stackinterno:- kreira
Request, - kroz sve middlewares propagira isti
Request, - na kraju zove
provider.public_send(request.operation, **provider_params), - vraća
Response(sresultilierror).
- kreira
- Gradi chain oko konkretnog providera:
Enabling Middleware
Global Middleware
Primjenjuje se na sve Vectra::Client instance.
require "vectra"
# Global logging + retry + cost tracking
Vectra::Client.use Vectra::Middleware::Logging
Vectra::Client.use Vectra::Middleware::Retry, max_attempts: 5
Vectra::Client.use Vectra::Middleware::CostTracker
Sve sljedeće Vectra::Client.new(...) instance će koristiti ovaj globalni stack.
Per‑Client Middleware
Dodatni ili prilagođeni middleware samo za jedan klijent:
client = Vectra::Client.new(
provider: :qdrant,
index: "products",
middleware: [
Vectra::Middleware::PIIRedaction,
Vectra::Middleware::Instrumentation
]
)
- Per‑client middleware se izvodi nakon globalnog, u istom chainu.
- Redoslijed u arrayu definira redoslijed ekzekucije (zadnji je najunutarnji, tik do providera).
Koje operacije prolaze kroz stack?
Sve standardne operacije Vectra::Clienta koriste middleware stack:
upsertqueryfetchupdatedeletelist_indexesdescribe_indexstatshybrid_search
To znači da middleware može:
- logirati / instrumentirati sve pozive prema provideru,
- raditi PII redakciju na
upsertzahtjevima, - brojati i retry-ati i read i write operacije,
- računati trošak po operaciji (npr. za billing / budžete).
Built‑in Middleware
Logging (Vectra::Middleware::Logging)
Što radi:
- logira početak i kraj svake operacije (
operation,index,namespace), - mjeri trajanje i sprema ga u
response.metadata[:duration_ms].
Konfiguracija:
# Globalno
Vectra::Client.use Vectra::Middleware::Logging
# S custom loggerom
logger = Logger.new($stdout)
Vectra::Client.use Vectra::Middleware::Logging, logger: logger
Tipična upotreba: debugiranje, audit logovi, korelacija s HTTP logovima.
Retry (Vectra::Middleware::Retry)
Što radi:
- automatski retry-a transient greške:
Vectra::RateLimitErrorVectra::ConnectionErrorVectra::TimeoutErrorVectra::ServerError
- koristi exponential ili linear backoff,
- upisuje broj retry-a u
response.metadata[:retry_count].
Konfiguracija:
# 3 pokušaja, exponential backoff (default)
Vectra::Client.use Vectra::Middleware::Retry
# 5 pokušaja, linearni backoff
Vectra::Client.use Vectra::Middleware::Retry,
max_attempts: 5,
backoff: :linear
# Fiksni delay 1.0s
Vectra::Client.use Vectra::Middleware::Retry,
max_attempts: 3,
backoff: 1.0
Tipična upotreba: zaštita od povremenih mrežnih problema i rate‑limit grešaka.
Instrumentation (Vectra::Middleware::Instrumentation)
Što radi:
- emitira događaje preko postojećeg
Vectra::Instrumentationsustava, - prikuplja trajanje, status, error class, dodatni
metadata.
Primjer:
Vectra::Client.use Vectra::Middleware::Instrumentation
Vectra.on_operation do |event|
# event[:operation], event[:provider], event[:duration_ms], event[:success], ...
StatsD.timing("vectra.#{event[:operation]}", event[:duration_ms])
end
Tipična upotreba: integracija s Prometheus/Grafana, Datadog, New Relic…
PII Redaction (Vectra::Middleware::PIIRedaction)
Što radi:
- prije
upsertoperacija prolazi krozvectors[:metadata], - prepoznaje PII pattern-e (email, phone, SSN, credit card) i zamjenjuje ih placeholderom
npr.
[REDACTED_EMAIL],[REDACTED_PHONE], itd.
Primjer:
Vectra::Client.use Vectra::Middleware::PIIRedaction
client.upsert(
index: "sensitive",
vectors: [
{
id: "user-1",
values: [0.1, 0.2, 0.3],
metadata: {
email: "user@example.com",
phone: "555-1234",
note: "Contact at user@example.com"
}
}
]
)
Nakon upsert‑a, provider će vidjeti već redaktirani metadata.
Custom patterni:
patterns = {
credit_card: /\b\d{4}[-\s]?\d{4}[-\s]?\d{4}[-\s]?\d{4}\b/,
api_key: /sk-[a-zA-Z0-9]{32}/
}
Vectra::Client.use Vectra::Middleware::PIIRedaction, patterns: patterns
Tipična upotreba: GDPR, SOC2, PCI‑DSS okruženja gdje je zabranjen PII u vektor bazi.
CostTracker (Vectra::Middleware::CostTracker)
Što radi:
- procjenjuje trošak po operaciji na temelju providera i tipa operacije (
read/write), - upisuje trošak u
response.metadata[:cost_usd], - opcionalno zove
on_costcallback za real‑time praćenje.
Primjer:
Vectra::Client.use Vectra::Middleware::CostTracker,
on_cost: ->(event) {
puts "💰 Cost: $#{event[:cost_usd].round(6)} for #{event[:operation]} (#{event[:provider]})"
}
Custom pricing:
pricing = {
pinecone: { read: 0.0001, write: 0.0002 },
qdrant: { read: 0.00005, write: 0.0001 }
}
Vectra::Client.use Vectra::Middleware::CostTracker, pricing: pricing
Tipična upotreba: unutarnji billing, budget guardrails, cost dashboards.
Request ID Tracking (Vectra::Middleware::RequestId)
Što radi:
- Generira unique request ID za svaku operaciju
- Propagira ID kroz request/response metadata i logove
- Standardna praksa u production API-jima za request tracing i debugging
Primjer:
# Globalno
Vectra::Client.use Vectra::Middleware::RequestId
# S custom prefixom
Vectra::Client.use Vectra::Middleware::RequestId, prefix: "myapp"
# S custom generatorom
Vectra::Client.use Vectra::Middleware::RequestId,
generator: ->(prefix) { "#{prefix}-#{Time.now.to_i}-#{SecureRandom.hex(8)}" }
# S callback-om za tracking
captured_ids = []
Vectra::Client.use Vectra::Middleware::RequestId,
on_assign: ->(id) { captured_ids << id }
# S custom loggerom (request ID se logira u before/after/on_error)
logger = Logger.new($stdout)
Vectra::Client.use Vectra::Middleware::RequestId, logger: logger
Pristup request ID-u:
# Request ID je automatski dostupan u metadata
client.upsert(index: "test", vectors: [...])
# U custom middleware-u:
def after(request, response)
request_id = request.metadata[:request_id]
response.metadata[:request_id] = request_id
# Logiraj s request ID-om za tracing
end
Tipična upotreba: distributed tracing, log correlation, debugging production issues.
Dry Run / Explain Mode (Vectra::Middleware::DryRun)
Što radi:
- Presreće write operacije i logira što bi se izvršilo umjesto stvarnog izvršavanja
- Vraća mock response bez side effects
- Korisno za debugging, testiranje i razumijevanje operacija
Primjer:
# Enable dry run mode
client = Vectra::Client.new(
provider: :qdrant,
middleware: [Vectra::Middleware::DryRun]
)
# Operacije će biti logirane ali neće se izvršiti
client.upsert(
index: "products",
vectors: [
{ id: "1", values: [0.1, 0.2, 0.3] },
{ id: "2", values: [0.4, 0.5, 0.6] }
]
)
# => [DRY RUN] UPSERT index=products namespace=default vectors=2
# Read operacije prolaze normalno
results = client.query(index: "products", vector: [0.1, 0.2, 0.3], top_k: 10)
# => Normal query results (dry run ne presreće read operacije)
S custom loggerom:
logger = Logger.new($stdout)
Vectra::Client.use Vectra::Middleware::DryRun, logger: logger
S custom formatterom:
formatter = ->(request) {
"DRY RUN: #{request.operation} on #{request.index} with #{request.params[:vectors]&.size || 0} vectors"
}
Vectra::Client.use Vectra::Middleware::DryRun, formatter: formatter
S callback-om:
plans = []
Vectra::Client.use Vectra::Middleware::DryRun,
on_dry_run: ->(plan) {
plans << plan
# plan[:operation], plan[:index], plan[:vector_count], etc.
}
Tipična upotreba: testing, debugging, understanding operation behavior, preview changes.
Custom Middleware
Najjednostavniji način je naslijediti Vectra::Middleware::Base i override-ati hookove:
class MyAuditMiddleware < Vectra::Middleware::Base
def before(request)
AuditLog.create!(
operation: request.operation,
index: request.index,
namespace: request.namespace,
provider: request.provider
)
end
def after(_request, response)
puts "Duration: #{response.metadata[:duration_ms]}ms"
end
def on_error(request, error)
ErrorTracker.notify(error, context: { operation: request.operation })
end
end
Vectra::Client.use MyAuditMiddleware
Savjeti:
- Ne mijenjaj strukturu
request.paramsna način koji provider ne očekuje. - Svoje pomoćne podatke stavljaj u
response.metadatailirequest.metadata. - Ako hvataš greške u
on_error, nemoj ih gutati – middleware stack će ih ponovo baciti.
Primjer: examples/middleware_demo.rb
U repozitoriju imaš kompletan demo:
- konfigurira globalni stack (
Logging,Retry,CostTracker), - pokazuje per‑client PII redaction,
- definira custom
TimingMiddleware, - demonstrira kako izgleda output u konzoli.
Pokretanje:
bundle exec ruby examples/middleware_demo.rb
Ovaj demo je dobar “živi” primjer kako kombinirati više middleware-a u praksi.