Class: Legion::CLI::Chat::Tools::GenerateInsights
- Inherits:
-
Tools::Base
- Object
- Tools::Base
- Legion::CLI::Chat::Tools::GenerateInsights
show all
- Defined in:
- lib/legion/cli/chat/tools/generate_insights.rb
Constant Summary
collapse
- DEFAULT_PORT =
4567
- DEFAULT_HOST =
'127.0.0.1'
Class Method Summary
collapse
Methods inherited from Tools::Base
deferred, deferred?, description, error_response, extension, handle_exception, input_schema, log, mcp_category, mcp_tier, runner, sticky, tags, text_response, tool_name, trigger_words
Class Method Details
.add_anomaly_recs(recs, data) ⇒ Object
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
|
# File 'lib/legion/cli/chat/tools/generate_insights.rb', line 198
def self.add_anomaly_recs(recs, data)
return unless data
anomalies = (data[:data] || data)[:anomalies] || []
anomalies.each do |a|
case a[:metric]
when /cost/i
recs << 'Review recent high-cost operations — consider model downgrade for non-critical tasks'
when /latency/i
recs << 'Investigate latency spike — check provider health or fleet worker load'
when /failure/i
recs << 'Elevated failure rate — check extension health and transport connectivity'
end
end
end
|
.add_trend_recs(recs, data) ⇒ Object
214
215
216
217
218
219
220
221
222
223
224
225
|
# File 'lib/legion/cli/chat/tools/generate_insights.rb', line 214
def self.add_trend_recs(recs, data)
return unless data
buckets = (data[:data] || data)[:buckets] || []
return if buckets.size < 2
last = buckets.last
recs << 'Failure rate above 10% in most recent period — investigate immediately' if last[:failure_rate].to_f > 0.1
return unless last[:count].to_i.zero? && buckets.size > 2
recs << 'No recent activity detected — verify daemon extensions are running'
end
|
.api_get(path) ⇒ Object
243
244
245
246
247
248
249
250
|
# File 'lib/legion/cli/chat/tools/generate_insights.rb', line 243
def self.api_get(path)
uri = URI("http://#{DEFAULT_HOST}:#{api_port}#{path}")
http = Net::HTTP.new(uri.host, uri.port)
http.open_timeout = 2
http.read_timeout = 5
response = http.get(uri.request_uri)
::JSON.parse(response.body, symbolize_names: true)
end
|
.api_port ⇒ Object
252
253
254
255
256
257
258
|
# File 'lib/legion/cli/chat/tools/generate_insights.rb', line 252
def self.api_port
return DEFAULT_PORT unless defined?(Legion::Settings)
Legion::Settings[:api]&.dig(:port) || DEFAULT_PORT
rescue StandardError
DEFAULT_PORT
end
|
.call ⇒ Object
26
27
28
29
30
31
32
33
34
35
36
|
# File 'lib/legion/cli/chat/tools/generate_insights.rb', line 26
def self.call
sections = gather_sections
return 'Legion daemon not running (cannot reach API).' if sections.values.all?(&:nil?)
format_insights(sections)
rescue Errno::ECONNREFUSED
'Legion daemon not running (cannot reach API).'
rescue StandardError => e
Legion::Logging.warn("GenerateInsights#execute failed: #{e.message}") if defined?(Legion::Logging)
"Error generating insights: #{e.message}"
end
|
78
79
80
81
82
83
84
85
86
87
88
89
|
# File 'lib/legion/cli/chat/tools/generate_insights.rb', line 78
def self.format_anomaly_section(data)
return nil unless data
d = data[:data] || data
anomalies = d[:anomalies] || []
if anomalies.empty?
'Anomalies: None detected (system nominal)'
else
items = anomalies.map { |a| " - [#{(a[:severity] || 'warning').upcase}] #{a[:metric]} (#{a[:ratio]}x)" }
"Anomalies (#{anomalies.size}):\n#{items.join("\n")}"
end
end
|
106
107
108
109
110
111
112
113
114
|
# File 'lib/legion/cli/chat/tools/generate_insights.rb', line 106
def self.format_apollo_section(data)
return nil unless data
d = data[:data] || data
return nil if d[:error]
"Knowledge: #{d[:total_entries] || 0} entries | 24h: #{d[:recent_24h] || 0} | " \
"Confidence: #{d[:avg_confidence] || 0}"
end
|
127
128
129
130
131
132
133
134
135
136
137
138
|
# File 'lib/legion/cli/chat/tools/generate_insights.rb', line 127
def self.format_graph_section(data)
return nil unless data
d = data[:data] || data
return nil if d[:error]
disputed = d[:disputed_entries] || 0
domains = (d[:domains] || {}).size
relations = d[:total_relations] || 0
"Graph: #{domains} domains | #{relations} relations | #{disputed} disputed"
end
|
71
72
73
74
75
76
|
# File 'lib/legion/cli/chat/tools/generate_insights.rb', line 71
def self.format_health(data)
return nil unless data
d = data[:data] || data
"Health: #{d[:status] || 'unknown'} | Version: #{d[:version] || '?'}"
end
|
57
58
59
60
61
62
63
64
65
66
67
68
69
|
# File 'lib/legion/cli/chat/tools/generate_insights.rb', line 57
def self.format_insights(sections)
lines = ["System Insights Report\n"]
lines << format_health(sections[:health])
lines << format_anomaly_section(sections[:anomalies])
lines << format_trend_section(sections[:trend])
lines << format_apollo_section(sections[:apollo])
lines << format_graph_section(sections[:graph])
lines << format_worker_section(sections[:workers])
lines << format_scheduling_section(sections[:scheduling])
lines << format_llm_section(sections[:llm])
lines << recommendations(sections)
lines.compact.join("\n\n")
end
|
149
150
151
152
153
154
155
156
157
158
|
# File 'lib/legion/cli/chat/tools/generate_insights.rb', line 149
def self.format_llm_section(data)
return nil unless data
parts = []
parts << "Escalations: #{data[:escalations]}" if data[:escalations]
parts << "Shadow evals: #{data[:shadow_evals]}" if data[:shadow_evals]
return nil if parts.empty?
"LLM: #{parts.join(' | ')}"
end
|
140
141
142
143
144
145
146
147
|
# File 'lib/legion/cli/chat/tools/generate_insights.rb', line 140
def self.format_scheduling_section(data)
return nil unless data
peak = data[:peak_hours] ? 'PEAK' : 'off-peak'
batch_size = data.dig(:batch, :queue_size) || 0
"Scheduling: #{peak} | Batch queue: #{batch_size}"
end
|
91
92
93
94
95
96
97
98
99
100
101
102
103
104
|
# File 'lib/legion/cli/chat/tools/generate_insights.rb', line 91
def self.format_trend_section(data)
return nil unless data
d = data[:data] || data
buckets = d[:buckets] || []
return nil if buckets.empty?
first = buckets.first
last = buckets.last
vol_change = percent_change(first[:count], last[:count])
cost_change = percent_change(first[:avg_cost], last[:avg_cost])
"Trend (24h): Volume #{vol_change} | Cost #{cost_change}"
end
|
116
117
118
119
120
121
122
123
124
125
|
# File 'lib/legion/cli/chat/tools/generate_insights.rb', line 116
def self.format_worker_section(data)
return nil unless data
workers = data[:data] || []
workers = Array(workers)
return nil if workers.empty?
active = workers.count { |w| w[:lifecycle_state] == 'active' }
"Workers: #{active}/#{workers.size} active"
end
|
.gather_sections ⇒ Object
38
39
40
41
42
43
44
45
46
47
48
49
|
# File 'lib/legion/cli/chat/tools/generate_insights.rb', line 38
def self.gather_sections
{
health: safe_fetch('/api/health'),
anomalies: safe_fetch('/api/traces/anomalies'),
trend: safe_fetch('/api/traces/trend?hours=24&buckets=6'),
apollo: safe_fetch('/api/apollo/stats'),
graph: safe_fetch('/api/apollo/graph'),
workers: safe_fetch('/api/workers'),
scheduling: scheduling_status,
llm: llm_status
}
end
|
.llm_status ⇒ Object
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
|
# File 'lib/legion/cli/chat/tools/generate_insights.rb', line 173
def self.llm_status
result = {}
if defined?(Legion::LLM::EscalationTracker)
s = Legion::LLM::EscalationTracker.summary
result[:escalations] = s[:total_escalations]
end
if defined?(Legion::LLM::ShadowEval)
s = Legion::LLM::ShadowEval.summary
result[:shadow_evals] = s[:total_evaluations]
end
result.empty? ? nil : result
rescue StandardError => e
Legion::Logging.debug("GenerateInsights#llm_status failed: #{e.message}") if defined?(Legion::Logging)
nil
end
|
.percent_change(first_val, last_val) ⇒ Object
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
|
# File 'lib/legion/cli/chat/tools/generate_insights.rb', line 227
def self.percent_change(first_val, last_val)
f = (first_val || 0).to_f
l = (last_val || 0).to_f
return 'stable' if f.zero? && l.zero?
return 'rising' if f.zero?
pct = ((l - f) / f * 100).round(0)
if pct > 10
"rising (+#{pct}%)"
elsif pct < -10
"falling (#{pct}%)"
else
"stable (#{pct}%)"
end
end
|
.recommendations(sections) ⇒ Object
189
190
191
192
193
194
195
196
|
# File 'lib/legion/cli/chat/tools/generate_insights.rb', line 189
def self.recommendations(sections)
recs = []
add_anomaly_recs(recs, sections[:anomalies])
add_trend_recs(recs, sections[:trend])
return nil if recs.empty?
"Recommendations:\n#{recs.map { |r| " * #{r}" }.join("\n")}"
end
|
.safe_fetch(path) ⇒ Object
51
52
53
54
55
|
# File 'lib/legion/cli/chat/tools/generate_insights.rb', line 51
def self.safe_fetch(path)
api_get(path)
rescue StandardError
nil
end
|
.scheduling_status ⇒ Object
160
161
162
163
164
165
166
167
168
169
170
171
|
# File 'lib/legion/cli/chat/tools/generate_insights.rb', line 160
def self.scheduling_status
result = {}
if defined?(Legion::LLM::Scheduling)
s = Legion::LLM::Scheduling.status
result.merge!(s)
end
result[:batch] = Legion::LLM::Batch.status if defined?(Legion::LLM::Batch)
result.empty? ? nil : result
rescue StandardError => e
Legion::Logging.debug("GenerateInsights#scheduling_status failed: #{e.message}") if defined?(Legion::Logging)
nil
end
|