Module: Legion::MCP::Observer
- Extended by:
- Logging::Helper
- Defined in:
- lib/legion/mcp/observer.rb
Constant Summary collapse
- RING_BUFFER_MAX =
500- INTENT_BUFFER_MAX =
200
Class Method Summary collapse
- .all_tool_stats ⇒ Object
- .buffer_mutex ⇒ Object
-
.counters ⇒ Object
Internal state accessors.
- .counters_mutex ⇒ Object
- .intent_buffer ⇒ Object
- .intent_mutex ⇒ Object
- .recent(limit = 10) ⇒ Object
- .recent_intents(limit = 10) ⇒ Object
- .record(tool_name:, duration_ms:, success:, params_keys: [], error: nil) ⇒ Object
- .record_intent(intent, matched_tool_name) ⇒ Object
- .record_intent_with_result(intent:, tool_name:, success:, request_id: nil) ⇒ Object
- .reset! ⇒ Object
- .ring_buffer ⇒ Object
- .started_at ⇒ Object
- .stats ⇒ Object
- .tool_stats(tool_name) ⇒ Object
- .try_embed(text) ⇒ Object
Class Method Details
.all_tool_stats ⇒ Object
95 96 97 98 |
# File 'lib/legion/mcp/observer.rb', line 95 def all_tool_stats names = counters_mutex.synchronize { counters.keys.dup } names.to_h { |name| [name, tool_stats(name)] } end |
.buffer_mutex ⇒ Object
148 149 150 |
# File 'lib/legion/mcp/observer.rb', line 148 def buffer_mutex @buffer_mutex ||= Mutex.new end |
.counters ⇒ Object
Internal state accessors
136 137 138 |
# File 'lib/legion/mcp/observer.rb', line 136 def counters @counters ||= {} end |
.counters_mutex ⇒ Object
140 141 142 |
# File 'lib/legion/mcp/observer.rb', line 140 def counters_mutex @counters_mutex ||= Mutex.new end |
.intent_buffer ⇒ Object
152 153 154 |
# File 'lib/legion/mcp/observer.rb', line 152 def intent_buffer @intent_buffer ||= [] end |
.intent_mutex ⇒ Object
156 157 158 |
# File 'lib/legion/mcp/observer.rb', line 156 def intent_mutex @intent_mutex ||= Mutex.new end |
.recent(limit = 10) ⇒ Object
120 121 122 |
# File 'lib/legion/mcp/observer.rb', line 120 def recent(limit = 10) buffer_mutex.synchronize { ring_buffer.last(limit) } end |
.recent_intents(limit = 10) ⇒ Object
124 125 126 |
# File 'lib/legion/mcp/observer.rb', line 124 def recent_intents(limit = 10) intent_mutex.synchronize { intent_buffer.last(limit) } end |
.record(tool_name:, duration_ms:, success:, params_keys: [], error: nil) ⇒ Object
17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
# File 'lib/legion/mcp/observer.rb', line 17 def record(tool_name:, duration_ms:, success:, params_keys: [], error: nil) now = Time.now counters_mutex.synchronize do entry = counters[tool_name] || { call_count: 0, total_latency_ms: 0.0, failure_count: 0, last_used: nil, last_error: nil } counters[tool_name] = { call_count: entry[:call_count] + 1, total_latency_ms: entry[:total_latency_ms] + duration_ms.to_f, failure_count: entry[:failure_count] + (success ? 0 : 1), last_used: now, last_error: success ? entry[:last_error] : error } end buffer_mutex.synchronize do ring_buffer << { tool_name: tool_name, duration_ms: duration_ms, success: success, params_keys: params_keys, error: error, recorded_at: now } ring_buffer.shift if ring_buffer.size > RING_BUFFER_MAX end end |
.record_intent(intent, matched_tool_name) ⇒ Object
45 46 47 48 49 50 |
# File 'lib/legion/mcp/observer.rb', line 45 def record_intent(intent, matched_tool_name) intent_mutex.synchronize do intent_buffer << { intent: intent, matched_tool: matched_tool_name, recorded_at: Time.now } intent_buffer.shift if intent_buffer.size > INTENT_BUFFER_MAX end end |
.record_intent_with_result(intent:, tool_name:, success:, request_id: nil) ⇒ Object
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 |
# File 'lib/legion/mcp/observer.rb', line 52 def record_intent_with_result(intent:, tool_name:, success:, request_id: nil) record_intent(intent, tool_name) return unless success return unless defined?(Legion::MCP::PatternStore) normalized = intent.to_s.strip.downcase.gsub(/\s+/, ' ') intent_hash = Digest::SHA256.hexdigest(normalized) promotion = Legion::MCP::PatternStore.record_candidate( intent_hash: intent_hash, tool_chain: [tool_name], intent_text: intent, request_id: request_id ) return unless promotion&.dig(:promote) Legion::MCP::PatternStore.promote_candidate( intent_hash: promotion[:intent_hash], tool_chain: promotion[:tool_chain], intent_text: promotion[:intent_text], intent_vector: (normalized), request_id: request_id ) end |
.reset! ⇒ Object
128 129 130 131 132 133 |
# File 'lib/legion/mcp/observer.rb', line 128 def reset! counters_mutex.synchronize { counters.clear } buffer_mutex.synchronize { ring_buffer.clear } intent_mutex.synchronize { intent_buffer.clear } @started_at = Time.now end |
.ring_buffer ⇒ Object
144 145 146 |
# File 'lib/legion/mcp/observer.rb', line 144 def ring_buffer @ring_buffer ||= [] end |
.started_at ⇒ Object
160 161 162 |
# File 'lib/legion/mcp/observer.rb', line 160 def started_at @started_at ||= Time.now end |
.stats ⇒ Object
100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 |
# File 'lib/legion/mcp/observer.rb', line 100 def stats all_names = counters_mutex.synchronize { counters.keys.dup } total = all_names.sum { |n| counters_mutex.synchronize { counters[n][:call_count] } } failures = all_names.sum { |n| counters_mutex.synchronize { counters[n][:failure_count] } } rate = total.positive? ? (failures.to_f / total).round(4) : 0.0 top = all_names .map { |n| tool_stats(n) } .sort_by { |s| -s[:call_count] } .first(10) { total_calls: total, tool_count: all_names.size, failure_rate: rate, top_tools: top, since: started_at } end |
.tool_stats(tool_name) ⇒ Object
78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 |
# File 'lib/legion/mcp/observer.rb', line 78 def tool_stats(tool_name) entry = counters_mutex.synchronize { counters[tool_name] } return nil unless entry count = entry[:call_count] avg = count.positive? ? (entry[:total_latency_ms] / count).round(2) : 0.0 { name: tool_name, call_count: count, avg_latency_ms: avg, failure_count: entry[:failure_count], last_used: entry[:last_used], last_error: entry[:last_error] } end |
.try_embed(text) ⇒ Object
164 165 166 167 168 169 170 171 172 173 174 175 |
# File 'lib/legion/mcp/observer.rb', line 164 def (text) return nil unless defined?(Legion::MCP::EmbeddingIndex) = Legion::MCP::EmbeddingIndex.instance_variable_get(:@embedder) return nil unless .call(text) rescue StandardError => e handle_exception(e, level: :debug, operation: 'legion.mcp.observer.try_embed') LoggingSupport.debug('observer.embed.failed', error: e.) nil end |