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
# 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
end

Instance Method Details

#add(id:, embedding:, metadata: {}) ⇒ Object

Parameters:

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


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

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



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

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

#remove(id:) ⇒ Object



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

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



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

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