Class: Llmemory::VectorStore::ActiveRecordStore
Overview
Persists embeddings in llmemory_embeddings (pgvector).
Use when long_term_store is :active_record so hybrid search finds persisted embeddings.
Constant Summary
ActiveRecordHelpers::UNIQUE_RETRY_LIMIT
Class Method Summary
collapse
Instance Method Summary
collapse
-
#delete(id:, user_id: nil) ⇒ Object
-
#embed(text) ⇒ Object
-
#initialize(embedding_provider: nil, source_type: "edge", cipher: nil) ⇒ ActiveRecordStore
constructor
A new instance of ActiveRecordStore.
-
#last_usage ⇒ Object
-
#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, source_type: "edge", cipher: nil) ⇒ ActiveRecordStore
Returns a new instance of ActiveRecordStore.
12
13
14
15
16
17
|
# File 'lib/llmemory/vector_store/active_record_store.rb', line 12
def initialize(embedding_provider: nil, source_type: "edge", cipher: nil)
self.class.load_model!
@embedding_provider = embedding_provider
@source_type = source_type.to_s
@cipher = cipher || Llmemory.build_cipher
end
|
Class Method Details
.load_model! ⇒ Object
19
20
21
22
23
24
|
# File 'lib/llmemory/vector_store/active_record_store.rb', line 19
def self.load_model!
return if @model_loaded
require "active_record"
require_relative "active_record_embedding"
@model_loaded = true
end
|
Instance Method Details
#delete(id:, user_id: nil) ⇒ Object
88
89
90
91
92
93
94
95
96
97
|
# File 'lib/llmemory/vector_store/active_record_store.rb', line 88
def delete(id:, user_id: nil)
return false if user_id.nil? || user_id.to_s.empty?
Llmemory::VectorStore::ActiveRecordEmbedding.where(
user_id: user_id.to_s,
source_type: @source_type,
source_id: id.to_s
).delete_all
true
end
|
#embed(text) ⇒ Object
26
27
28
29
|
# File 'lib/llmemory/vector_store/active_record_store.rb', line 26
def embed(text)
return Array.new(1536, 0.0) unless @embedding_provider&.respond_to?(:embed)
@embedding_provider.embed(text)
end
|
#last_usage ⇒ Object
31
32
33
34
35
|
# File 'lib/llmemory/vector_store/active_record_store.rb', line 31
def last_usage
return @embedding_provider.last_usage if @embedding_provider&.respond_to?(:last_usage)
Llmemory::LLM::Usage.zero
end
|
#search(query_embedding, top_k: 10, user_id: nil) ⇒ Object
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
|
# File 'lib/llmemory/vector_store/active_record_store.rb', line 53
def search(query_embedding, top_k: 10, user_id: nil)
return [] if user_id.nil? || user_id.to_s.empty?
vec = query_embedding.to_a.map(&:to_f)
return [] if vec.empty?
sanitized_vec = vec.map { |v| v.finite? ? v : 0.0 }
vector_literal = "[#{sanitized_vec.join(',')}]"
scope = Llmemory::VectorStore::ActiveRecordEmbedding.where(user_id: user_id.to_s, source_type: @source_type)
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" => decrypt_text_content(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
81
82
83
84
85
86
|
# File 'lib/llmemory/vector_store/active_record_store.rb', line 81
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)
query_embedding = @embedding_provider.embed(query_text)
search(query_embedding, top_k: top_k, user_id: user_id)
end
|
#store(id:, embedding:, metadata: {}, user_id: nil) ⇒ Object
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
|
# File 'lib/llmemory/vector_store/active_record_store.rb', line 37
def store(id:, embedding:, metadata: {}, user_id: nil)
return id if user_id.nil? || user_id.to_s.empty?
text_content = (metadata || {}).dig("text") || (metadata || {}).dig(:text)
with_unique_retry do
rec = Llmemory::VectorStore::ActiveRecordEmbedding.find_or_initialize_by(
user_id: user_id.to_s,
source_type: @source_type,
source_id: id.to_s
)
rec.embedding = embedding.to_a.map(&:to_f)
rec.text_content = encrypt_text_content(text_content)
rec.save!
end
id
end
|