Class: Llmemory::VectorStore::ActiveRecordStore
- Defined in:
- lib/llmemory/vector_store/active_record_store.rb
Overview
Persists embeddings in llmemory_embeddings (pgvector). Use when long_term_store is :active_record so hybrid search finds persisted embeddings.
Class Method Summary collapse
Instance Method Summary collapse
- #embed(text) ⇒ Object
-
#initialize(embedding_provider: nil) ⇒ ActiveRecordStore
constructor
A new instance of ActiveRecordStore.
- #search(query_embedding, top_k: 10, user_id: nil) ⇒ Object
- #search_by_text(query_text, top_k: 10, user_id: nil) ⇒ Object
- #store(id:, embedding:, metadata: {}, user_id: nil) ⇒ Object
Constructor Details
#initialize(embedding_provider: nil) ⇒ ActiveRecordStore
Returns a new instance of ActiveRecordStore.
10 11 12 13 |
# File 'lib/llmemory/vector_store/active_record_store.rb', line 10 def initialize(embedding_provider: nil) self.class.load_model! @embedding_provider = end |
Class Method Details
.load_model! ⇒ Object
15 16 17 18 19 20 |
# File 'lib/llmemory/vector_store/active_record_store.rb', line 15 def self.load_model! return if @model_loaded require "active_record" require_relative "active_record_embedding" @model_loaded = true end |
Instance Method Details
#embed(text) ⇒ Object
22 23 24 25 |
# File 'lib/llmemory/vector_store/active_record_store.rb', line 22 def (text) return Array.new(1536, 0.0) unless @embedding_provider&.respond_to?(:embed) @embedding_provider.(text) end |
#search(query_embedding, top_k: 10, user_id: nil) ⇒ Object
41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 |
# File 'lib/llmemory/vector_store/active_record_store.rb', line 41 def search(, top_k: 10, user_id: nil) return [] if user_id.nil? || user_id.to_s.empty? vec = .to_a.map(&:to_f) return [] if vec.empty? # Sanitize vector for pgvector (only floats allowed) sanitized_vec = vec.map { |v| v.finite? ? v : 0.0 } vector_literal = "[#{sanitized_vec.join(',')}]" # pgvector cosine distance <=> (0 = same, 2 = opposite); score = 1 - distance for similarity scope = Llmemory::VectorStore::ActiveRecordEmbedding.where(user_id: user_id.to_s) rows = scope.select( Llmemory::VectorStore::ActiveRecordEmbedding.arel_table[Arel.star], Arel.sql("(embedding <=> '#{vector_literal}'::vector) AS distance") ).order(Arel.sql("embedding <=> '#{vector_literal}'::vector")).limit(top_k) rows.map do |r| distance = r["distance"] || r.attributes["distance"] || 0.0 score = (1.0 - distance.to_f).clamp(-1.0, 1.0) { id: r.source_id, score: score, metadata: { "text" => r.text_content, "created_at" => r.created_at, "user_id" => r.user_id } } end end |
#search_by_text(query_text, top_k: 10, user_id: nil) ⇒ Object
65 66 67 68 69 70 |
# File 'lib/llmemory/vector_store/active_record_store.rb', line 65 def search_by_text(query_text, top_k: 10, user_id: nil) return [] if user_id.nil? || user_id.to_s.empty? return [] unless @embedding_provider&.respond_to?(:embed) = @embedding_provider.(query_text) search(, top_k: top_k, user_id: user_id) end |
#store(id:, embedding:, metadata: {}, user_id: nil) ⇒ Object
27 28 29 30 31 32 33 34 35 36 37 38 39 |
# File 'lib/llmemory/vector_store/active_record_store.rb', line 27 def store(id:, embedding:, metadata: {}, user_id: nil) return id if user_id.nil? || user_id.to_s.empty? text_content = ( || {}).dig("text") || ( || {}).dig(:text) rec = Llmemory::VectorStore::ActiveRecordEmbedding.find_or_initialize_by( user_id: user_id.to_s, source_type: "edge", source_id: id.to_s ) rec. = .to_a.map(&:to_f) rec.text_content = text_content rec.save! id end |