Class: Phronomy::Memory::Storage::ActiveRecord

Inherits:
Base
  • Object
show all
Defined in:
lib/phronomy/memory/storage/active_record.rb

Overview

ActiveRecord-backed storage for conversation messages. Persists messages to a relational database via user-supplied AR model classes.

The message model_class must respond to: .where(thread_id:).order(:created_at) — returns a collection of records .where(thread_id:).delete_all .create!(thread_id:, role:, content:, tool_calls_json:, model_id:) Each record must expose: #role, #content, #tool_calls_json, #model_id

The raw_model_class (optional) must respond to: .where(thread_id:).order(:seq) — returns records in seq order .where(thread_id:).delete_all .create!(thread_id:, seq:, role:, content:, tool_calls_json:, model_id:) Each record must expose: #seq, #role, #content, #tool_calls_json, #model_id

The compaction_model_class (optional) must respond to: .where(thread_id:).order(:start_seq) .where(thread_id:).delete_all .create!(thread_id:, start_seq:, end_seq:, summary_text:) Each record must expose: #start_seq, #end_seq, #summary_text

When raw_model_class or compaction_model_class are nil, the corresponding operations raise NotImplementedError — use InMemory storage if you do not need full raw/compaction persistence.

Examples:

storage = Phronomy::Memory::Storage::ActiveRecord.new(
  model_class:            PhronomyMessage,
  raw_model_class:        PhronomyRawMessage,
  compaction_model_class: PhronomyCompaction
)
manager = Phronomy::Memory::ConversationManager.new(storage: storage, ...)

Instance Method Summary collapse

Methods inherited from Base

#purge

Constructor Details

#initialize(model_class:, raw_model_class: nil, compaction_model_class: nil) ⇒ ActiveRecord

Returns a new instance of ActiveRecord.

Parameters:

  • model_class (Class)

    AR model for the legacy load/save interface

  • raw_model_class (Class, nil) (defaults to: nil)

    AR model for raw message storage

  • compaction_model_class (Class, nil) (defaults to: nil)

    AR model for compaction records



45
46
47
48
49
# File 'lib/phronomy/memory/storage/active_record.rb', line 45

def initialize(model_class:, raw_model_class: nil, compaction_model_class: nil)
  @model_class = model_class
  @raw_model_class = raw_model_class
  @compaction_model_class = compaction_model_class
end

Instance Method Details

#append_raw(thread_id:, messages:, starting_seq:) ⇒ Object

Parameters:

  • thread_id (String)
  • messages (Array)
  • starting_seq (Integer)


97
98
99
100
101
102
103
104
105
106
107
108
109
110
# File 'lib/phronomy/memory/storage/active_record.rb', line 97

def append_raw(thread_id:, messages:, starting_seq:)
  return unless @raw_model_class

  messages.each_with_index do |msg, i|
    @raw_model_class.create!(
      thread_id: thread_id,
      seq: starting_seq + i,
      role: msg.role.to_s,
      content: msg.content.to_s,
      tool_calls_json: serialize_tool_calls(msg),
      model_id: (msg.model_id if msg.respond_to?(:model_id))
    )
  end
end

#clear(thread_id:) ⇒ Object

Parameters:

  • thread_id (String)


84
85
86
87
88
# File 'lib/phronomy/memory/storage/active_record.rb', line 84

def clear(thread_id:)
  @model_class.where(thread_id: thread_id).delete_all
  clear_raw(thread_id: thread_id)
  clear_compactions(thread_id: thread_id)
end

#clear_compactions(thread_id:) ⇒ Object

Parameters:

  • thread_id (String)


154
155
156
# File 'lib/phronomy/memory/storage/active_record.rb', line 154

def clear_compactions(thread_id:)
  @compaction_model_class&.where(thread_id: thread_id)&.delete_all
end

#clear_raw(thread_id:) ⇒ Object

Parameters:

  • thread_id (String)


122
123
124
# File 'lib/phronomy/memory/storage/active_record.rb', line 122

def clear_raw(thread_id:)
  @raw_model_class&.where(thread_id: thread_id)&.delete_all
end

#load(thread_id:) ⇒ Array<OpenStruct>

Load all messages for a thread, ordered by creation time.

Parameters:

  • thread_id (String)

Returns:

  • (Array<OpenStruct>)


59
60
61
62
# File 'lib/phronomy/memory/storage/active_record.rb', line 59

def load(thread_id:)
  records = @model_class.where(thread_id: thread_id).order(:created_at).to_a
  records.map { |r| to_message_struct(r) }
end

#load_compactions(thread_id:) ⇒ Array<Hash>

Parameters:

  • thread_id (String)

Returns:

  • (Array<Hash>)


146
147
148
149
150
151
# File 'lib/phronomy/memory/storage/active_record.rb', line 146

def load_compactions(thread_id:)
  return [] unless @compaction_model_class

  records = @compaction_model_class.where(thread_id: thread_id).order(:start_seq).to_a
  records.map { |r| {start_seq: r.start_seq, end_seq: r.end_seq, summary_text: r.summary_text} }
end

#load_raw(thread_id:) ⇒ Array<Hash>

Parameters:

  • thread_id (String)

Returns:

  • (Array<Hash>)


114
115
116
117
118
119
# File 'lib/phronomy/memory/storage/active_record.rb', line 114

def load_raw(thread_id:)
  return [] unless @raw_model_class

  records = @raw_model_class.where(thread_id: thread_id).order(:seq).to_a
  records.map { |r| {seq: r.seq, message: to_message_struct(r)} }
end

#purge_older_than(thread_id:, older_than:) ⇒ Object

Remove messages for a thread that were created before +older_than+. Only the legacy message store is filtered; raw and compaction records are left untouched because they use seq-based addressing.

Parameters:

  • thread_id (String)
  • older_than (Time)


164
165
166
# File 'lib/phronomy/memory/storage/active_record.rb', line 164

def purge_older_than(thread_id:, older_than:)
  @model_class.where(thread_id: thread_id).where("created_at < ?", older_than).delete_all
end

#save(thread_id:, messages:) ⇒ Object

Replace all stored messages for a thread.

Parameters:

  • thread_id (String)
  • messages (Array)


68
69
70
71
72
73
74
75
76
77
78
79
80
81
# File 'lib/phronomy/memory/storage/active_record.rb', line 68

def save(thread_id:, messages:)
  @model_class.transaction do
    @model_class.where(thread_id: thread_id).delete_all
    messages.each do |msg|
      @model_class.create!(
        thread_id: thread_id,
        role: msg.role.to_s,
        content: msg.content.to_s,
        tool_calls_json: serialize_tool_calls(msg),
        model_id: (msg.model_id if msg.respond_to?(:model_id))
      )
    end
  end
end

#save_compaction(thread_id:, start_seq:, end_seq:, summary_text:) ⇒ Object

Parameters:

  • thread_id (String)
  • start_seq (Integer)
  • end_seq (Integer)
  • summary_text (String)


134
135
136
137
138
139
140
141
142
# File 'lib/phronomy/memory/storage/active_record.rb', line 134

def save_compaction(thread_id:, start_seq:, end_seq:, summary_text:)
  ensure_compaction_model!
  @compaction_model_class.create!(
    thread_id: thread_id,
    start_seq: start_seq,
    end_seq: end_seq,
    summary_text: summary_text
  )
end