Class: Engram::Adapters::PgvectorStore

Inherits:
Object
  • Object
show all
Includes:
Ports::MemoryStore
Defined in:
lib/engram/adapters/pgvector_store.rb

Overview

MemoryStore backed by PostgreSQL + pgvector via the neighbor gem.

Requires the host app to provide ActiveRecord, the neighbor gem, and an AR model (default: Engram::MemoryRecord) created by the install generator. These are NOT hard dependencies of engram; this adapter only references them at call time.

Instance Method Summary collapse

Constructor Details

#initialize(model: nil) ⇒ PgvectorStore

Returns a new instance of PgvectorStore.



13
14
15
# File 'lib/engram/adapters/pgvector_store.rb', line 13

def initialize(model: nil)
  @model = model
end

Instance Method Details

#add(record) ⇒ Object



17
18
19
20
21
22
23
24
25
26
27
28
29
# File 'lib/engram/adapters/pgvector_store.rb', line 17

def add(record)
  validate_scope!(record.scope)

  row = model.create!(
    content: record.content,
    scope: record.scope,
    kind: record.kind.to_s,
    importance: record.importance,
    metadata: record.,
    embedding: record.embedding
  )
  to_record(row)
end

#all(scope:, limit: nil, offset: 0, after_id: nil) ⇒ Object



44
45
46
47
48
49
50
# File 'lib/engram/adapters/pgvector_store.rb', line 44

def all(scope:, limit: nil, offset: 0, after_id: nil)
  query = model.where(scope: scope).order(:id)
  query = query.where("id > ?", after_id) if after_id
  query = query.limit(limit) if limit
  query = query.offset(offset) if offset > 0
  query.map { |row| to_record(row) }
end

#delete(scope:, id:) ⇒ Object



70
71
72
# File 'lib/engram/adapters/pgvector_store.rb', line 70

def delete(scope:, id:)
  model.where(id: id, scope: scope).delete_all
end

#search(embedding:, scope:, limit:, kinds: nil, embedding_metadata: nil) ⇒ Object



31
32
33
34
35
36
37
38
39
40
41
42
# File 'lib/engram/adapters/pgvector_store.rb', line 31

def search(embedding:, scope:, limit:, kinds: nil, embedding_metadata: nil)
  Engram::EmbeddingMetadata.validate_query!(embedding, )
  query = model.where(scope: scope)
  normalized_kinds = normalize_kinds(kinds)
  query = query.where(kind: normalized_kinds) if normalized_kinds

  query
    .nearest_neighbors(:embedding, embedding, distance: "cosine")
    .limit(limit)
    .map { |row| to_record(row) }
    .tap { |records| validate_query_records!(records, embedding, ) }
end

#touch(scope:, id:, at: Time.now) ⇒ Object



74
75
76
# File 'lib/engram/adapters/pgvector_store.rb', line 74

def touch(scope:, id:, at: Time.now)
  model.where(id: id, scope: scope).update_all(last_accessed_at: at)
end

#update(scope:, id:, record:) ⇒ Object



52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
# File 'lib/engram/adapters/pgvector_store.rb', line 52

def update(scope:, id:, record:)
  raise Engram::Error, "cannot move memory across scopes" unless record.scope == scope

  model.transaction do
    row = model.lock.find_by!(id: id, scope: scope)
    row.update!(
      content: record.content,
      kind: record.kind.to_s,
      importance: record.importance,
      metadata: record.,
      embedding: record.embedding
    )
    to_record(row)
  end
rescue ActiveRecord::RecordNotFound
  raise Engram::Error, "no memory with id #{id.inspect} in scope #{scope.inspect}"
end