Class: SmartPrompt::HistoryManager
- Inherits:
-
Object
- Object
- SmartPrompt::HistoryManager
- Defined in:
- lib/smart_prompt/history_manager.rb
Overview
HistoryManager manages multiple conversation sessions with isolation and configuration
Instance Attribute Summary collapse
-
#config ⇒ Object
readonly
Returns the value of attribute config.
Instance Method Summary collapse
-
#add_message(session_id, message, options = {}) ⇒ Object
Add a message to a session.
-
#cleanup_expired_sessions ⇒ Object
Manually trigger cleanup of expired sessions.
-
#clear_session(session_id, keep_system_messages: true) ⇒ Object
Clear a session’s history.
-
#delete_session(session_id) ⇒ Object
Delete a session completely.
-
#export_metrics(format: :prometheus) ⇒ Object
Export metrics in a standard format (Prometheus-style).
-
#export_session(session_id, format: :json) ⇒ Object
Export a session’s data.
-
#get_context(session_id, max_tokens = nil, strategy = nil) ⇒ Object
Get context (messages) from a session.
-
#get_session(session_id, options = {}) ⇒ Object
Get or create a session.
-
#get_stats(session_id = nil) ⇒ Object
Get statistics for a session or all sessions.
-
#initialize(config = {}) ⇒ HistoryManager
constructor
A new instance of HistoryManager.
-
#lru_session_id ⇒ Object
Get the least recently used session ID.
-
#search_messages(session_id, query, options = {}) ⇒ Object
Search messages in a session.
-
#session_exists?(session_id) ⇒ Boolean
Check if a session exists.
-
#session_ids ⇒ Object
Get list of all session IDs.
-
#shutdown ⇒ Object
Shutdown the history manager gracefully.
Constructor Details
#initialize(config = {}) ⇒ HistoryManager
Returns a new instance of HistoryManager.
8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
# File 'lib/smart_prompt/history_manager.rb', line 8 def initialize(config = {}) @config = default_config.merge(config) @session_cache = LRUCache.new(@config[:cache_size]) @persistence = PersistenceLayer.new(@config[:persistence] || {}) @cleanup_thread = nil @cleanup_mutex = Mutex.new @session_mutex = Mutex.new # Add mutex for session creation @shutdown_requested = false # Initialize metrics tracking @metrics = { sessions_created: 0, sessions_deleted: 0, messages_added: 0, context_retrievals: 0, cache_hits: 0, cache_misses: 0, persistence_errors: 0, compression_operations: 0, tokens_saved_by_compression: 0 } @metrics_mutex = Mutex.new # Log initialization log_info "HistoryManager initialized with cache_size=#{@config[:cache_size]}" # Start cleanup thread if auto_cleanup is enabled start_cleanup_thread if @config[:cleanup][:auto_cleanup] end |
Instance Attribute Details
#config ⇒ Object (readonly)
Returns the value of attribute config.
6 7 8 |
# File 'lib/smart_prompt/history_manager.rb', line 6 def config @config end |
Instance Method Details
#add_message(session_id, message, options = {}) ⇒ Object
Add a message to a session
83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 |
# File 'lib/smart_prompt/history_manager.rb', line 83 def (session_id, , = {}) begin session = get_session(session_id, ) msg = session.() increment_metric(:messages_added) log_debug "Message added to session #{session_id}: role=#{msg.role}, tokens=#{msg.token_count}" # Persist the session asynchronously begin @persistence.save_async(session) rescue => e increment_metric(:persistence_errors) log_error "Persistence failed for session #{session_id}", e # Continue without persistence end session rescue => e log_error "Failed to add message to session #{session_id}", e raise HistoryManagerError, "Failed to add message: #{e.}" end end |
#cleanup_expired_sessions ⇒ Object
Manually trigger cleanup of expired sessions
336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 |
# File 'lib/smart_prompt/history_manager.rb', line 336 def cleanup_expired_sessions return unless @config[:cleanup] session_ttl = @config[:cleanup][:session_ttl] cleanup_callback = @config[:cleanup][:cleanup_callback] expired_session_ids = [] @cleanup_mutex.synchronize do @session_cache.keys.each do |session_id| session = @session_cache.get(session_id) next unless session # Check if session has expired based on TTL age = Time.now - session.updated_at should_cleanup = age > session_ttl # If custom callback is provided, use it to determine cleanup if cleanup_callback && cleanup_callback.respond_to?(:call) should_cleanup = cleanup_callback.call(session, age) end if should_cleanup expired_session_ids << session_id log_debug "Session #{session_id} marked for cleanup (age: #{age.to_i}s, ttl: #{session_ttl}s)" end end # Remove expired sessions expired_session_ids.each do |session_id| delete_session(session_id) end end if expired_session_ids.any? log_info "Cleanup completed: #{expired_session_ids.count} expired sessions removed" else log_debug "Cleanup completed: no expired sessions found" end expired_session_ids end |
#clear_session(session_id, keep_system_messages: true) ⇒ Object
Clear a session’s history
157 158 159 160 161 162 163 164 165 166 167 168 169 |
# File 'lib/smart_prompt/history_manager.rb', line 157 def clear_session(session_id, keep_system_messages: true) begin session = get_session(session_id) = session. session.clear(preserve_system: ) = session. log_info "Session #{session_id} cleared: #{} -> #{} messages (keep_system=#{})" rescue => e log_error "Failed to clear session #{session_id}", e raise HistoryManagerError, "Failed to clear session: #{e.}" end end |
#delete_session(session_id) ⇒ Object
Delete a session completely
172 173 174 175 176 177 178 179 180 181 182 183 184 185 |
# File 'lib/smart_prompt/history_manager.rb', line 172 def delete_session(session_id) begin @session_cache.delete(session_id) # Delete from persistence @persistence.delete(session_id) increment_metric(:sessions_deleted) log_info "Session #{session_id} deleted" rescue => e log_error "Failed to delete session #{session_id}", e raise HistoryManagerError, "Failed to delete session: #{e.}" end end |
#export_metrics(format: :prometheus) ⇒ Object
Export metrics in a standard format (Prometheus-style)
306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 |
# File 'lib/smart_prompt/history_manager.rb', line 306 def export_metrics(format: :prometheus) stats = get_stats case format when :prometheus export_prometheus_metrics(stats) when :json require 'json' JSON.pretty_generate(stats) when :hash stats else raise ArgumentError, "Unsupported format: #{format}" end end |
#export_session(session_id, format: :json) ⇒ Object
Export a session’s data
188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 |
# File 'lib/smart_prompt/history_manager.rb', line 188 def export_session(session_id, format: :json) begin session = get_session(session_id) result = case format when :json require 'json' JSON.pretty_generate(session.to_h) when :hash session.to_h else raise ArgumentError, "Unsupported format: #{format}" end log_info "Session #{session_id} exported in #{format} format" result rescue => e log_error "Failed to export session #{session_id}", e raise HistoryManagerError, "Failed to export session: #{e.}" end end |
#get_context(session_id, max_tokens = nil, strategy = nil) ⇒ Object
Get context (messages) from a session
108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 |
# File 'lib/smart_prompt/history_manager.rb', line 108 def get_context(session_id, max_tokens = nil, strategy = nil) begin session = get_session(session_id) = session. increment_metric(:context_retrievals) # If no token limit specified, return all messages if max_tokens.nil? log_debug "Context retrieved for session #{session_id}: all #{.count} messages (#{session.total_tokens} tokens)" return end # Simple token limiting for now (will be enhanced with strategies later) = [] current_tokens = 0 # Always include system messages first = .select(&:system_message?) .each do |msg| << msg current_tokens += msg.token_count || 0 end # Add non-system messages from most recent, respecting token limit = .reject(&:system_message?) .reverse_each do |msg| msg_tokens = msg.token_count || 0 if current_tokens + msg_tokens <= max_tokens << msg current_tokens += msg_tokens else break end end # Return in chronological order result = .sort_by(&:timestamp) log_debug "Context selected for session #{session_id}: #{result.count}/#{.count} messages, #{current_tokens}/#{max_tokens} tokens" result rescue => e log_error "Failed to get context for session #{session_id}", e raise HistoryManagerError, "Failed to get context: #{e.}" end end |
#get_session(session_id, options = {}) ⇒ Object
Get or create a session
39 40 41 42 43 44 45 46 47 48 49 50 51 52 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 80 |
# File 'lib/smart_prompt/history_manager.rb', line 39 def get_session(session_id, = {}) # Check if session is in cache session = @session_cache.get(session_id) if session # Cache hit increment_metric(:cache_hits) log_debug "Session #{session_id} retrieved from cache" return session end # Cache miss - use mutex to prevent race conditions @session_mutex.synchronize do # Double-check after acquiring lock session = @session_cache.get(session_id) return session if session # Cache miss increment_metric(:cache_misses) log_debug "Session #{session_id} not in cache, loading or creating" # Try to load from persistence first session_data = @persistence.load(session_id) if session_data # Restore session from persisted data session = restore_session(session_data, ) log_info "Session #{session_id} restored from persistence (#{session.} messages, #{session.total_tokens} tokens)" else # Create new session session_config = @config[:session_defaults].merge() session = Session.new(session_id, session_config) increment_metric(:sessions_created) log_info "Session #{session_id} created with config: max_messages=#{session_config[:max_messages]}, max_tokens=#{session_config[:max_tokens]}, strategy=#{session_config[:context_strategy]}" end # Add to cache (will handle eviction if needed) @session_cache.put(session_id, session) session end end |
#get_stats(session_id = nil) ⇒ Object
Get statistics for a session or all sessions
228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 |
# File 'lib/smart_prompt/history_manager.rb', line 228 def get_stats(session_id = nil) begin if session_id # Session-specific statistics session = get_session(session_id) { session_id: session_id, message_count: session., total_tokens: session.total_tokens, created_at: session.created_at, updated_at: session.updated_at, config: session.config } else # System-wide statistics @metrics_mutex.synchronize do cache_total = @metrics[:cache_hits] + @metrics[:cache_misses] cache_hit_rate = cache_total > 0 ? @metrics[:cache_hits].to_f / cache_total : 0.0 { # Session metrics active_sessions: @session_cache.size, sessions_created: @metrics[:sessions_created], sessions_deleted: @metrics[:sessions_deleted], # Message metrics total_messages: @session_cache.values.sum(&:message_count), messages_added: @metrics[:messages_added], messages_per_session_avg: @session_cache.size > 0 ? @session_cache.values.sum(&:message_count).to_f / @session_cache.size : 0.0, # Token metrics total_tokens: @session_cache.values.sum(&:total_tokens), tokens_per_session_avg: @session_cache.size > 0 ? @session_cache.values.sum(&:total_tokens).to_f / @session_cache.size : 0.0, tokens_per_message_avg: @session_cache.values.sum(&:message_count) > 0 ? @session_cache.values.sum(&:total_tokens).to_f / @session_cache.values.sum(&:message_count) : 0.0, # Cache metrics cache_size: @config[:cache_size], cache_hits: @metrics[:cache_hits], cache_misses: @metrics[:cache_misses], cache_hit_rate: cache_hit_rate, # Operation metrics context_retrievals: @metrics[:context_retrievals], # Compression metrics compression_operations: @metrics[:compression_operations], tokens_saved_by_compression: @metrics[:tokens_saved_by_compression], # Error metrics persistence_errors: @metrics[:persistence_errors] } end end rescue => e log_error "Failed to get statistics#{session_id ? " for session #{session_id}" : ""}", e raise HistoryManagerError, "Failed to get statistics: #{e.}" end end |
#lru_session_id ⇒ Object
Get the least recently used session ID
301 302 303 |
# File 'lib/smart_prompt/history_manager.rb', line 301 def lru_session_id @session_cache.lru_key end |
#search_messages(session_id, query, options = {}) ⇒ Object
Search messages in a session
210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 |
# File 'lib/smart_prompt/history_manager.rb', line 210 def (session_id, query, = {}) begin session = get_session(session_id) = session. results = .select do |msg| msg.content.to_s.include?(query) end log_debug "Search in session #{session_id} for '#{query}': #{results.count}/#{.count} matches" results rescue => e log_error "Failed to search messages in session #{session_id}", e raise HistoryManagerError, "Failed to search messages: #{e.}" end end |
#session_exists?(session_id) ⇒ Boolean
Check if a session exists
291 292 293 |
# File 'lib/smart_prompt/history_manager.rb', line 291 def session_exists?(session_id) @session_cache.key?(session_id) end |
#session_ids ⇒ Object
Get list of all session IDs
296 297 298 |
# File 'lib/smart_prompt/history_manager.rb', line 296 def session_ids @session_cache.keys end |
#shutdown ⇒ Object
Shutdown the history manager gracefully
323 324 325 326 327 328 329 330 331 332 333 |
# File 'lib/smart_prompt/history_manager.rb', line 323 def shutdown @shutdown_requested = true # Stop cleanup thread if @cleanup_thread @cleanup_thread.join(5) # Wait up to 5 seconds for thread to finish @cleanup_thread = nil end @persistence.shutdown if @persistence end |