smart_prompt 0.4.4 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +10 -10
  3. data/README.cn.md +307 -64
  4. data/README.md +311 -64
  5. data/Rakefile +10 -1
  6. data/config/anthropic_config.yml +151 -0
  7. data/config/image_generation_config.yml +22 -0
  8. data/config/multimodal_config.yml +85 -0
  9. data/config/sensenova_config.yml +63 -0
  10. data/config/zhipu_config.yml +73 -0
  11. data/examples/anthropic_basic_chat.rb +143 -0
  12. data/examples/anthropic_example.rb +232 -0
  13. data/examples/anthropic_multimodal.rb +212 -0
  14. data/examples/anthropic_streaming.rb +312 -0
  15. data/examples/anthropic_tool_calling.rb +393 -0
  16. data/examples/automatic_cleanup_example.rb +109 -0
  17. data/examples/history_management_examples.rb +522 -0
  18. data/examples/image_generation_example.rb +130 -0
  19. data/examples/monitoring_example.rb +121 -0
  20. data/examples/multimodal_example.rb +63 -0
  21. data/examples/relevance_based_strategy_example.rb +87 -0
  22. data/examples/sensenova_example.rb +129 -0
  23. data/examples/stt_example.rb +287 -0
  24. data/examples/tts_example.rb +244 -0
  25. data/examples/video_generation_example.rb +189 -0
  26. data/examples/zhipu_example.rb +151 -0
  27. data/lib/smart_prompt/anthropic_adapter.rb +363 -281
  28. data/lib/smart_prompt/compression_engine.rb +201 -0
  29. data/lib/smart_prompt/context_strategy.rb +22 -0
  30. data/lib/smart_prompt/conversation.rb +81 -191
  31. data/lib/smart_prompt/engine.rb +36 -19
  32. data/lib/smart_prompt/history_manager.rb +596 -0
  33. data/lib/smart_prompt/hybrid_strategy.rb +222 -0
  34. data/lib/smart_prompt/image_generation_adapter.rb +297 -0
  35. data/lib/smart_prompt/lru_cache.rb +133 -0
  36. data/lib/smart_prompt/message.rb +57 -0
  37. data/lib/smart_prompt/multimodal_adapter.rb +277 -0
  38. data/lib/smart_prompt/openai_adapter.rb +1 -25
  39. data/lib/smart_prompt/persistence_layer.rb +197 -0
  40. data/lib/smart_prompt/relevance_based_strategy.rb +221 -0
  41. data/lib/smart_prompt/sensenova_adapter.rb +410 -0
  42. data/lib/smart_prompt/session.rb +140 -0
  43. data/lib/smart_prompt/sliding_window_strategy.rb +100 -0
  44. data/lib/smart_prompt/stt_adapter.rb +381 -0
  45. data/lib/smart_prompt/summary_based_strategy.rb +152 -0
  46. data/lib/smart_prompt/token_counter.rb +74 -0
  47. data/lib/smart_prompt/tts_adapter.rb +403 -0
  48. data/lib/smart_prompt/version.rb +1 -1
  49. data/lib/smart_prompt/video_generation_adapter.rb +330 -0
  50. data/lib/smart_prompt/worker.rb +25 -3
  51. data/lib/smart_prompt/zhipu_adapter.rb +616 -0
  52. data/lib/smart_prompt.rb +22 -2
  53. data/workers/history_management_examples.rb +407 -0
  54. data/workers/image_generation_workers.rb +119 -0
  55. data/workers/multimodal_workers.rb +110 -0
  56. data/workers/sensenova_workers.rb +62 -0
  57. data/workers/stt_workers.rb +195 -0
  58. data/workers/tts_workers.rb +388 -0
  59. data/workers/video_generation_workers.rb +264 -0
  60. data/workers/zhipu_workers.rb +113 -0
  61. metadata +84 -8
