agentic 0.1.0 → 0.2.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.
- checksums.yaml +4 -4
- data/.agentic.yml +2 -0
- data/.architecture/decisions/ArchitecturalFeatureBuilder.md +136 -0
- data/.architecture/decisions/ArchitectureConsiderations.md +200 -0
- data/.architecture/decisions/adr_001_observer_pattern_implementation.md +196 -0
- data/.architecture/decisions/adr_002_plan_orchestrator.md +320 -0
- data/.architecture/decisions/adr_003_plan_orchestrator_interface.md +179 -0
- data/.architecture/decisions/adrs/ADR-001-dependency-management.md +147 -0
- data/.architecture/decisions/adrs/ADR-002-system-boundaries.md +162 -0
- data/.architecture/decisions/adrs/ADR-003-content-safety.md +158 -0
- data/.architecture/decisions/adrs/ADR-004-agent-permissions.md +161 -0
- data/.architecture/decisions/adrs/ADR-005-adaptation-engine.md +127 -0
- data/.architecture/decisions/adrs/ADR-006-extension-system.md +273 -0
- data/.architecture/decisions/adrs/ADR-007-learning-system.md +156 -0
- data/.architecture/decisions/adrs/ADR-008-prompt-generation.md +325 -0
- data/.architecture/decisions/adrs/ADR-009-task-failure-handling.md +353 -0
- data/.architecture/decisions/adrs/ADR-010-task-input-handling.md +251 -0
- data/.architecture/decisions/adrs/ADR-011-task-observable-pattern.md +391 -0
- data/.architecture/decisions/adrs/ADR-012-task-output-handling.md +205 -0
- data/.architecture/decisions/adrs/ADR-013-architecture-alignment.md +211 -0
- data/.architecture/decisions/adrs/ADR-014-agent-capability-registry.md +80 -0
- data/.architecture/decisions/adrs/ADR-015-persistent-agent-store.md +100 -0
- data/.architecture/decisions/adrs/ADR-016-agent-assembly-engine.md +117 -0
- data/.architecture/decisions/adrs/ADR-017-streaming-observability.md +171 -0
- data/.architecture/decisions/capability_tools_distinction.md +150 -0
- data/.architecture/decisions/cli_command_structure.md +61 -0
- data/.architecture/implementation/agent_self_assembly_implementation.md +267 -0
- data/.architecture/implementation/agent_self_assembly_summary.md +138 -0
- data/.architecture/members.yml +187 -0
- data/.architecture/planning/self_implementation_exercise.md +295 -0
- data/.architecture/planning/session_compaction_rule.md +43 -0
- data/.architecture/planning/streaming_observability_feature.md +223 -0
- data/.architecture/principles.md +151 -0
- data/.architecture/recalibration/0-2-0.md +92 -0
- data/.architecture/recalibration/agent_self_assembly.md +238 -0
- data/.architecture/recalibration/cli_command_structure.md +91 -0
- data/.architecture/recalibration/implementation_roadmap_0-2-0.md +301 -0
- data/.architecture/recalibration/progress_tracking_0-2-0.md +114 -0
- data/.architecture/recalibration_process.md +127 -0
- data/.architecture/reviews/0-2-0.md +181 -0
- data/.architecture/reviews/cli_command_duplication.md +98 -0
- data/.architecture/templates/adr.md +105 -0
- data/.architecture/templates/implementation_roadmap.md +125 -0
- data/.architecture/templates/progress_tracking.md +89 -0
- data/.architecture/templates/recalibration_plan.md +70 -0
- data/.architecture/templates/version_comparison.md +124 -0
- data/.claude/settings.local.json +13 -0
- data/.claude-sessions/001-task-class-architecture-implementation.md +129 -0
- data/.claude-sessions/002-plan-orchestrator-interface-review.md +105 -0
- data/.claude-sessions/architecture-governance-implementation.md +37 -0
- data/.claude-sessions/architecture-review-session.md +27 -0
- data/ArchitecturalFeatureBuilder.md +136 -0
- data/ArchitectureConsiderations.md +229 -0
- data/CHANGELOG.md +57 -2
- data/CLAUDE.md +111 -0
- data/CONTRIBUTING.md +286 -0
- data/MAINTAINING.md +301 -0
- data/README.md +582 -28
- data/docs/agent_capabilities_api.md +259 -0
- data/docs/artifact_extension_points.md +757 -0
- data/docs/artifact_generation_architecture.md +323 -0
- data/docs/artifact_implementation_plan.md +596 -0
- data/docs/artifact_integration_points.md +345 -0
- data/docs/artifact_verification_strategies.md +581 -0
- data/docs/streaming_observability_architecture.md +510 -0
- data/exe/agentic +6 -1
- data/lefthook.yml +5 -0
- data/lib/agentic/adaptation_engine.rb +124 -0
- data/lib/agentic/agent.rb +181 -4
- data/lib/agentic/agent_assembly_engine.rb +442 -0
- data/lib/agentic/agent_capability_registry.rb +260 -0
- data/lib/agentic/agent_config.rb +63 -0
- data/lib/agentic/agent_specification.rb +46 -0
- data/lib/agentic/capabilities/examples.rb +530 -0
- data/lib/agentic/capabilities.rb +14 -0
- data/lib/agentic/capability_provider.rb +146 -0
- data/lib/agentic/capability_specification.rb +118 -0
- data/lib/agentic/cli/agent.rb +31 -0
- data/lib/agentic/cli/capabilities.rb +191 -0
- data/lib/agentic/cli/config.rb +134 -0
- data/lib/agentic/cli/execution_observer.rb +796 -0
- data/lib/agentic/cli.rb +1068 -0
- data/lib/agentic/default_agent_provider.rb +35 -0
- data/lib/agentic/errors/llm_error.rb +184 -0
- data/lib/agentic/execution_plan.rb +53 -0
- data/lib/agentic/execution_result.rb +91 -0
- data/lib/agentic/expected_answer_format.rb +46 -0
- data/lib/agentic/extension/domain_adapter.rb +109 -0
- data/lib/agentic/extension/plugin_manager.rb +163 -0
- data/lib/agentic/extension/protocol_handler.rb +116 -0
- data/lib/agentic/extension.rb +45 -0
- data/lib/agentic/factory_methods.rb +9 -1
- data/lib/agentic/generation_stats.rb +61 -0
- data/lib/agentic/learning/README.md +84 -0
- data/lib/agentic/learning/capability_optimizer.rb +613 -0
- data/lib/agentic/learning/execution_history_store.rb +251 -0
- data/lib/agentic/learning/pattern_recognizer.rb +500 -0
- data/lib/agentic/learning/strategy_optimizer.rb +706 -0
- data/lib/agentic/learning.rb +131 -0
- data/lib/agentic/llm_assisted_composition_strategy.rb +188 -0
- data/lib/agentic/llm_client.rb +215 -15
- data/lib/agentic/llm_config.rb +65 -1
- data/lib/agentic/llm_response.rb +163 -0
- data/lib/agentic/logger.rb +1 -1
- data/lib/agentic/observable.rb +51 -0
- data/lib/agentic/persistent_agent_store.rb +385 -0
- data/lib/agentic/plan_execution_result.rb +129 -0
- data/lib/agentic/plan_orchestrator.rb +464 -0
- data/lib/agentic/plan_orchestrator_config.rb +57 -0
- data/lib/agentic/retry_config.rb +63 -0
- data/lib/agentic/retry_handler.rb +125 -0
- data/lib/agentic/structured_outputs.rb +1 -1
- data/lib/agentic/task.rb +193 -0
- data/lib/agentic/task_definition.rb +39 -0
- data/lib/agentic/task_execution_result.rb +92 -0
- data/lib/agentic/task_failure.rb +66 -0
- data/lib/agentic/task_output_schemas.rb +112 -0
- data/lib/agentic/task_planner.rb +54 -19
- data/lib/agentic/task_result.rb +48 -0
- data/lib/agentic/ui.rb +244 -0
- data/lib/agentic/verification/critic_framework.rb +116 -0
- data/lib/agentic/verification/llm_verification_strategy.rb +60 -0
- data/lib/agentic/verification/schema_verification_strategy.rb +47 -0
- data/lib/agentic/verification/verification_hub.rb +62 -0
- data/lib/agentic/verification/verification_result.rb +50 -0
- data/lib/agentic/verification/verification_strategy.rb +26 -0
- data/lib/agentic/version.rb +1 -1
- data/lib/agentic.rb +74 -2
- data/plugins/README.md +41 -0
- metadata +245 -6
@@ -0,0 +1,500 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Agentic
|
4
|
+
module Learning
|
5
|
+
# PatternRecognizer identifies patterns and optimization opportunities from execution history.
|
6
|
+
# It analyzes historical task and plan executions to detect recurring patterns,
|
7
|
+
# success/failure correlations, and potential optimization points.
|
8
|
+
#
|
9
|
+
# @example Analyzing patterns in task executions
|
10
|
+
# history_store = Agentic::Learning::ExecutionHistoryStore.new
|
11
|
+
# recognizer = Agentic::Learning::PatternRecognizer.new(history_store: history_store)
|
12
|
+
# patterns = recognizer.analyze_agent_performance("research_agent")
|
13
|
+
#
|
14
|
+
class PatternRecognizer
|
15
|
+
# Initialize a new PatternRecognizer
|
16
|
+
#
|
17
|
+
# @param options [Hash] Configuration options
|
18
|
+
# @option options [Logger] :logger Custom logger (defaults to Agentic.logger)
|
19
|
+
# @option options [ExecutionHistoryStore] :history_store The history store to analyze
|
20
|
+
# @option options [Integer] :min_sample_size Minimum sample size for pattern detection (defaults to 10)
|
21
|
+
# @option options [Float] :significance_threshold Statistical significance threshold (defaults to 0.05)
|
22
|
+
# @option options [Integer] :time_window_days Time window in days for analysis (defaults to 30)
|
23
|
+
def initialize(options = {})
|
24
|
+
@logger = options[:logger] || Agentic.logger
|
25
|
+
@history_store = options[:history_store] || raise(ArgumentError, "history_store is required")
|
26
|
+
@min_sample_size = options[:min_sample_size] || 10
|
27
|
+
@significance_threshold = options[:significance_threshold] || 0.05
|
28
|
+
@time_window_days = options[:time_window_days] || 30
|
29
|
+
@pattern_cache = {}
|
30
|
+
@cache_expiry = {}
|
31
|
+
end
|
32
|
+
|
33
|
+
# Analyze performance patterns for a specific agent type
|
34
|
+
#
|
35
|
+
# @param agent_type [String] The agent type to analyze
|
36
|
+
# @param options [Hash] Analysis options
|
37
|
+
# @option options [Array<Symbol>] :metrics Specific metrics to analyze (defaults to all)
|
38
|
+
# @option options [Boolean] :force_refresh Force a fresh analysis even if cached (defaults to false)
|
39
|
+
# @return [Hash] Analysis results with identified patterns
|
40
|
+
def analyze_agent_performance(agent_type, options = {})
|
41
|
+
cache_key = "agent_perf:#{agent_type}:#{options[:metrics]}"
|
42
|
+
|
43
|
+
# Check cache first if not forcing refresh
|
44
|
+
if !options[:force_refresh] && @pattern_cache[cache_key] && @cache_expiry[cache_key] && @cache_expiry[cache_key] > Time.now
|
45
|
+
return @pattern_cache[cache_key]
|
46
|
+
end
|
47
|
+
|
48
|
+
# Fetch relevant history
|
49
|
+
history = fetch_agent_history(agent_type)
|
50
|
+
|
51
|
+
if history.size < @min_sample_size
|
52
|
+
@logger.info("Insufficient data to analyze patterns for #{agent_type} (#{history.size} < #{@min_sample_size})")
|
53
|
+
return {insufficient_data: true, sample_size: history.size, required_size: @min_sample_size}
|
54
|
+
end
|
55
|
+
|
56
|
+
# Perform analysis
|
57
|
+
patterns = {
|
58
|
+
success_rate: calculate_success_rate(history),
|
59
|
+
performance_trends: analyze_performance_trends(history, options[:metrics]),
|
60
|
+
failure_patterns: identify_failure_patterns(history),
|
61
|
+
optimization_opportunities: identify_optimization_opportunities(history)
|
62
|
+
}
|
63
|
+
|
64
|
+
# Cache results
|
65
|
+
@pattern_cache[cache_key] = patterns
|
66
|
+
@cache_expiry[cache_key] = Time.now + 3600 # Cache for 1 hour
|
67
|
+
|
68
|
+
patterns
|
69
|
+
end
|
70
|
+
|
71
|
+
# Identify correlation between task properties and success/performance
|
72
|
+
#
|
73
|
+
# @param task_property [Symbol] The property to analyze correlation for
|
74
|
+
# @param performance_metric [Symbol] The performance metric to correlate with
|
75
|
+
# @return [Hash] Correlation analysis results
|
76
|
+
def analyze_correlation(task_property, performance_metric)
|
77
|
+
# Fetch all history within time window
|
78
|
+
end_time = Time.now
|
79
|
+
start_time = end_time - (@time_window_days * 24 * 60 * 60)
|
80
|
+
|
81
|
+
history = @history_store.get_history(start_time: start_time, end_time: end_time)
|
82
|
+
|
83
|
+
if history.size < @min_sample_size
|
84
|
+
return {insufficient_data: true, sample_size: history.size}
|
85
|
+
end
|
86
|
+
|
87
|
+
# Extract property and metric values
|
88
|
+
data_points = history.map do |record|
|
89
|
+
property_value = extract_property_value(record, task_property)
|
90
|
+
metric_value = extract_metric_value(record, performance_metric)
|
91
|
+
|
92
|
+
{property: property_value, metric: metric_value} if property_value && metric_value
|
93
|
+
end.compact
|
94
|
+
|
95
|
+
# Calculate correlation
|
96
|
+
if data_points.size < @min_sample_size
|
97
|
+
return {insufficient_data: true, sample_size: data_points.size}
|
98
|
+
end
|
99
|
+
|
100
|
+
correlation = calculate_correlation(data_points)
|
101
|
+
|
102
|
+
{
|
103
|
+
correlation_coefficient: correlation[:coefficient],
|
104
|
+
statistical_significance: correlation[:significance],
|
105
|
+
sample_size: data_points.size,
|
106
|
+
significant: correlation[:significance] < @significance_threshold
|
107
|
+
}
|
108
|
+
end
|
109
|
+
|
110
|
+
# Recommend optimization strategies based on recognized patterns
|
111
|
+
#
|
112
|
+
# @param agent_type [String] The agent type to generate recommendations for
|
113
|
+
# @return [Array<Hash>] List of recommended optimization strategies
|
114
|
+
def recommend_optimizations(agent_type)
|
115
|
+
# Start with performance analysis
|
116
|
+
performance = analyze_agent_performance(agent_type, force_refresh: true)
|
117
|
+
|
118
|
+
if performance[:insufficient_data]
|
119
|
+
return [{type: :insufficient_data, message: "Need more execution data to make recommendations"}]
|
120
|
+
end
|
121
|
+
|
122
|
+
recommendations = []
|
123
|
+
|
124
|
+
# Check success rate
|
125
|
+
if performance[:success_rate][:overall] < 0.8
|
126
|
+
recommendations << {
|
127
|
+
type: :success_rate,
|
128
|
+
priority: :high,
|
129
|
+
message: "Improve success rate (currently #{(performance[:success_rate][:overall] * 100).round(1)}%)",
|
130
|
+
suggestions: generate_success_rate_suggestions(performance)
|
131
|
+
}
|
132
|
+
end
|
133
|
+
|
134
|
+
# Check performance trends
|
135
|
+
slow_metrics = performance[:performance_trends].select { |_, v| v[:trend] == :increasing && v[:significant] }
|
136
|
+
if slow_metrics.any?
|
137
|
+
recommendations << {
|
138
|
+
type: :performance,
|
139
|
+
priority: :medium,
|
140
|
+
message: "Performance degradation detected in #{slow_metrics.keys.join(", ")}",
|
141
|
+
suggestions: generate_performance_suggestions(slow_metrics)
|
142
|
+
}
|
143
|
+
end
|
144
|
+
|
145
|
+
# Check failure patterns
|
146
|
+
if performance[:failure_patterns]&.any?
|
147
|
+
recommendations << {
|
148
|
+
type: :failures,
|
149
|
+
priority: :high,
|
150
|
+
message: "Address common failure patterns",
|
151
|
+
patterns: performance[:failure_patterns].first(3),
|
152
|
+
suggestions: generate_failure_suggestions(performance[:failure_patterns])
|
153
|
+
}
|
154
|
+
end
|
155
|
+
|
156
|
+
# Check optimization opportunities
|
157
|
+
if performance[:optimization_opportunities]&.any?
|
158
|
+
recommendations << {
|
159
|
+
type: :optimization,
|
160
|
+
priority: :medium,
|
161
|
+
message: "Potential optimization opportunities identified",
|
162
|
+
opportunities: performance[:optimization_opportunities],
|
163
|
+
suggestions: performance[:optimization_opportunities].map { |o| o[:suggestion] }
|
164
|
+
}
|
165
|
+
end
|
166
|
+
|
167
|
+
recommendations
|
168
|
+
end
|
169
|
+
|
170
|
+
private
|
171
|
+
|
172
|
+
def fetch_agent_history(agent_type)
|
173
|
+
end_time = Time.now
|
174
|
+
start_time = end_time - (@time_window_days * 24 * 60 * 60)
|
175
|
+
|
176
|
+
@history_store.get_history(
|
177
|
+
agent_type: agent_type,
|
178
|
+
start_time: start_time,
|
179
|
+
end_time: end_time
|
180
|
+
)
|
181
|
+
end
|
182
|
+
|
183
|
+
def calculate_success_rate(history)
|
184
|
+
return {overall: 0, sample_size: 0} if history.empty?
|
185
|
+
|
186
|
+
# Overall success rate
|
187
|
+
successful = history.count { |record| record[:success] }
|
188
|
+
overall_rate = successful.to_f / history.size
|
189
|
+
|
190
|
+
# Success rate over time
|
191
|
+
time_periods = split_into_time_periods(history, 5) # Split into 5 periods
|
192
|
+
period_rates = time_periods.map do |period|
|
193
|
+
successful = period.count { |record| record[:success] }
|
194
|
+
successful.to_f / period.size
|
195
|
+
end
|
196
|
+
|
197
|
+
# Trend analysis
|
198
|
+
trend = if period_rates.size >= 3
|
199
|
+
if period_rates.last > period_rates.first
|
200
|
+
:improving
|
201
|
+
elsif period_rates.last < period_rates.first
|
202
|
+
:declining
|
203
|
+
else
|
204
|
+
:stable
|
205
|
+
end
|
206
|
+
else
|
207
|
+
:insufficient_data
|
208
|
+
end
|
209
|
+
|
210
|
+
{
|
211
|
+
overall: overall_rate,
|
212
|
+
sample_size: history.size,
|
213
|
+
period_rates: period_rates,
|
214
|
+
trend: trend
|
215
|
+
}
|
216
|
+
end
|
217
|
+
|
218
|
+
def analyze_performance_trends(history, metric_keys = nil)
|
219
|
+
return {} if history.empty?
|
220
|
+
|
221
|
+
# Extract all available metrics if none specified
|
222
|
+
metric_keys ||= history.flat_map { |r| r[:metrics]&.keys || [] }.uniq.map(&:to_sym)
|
223
|
+
|
224
|
+
# Analyze each metric
|
225
|
+
metric_keys.each_with_object({}) do |metric, results|
|
226
|
+
# Extract metric values
|
227
|
+
values = history.map { |record| record.dig(:metrics, metric.to_s) }.compact
|
228
|
+
|
229
|
+
next if values.empty?
|
230
|
+
|
231
|
+
# Split into time periods
|
232
|
+
time_periods = split_into_time_periods(history, 5)
|
233
|
+
period_values = time_periods.map do |period|
|
234
|
+
period_metrics = period.map { |record| record.dig(:metrics, metric.to_s) }.compact
|
235
|
+
period_metrics.empty? ? nil : period_metrics.sum / period_metrics.size.to_f
|
236
|
+
end.compact
|
237
|
+
|
238
|
+
# Determine trend
|
239
|
+
trend = if period_values.size >= 3
|
240
|
+
if period_values.last > period_values.first * 1.2
|
241
|
+
:increasing # 20% increase
|
242
|
+
elsif period_values.last < period_values.first * 0.8
|
243
|
+
:decreasing # 20% decrease
|
244
|
+
else
|
245
|
+
:stable
|
246
|
+
end
|
247
|
+
else
|
248
|
+
:insufficient_data
|
249
|
+
end
|
250
|
+
|
251
|
+
# Calculate statistical significance
|
252
|
+
significant = period_values.size >= 3 &&
|
253
|
+
trend != :stable &&
|
254
|
+
calculate_significance(period_values.first, period_values.last, period_values.size) < @significance_threshold
|
255
|
+
|
256
|
+
results[metric] = {
|
257
|
+
avg_value: values.sum / values.size.to_f,
|
258
|
+
min_value: values.min,
|
259
|
+
max_value: values.max,
|
260
|
+
trend: trend,
|
261
|
+
period_values: period_values,
|
262
|
+
significant: significant
|
263
|
+
}
|
264
|
+
end
|
265
|
+
end
|
266
|
+
|
267
|
+
def identify_failure_patterns(history)
|
268
|
+
failures = history.reject { |record| record[:success] }
|
269
|
+
return [] if failures.empty?
|
270
|
+
|
271
|
+
# Group failures by context patterns
|
272
|
+
failure_groups = {}
|
273
|
+
|
274
|
+
failures.each do |failure|
|
275
|
+
# Extract failure pattern from context
|
276
|
+
pattern = extract_failure_pattern(failure)
|
277
|
+
|
278
|
+
failure_groups[pattern] ||= []
|
279
|
+
failure_groups[pattern] << failure
|
280
|
+
end
|
281
|
+
|
282
|
+
# Sort by frequency and return top patterns
|
283
|
+
failure_groups
|
284
|
+
.map { |pattern, occurrences| {pattern: pattern, count: occurrences.size, examples: occurrences.first(3)} }
|
285
|
+
.sort_by { |p| -p[:count] }
|
286
|
+
.first(5)
|
287
|
+
end
|
288
|
+
|
289
|
+
def identify_optimization_opportunities(history)
|
290
|
+
opportunities = []
|
291
|
+
|
292
|
+
# Look for consistently slow tasks
|
293
|
+
duration_by_task = {}
|
294
|
+
history.each do |record|
|
295
|
+
next unless record[:task_id]
|
296
|
+
duration_by_task[record[:task_id]] ||= []
|
297
|
+
duration_by_task[record[:task_id]] << record[:duration_ms]
|
298
|
+
end
|
299
|
+
|
300
|
+
# Find tasks with high average duration
|
301
|
+
slow_tasks = duration_by_task
|
302
|
+
.select { |_, durations| durations.size >= 3 }
|
303
|
+
.map { |task_id, durations| [task_id, durations.sum / durations.size.to_f] }
|
304
|
+
.sort_by { |_, avg_duration| -avg_duration }
|
305
|
+
.first(3)
|
306
|
+
|
307
|
+
slow_tasks.each do |task_id, avg_duration|
|
308
|
+
opportunities << {
|
309
|
+
type: :slow_task,
|
310
|
+
task_id: task_id,
|
311
|
+
avg_duration_ms: avg_duration.round,
|
312
|
+
suggestion: "Optimize slow task #{task_id} (avg: #{(avg_duration / 1000).round(2)}s)"
|
313
|
+
}
|
314
|
+
end
|
315
|
+
|
316
|
+
# Look for resource-intensive tasks
|
317
|
+
if history.any? { |r| r.dig(:metrics, "tokens_used") }
|
318
|
+
tokens_by_task = {}
|
319
|
+
history.each do |record|
|
320
|
+
next unless record[:task_id] && record.dig(:metrics, "tokens_used")
|
321
|
+
tokens_by_task[record[:task_id]] ||= []
|
322
|
+
tokens_by_task[record[:task_id]] << record[:metrics]["tokens_used"]
|
323
|
+
end
|
324
|
+
|
325
|
+
token_heavy_tasks = tokens_by_task
|
326
|
+
.select { |_, tokens| tokens.size >= 3 }
|
327
|
+
.map { |task_id, tokens| [task_id, tokens.sum / tokens.size.to_f] }
|
328
|
+
.sort_by { |_, avg_tokens| -avg_tokens }
|
329
|
+
.first(3)
|
330
|
+
|
331
|
+
token_heavy_tasks.each do |task_id, avg_tokens|
|
332
|
+
opportunities << {
|
333
|
+
type: :token_heavy,
|
334
|
+
task_id: task_id,
|
335
|
+
avg_tokens: avg_tokens.round,
|
336
|
+
suggestion: "Reduce token usage in task #{task_id} (avg: #{avg_tokens.round} tokens)"
|
337
|
+
}
|
338
|
+
end
|
339
|
+
end
|
340
|
+
|
341
|
+
opportunities
|
342
|
+
end
|
343
|
+
|
344
|
+
def split_into_time_periods(history, num_periods)
|
345
|
+
return [] if history.empty?
|
346
|
+
|
347
|
+
# Sort by timestamp
|
348
|
+
sorted = history.sort_by { |r| r[:timestamp] }
|
349
|
+
|
350
|
+
# Calculate time range
|
351
|
+
start_time = Time.parse(sorted.first[:timestamp])
|
352
|
+
end_time = Time.parse(sorted.last[:timestamp])
|
353
|
+
total_duration = end_time - start_time
|
354
|
+
|
355
|
+
return [sorted] if total_duration < 60 # If less than a minute, return as single period
|
356
|
+
|
357
|
+
# Split into periods
|
358
|
+
period_duration = total_duration / num_periods
|
359
|
+
|
360
|
+
periods = []
|
361
|
+
num_periods.times do |i|
|
362
|
+
period_start = start_time + (i * period_duration)
|
363
|
+
period_end = period_start + period_duration
|
364
|
+
|
365
|
+
period_records = sorted.select do |record|
|
366
|
+
record_time = Time.parse(record[:timestamp])
|
367
|
+
record_time >= period_start && (i == num_periods - 1 || record_time < period_end)
|
368
|
+
end
|
369
|
+
|
370
|
+
periods << period_records unless period_records.empty?
|
371
|
+
end
|
372
|
+
|
373
|
+
periods
|
374
|
+
end
|
375
|
+
|
376
|
+
def extract_property_value(record, property)
|
377
|
+
case property
|
378
|
+
when :agent_type
|
379
|
+
record[:agent_type]
|
380
|
+
when :duration
|
381
|
+
record[:duration_ms]
|
382
|
+
when :task_id
|
383
|
+
record[:task_id]
|
384
|
+
when :plan_id
|
385
|
+
record[:plan_id]
|
386
|
+
else
|
387
|
+
# Try to extract from metrics or context
|
388
|
+
record.dig(:metrics, property.to_s) || record.dig(:context, property.to_s)
|
389
|
+
end
|
390
|
+
end
|
391
|
+
|
392
|
+
def extract_metric_value(record, metric)
|
393
|
+
case metric
|
394
|
+
when :success
|
395
|
+
record[:success] ? 1 : 0
|
396
|
+
when :duration
|
397
|
+
record[:duration_ms]
|
398
|
+
else
|
399
|
+
# Try to extract from metrics
|
400
|
+
record.dig(:metrics, metric.to_s)
|
401
|
+
end
|
402
|
+
end
|
403
|
+
|
404
|
+
def calculate_correlation(data_points)
|
405
|
+
# Simple correlation calculation - can be replaced with more sophisticated approach
|
406
|
+
x_values = data_points.map { |p| p[:property].to_f }
|
407
|
+
y_values = data_points.map { |p| p[:metric].to_f }
|
408
|
+
|
409
|
+
n = x_values.size
|
410
|
+
sum_x = x_values.sum
|
411
|
+
sum_y = y_values.sum
|
412
|
+
sum_xy = x_values.zip(y_values).map { |x, y| x * y }.sum
|
413
|
+
sum_x2 = x_values.map { |x| x**2 }.sum
|
414
|
+
sum_y2 = y_values.map { |y| y**2 }.sum
|
415
|
+
|
416
|
+
numerator = n * sum_xy - sum_x * sum_y
|
417
|
+
denominator = Math.sqrt((n * sum_x2 - sum_x**2) * (n * sum_y2 - sum_y**2))
|
418
|
+
|
419
|
+
coefficient = denominator.zero? ? 0 : numerator / denominator
|
420
|
+
|
421
|
+
# Calculate p-value (simplified)
|
422
|
+
t = coefficient * Math.sqrt((n - 2) / (1 - coefficient**2))
|
423
|
+
p_value = 2 * (1 - calculate_t_distribution(t.abs, n - 2))
|
424
|
+
|
425
|
+
{coefficient: coefficient, significance: p_value}
|
426
|
+
end
|
427
|
+
|
428
|
+
def calculate_significance(value1, value2, sample_size)
|
429
|
+
# Simplified significance calculation
|
430
|
+
# In a real implementation, use a proper statistical library
|
431
|
+
diff = (value2 - value1).abs
|
432
|
+
variance = (diff * 0.5)**2 # Simplified variance estimate
|
433
|
+
|
434
|
+
# t-statistic for paired samples
|
435
|
+
t = diff / Math.sqrt(variance / sample_size)
|
436
|
+
|
437
|
+
# Approximate p-value
|
438
|
+
2 * (1 - calculate_t_distribution(t.abs, sample_size - 1))
|
439
|
+
end
|
440
|
+
|
441
|
+
def calculate_t_distribution(t, df)
|
442
|
+
# Very simplified t-distribution approximation
|
443
|
+
# In a real implementation, use a proper statistical library
|
444
|
+
if t > 10
|
445
|
+
1.0
|
446
|
+
else
|
447
|
+
0.5 + 0.5 * (1 - Math.exp(-0.09 * t * Math.sqrt(df)))
|
448
|
+
end
|
449
|
+
end
|
450
|
+
|
451
|
+
def extract_failure_pattern(failure)
|
452
|
+
# Extract a simplified pattern from failure context
|
453
|
+
# In a real implementation, use more sophisticated pattern recognition
|
454
|
+
|
455
|
+
if failure[:context] && failure[:context][:error_type]
|
456
|
+
"Error: #{failure[:context][:error_type]}"
|
457
|
+
elsif failure[:context] && failure[:context][:failure_reason]
|
458
|
+
"Reason: #{failure[:context][:failure_reason]}"
|
459
|
+
else
|
460
|
+
"Unknown failure"
|
461
|
+
end
|
462
|
+
end
|
463
|
+
|
464
|
+
def generate_success_rate_suggestions(performance)
|
465
|
+
suggestions = []
|
466
|
+
|
467
|
+
if performance[:failure_patterns].any?
|
468
|
+
top_failure = performance[:failure_patterns].first
|
469
|
+
suggestions << "Address most common failure pattern: #{top_failure[:pattern]}"
|
470
|
+
end
|
471
|
+
|
472
|
+
if performance[:success_rate][:trend] == :declining
|
473
|
+
suggestions << "Investigate recent changes that may have affected success rate"
|
474
|
+
end
|
475
|
+
|
476
|
+
suggestions << "Implement more robust error handling" if suggestions.empty?
|
477
|
+
suggestions
|
478
|
+
end
|
479
|
+
|
480
|
+
def generate_performance_suggestions(slow_metrics)
|
481
|
+
slow_metrics.map do |metric, data|
|
482
|
+
case metric
|
483
|
+
when :duration_ms, :duration
|
484
|
+
"Optimize execution time (#{(data[:avg_value] / 1000).round(2)}s avg)"
|
485
|
+
when :tokens_used
|
486
|
+
"Reduce token usage (#{data[:avg_value].round} avg)"
|
487
|
+
else
|
488
|
+
"Optimize #{metric} metric (#{data[:avg_value].round(2)} avg)"
|
489
|
+
end
|
490
|
+
end
|
491
|
+
end
|
492
|
+
|
493
|
+
def generate_failure_suggestions(failure_patterns)
|
494
|
+
failure_patterns.first(3).map do |pattern|
|
495
|
+
"Fix '#{pattern[:pattern]}' failure (occurs #{pattern[:count]} times)"
|
496
|
+
end
|
497
|
+
end
|
498
|
+
end
|
499
|
+
end
|
500
|
+
end
|