whoosh 1.1.0 → 1.2.1
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/lib/whoosh/app.rb +5 -0
- data/lib/whoosh/cli/main.rb +58 -0
- data/lib/whoosh/cli/project_generator.rb +43 -1
- data/lib/whoosh/vector_store/memory_store.rb +79 -0
- data/lib/whoosh/vector_store.rb +42 -0
- data/lib/whoosh/version.rb +1 -1
- data/lib/whoosh.rb +1 -0
- metadata +3 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: f647a91b22fcee8322c6af7bf0ab0ac0108aa3113e2d4db89a3f187bb6a1caa3
|
|
4
|
+
data.tar.gz: e46c07b2f4fbaf1233bb52a321ff161952989313adbbfebdb5b7bf1c08675f08
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 1ca2dbc63ab481d5d61260cfbfca37768a9498fa74fe90d68f039884f05ca2a79f0fa08da07fdca04ba175e95ab0997d5cc00acf0e8b2bbceef5ca576f8fef91
|
|
7
|
+
data.tar.gz: b0361d6a812b80e093125f7be7d5681977ab615b6ebccacbe87753352223e6058e0076dd2eace8e3092a60a57e3387b9ebe0baed4b2f623befeb8f1c38d9435e
|
data/lib/whoosh/app.rb
CHANGED
|
@@ -29,6 +29,7 @@ module Whoosh
|
|
|
29
29
|
auto_register_database
|
|
30
30
|
auto_register_storage
|
|
31
31
|
auto_register_http
|
|
32
|
+
auto_register_vectors
|
|
32
33
|
auto_configure_jobs
|
|
33
34
|
@metrics = Metrics.new
|
|
34
35
|
auto_register_metrics
|
|
@@ -302,6 +303,10 @@ module Whoosh
|
|
|
302
303
|
@di.provide(:http) { HTTP }
|
|
303
304
|
end
|
|
304
305
|
|
|
306
|
+
def auto_register_vectors
|
|
307
|
+
@di.provide(:vectors) { VectorStore.build(@config.data) }
|
|
308
|
+
end
|
|
309
|
+
|
|
305
310
|
def auto_configure_jobs
|
|
306
311
|
backend = Jobs.build_backend(@config.data)
|
|
307
312
|
Jobs.configure(backend: backend, di: @di)
|
data/lib/whoosh/cli/main.rb
CHANGED
|
@@ -224,6 +224,64 @@ module Whoosh
|
|
|
224
224
|
end
|
|
225
225
|
}
|
|
226
226
|
|
|
227
|
+
desc "ci", "Run full CI pipeline (lint + security + tests)"
|
|
228
|
+
def ci
|
|
229
|
+
puts "=> Whoosh CI Pipeline"
|
|
230
|
+
puts "=" * 50
|
|
231
|
+
puts ""
|
|
232
|
+
|
|
233
|
+
steps = []
|
|
234
|
+
|
|
235
|
+
# Step 1: Rubocop (lint)
|
|
236
|
+
if system("bundle exec rubocop --version > /dev/null 2>&1")
|
|
237
|
+
steps << { name: "Rubocop (lint)", cmd: "bundle exec rubocop --format simple" }
|
|
238
|
+
else
|
|
239
|
+
puts " [skip] Rubocop not installed (add rubocop to Gemfile)"
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
# Step 2: Brakeman (security)
|
|
243
|
+
if system("bundle exec brakeman --version > /dev/null 2>&1")
|
|
244
|
+
steps << { name: "Brakeman (security)", cmd: "bundle exec brakeman -q --no-pager" }
|
|
245
|
+
else
|
|
246
|
+
puts " [skip] Brakeman not installed (add brakeman to Gemfile)"
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
# Step 3: RSpec (tests)
|
|
250
|
+
if system("bundle exec rspec --version > /dev/null 2>&1")
|
|
251
|
+
steps << { name: "RSpec (tests)", cmd: "bundle exec rspec --format progress" }
|
|
252
|
+
else
|
|
253
|
+
puts " [skip] RSpec not installed"
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
# Step 4: Gem build check
|
|
257
|
+
gemspec = Dir.glob("*.gemspec").first
|
|
258
|
+
if gemspec
|
|
259
|
+
steps << { name: "Gem build", cmd: "gem build #{gemspec} --quiet" }
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
failed = []
|
|
263
|
+
steps.each_with_index do |step, i|
|
|
264
|
+
puts "--- [#{i + 1}/#{steps.length}] #{step[:name]} ---"
|
|
265
|
+
success = system(step[:cmd])
|
|
266
|
+
if success
|
|
267
|
+
puts " ✓ #{step[:name]} passed"
|
|
268
|
+
else
|
|
269
|
+
puts " ✗ #{step[:name]} FAILED"
|
|
270
|
+
failed << step[:name]
|
|
271
|
+
end
|
|
272
|
+
puts ""
|
|
273
|
+
end
|
|
274
|
+
|
|
275
|
+
puts "=" * 50
|
|
276
|
+
if failed.empty?
|
|
277
|
+
puts "=> All checks passed! ✓"
|
|
278
|
+
exit 0
|
|
279
|
+
else
|
|
280
|
+
puts "=> FAILED: #{failed.join(', ')}"
|
|
281
|
+
exit 1
|
|
282
|
+
end
|
|
283
|
+
end
|
|
284
|
+
|
|
227
285
|
desc "new NAME", "Create a new Whoosh project"
|
|
228
286
|
option :minimal, type: :boolean, default: false
|
|
229
287
|
option :full, type: :boolean, default: false
|
|
@@ -31,6 +31,7 @@ module Whoosh
|
|
|
31
31
|
write(dir, ".rspec", rspec_config)
|
|
32
32
|
write(dir, "Dockerfile", dockerfile)
|
|
33
33
|
write(dir, ".dockerignore", dockerignore)
|
|
34
|
+
write(dir, ".rubocop.yml", rubocop_config)
|
|
34
35
|
write(dir, "README.md", readme(name))
|
|
35
36
|
|
|
36
37
|
# Create empty SQLite DB directory
|
|
@@ -55,6 +56,8 @@ module Whoosh
|
|
|
55
56
|
puts " http://localhost:9292/docs # Swagger UI"
|
|
56
57
|
puts " http://localhost:9292/metrics # Prometheus metrics"
|
|
57
58
|
puts ""
|
|
59
|
+
puts " whoosh ci # run lint + security + tests"
|
|
60
|
+
puts ""
|
|
58
61
|
end
|
|
59
62
|
|
|
60
63
|
class << self
|
|
@@ -145,6 +148,8 @@ module Whoosh
|
|
|
145
148
|
group :development, :test do
|
|
146
149
|
gem "rspec"
|
|
147
150
|
gem "rack-test"
|
|
151
|
+
gem "rubocop", require: false
|
|
152
|
+
gem "brakeman", require: false
|
|
148
153
|
end
|
|
149
154
|
GEM
|
|
150
155
|
|
|
@@ -175,8 +180,10 @@ module Whoosh
|
|
|
175
180
|
max_connections: 10
|
|
176
181
|
log_level: debug
|
|
177
182
|
|
|
183
|
+
# Cache & Jobs auto-detect:
|
|
184
|
+
# No REDIS_URL → in-memory (just works)
|
|
185
|
+
# Set REDIS_URL → auto-switches to Redis
|
|
178
186
|
cache:
|
|
179
|
-
store: memory
|
|
180
187
|
default_ttl: 300
|
|
181
188
|
|
|
182
189
|
jobs:
|
|
@@ -188,6 +195,12 @@ module Whoosh
|
|
|
188
195
|
level: info
|
|
189
196
|
format: json
|
|
190
197
|
|
|
198
|
+
# Vector store auto-detect:
|
|
199
|
+
# zvec gem installed → uses zvec, otherwise → in-memory
|
|
200
|
+
# vector:
|
|
201
|
+
# adapter: auto
|
|
202
|
+
# path: db/vectors
|
|
203
|
+
|
|
191
204
|
docs:
|
|
192
205
|
enabled: true
|
|
193
206
|
|
|
@@ -341,6 +354,35 @@ module Whoosh
|
|
|
341
354
|
DOCKERFILE
|
|
342
355
|
end
|
|
343
356
|
|
|
357
|
+
def rubocop_config
|
|
358
|
+
<<~YAML
|
|
359
|
+
AllCops:
|
|
360
|
+
TargetRubyVersion: 3.4
|
|
361
|
+
NewCops: enable
|
|
362
|
+
SuggestExtensions: false
|
|
363
|
+
Exclude:
|
|
364
|
+
- db/migrations/**/*
|
|
365
|
+
- vendor/**/*
|
|
366
|
+
|
|
367
|
+
Style/FrozenStringLiteralComment:
|
|
368
|
+
Enabled: true
|
|
369
|
+
|
|
370
|
+
Style/StringLiterals:
|
|
371
|
+
EnforcedStyle: double_quotes
|
|
372
|
+
|
|
373
|
+
Layout/LineLength:
|
|
374
|
+
Max: 120
|
|
375
|
+
|
|
376
|
+
Metrics/MethodLength:
|
|
377
|
+
Max: 25
|
|
378
|
+
|
|
379
|
+
Metrics/BlockLength:
|
|
380
|
+
Exclude:
|
|
381
|
+
- spec/**/*
|
|
382
|
+
- test/**/*
|
|
383
|
+
YAML
|
|
384
|
+
end
|
|
385
|
+
|
|
344
386
|
def dockerignore
|
|
345
387
|
<<~IGNORE
|
|
346
388
|
.git
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Whoosh
|
|
4
|
+
module VectorStore
|
|
5
|
+
class MemoryStore
|
|
6
|
+
def initialize
|
|
7
|
+
@collections = {}
|
|
8
|
+
@mutex = Mutex.new
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
# Store a vector with metadata
|
|
12
|
+
def insert(collection, id:, vector:, metadata: {})
|
|
13
|
+
@mutex.synchronize do
|
|
14
|
+
@collections[collection] ||= {}
|
|
15
|
+
@collections[collection][id] = { vector: vector, metadata: metadata }
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# Search by cosine similarity, return top-k results
|
|
20
|
+
def search(collection, vector:, limit: 10)
|
|
21
|
+
@mutex.synchronize do
|
|
22
|
+
items = @collections[collection]
|
|
23
|
+
return [] unless items && !items.empty?
|
|
24
|
+
|
|
25
|
+
scored = items.map do |id, data|
|
|
26
|
+
score = cosine_similarity(vector, data[:vector])
|
|
27
|
+
{ id: id, score: score, metadata: data[:metadata] }
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
scored.sort_by { |r| -r[:score] }.first(limit)
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Delete a vector
|
|
35
|
+
def delete(collection, id:)
|
|
36
|
+
@mutex.synchronize do
|
|
37
|
+
@collections[collection]&.delete(id)
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Count vectors in a collection
|
|
42
|
+
def count(collection)
|
|
43
|
+
@mutex.synchronize do
|
|
44
|
+
@collections[collection]&.size || 0
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Drop a collection
|
|
49
|
+
def drop(collection)
|
|
50
|
+
@mutex.synchronize do
|
|
51
|
+
@collections.delete(collection)
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def close
|
|
56
|
+
# No-op
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
private
|
|
60
|
+
|
|
61
|
+
def cosine_similarity(a, b)
|
|
62
|
+
return 0.0 if a.empty? || b.empty? || a.length != b.length
|
|
63
|
+
|
|
64
|
+
dot = 0.0
|
|
65
|
+
mag_a = 0.0
|
|
66
|
+
mag_b = 0.0
|
|
67
|
+
|
|
68
|
+
a.length.times do |i|
|
|
69
|
+
dot += a[i] * b[i]
|
|
70
|
+
mag_a += a[i] * a[i]
|
|
71
|
+
mag_b += b[i] * b[i]
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
denom = Math.sqrt(mag_a) * Math.sqrt(mag_b)
|
|
75
|
+
denom.zero? ? 0.0 : dot / denom
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Whoosh
|
|
4
|
+
module VectorStore
|
|
5
|
+
autoload :MemoryStore, "whoosh/vector_store/memory_store"
|
|
6
|
+
|
|
7
|
+
# Auto-detect: zvec gem → use it, otherwise → in-memory
|
|
8
|
+
def self.build(config_data = {})
|
|
9
|
+
vector_config = config_data["vector"] || {}
|
|
10
|
+
adapter = vector_config["adapter"] || "auto"
|
|
11
|
+
|
|
12
|
+
case adapter
|
|
13
|
+
when "auto"
|
|
14
|
+
# Try zvec first, fall back to memory
|
|
15
|
+
if zvec_available?
|
|
16
|
+
require "whoosh/vector_store/zvec_store"
|
|
17
|
+
ZvecStore.new(**zvec_options(vector_config))
|
|
18
|
+
else
|
|
19
|
+
MemoryStore.new
|
|
20
|
+
end
|
|
21
|
+
when "memory"
|
|
22
|
+
MemoryStore.new
|
|
23
|
+
when "zvec"
|
|
24
|
+
require "whoosh/vector_store/zvec_store"
|
|
25
|
+
ZvecStore.new(**zvec_options(vector_config))
|
|
26
|
+
else
|
|
27
|
+
MemoryStore.new
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def self.zvec_available?
|
|
32
|
+
require "zvec"
|
|
33
|
+
true
|
|
34
|
+
rescue LoadError
|
|
35
|
+
false
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def self.zvec_options(config)
|
|
39
|
+
{ path: config["path"] || "db/vectors" }
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
data/lib/whoosh/version.rb
CHANGED
data/lib/whoosh.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: whoosh
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.1
|
|
4
|
+
version: 1.2.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Johannes Dwi Cahyo
|
|
@@ -241,6 +241,8 @@ files:
|
|
|
241
241
|
- lib/whoosh/test.rb
|
|
242
242
|
- lib/whoosh/types.rb
|
|
243
243
|
- lib/whoosh/uploaded_file.rb
|
|
244
|
+
- lib/whoosh/vector_store.rb
|
|
245
|
+
- lib/whoosh/vector_store/memory_store.rb
|
|
244
246
|
- lib/whoosh/version.rb
|
|
245
247
|
homepage: https://github.com/johannesdwicahyo/whoosh
|
|
246
248
|
licenses:
|