@@ -0,0 +1,596 @@
1
+ require 'thread'
2
+
3
+ module SmartPrompt
4
+ # HistoryManager manages multiple conversation sessions with isolation and configuration
5
+ class HistoryManager
6
+ attr_reader :config
7
+
8
+ def initialize(config = {})
9
+ @config = default_config.merge(config)
10
+ @session_cache = LRUCache.new(@config[:cache_size])
11
+ @persistence = PersistenceLayer.new(@config[:persistence] || {})
12
+ @cleanup_thread = nil
13
+ @cleanup_mutex = Mutex.new
14
+ @session_mutex = Mutex.new # Add mutex for session creation
15
+ @shutdown_requested = false
16
+
17
+ # Initialize metrics tracking
18
+ @metrics = {
19
+ sessions_created: 0,
20
+ sessions_deleted: 0,
21
+ messages_added: 0,
22
+ context_retrievals: 0,
23
+ cache_hits: 0,
24
+ cache_misses: 0,
25
+ persistence_errors: 0,
26
+ compression_operations: 0,
27
+ tokens_saved_by_compression: 0
28
+ }
29
+ @metrics_mutex = Mutex.new
30
+
31
+ # Log initialization
32
+ log_info "HistoryManager initialized with cache_size=#{@config[:cache_size]}"
33
+
34
+ # Start cleanup thread if auto_cleanup is enabled
35
+ start_cleanup_thread if @config[:cleanup][:auto_cleanup]
36
+ end
37
+
38
+ # Get or create a session
39
+ def get_session(session_id, options = {})
40
+ # Check if session is in cache
41
+ session = @session_cache.get(session_id)
42
+
43
+ if session
44
+ # Cache hit
45
+ increment_metric(:cache_hits)
46
+ log_debug "Session #{session_id} retrieved from cache"
47
+ return session
48
+ end
49
+
50
+ # Cache miss - use mutex to prevent race conditions
51
+ @session_mutex.synchronize do
52
+ # Double-check after acquiring lock
53
+ session = @session_cache.get(session_id)
54
+ return session if session
55
+
56
+ # Cache miss
57
+ increment_metric(:cache_misses)
58
+ log_debug "Session #{session_id} not in cache, loading or creating"
59
+
60
+ # Try to load from persistence first
61
+ session_data = @persistence.load(session_id)
62
+
63
+ if session_data
64
+ # Restore session from persisted data
65
+ session = restore_session(session_data, options)
66
+ log_info "Session #{session_id} restored from persistence (#{session.message_count} messages, #{session.total_tokens} tokens)"
67
+ else
68
+ # Create new session
69
+ session_config = @config[:session_defaults].merge(options)
70
+ session = Session.new(session_id, session_config)
71
+ increment_metric(:sessions_created)
72
+ 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]}"
73
+ end
74
+
75
+ # Add to cache (will handle eviction if needed)
76
+ @session_cache.put(session_id, session)
77
+
78
+ session
79
+ end
80
+ end
81
+
82
+ # Add a message to a session
83
+ def add_message(session_id, message, options = {})
84
+ begin
85
+ session = get_session(session_id, options)
86
+ msg = session.add_message(message)
87
+
88
+ increment_metric(:messages_added)
89
+ log_debug "Message added to session #{session_id}: role=#{msg.role}, tokens=#{msg.token_count}"
90
+
91
+ # Persist the session asynchronously
92
+ begin
93
+ @persistence.save_async(session)
94
+ rescue => e
95
+ increment_metric(:persistence_errors)
96
+ log_error "Persistence failed for session #{session_id}", e
97
+ # Continue without persistence
98
+ end
99
+
100
+ session
101
+ rescue => e
102
+ log_error "Failed to add message to session #{session_id}", e
103
+ raise HistoryManagerError, "Failed to add message: #{e.message}"
104
+ end
105
+ end
106
+
107
+ # Get context (messages) from a session
108
+ def get_context(session_id, max_tokens = nil, strategy = nil)
109
+ begin
110
+ session = get_session(session_id)
111
+ messages = session.get_messages
112
+
113
+ increment_metric(:context_retrievals)
114
+
115
+ # If no token limit specified, return all messages
116
+ if max_tokens.nil?
117
+ log_debug "Context retrieved for session #{session_id}: all #{messages.count} messages (#{session.total_tokens} tokens)"
118
+ return messages
119
+ end
120
+
121
+ # Simple token limiting for now (will be enhanced with strategies later)
122
+ selected_messages = []
123
+ current_tokens = 0
124
+
125
+ # Always include system messages first
126
+ system_messages = messages.select(&:system_message?)
127
+ system_messages.each do |msg|
128
+ selected_messages << msg
129
+ current_tokens += msg.token_count || 0
130
+ end
131
+
132
+ # Add non-system messages from most recent, respecting token limit
133
+ non_system_messages = messages.reject(&:system_message?)
134
+ non_system_messages.reverse_each do |msg|
135
+ msg_tokens = msg.token_count || 0
136
+ if current_tokens + msg_tokens <= max_tokens
137
+ selected_messages << msg
138
+ current_tokens += msg_tokens
139
+ else
140
+ break
141
+ end
142
+ end
143
+
144
+ # Return in chronological order
145
+ result = selected_messages.sort_by(&:timestamp)
146
+
147
+ log_debug "Context selected for session #{session_id}: #{result.count}/#{messages.count} messages, #{current_tokens}/#{max_tokens} tokens"
148
+
149
+ result
150
+ rescue => e
151
+ log_error "Failed to get context for session #{session_id}", e
152
+ raise HistoryManagerError, "Failed to get context: #{e.message}"
153
+ end
154
+ end
155
+
156
+ # Clear a session's history
157
+ def clear_session(session_id, keep_system_messages: true)
158
+ begin
159
+ session = get_session(session_id)
160
+ messages_before = session.message_count
161
+ session.clear(preserve_system: keep_system_messages)
162
+ messages_after = session.message_count
163
+
164
+ log_info "Session #{session_id} cleared: #{messages_before} -> #{messages_after} messages (keep_system=#{keep_system_messages})"
165
+ rescue => e
166
+ log_error "Failed to clear session #{session_id}", e
167
+ raise HistoryManagerError, "Failed to clear session: #{e.message}"
168
+ end
169
+ end
170
+
171
+ # Delete a session completely
172
+ def delete_session(session_id)
173
+ begin
174
+ @session_cache.delete(session_id)
175
+
176
+ # Delete from persistence
177
+ @persistence.delete(session_id)
178
+
179
+ increment_metric(:sessions_deleted)
180
+ log_info "Session #{session_id} deleted"
181
+ rescue => e
182
+ log_error "Failed to delete session #{session_id}", e
183
+ raise HistoryManagerError, "Failed to delete session: #{e.message}"
184
+ end
185
+ end
186
+
187
+ # Export a session's data
188
+ def export_session(session_id, format: :json)
189
+ begin
190
+ session = get_session(session_id)
191
+ result = case format
192
+ when :json
193
+ require 'json'
194
+ JSON.pretty_generate(session.to_h)
195
+ when :hash
196
+ session.to_h
197
+ else
198
+ raise ArgumentError, "Unsupported format: #{format}"
199
+ end
200
+
201
+ log_info "Session #{session_id} exported in #{format} format"
202
+ result
203
+ rescue => e
204
+ log_error "Failed to export session #{session_id}", e
205
+ raise HistoryManagerError, "Failed to export session: #{e.message}"
206
+ end
207
+ end
208
+
209
+ # Search messages in a session
210
+ def search_messages(session_id, query, options = {})
211
+ begin
212
+ session = get_session(session_id)
213
+ messages = session.get_messages
214
+
215
+ results = messages.select do |msg|
216
+ msg.content.to_s.include?(query)
217
+ end
218
+
219
+ log_debug "Search in session #{session_id} for '#{query}': #{results.count}/#{messages.count} matches"
220
+ results
221
+ rescue => e
222
+ log_error "Failed to search messages in session #{session_id}", e
223
+ raise HistoryManagerError, "Failed to search messages: #{e.message}"
224
+ end
225
+ end
226
+
227
+ # Get statistics for a session or all sessions
228
+ def get_stats(session_id = nil)
229
+ begin
230
+ if session_id
231
+ # Session-specific statistics
232
+ session = get_session(session_id)
233
+ {
234
+ session_id: session_id,
235
+ message_count: session.message_count,
236
+ total_tokens: session.total_tokens,
237
+ created_at: session.created_at,
238
+ updated_at: session.updated_at,
239
+ config: session.config
240
+ }
241
+ else
242
+ # System-wide statistics
243
+ @metrics_mutex.synchronize do
244
+ cache_total = @metrics[:cache_hits] + @metrics[:cache_misses]
245
+ cache_hit_rate = cache_total > 0 ? @metrics[:cache_hits].to_f / cache_total : 0.0
246
+
247
+ {
248
+ # Session metrics
249
+ active_sessions: @session_cache.size,
250
+ sessions_created: @metrics[:sessions_created],
251
+ sessions_deleted: @metrics[:sessions_deleted],
252
+
253
+ # Message metrics
254
+ total_messages: @session_cache.values.sum(&:message_count),
255
+ messages_added: @metrics[:messages_added],
256
+ messages_per_session_avg: @session_cache.size > 0 ?
257
+ @session_cache.values.sum(&:message_count).to_f / @session_cache.size : 0.0,
258
+
259
+ # Token metrics
260
+ total_tokens: @session_cache.values.sum(&:total_tokens),
261
+ tokens_per_session_avg: @session_cache.size > 0 ?
262
+ @session_cache.values.sum(&:total_tokens).to_f / @session_cache.size : 0.0,
263
+ tokens_per_message_avg: @session_cache.values.sum(&:message_count) > 0 ?
264
+ @session_cache.values.sum(&:total_tokens).to_f / @session_cache.values.sum(&:message_count) : 0.0,
265
+
266
+ # Cache metrics
267
+ cache_size: @config[:cache_size],
268
+ cache_hits: @metrics[:cache_hits],
269
+ cache_misses: @metrics[:cache_misses],
270
+ cache_hit_rate: cache_hit_rate,
271
+
272
+ # Operation metrics
273
+ context_retrievals: @metrics[:context_retrievals],
274
+
275
+ # Compression metrics
276
+ compression_operations: @metrics[:compression_operations],
277
+ tokens_saved_by_compression: @metrics[:tokens_saved_by_compression],
278
+
279
+ # Error metrics
280
+ persistence_errors: @metrics[:persistence_errors]
281
+ }
282
+ end
283
+ end
284
+ rescue => e
285
+ log_error "Failed to get statistics#{session_id ? " for session #{session_id}" : ""}", e
286
+ raise HistoryManagerError, "Failed to get statistics: #{e.message}"
287
+ end
288
+ end
289
+
290
+ # Check if a session exists
291
+ def session_exists?(session_id)
292
+ @session_cache.key?(session_id)
293
+ end
294
+
295
+ # Get list of all session IDs
296
+ def session_ids
297
+ @session_cache.keys
298
+ end
299
+
300
+ # Get the least recently used session ID
301
+ def lru_session_id
302
+ @session_cache.lru_key
303
+ end
304
+
305
+ # Export metrics in a standard format (Prometheus-style)
306
+ def export_metrics(format: :prometheus)
307
+ stats = get_stats
308
+
309
+ case format
310
+ when :prometheus
311
+ export_prometheus_metrics(stats)
312
+ when :json
313
+ require 'json'
314
+ JSON.pretty_generate(stats)
315
+ when :hash
316
+ stats
317
+ else
318
+ raise ArgumentError, "Unsupported format: #{format}"
319
+ end
320
+ end
321
+
322
+ # Shutdown the history manager gracefully
323
+ def shutdown
324
+ @shutdown_requested = true
325
+
326
+ # Stop cleanup thread
327
+ if @cleanup_thread
328
+ @cleanup_thread.join(5) # Wait up to 5 seconds for thread to finish
329
+ @cleanup_thread = nil
330
+ end
331
+
332
+ @persistence.shutdown if @persistence
333
+ end
334
+
335
+ # Manually trigger cleanup of expired sessions
336
+ def cleanup_expired_sessions
337
+ return unless @config[:cleanup]
338
+
339
+ session_ttl = @config[:cleanup][:session_ttl]
340
+ cleanup_callback = @config[:cleanup][:cleanup_callback]
341
+
342
+ expired_session_ids = []
343
+
344
+ @cleanup_mutex.synchronize do
345
+ @session_cache.keys.each do |session_id|
346
+ session = @session_cache.get(session_id)
347
+ next unless session
348
+
349
+ # Check if session has expired based on TTL
350
+ age = Time.now - session.updated_at
351
+ should_cleanup = age > session_ttl
352
+
353
+ # If custom callback is provided, use it to determine cleanup
354
+ if cleanup_callback && cleanup_callback.respond_to?(:call)
355
+ should_cleanup = cleanup_callback.call(session, age)
356
+ end
357
+
358
+ if should_cleanup
359
+ expired_session_ids << session_id
360
+ log_debug "Session #{session_id} marked for cleanup (age: #{age.to_i}s, ttl: #{session_ttl}s)"
361
+ end
362
+ end
363
+
364
+ # Remove expired sessions
365
+ expired_session_ids.each do |session_id|
366
+ delete_session(session_id)
367
+ end
368
+ end
369
+
370
+ if expired_session_ids.any?
371
+ log_info "Cleanup completed: #{expired_session_ids.count} expired sessions removed"
372
+ else
373
+ log_debug "Cleanup completed: no expired sessions found"
374
+ end
375
+
376
+ expired_session_ids
377
+ end
378
+
379
+ private
380
+
381
+ # Restore a session from persisted data
382
+ def restore_session(session_data, options = {})
383
+ session_config = @config[:session_defaults].merge(options)
384
+ session = Session.new(session_data[:id], session_config)
385
+
386
+ # Restore metadata
387
+ session.instance_variable_set(:@metadata, session_data[:metadata] || {})
388
+
389
+ # Restore timestamps
390
+ session.instance_variable_set(:@created_at, Time.parse(session_data[:created_at])) if session_data[:created_at]
391
+ session.instance_variable_set(:@updated_at, Time.parse(session_data[:updated_at])) if session_data[:updated_at]
392
+
393
+ # Restore messages
394
+ if session_data[:messages]
395
+ session_data[:messages].each do |msg_data|
396
+ session.add_message(msg_data)
397
+ end
398
+ end
399
+
400
+ session
401
+ end
402
+
403
+ # Start the cleanup thread
404
+ def start_cleanup_thread
405
+ return if @cleanup_thread && @cleanup_thread.alive?
406
+
407
+ cleanup_interval = @config[:cleanup][:cleanup_interval]
408
+
409
+ log_info "Starting cleanup thread with interval=#{cleanup_interval}s, ttl=#{@config[:cleanup][:session_ttl]}s"
410
+
411
+ @cleanup_thread = Thread.new do
412
+ loop do
413
+ break if @shutdown_requested
414
+
415
+ begin
416
+ sleep(cleanup_interval)
417
+ break if @shutdown_requested
418
+
419
+ # Perform cleanup
420
+ cleanup_expired_sessions
421
+ rescue => e
422
+ # Log error but keep thread running
423
+ log_error "Cleanup thread error", e
424
+ end
425
+ end
426
+
427
+ log_info "Cleanup thread stopped"
428
+ end
429
+
430
+ @cleanup_thread
431
+ end
432
+
433
+ # Default configuration
434
+ def default_config
435
+ {
436
+ cache_size: 100,
437
+ session_defaults: {
438
+ max_messages: 100,
439
+ max_tokens: 4000,
440
+ context_strategy: :sliding_window,
441
+ preserve_system_messages: true
442
+ },
443
+ persistence: {
444
+ enabled: true,
445
+ backend: :filesystem,
446
+ storage_path: "./history_data",
447
+ async: true
448
+ },
449
+ cleanup: {
450
+ auto_cleanup: false,
451
+ cleanup_interval: 3600, # 1 hour in seconds
452
+ session_ttl: 86400, # 24 hours in seconds
453
+ cleanup_callback: nil
454
+ },
455
+ monitoring: {
456
+ enabled: true,
457
+ log_level: :info
458
+ }
459
+ }
460
+ end
461
+
462
+ # Increment a metric counter
463
+ def increment_metric(metric_name, amount = 1)
464
+ @metrics_mutex.synchronize do
465
+ @metrics[metric_name] ||= 0
466
+ @metrics[metric_name] += amount
467
+ end
468
+ end
469
+
470
+ # Logging helper methods
471
+ def log_info(message)
472
+ return unless monitoring_enabled?
473
+ return unless log_level_enabled?(:info)
474
+ SmartPrompt.logger.info "[HistoryManager] #{message}"
475
+ end
476
+
477
+ def log_debug(message)
478
+ return unless monitoring_enabled?
479
+ return unless log_level_enabled?(:debug)
480
+ SmartPrompt.logger.debug "[HistoryManager] #{message}"
481
+ end
482
+
483
+ def log_warn(message)
484
+ return unless monitoring_enabled?
485
+ SmartPrompt.logger.warn "[HistoryManager] #{message}"
486
+ end
487
+
488
+ def log_error(message, exception = nil)
489
+ return unless monitoring_enabled?
490
+
491
+ error_msg = "[HistoryManager] #{message}"
492
+ if exception
493
+ error_msg += ": #{exception.class.name} - #{exception.message}"
494
+ error_msg += "\n#{exception.backtrace.first(5).join("\n")}" if exception.backtrace
495
+ end
496
+
497
+ SmartPrompt.logger.error error_msg
498
+ end
499
+
500
+ def monitoring_enabled?
501
+ @config[:monitoring] && @config[:monitoring][:enabled] != false
502
+ end
503
+
504
+ def log_level_enabled?(level)
505
+ return true unless @config[:monitoring]
506
+
507
+ configured_level = @config[:monitoring][:log_level] || :info
508
+ # Convert to symbol if it's a string
509
+ configured_level = configured_level.to_sym if configured_level.is_a?(String)
510
+ level_priority = { debug: 0, info: 1, warn: 2, error: 3 }
511
+
512
+ # Return true if either level is not in the priority hash (to avoid nil comparison)
513
+ return true unless level_priority.key?(level) && level_priority.key?(configured_level)
514
+
515
+ level_priority[level] >= level_priority[configured_level]
516
+ end
517
+
518
+ # Export metrics in Prometheus format
519
+ def export_prometheus_metrics(stats)
520
+ lines = []
521
+
522
+ # Session metrics
523
+ lines << "# HELP smart_prompt_active_sessions Number of active sessions in cache"
524
+ lines << "# TYPE smart_prompt_active_sessions gauge"
525
+ lines << "smart_prompt_active_sessions #{stats[:active_sessions]}"
526
+
527
+ lines << "# HELP smart_prompt_sessions_created_total Total number of sessions created"
528
+ lines << "# TYPE smart_prompt_sessions_created_total counter"
529
+ lines << "smart_prompt_sessions_created_total #{stats[:sessions_created]}"
530
+
531
+ lines << "# HELP smart_prompt_sessions_deleted_total Total number of sessions deleted"
532
+ lines << "# TYPE smart_prompt_sessions_deleted_total counter"
533
+ lines << "smart_prompt_sessions_deleted_total #{stats[:sessions_deleted]}"
534
+
535
+ # Message metrics
536
+ lines << "# HELP smart_prompt_total_messages Total number of messages across all sessions"
537
+ lines << "# TYPE smart_prompt_total_messages gauge"
538
+ lines << "smart_prompt_total_messages #{stats[:total_messages]}"
539
+
540
+ lines << "# HELP smart_prompt_messages_added_total Total number of messages added"
541
+ lines << "# TYPE smart_prompt_messages_added_total counter"
542
+ lines << "smart_prompt_messages_added_total #{stats[:messages_added]}"
543
+
544
+ lines << "# HELP smart_prompt_messages_per_session_avg Average messages per session"
545
+ lines << "# TYPE smart_prompt_messages_per_session_avg gauge"
546
+ lines << "smart_prompt_messages_per_session_avg #{stats[:messages_per_session_avg]}"
547
+
548
+ # Token metrics
549
+ lines << "# HELP smart_prompt_total_tokens Total number of tokens across all sessions"
550
+ lines << "# TYPE smart_prompt_total_tokens gauge"
551
+ lines << "smart_prompt_total_tokens #{stats[:total_tokens]}"
552
+
553
+ lines << "# HELP smart_prompt_tokens_per_session_avg Average tokens per session"
554
+ lines << "# TYPE smart_prompt_tokens_per_session_avg gauge"
555
+ lines << "smart_prompt_tokens_per_session_avg #{stats[:tokens_per_session_avg]}"
556
+
557
+ lines << "# HELP smart_prompt_tokens_per_message_avg Average tokens per message"
558
+ lines << "# TYPE smart_prompt_tokens_per_message_avg gauge"
559
+ lines << "smart_prompt_tokens_per_message_avg #{stats[:tokens_per_message_avg]}"
560
+
561
+ # Cache metrics
562
+ lines << "# HELP smart_prompt_cache_hits_total Total number of cache hits"
563
+ lines << "# TYPE smart_prompt_cache_hits_total counter"
564
+ lines << "smart_prompt_cache_hits_total #{stats[:cache_hits]}"
565
+
566
+ lines << "# HELP smart_prompt_cache_misses_total Total number of cache misses"
567
+ lines << "# TYPE smart_prompt_cache_misses_total counter"
568
+ lines << "smart_prompt_cache_misses_total #{stats[:cache_misses]}"
569
+
570
+ lines << "# HELP smart_prompt_cache_hit_rate Cache hit rate (0.0-1.0)"
571
+ lines << "# TYPE smart_prompt_cache_hit_rate gauge"
572
+ lines << "smart_prompt_cache_hit_rate #{stats[:cache_hit_rate]}"
573
+
574
+ # Operation metrics
575
+ lines << "# HELP smart_prompt_context_retrievals_total Total number of context retrievals"
576
+ lines << "# TYPE smart_prompt_context_retrievals_total counter"
577
+ lines << "smart_prompt_context_retrievals_total #{stats[:context_retrievals]}"
578
+
579
+ # Compression metrics
580
+ lines << "# HELP smart_prompt_compression_operations_total Total number of compression operations"
581
+ lines << "# TYPE smart_prompt_compression_operations_total counter"
582
+ lines << "smart_prompt_compression_operations_total #{stats[:compression_operations]}"
583
+
584
+ lines << "# HELP smart_prompt_tokens_saved_by_compression_total Total tokens saved by compression"
585
+ lines << "# TYPE smart_prompt_tokens_saved_by_compression_total counter"
586
+ lines << "smart_prompt_tokens_saved_by_compression_total #{stats[:tokens_saved_by_compression]}"
587
+
588
+ # Error metrics
589
+ lines << "# HELP smart_prompt_persistence_errors_total Total number of persistence errors"
590
+ lines << "# TYPE smart_prompt_persistence_errors_total counter"
591
+ lines << "smart_prompt_persistence_errors_total #{stats[:persistence_errors]}"
592
+
593
+ lines.join("\n")
594
+ end
595
+ end
596
+ end