Class: Phronomy::VectorStore::RedisSearch

Inherits:
Base
  • Object
show all
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.

Examples:

Usage

redis = Redis.new(url: "redis://localhost:6379")
store = Phronomy::VectorStore::RedisSearch.new(redis: redis, dimension: 1536)
store.add(id: "doc1", embedding: [0.1, 0.9], metadata: {text: "hello"})
results = store.search(query_embedding: [0.1, 0.8], k: 5)

Instance Method Summary collapse

Constructor Details

#initialize(redis:, index_name: "phronomy_vectors", dimension: nil) ⇒ RedisSearch

Returns a new instance of RedisSearch.

Parameters:

  • redis (Redis)

    configured Redis client

  • index_name (String) (defaults to: "phronomy_vectors")

    RediSearch index name

  • dimension (Integer, nil) (defaults to: nil)

    vector dimension; auto-detected on first add



29
30
31
32
33
34
35
36
37
38
39
40
41
42
# File 'lib/phronomy/vector_store/redis_search.rb', line 29

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: {}) ⇒ Object

Parameters:

  • id (String)
  • embedding (Array<Float>)
  • metadata (Hash) (defaults to: {})


47
48
49
50
51
52
53
54
55
# File 'lib/phronomy/vector_store/redis_search.rb', line 47

def add(id:, embedding:, metadata: {})
  ensure_index!(embedding.length)
  @redis.call(
    "HSET", "#{DOC_PREFIX}#{id}",
    "embedding", pack_vector(embedding),
    "metadata", .to_json
  )
  self
end

#clearObject



82
83
84
85
86
87
88
89
90
91
92
# File 'lib/phronomy/vector_store/redis_search.rb', line 82

def clear
  @mutex.synchronize do
    begin
      @redis.call("FT.DROPINDEX", @index_name, "DD")
    rescue => e
      raise unless e.message.to_s.include?("Unknown Index name")
    end
    @index_created = false
  end
  self
end

#remove(id:) ⇒ Object



77
78
79
80
# File 'lib/phronomy/vector_store/redis_search.rb', line 77

def remove(id:)
  @redis.call("DEL", "#{DOC_PREFIX}#{id}")
  self
end

#search(query_embedding:, k: 5) ⇒ Array<Hash>

Returns sorted by descending similarity score.

Parameters:

  • query_embedding (Array<Float>)
  • k (Integer) (defaults to: 5)

Returns:

  • (Array<Hash>)

    sorted by descending similarity score



60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
# File 'lib/phronomy/vector_store/redis_search.rb', line 60

def search(query_embedding:, k: 5)
  ensure_index!(query_embedding.length)
  k_safe = Integer(k)
  blob = pack_vector(query_embedding)

  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