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,706 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Agentic
|
4
|
+
module Learning
|
5
|
+
# StrategyOptimizer improves execution strategies based on historical performance data.
|
6
|
+
# It uses insights from the PatternRecognizer to automatically generate optimized
|
7
|
+
# strategies for tasks, agents, and plans.
|
8
|
+
#
|
9
|
+
# @example Optimizing a prompt template
|
10
|
+
# history_store = Agentic::Learning::ExecutionHistoryStore.new
|
11
|
+
# recognizer = Agentic::Learning::PatternRecognizer.new(history_store: history_store)
|
12
|
+
# optimizer = Agentic::Learning::StrategyOptimizer.new(
|
13
|
+
# pattern_recognizer: recognizer,
|
14
|
+
# history_store: history_store
|
15
|
+
# )
|
16
|
+
#
|
17
|
+
# improved_prompt = optimizer.optimize_prompt_template(
|
18
|
+
# original_template: "Please research the following topic: {topic}",
|
19
|
+
# agent_type: "research_agent"
|
20
|
+
# )
|
21
|
+
#
|
22
|
+
class StrategyOptimizer
|
23
|
+
# Initialize a new StrategyOptimizer
|
24
|
+
#
|
25
|
+
# @param options [Hash] Configuration options
|
26
|
+
# @option options [Logger] :logger Custom logger (defaults to Agentic.logger)
|
27
|
+
# @option options [PatternRecognizer] :pattern_recognizer Pattern recognizer for insights
|
28
|
+
# @option options [ExecutionHistoryStore] :history_store History store for performance data
|
29
|
+
# @option options [LlmClient] :llm_client LLM client for generating optimizations (optional)
|
30
|
+
# @option options [Integer] :optimization_interval_hours Hours between optimization attempts (defaults to 24)
|
31
|
+
# @option options [Boolean] :auto_apply_optimizations Whether to automatically apply optimizations (defaults to false)
|
32
|
+
def initialize(options = {})
|
33
|
+
@logger = options[:logger] || Agentic.logger
|
34
|
+
@pattern_recognizer = options[:pattern_recognizer] || raise(ArgumentError, "pattern_recognizer is required")
|
35
|
+
@history_store = options[:history_store] || raise(ArgumentError, "history_store is required")
|
36
|
+
@llm_client = options[:llm_client]
|
37
|
+
@optimization_interval_hours = options[:optimization_interval_hours] || 24
|
38
|
+
@auto_apply_optimizations = options.fetch(:auto_apply_optimizations, false)
|
39
|
+
@optimization_cache = {}
|
40
|
+
@last_optimization = {}
|
41
|
+
end
|
42
|
+
|
43
|
+
# Optimize a prompt template based on historical performance
|
44
|
+
#
|
45
|
+
# @param original_template [String] The original prompt template
|
46
|
+
# @param agent_type [String] The agent type using this prompt
|
47
|
+
# @param options [Hash] Optimization options
|
48
|
+
# @option options [Boolean] :force Force optimization even if recently optimized
|
49
|
+
# @option options [Symbol] :optimization_strategy Strategy to use (:conservative, :balanced, :aggressive)
|
50
|
+
# @option options [Hash] :context Additional context for optimization
|
51
|
+
# @return [Hash] Optimization result with improved template and explanation
|
52
|
+
def optimize_prompt_template(original_template, agent_type, options = {})
|
53
|
+
cache_key = "prompt:#{agent_type}:#{Digest::MD5.hexdigest(original_template)}"
|
54
|
+
|
55
|
+
# Check cache and optimization interval
|
56
|
+
unless options[:force]
|
57
|
+
if @optimization_cache[cache_key] &&
|
58
|
+
@last_optimization[cache_key] &&
|
59
|
+
@last_optimization[cache_key] > Time.now - (@optimization_interval_hours * 3600)
|
60
|
+
return @optimization_cache[cache_key]
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
# Get performance data
|
65
|
+
performance = @pattern_recognizer.analyze_agent_performance(agent_type)
|
66
|
+
|
67
|
+
if performance[:insufficient_data]
|
68
|
+
@logger.info("Insufficient data to optimize prompt for #{agent_type}")
|
69
|
+
return {
|
70
|
+
optimized: false,
|
71
|
+
reason: "Insufficient performance data",
|
72
|
+
original_template: original_template,
|
73
|
+
improved_template: original_template
|
74
|
+
}
|
75
|
+
end
|
76
|
+
|
77
|
+
# Generate optimization
|
78
|
+
optimization = if @llm_client
|
79
|
+
generate_optimized_prompt_with_llm(original_template, agent_type, performance, options)
|
80
|
+
else
|
81
|
+
generate_optimized_prompt_heuristic(original_template, agent_type, performance, options)
|
82
|
+
end
|
83
|
+
|
84
|
+
# Cache result
|
85
|
+
@optimization_cache[cache_key] = optimization
|
86
|
+
@last_optimization[cache_key] = Time.now
|
87
|
+
|
88
|
+
optimization
|
89
|
+
end
|
90
|
+
|
91
|
+
# Optimize LLM parameters based on historical performance
|
92
|
+
#
|
93
|
+
# @param original_params [Hash] The original LLM parameters
|
94
|
+
# @param agent_type [String] The agent type using these parameters
|
95
|
+
# @param options [Hash] Optimization options
|
96
|
+
# @option options [Boolean] :force Force optimization even if recently optimized
|
97
|
+
# @option options [Symbol] :optimization_strategy Strategy to use (:conservative, :balanced, :aggressive)
|
98
|
+
# @return [Hash] Optimization result with improved parameters and explanation
|
99
|
+
def optimize_llm_parameters(original_params, agent_type, options = {})
|
100
|
+
cache_key = "params:#{agent_type}:#{Digest::MD5.hexdigest(original_params.to_s)}"
|
101
|
+
|
102
|
+
# Check cache and optimization interval
|
103
|
+
unless options[:force]
|
104
|
+
if @optimization_cache[cache_key] &&
|
105
|
+
@last_optimization[cache_key] &&
|
106
|
+
@last_optimization[cache_key] > Time.now - (@optimization_interval_hours * 3600)
|
107
|
+
return @optimization_cache[cache_key]
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
# Get performance data
|
112
|
+
performance = @pattern_recognizer.analyze_agent_performance(agent_type)
|
113
|
+
|
114
|
+
if performance[:insufficient_data]
|
115
|
+
@logger.info("Insufficient data to optimize LLM parameters for #{agent_type}")
|
116
|
+
return {
|
117
|
+
optimized: false,
|
118
|
+
reason: "Insufficient performance data",
|
119
|
+
original_params: original_params,
|
120
|
+
improved_params: original_params.dup
|
121
|
+
}
|
122
|
+
end
|
123
|
+
|
124
|
+
# Generate optimization
|
125
|
+
optimization = generate_optimized_parameters(original_params, agent_type, performance, options)
|
126
|
+
|
127
|
+
# Cache result
|
128
|
+
@optimization_cache[cache_key] = optimization
|
129
|
+
@last_optimization[cache_key] = Time.now
|
130
|
+
|
131
|
+
optimization
|
132
|
+
end
|
133
|
+
|
134
|
+
# Optimize task sequence based on historical performance
|
135
|
+
#
|
136
|
+
# @param original_sequence [Array<Hash>] Original task sequence
|
137
|
+
# @param plan_type [String] The type of plan
|
138
|
+
# @param options [Hash] Optimization options
|
139
|
+
# @option options [Boolean] :force Force optimization even if recently optimized
|
140
|
+
# @return [Hash] Optimization result with improved sequence and explanation
|
141
|
+
def optimize_task_sequence(original_sequence, plan_type, options = {})
|
142
|
+
cache_key = "sequence:#{plan_type}:#{Digest::MD5.hexdigest(original_sequence.to_s)}"
|
143
|
+
|
144
|
+
# Check cache and optimization interval
|
145
|
+
unless options[:force]
|
146
|
+
if @optimization_cache[cache_key] &&
|
147
|
+
@last_optimization[cache_key] &&
|
148
|
+
@last_optimization[cache_key] > Time.now - (@optimization_interval_hours * 3600)
|
149
|
+
return @optimization_cache[cache_key]
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
# Get historical plan executions
|
154
|
+
end_time = Time.now
|
155
|
+
start_time = end_time - (30 * 24 * 60 * 60) # 30 days
|
156
|
+
|
157
|
+
plan_history = @history_store.get_history(
|
158
|
+
plan_id: plan_type,
|
159
|
+
start_time: start_time,
|
160
|
+
end_time: end_time
|
161
|
+
)
|
162
|
+
|
163
|
+
if plan_history.size < 5
|
164
|
+
@logger.info("Insufficient data to optimize task sequence for #{plan_type}")
|
165
|
+
return {
|
166
|
+
optimized: false,
|
167
|
+
reason: "Insufficient plan execution data",
|
168
|
+
original_sequence: original_sequence,
|
169
|
+
improved_sequence: original_sequence.dup
|
170
|
+
}
|
171
|
+
end
|
172
|
+
|
173
|
+
# Generate optimization
|
174
|
+
optimization = generate_optimized_sequence(original_sequence, plan_history, options)
|
175
|
+
|
176
|
+
# Cache result
|
177
|
+
@optimization_cache[cache_key] = optimization
|
178
|
+
@last_optimization[cache_key] = Time.now
|
179
|
+
|
180
|
+
optimization
|
181
|
+
end
|
182
|
+
|
183
|
+
# Apply learned optimizations to existing configurations
|
184
|
+
#
|
185
|
+
# @param target [Symbol] Type of target to optimize (:prompts, :parameters, :sequences)
|
186
|
+
# @param registry [Hash] Registry of current configurations
|
187
|
+
# @return [Hash] Results of optimization applications
|
188
|
+
def apply_optimizations(target, registry)
|
189
|
+
results = {}
|
190
|
+
|
191
|
+
case target
|
192
|
+
when :prompts
|
193
|
+
registry.each do |key, template|
|
194
|
+
agent_type = extract_agent_type_from_key(key)
|
195
|
+
next unless agent_type
|
196
|
+
|
197
|
+
result = optimize_prompt_template(template, agent_type)
|
198
|
+
results[key] = result
|
199
|
+
|
200
|
+
if result[:optimized] && @auto_apply_optimizations
|
201
|
+
# Logic to apply optimization to registry would go here
|
202
|
+
@logger.info("Auto-applied optimized prompt for #{key}")
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
when :parameters
|
207
|
+
registry.each do |key, params|
|
208
|
+
agent_type = extract_agent_type_from_key(key)
|
209
|
+
next unless agent_type
|
210
|
+
|
211
|
+
result = optimize_llm_parameters(params, agent_type)
|
212
|
+
results[key] = result
|
213
|
+
|
214
|
+
if result[:optimized] && @auto_apply_optimizations
|
215
|
+
# Logic to apply optimization to registry would go here
|
216
|
+
@logger.info("Auto-applied optimized parameters for #{key}")
|
217
|
+
end
|
218
|
+
end
|
219
|
+
|
220
|
+
when :sequences
|
221
|
+
registry.each do |key, sequence|
|
222
|
+
plan_type = key.to_s
|
223
|
+
|
224
|
+
result = optimize_task_sequence(sequence, plan_type)
|
225
|
+
results[key] = result
|
226
|
+
|
227
|
+
if result[:optimized] && @auto_apply_optimizations
|
228
|
+
# Logic to apply optimization to registry would go here
|
229
|
+
@logger.info("Auto-applied optimized sequence for #{key}")
|
230
|
+
end
|
231
|
+
end
|
232
|
+
end
|
233
|
+
|
234
|
+
results
|
235
|
+
end
|
236
|
+
|
237
|
+
# Generate a performance report for a specific agent type
|
238
|
+
#
|
239
|
+
# @param agent_type [String] The agent type to report on
|
240
|
+
# @return [Hash] Performance report with metrics and optimization suggestions
|
241
|
+
def generate_performance_report(agent_type)
|
242
|
+
performance = @pattern_recognizer.analyze_agent_performance(agent_type)
|
243
|
+
|
244
|
+
if performance[:insufficient_data]
|
245
|
+
return {
|
246
|
+
agent_type: agent_type,
|
247
|
+
status: :insufficient_data,
|
248
|
+
message: "Not enough execution data to generate a meaningful report"
|
249
|
+
}
|
250
|
+
end
|
251
|
+
|
252
|
+
# Get recommendations
|
253
|
+
recommendations = @pattern_recognizer.recommend_optimizations(agent_type)
|
254
|
+
|
255
|
+
{
|
256
|
+
agent_type: agent_type,
|
257
|
+
status: :complete,
|
258
|
+
timestamp: Time.now.iso8601,
|
259
|
+
metrics: {
|
260
|
+
success_rate: performance[:success_rate][:overall],
|
261
|
+
trend: performance[:success_rate][:trend],
|
262
|
+
sample_size: performance[:success_rate][:sample_size]
|
263
|
+
},
|
264
|
+
performance_trends: performance[:performance_trends],
|
265
|
+
failure_patterns: performance[:failure_patterns],
|
266
|
+
recommendations: recommendations
|
267
|
+
}
|
268
|
+
end
|
269
|
+
|
270
|
+
private
|
271
|
+
|
272
|
+
def generate_optimized_prompt_with_llm(original_template, agent_type, performance, options)
|
273
|
+
return {optimized: false, reason: "LLM client not available"} unless @llm_client
|
274
|
+
|
275
|
+
# Get failure patterns and recommendations
|
276
|
+
recommendations = @pattern_recognizer.recommend_optimizations(agent_type)
|
277
|
+
|
278
|
+
# Prepare context for the LLM
|
279
|
+
context = {
|
280
|
+
original_template: original_template,
|
281
|
+
agent_type: agent_type,
|
282
|
+
success_rate: performance[:success_rate][:overall],
|
283
|
+
trend: performance[:success_rate][:trend],
|
284
|
+
failure_patterns: performance[:failure_patterns].first(3),
|
285
|
+
recommendations: recommendations.first(3)
|
286
|
+
}
|
287
|
+
|
288
|
+
# Generate optimization prompt
|
289
|
+
optimization_prompt = <<~PROMPT
|
290
|
+
I need to optimize this prompt template for a #{agent_type}:
|
291
|
+
|
292
|
+
"""
|
293
|
+
#{original_template}
|
294
|
+
"""
|
295
|
+
|
296
|
+
Performance data:
|
297
|
+
- Success rate: #{(context[:success_rate] * 100).round(1)}%
|
298
|
+
- Trend: #{context[:trend]}
|
299
|
+
|
300
|
+
Common failure patterns:
|
301
|
+
#{context[:failure_patterns].map { |p| "- #{p[:pattern]} (#{p[:count]} occurrences)" }.join("\n")}
|
302
|
+
|
303
|
+
Recommendations:
|
304
|
+
#{recommendations.map { |r| "- #{r[:message]}" }.join("\n")}
|
305
|
+
|
306
|
+
Please provide an improved version of this prompt template that addresses these issues.
|
307
|
+
Format your response as:
|
308
|
+
|
309
|
+
IMPROVED_TEMPLATE:
|
310
|
+
[Your improved template here]
|
311
|
+
|
312
|
+
EXPLANATION:
|
313
|
+
[Explanation of changes and how they address the issues]
|
314
|
+
PROMPT
|
315
|
+
|
316
|
+
# Generate optimization with LLM
|
317
|
+
response = @llm_client.complete(optimization_prompt)
|
318
|
+
|
319
|
+
# Parse response
|
320
|
+
improved_template = extract_improved_template(response)
|
321
|
+
explanation = extract_explanation(response)
|
322
|
+
|
323
|
+
if improved_template && improved_template != original_template
|
324
|
+
{
|
325
|
+
optimized: true,
|
326
|
+
original_template: original_template,
|
327
|
+
improved_template: improved_template,
|
328
|
+
explanation: explanation,
|
329
|
+
confidence: calculate_confidence(performance, improved_template, original_template)
|
330
|
+
}
|
331
|
+
else
|
332
|
+
{
|
333
|
+
optimized: false,
|
334
|
+
reason: "LLM optimization did not produce a different template",
|
335
|
+
original_template: original_template,
|
336
|
+
improved_template: original_template
|
337
|
+
}
|
338
|
+
end
|
339
|
+
end
|
340
|
+
|
341
|
+
def generate_optimized_prompt_heuristic(original_template, agent_type, performance, options)
|
342
|
+
# Simple heuristic-based optimization without LLM
|
343
|
+
improved_template = original_template.dup
|
344
|
+
changes = []
|
345
|
+
|
346
|
+
# Check for common failure patterns and apply heuristic improvements
|
347
|
+
Array(performance[:failure_patterns]).each do |pattern|
|
348
|
+
case pattern[:pattern]
|
349
|
+
when /timeout/i, /too slow/i
|
350
|
+
if !improved_template.include?("time limit") && !improved_template.include?("time constraint")
|
351
|
+
improved_template = improved_template.sub(/\A/, "You must complete this task efficiently within the time limit. ")
|
352
|
+
changes << "Added time constraint reminder"
|
353
|
+
end
|
354
|
+
|
355
|
+
when /unclear instructions/i, /ambiguous/i
|
356
|
+
if !improved_template.include?("clear and specific")
|
357
|
+
improved_template = improved_template.sub(/\A/, "Be clear and specific in your response. ")
|
358
|
+
changes << "Added clarity instruction"
|
359
|
+
end
|
360
|
+
|
361
|
+
when /missing information/i, /incomplete/i
|
362
|
+
if !improved_template.include?("comprehensive") && !improved_template.include?("complete")
|
363
|
+
improved_template = improved_template.sub(/\A/, "Provide a comprehensive and complete response. ")
|
364
|
+
changes << "Added comprehensiveness instruction"
|
365
|
+
end
|
366
|
+
end
|
367
|
+
end
|
368
|
+
|
369
|
+
# Check success rate trend
|
370
|
+
if performance[:success_rate][:trend] == :declining
|
371
|
+
if !improved_template.include?("step by step")
|
372
|
+
improved_template = improved_template.sub(/\A/, "Follow these instructions step by step. ")
|
373
|
+
changes << "Added step-by-step instruction due to declining success rate"
|
374
|
+
end
|
375
|
+
end
|
376
|
+
|
377
|
+
if changes.empty?
|
378
|
+
{
|
379
|
+
optimized: false,
|
380
|
+
reason: "No heuristic improvements identified",
|
381
|
+
original_template: original_template,
|
382
|
+
improved_template: original_template
|
383
|
+
}
|
384
|
+
else
|
385
|
+
{
|
386
|
+
optimized: true,
|
387
|
+
original_template: original_template,
|
388
|
+
improved_template: improved_template,
|
389
|
+
explanation: "Applied heuristic improvements: #{changes.join(", ")}",
|
390
|
+
confidence: 0.7 # Heuristic confidence is fixed
|
391
|
+
}
|
392
|
+
end
|
393
|
+
end
|
394
|
+
|
395
|
+
def generate_optimized_parameters(original_params, agent_type, performance, options)
|
396
|
+
# Copy original parameters
|
397
|
+
improved_params = original_params.dup
|
398
|
+
changes = []
|
399
|
+
strategy = options[:optimization_strategy] || :balanced
|
400
|
+
|
401
|
+
# Adjust parameters based on performance data
|
402
|
+
if performance[:performance_trends]
|
403
|
+
# Check for token usage trends
|
404
|
+
if performance[:performance_trends][:tokens_used] &&
|
405
|
+
performance[:performance_trends][:tokens_used][:trend] == :increasing &&
|
406
|
+
performance[:performance_trends][:tokens_used][:significant]
|
407
|
+
|
408
|
+
# Reduce max tokens if usage is increasing
|
409
|
+
if improved_params[:max_tokens] && improved_params[:max_tokens] > 100
|
410
|
+
case strategy
|
411
|
+
when :conservative
|
412
|
+
new_max = improved_params[:max_tokens] * 0.95 # Reduce by 5%
|
413
|
+
when :balanced
|
414
|
+
new_max = improved_params[:max_tokens] * 0.9 # Reduce by 10%
|
415
|
+
when :aggressive
|
416
|
+
new_max = improved_params[:max_tokens] * 0.8 # Reduce by 20%
|
417
|
+
end
|
418
|
+
|
419
|
+
improved_params[:max_tokens] = new_max.to_i
|
420
|
+
changes << "Reduced max_tokens from #{original_params[:max_tokens]} to #{improved_params[:max_tokens]}"
|
421
|
+
end
|
422
|
+
end
|
423
|
+
end
|
424
|
+
|
425
|
+
# Check success rate and adjust temperature
|
426
|
+
if performance[:success_rate][:overall] < 0.8 && improved_params[:temperature]
|
427
|
+
# Lower temperature for more predictable results if success rate is low
|
428
|
+
case strategy
|
429
|
+
when :conservative
|
430
|
+
if improved_params[:temperature] > 0.1
|
431
|
+
improved_params[:temperature] = [improved_params[:temperature] - 0.1, 0.1].max
|
432
|
+
changes << "Reduced temperature from #{original_params[:temperature]} to #{improved_params[:temperature]}"
|
433
|
+
end
|
434
|
+
when :balanced
|
435
|
+
if improved_params[:temperature] > 0.1
|
436
|
+
improved_params[:temperature] = [improved_params[:temperature] - 0.2, 0.1].max
|
437
|
+
changes << "Reduced temperature from #{original_params[:temperature]} to #{improved_params[:temperature]}"
|
438
|
+
end
|
439
|
+
when :aggressive
|
440
|
+
if improved_params[:temperature] > 0.05
|
441
|
+
improved_params[:temperature] = [improved_params[:temperature] - 0.3, 0.05].max
|
442
|
+
changes << "Reduced temperature from #{original_params[:temperature]} to #{improved_params[:temperature]}"
|
443
|
+
end
|
444
|
+
end
|
445
|
+
elsif performance[:success_rate][:overall] > 0.95 &&
|
446
|
+
performance[:success_rate][:trend] == :stable &&
|
447
|
+
improved_params[:temperature] &&
|
448
|
+
improved_params[:temperature] < 0.9
|
449
|
+
# Increase temperature slightly for more creative results if success rate is high and stable
|
450
|
+
case strategy
|
451
|
+
when :conservative
|
452
|
+
# No change for conservative strategy with high success
|
453
|
+
when :balanced
|
454
|
+
improved_params[:temperature] = [improved_params[:temperature] + 0.1, 0.9].min
|
455
|
+
changes << "Increased temperature from #{original_params[:temperature]} to #{improved_params[:temperature]}"
|
456
|
+
when :aggressive
|
457
|
+
improved_params[:temperature] = [improved_params[:temperature] + 0.2, 0.9].min
|
458
|
+
changes << "Increased temperature from #{original_params[:temperature]} to #{improved_params[:temperature]}"
|
459
|
+
end
|
460
|
+
end
|
461
|
+
|
462
|
+
if changes.empty?
|
463
|
+
{
|
464
|
+
optimized: false,
|
465
|
+
reason: "No parameter improvements identified",
|
466
|
+
original_params: original_params,
|
467
|
+
improved_params: original_params.dup
|
468
|
+
}
|
469
|
+
else
|
470
|
+
{
|
471
|
+
optimized: true,
|
472
|
+
original_params: original_params,
|
473
|
+
improved_params: improved_params,
|
474
|
+
explanation: "Applied parameter optimizations: #{changes.join(", ")}",
|
475
|
+
confidence: calculate_parameter_confidence(performance, original_params, improved_params)
|
476
|
+
}
|
477
|
+
end
|
478
|
+
end
|
479
|
+
|
480
|
+
def generate_optimized_sequence(original_sequence, plan_history, options)
|
481
|
+
# Copy original sequence
|
482
|
+
improved_sequence = original_sequence.map(&:dup)
|
483
|
+
changes = []
|
484
|
+
|
485
|
+
# Find slow tasks and potential parallelization opportunities
|
486
|
+
task_durations = {}
|
487
|
+
dependencies = {}
|
488
|
+
|
489
|
+
# Extract task durations and dependencies from history
|
490
|
+
plan_history.each do |execution|
|
491
|
+
next unless execution[:context] && execution[:context][:task_durations]
|
492
|
+
|
493
|
+
execution[:context][:task_durations].each do |task_id, duration|
|
494
|
+
task_durations[task_id] ||= []
|
495
|
+
task_durations[task_id] << duration
|
496
|
+
end
|
497
|
+
|
498
|
+
Array(execution[:context][:task_dependencies]).each do |task_id, deps|
|
499
|
+
dependencies[task_id] = deps
|
500
|
+
end
|
501
|
+
end
|
502
|
+
|
503
|
+
# Calculate average durations
|
504
|
+
avg_durations = {}
|
505
|
+
task_durations.each do |task_id, durations|
|
506
|
+
avg_durations[task_id] = durations.sum / durations.size.to_f
|
507
|
+
end
|
508
|
+
|
509
|
+
# Find slow tasks
|
510
|
+
slow_tasks = avg_durations.sort_by { |_, duration| -duration }.first(3).map(&:first)
|
511
|
+
|
512
|
+
# Look for parallelization opportunities
|
513
|
+
if dependencies.any?
|
514
|
+
# Identify tasks that have no dependencies between them
|
515
|
+
independent_tasks = []
|
516
|
+
|
517
|
+
original_sequence.each_with_index do |task, i|
|
518
|
+
next_task = original_sequence[i + 1]
|
519
|
+
next unless next_task
|
520
|
+
|
521
|
+
task_id = task[:id]
|
522
|
+
next_id = next_task[:id]
|
523
|
+
|
524
|
+
# Check if these tasks are independent
|
525
|
+
if (!dependencies[task_id] || !dependencies[task_id].include?(next_id)) &&
|
526
|
+
(!dependencies[next_id] || !dependencies[next_id].include?(task_id))
|
527
|
+
|
528
|
+
independent_tasks << [task_id, next_id]
|
529
|
+
end
|
530
|
+
end
|
531
|
+
|
532
|
+
# Apply parallelization
|
533
|
+
if independent_tasks.any?
|
534
|
+
# This is a simplified implementation - real parallelization would require
|
535
|
+
# more sophisticated task scheduling logic
|
536
|
+
pair = independent_tasks.first
|
537
|
+
|
538
|
+
# Mark tasks for parallel execution
|
539
|
+
improved_sequence.each do |task|
|
540
|
+
if pair.include?(task[:id])
|
541
|
+
task[:parallel] = true
|
542
|
+
task[:parallel_group] = "group_#{pair.join("_")}"
|
543
|
+
end
|
544
|
+
end
|
545
|
+
|
546
|
+
changes << "Marked tasks #{pair.join(" and ")} for parallel execution"
|
547
|
+
end
|
548
|
+
end
|
549
|
+
|
550
|
+
# Optimize slow tasks
|
551
|
+
slow_tasks.each do |task_id|
|
552
|
+
task_idx = improved_sequence.find_index { |t| t[:id] == task_id }
|
553
|
+
next unless task_idx
|
554
|
+
|
555
|
+
task = improved_sequence[task_idx]
|
556
|
+
|
557
|
+
# Add optimization hint
|
558
|
+
task[:optimization_hint] = "This task is slower than average (#{(avg_durations[task_id] / 1000).round(2)}s)"
|
559
|
+
|
560
|
+
# Suggest potential optimizations
|
561
|
+
if avg_durations[task_id] > 10000 # Very slow task (>10s)
|
562
|
+
task[:suggested_optimization] = "Consider breaking down into smaller sub-tasks"
|
563
|
+
changes << "Added breakdown suggestion for slow task #{task_id}"
|
564
|
+
elsif task[:description] && task[:description].length > 200
|
565
|
+
task[:suggested_optimization] = "Consider simplifying task description"
|
566
|
+
changes << "Added simplification suggestion for task #{task_id}"
|
567
|
+
else
|
568
|
+
task[:suggested_optimization] = "Review for optimization opportunities"
|
569
|
+
changes << "Added general optimization suggestion for task #{task_id}"
|
570
|
+
end
|
571
|
+
end
|
572
|
+
|
573
|
+
if changes.empty?
|
574
|
+
{
|
575
|
+
optimized: false,
|
576
|
+
reason: "No sequence improvements identified",
|
577
|
+
original_sequence: original_sequence,
|
578
|
+
improved_sequence: original_sequence.map(&:dup)
|
579
|
+
}
|
580
|
+
else
|
581
|
+
{
|
582
|
+
optimized: true,
|
583
|
+
original_sequence: original_sequence,
|
584
|
+
improved_sequence: improved_sequence,
|
585
|
+
explanation: "Applied sequence optimizations: #{changes.join(", ")}",
|
586
|
+
confidence: 0.8
|
587
|
+
}
|
588
|
+
end
|
589
|
+
end
|
590
|
+
|
591
|
+
def extract_improved_template(response)
|
592
|
+
# Extract the improved template from LLM response
|
593
|
+
match = response.match(/IMPROVED_TEMPLATE:\s*(.+?)(?:EXPLANATION:|$)/mi)
|
594
|
+
match ? match[1].strip : nil
|
595
|
+
end
|
596
|
+
|
597
|
+
def extract_explanation(response)
|
598
|
+
# Extract the explanation from LLM response
|
599
|
+
match = response.match(/EXPLANATION:\s*(.+?)$/mi)
|
600
|
+
match ? match[1].strip : "No explanation provided"
|
601
|
+
end
|
602
|
+
|
603
|
+
def calculate_confidence(performance, improved_template, original_template)
|
604
|
+
# Calculate confidence based on performance data and degree of change
|
605
|
+
|
606
|
+
# Base confidence on performance data quality
|
607
|
+
base_confidence = if performance[:success_rate][:sample_size] > 100
|
608
|
+
0.9
|
609
|
+
elsif performance[:success_rate][:sample_size] > 50
|
610
|
+
0.8
|
611
|
+
elsif performance[:success_rate][:sample_size] > 20
|
612
|
+
0.7
|
613
|
+
else
|
614
|
+
0.6
|
615
|
+
end
|
616
|
+
|
617
|
+
# Adjust based on degree of change
|
618
|
+
change_ratio = calculate_change_ratio(improved_template, original_template)
|
619
|
+
|
620
|
+
if change_ratio > 0.5
|
621
|
+
# Large changes reduce confidence
|
622
|
+
base_confidence *= 0.8
|
623
|
+
elsif change_ratio < 0.1
|
624
|
+
# Very small changes increase confidence
|
625
|
+
base_confidence *= 1.1
|
626
|
+
end
|
627
|
+
|
628
|
+
# Cap at 0.95
|
629
|
+
[base_confidence, 0.95].min
|
630
|
+
end
|
631
|
+
|
632
|
+
def calculate_change_ratio(improved, original)
|
633
|
+
return 0.0 if improved == original
|
634
|
+
|
635
|
+
# Calculate Levenshtein distance as a ratio of the original length
|
636
|
+
distance = levenshtein_distance(improved, original)
|
637
|
+
distance.to_f / original.length
|
638
|
+
end
|
639
|
+
|
640
|
+
def levenshtein_distance(str1, str2)
|
641
|
+
# Simple Levenshtein distance implementation
|
642
|
+
m = str1.length
|
643
|
+
n = str2.length
|
644
|
+
|
645
|
+
# Initialize the matrix
|
646
|
+
matrix = Array.new(m + 1) { Array.new(n + 1, 0) }
|
647
|
+
|
648
|
+
# Fill the first row and column
|
649
|
+
(0..m).each { |i| matrix[i][0] = i }
|
650
|
+
(0..n).each { |j| matrix[0][j] = j }
|
651
|
+
|
652
|
+
# Fill the rest of the matrix
|
653
|
+
(1..m).each do |i|
|
654
|
+
(1..n).each do |j|
|
655
|
+
cost = (str1[i - 1] == str2[j - 1]) ? 0 : 1
|
656
|
+
matrix[i][j] = [
|
657
|
+
matrix[i - 1][j] + 1,
|
658
|
+
matrix[i][j - 1] + 1,
|
659
|
+
matrix[i - 1][j - 1] + cost
|
660
|
+
].min
|
661
|
+
end
|
662
|
+
end
|
663
|
+
|
664
|
+
matrix[m][n]
|
665
|
+
end
|
666
|
+
|
667
|
+
def calculate_parameter_confidence(performance, original_params, improved_params)
|
668
|
+
# Calculate confidence for parameter optimizations
|
669
|
+
|
670
|
+
# Base confidence on performance data quality
|
671
|
+
base_confidence = if performance[:success_rate][:sample_size] > 100
|
672
|
+
0.85
|
673
|
+
elsif performance[:success_rate][:sample_size] > 50
|
674
|
+
0.75
|
675
|
+
elsif performance[:success_rate][:sample_size] > 20
|
676
|
+
0.65
|
677
|
+
else
|
678
|
+
0.5
|
679
|
+
end
|
680
|
+
|
681
|
+
# Count changed parameters
|
682
|
+
changes = 0
|
683
|
+
original_params.each do |key, value|
|
684
|
+
changes += 1 if improved_params[key] && improved_params[key] != value
|
685
|
+
end
|
686
|
+
|
687
|
+
# Adjust confidence based on number of changes
|
688
|
+
if changes > 2
|
689
|
+
# More changes reduce confidence
|
690
|
+
base_confidence *= 0.9
|
691
|
+
elsif changes == 1
|
692
|
+
# Single change increases confidence
|
693
|
+
base_confidence *= 1.05
|
694
|
+
end
|
695
|
+
|
696
|
+
# Cap at 0.9
|
697
|
+
[base_confidence, 0.9].min
|
698
|
+
end
|
699
|
+
|
700
|
+
def extract_agent_type_from_key(key)
|
701
|
+
# Extract agent type from registry key
|
702
|
+
key.to_s.split(".").first
|
703
|
+
end
|
704
|
+
end
|
705
|
+
end
|
706
|
+
end
|