Class: Phronomy::VectorStore::RedisSearch
- Defined in:
- lib/phronomy/vector_store/redis_search.rb
Overview
Redis-backed vector store using the RediSearch module (FT.* commands).
Requires:
- The +redis+ gem (add to your Gemfile)
- A Redis server with the RediSearch (RedisSearch) module enabled (or Redis Stack which bundles RediSearch)
Vectors are stored as FLOAT32 binary blobs in Redis Hash fields and searched using the KNN approximate-nearest-neighbour algorithm.
Instance Method Summary collapse
- #add(id:, embedding:, metadata: {}, cancellation_token: nil) ⇒ Object
- #clear ⇒ Object
-
#initialize(redis:, index_name: "phronomy_vectors", dimension: nil) ⇒ RedisSearch
constructor
A new instance of RedisSearch.
- #remove(id:) ⇒ Object
-
#search(query_embedding:, k: 5, cancellation_token: nil) ⇒ Array<Hash>
Sorted by descending similarity score.
-
#size ⇒ Object
Returns the number of documents indexed.
Constructor Details
#initialize(redis:, index_name: "phronomy_vectors", dimension: nil) ⇒ RedisSearch
Returns a new instance of RedisSearch.
34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
# File 'lib/phronomy/vector_store/redis_search.rb', line 34 def initialize(redis:, index_name: "phronomy_vectors", dimension: nil) begin require "redis" rescue LoadError raise LoadError, "redis gem is required for Phronomy::VectorStore::RedisSearch. " \ "Add `gem 'redis'` to your Gemfile." end @redis = redis @index_name = index_name @dimension = dimension @index_created = false @mutex = Mutex.new end |
Instance Method Details
#add(id:, embedding:, metadata: {}, cancellation_token: nil) ⇒ Object
54 55 56 57 58 59 60 61 62 63 64 65 66 67 |
# File 'lib/phronomy/vector_store/redis_search.rb', line 54 def add(id:, embedding:, metadata: {}, cancellation_token: nil) cancellation_token&.raise_if_cancelled! # Establish expected dimension on first add (not race-free for concurrent # first adds), then validate, then create/reuse the index. @dimension ||= .size (, @dimension) ensure_index!(@dimension) @redis.call( "HSET", "#{DOC_PREFIX}#{id}", "embedding", pack_vector(), "metadata", .to_json ) self end |
#clear ⇒ Object
116 117 118 119 120 121 122 123 124 125 126 |
# File 'lib/phronomy/vector_store/redis_search.rb', line 116 def clear @mutex.synchronize do begin @redis.call("FT.DROPINDEX", @index_name, "DD") rescue => e raise unless e..to_s.include?("Unknown Index name") end @index_created = false end self end |
#remove(id:) ⇒ Object
97 98 99 100 |
# File 'lib/phronomy/vector_store/redis_search.rb', line 97 def remove(id:) @redis.call("DEL", "#{DOC_PREFIX}#{id}") self end |
#search(query_embedding:, k: 5, cancellation_token: nil) ⇒ Array<Hash>
Returns sorted by descending similarity score.
74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 |
# File 'lib/phronomy/vector_store/redis_search.rb', line 74 def search(query_embedding:, k: 5, cancellation_token: nil) cancellation_token&.raise_if_cancelled! # search never establishes dimension. If dimension is unknown and the # index has not been created yet, there are no documents to return. return [] if @dimension.nil? && !@index_created (, @dimension) ensure_index!(@dimension) k_safe = validate_k!(k) blob = pack_vector() raw = @redis.call( "FT.SEARCH", @index_name, "*=>[KNN #{k_safe} @embedding $BLOB AS score]", "PARAMS", 2, "BLOB", blob, "SORTBY", "score", "RETURN", 2, "score", "metadata", "DIALECT", 2 ) parse_results(raw) end |
#size ⇒ Object
Returns the number of documents indexed. Queries FT.INFO when the index has been created; returns 0 otherwise.
104 105 106 107 108 109 110 111 112 113 114 |
# File 'lib/phronomy/vector_store/redis_search.rb', line 104 def size return 0 unless @index_created raw = @redis.call("FT.INFO", @index_name) return 0 unless raw.is_a?(Array) idx = raw.index("num_docs") idx ? raw[idx + 1].to_i : 0 rescue 0 end |