language-operator 0.1.61 → 0.1.62
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/.claude/commands/persona.md +9 -0
- data/.claude/commands/task.md +46 -1
- data/.rubocop.yml +13 -0
- data/.rubocop_custom/use_ux_helper.rb +44 -0
- data/CHANGELOG.md +8 -0
- data/Gemfile.lock +12 -1
- data/Makefile +26 -7
- data/Makefile.common +50 -0
- data/bin/aictl +8 -1
- data/components/agent/Gemfile +1 -1
- data/components/agent/bin/langop-agent +7 -0
- data/docs/README.md +58 -0
- data/docs/{dsl/best-practices.md → best-practices.md} +4 -4
- data/docs/cli-reference.md +274 -0
- data/docs/{dsl/constraints.md → constraints.md} +5 -5
- data/docs/how-agents-work.md +156 -0
- data/docs/installation.md +218 -0
- data/docs/quickstart.md +299 -0
- data/docs/understanding-generated-code.md +265 -0
- data/docs/using-tools.md +457 -0
- data/docs/webhooks.md +509 -0
- data/examples/ux_helpers_demo.rb +296 -0
- data/lib/language_operator/agent/base.rb +11 -1
- data/lib/language_operator/agent/executor.rb +23 -6
- data/lib/language_operator/agent/safety/safe_executor.rb +41 -39
- data/lib/language_operator/agent/task_executor.rb +346 -63
- data/lib/language_operator/agent/web_server.rb +110 -14
- data/lib/language_operator/agent/webhook_authenticator.rb +39 -5
- data/lib/language_operator/agent.rb +88 -2
- data/lib/language_operator/cli/base_command.rb +17 -11
- data/lib/language_operator/cli/command_loader.rb +72 -0
- data/lib/language_operator/cli/commands/agent/base.rb +837 -0
- data/lib/language_operator/cli/commands/agent/code_operations.rb +102 -0
- data/lib/language_operator/cli/commands/agent/helpers/cluster_llm_client.rb +116 -0
- data/lib/language_operator/cli/commands/agent/helpers/code_parser.rb +115 -0
- data/lib/language_operator/cli/commands/agent/helpers/synthesis_watcher.rb +96 -0
- data/lib/language_operator/cli/commands/agent/learning.rb +289 -0
- data/lib/language_operator/cli/commands/agent/lifecycle.rb +102 -0
- data/lib/language_operator/cli/commands/agent/logs.rb +125 -0
- data/lib/language_operator/cli/commands/agent/workspace.rb +327 -0
- data/lib/language_operator/cli/commands/cluster.rb +129 -84
- data/lib/language_operator/cli/commands/install.rb +1 -1
- data/lib/language_operator/cli/commands/model/base.rb +215 -0
- data/lib/language_operator/cli/commands/model/test.rb +165 -0
- data/lib/language_operator/cli/commands/persona.rb +16 -34
- data/lib/language_operator/cli/commands/quickstart.rb +3 -2
- data/lib/language_operator/cli/commands/status.rb +40 -67
- data/lib/language_operator/cli/commands/system/base.rb +44 -0
- data/lib/language_operator/cli/commands/system/exec.rb +147 -0
- data/lib/language_operator/cli/commands/system/helpers/llm_synthesis.rb +183 -0
- data/lib/language_operator/cli/commands/system/helpers/pod_manager.rb +212 -0
- data/lib/language_operator/cli/commands/system/helpers/template_loader.rb +57 -0
- data/lib/language_operator/cli/commands/system/helpers/template_validator.rb +174 -0
- data/lib/language_operator/cli/commands/system/schema.rb +92 -0
- data/lib/language_operator/cli/commands/system/synthesis_template.rb +151 -0
- data/lib/language_operator/cli/commands/system/synthesize.rb +224 -0
- data/lib/language_operator/cli/commands/system/validate_template.rb +130 -0
- data/lib/language_operator/cli/commands/tool/base.rb +271 -0
- data/lib/language_operator/cli/commands/tool/install.rb +255 -0
- data/lib/language_operator/cli/commands/tool/search.rb +69 -0
- data/lib/language_operator/cli/commands/tool/test.rb +115 -0
- data/lib/language_operator/cli/commands/use.rb +29 -6
- data/lib/language_operator/cli/errors/handler.rb +20 -17
- data/lib/language_operator/cli/errors/suggestions.rb +3 -5
- data/lib/language_operator/cli/errors/thor_errors.rb +55 -0
- data/lib/language_operator/cli/formatters/code_formatter.rb +4 -11
- data/lib/language_operator/cli/formatters/log_formatter.rb +8 -15
- data/lib/language_operator/cli/formatters/progress_formatter.rb +6 -8
- data/lib/language_operator/cli/formatters/status_formatter.rb +26 -7
- data/lib/language_operator/cli/formatters/table_formatter.rb +47 -36
- data/lib/language_operator/cli/formatters/value_formatter.rb +75 -0
- data/lib/language_operator/cli/helpers/cluster_context.rb +5 -3
- data/lib/language_operator/cli/helpers/kubeconfig_validator.rb +2 -1
- data/lib/language_operator/cli/helpers/label_utils.rb +97 -0
- data/lib/language_operator/{ux/concerns/provider_helpers.rb → cli/helpers/provider_helper.rb} +10 -29
- data/lib/language_operator/cli/helpers/schedule_builder.rb +21 -1
- data/lib/language_operator/cli/helpers/user_prompts.rb +19 -11
- data/lib/language_operator/cli/helpers/ux_helper.rb +538 -0
- data/lib/language_operator/{ux/concerns/input_validation.rb → cli/helpers/validation_helper.rb} +13 -66
- data/lib/language_operator/cli/main.rb +50 -40
- data/lib/language_operator/cli/templates/tools/generic.yaml +3 -0
- data/lib/language_operator/cli/wizards/agent_wizard.rb +12 -20
- data/lib/language_operator/cli/wizards/model_wizard.rb +271 -0
- data/lib/language_operator/cli/wizards/quickstart_wizard.rb +8 -34
- data/lib/language_operator/client/base.rb +28 -0
- data/lib/language_operator/client/config.rb +4 -1
- data/lib/language_operator/client/mcp_connector.rb +1 -1
- data/lib/language_operator/config/cluster_config.rb +3 -2
- data/lib/language_operator/config.rb +38 -11
- data/lib/language_operator/constants/kubernetes_labels.rb +80 -0
- data/lib/language_operator/constants.rb +13 -0
- data/lib/language_operator/dsl/http.rb +127 -10
- data/lib/language_operator/dsl.rb +153 -6
- data/lib/language_operator/errors.rb +50 -0
- data/lib/language_operator/kubernetes/client.rb +11 -6
- data/lib/language_operator/kubernetes/resource_builder.rb +58 -84
- data/lib/language_operator/templates/schema/agent_dsl_openapi.yaml +1 -1
- data/lib/language_operator/templates/schema/agent_dsl_schema.json +1 -1
- data/lib/language_operator/type_coercion.rb +118 -34
- data/lib/language_operator/utils/secure_path.rb +74 -0
- data/lib/language_operator/utils.rb +7 -0
- data/lib/language_operator/validators.rb +54 -2
- data/lib/language_operator/version.rb +1 -1
- data/synth/001/Makefile +10 -2
- data/synth/001/agent.rb +16 -15
- data/synth/001/output.log +27 -10
- data/synth/002/Makefile +10 -2
- data/synth/003/Makefile +1 -1
- data/synth/003/README.md +205 -133
- data/synth/003/agent.optimized.rb +66 -0
- data/synth/003/agent.synthesized.rb +41 -0
- metadata +111 -35
- data/docs/dsl/agent-reference.md +0 -604
- data/docs/dsl/mcp-integration.md +0 -1177
- data/docs/dsl/webhooks.md +0 -932
- data/docs/dsl/workflows.md +0 -744
- data/lib/language_operator/cli/commands/agent.rb +0 -1712
- data/lib/language_operator/cli/commands/model.rb +0 -366
- data/lib/language_operator/cli/commands/system.rb +0 -1259
- data/lib/language_operator/cli/commands/tool.rb +0 -654
- data/lib/language_operator/cli/formatters/optimization_formatter.rb +0 -226
- data/lib/language_operator/cli/helpers/pastel_helper.rb +0 -24
- data/lib/language_operator/learning/adapters/base_adapter.rb +0 -149
- data/lib/language_operator/learning/adapters/jaeger_adapter.rb +0 -221
- data/lib/language_operator/learning/adapters/signoz_adapter.rb +0 -435
- data/lib/language_operator/learning/adapters/tempo_adapter.rb +0 -239
- data/lib/language_operator/learning/optimizer.rb +0 -319
- data/lib/language_operator/learning/pattern_detector.rb +0 -260
- data/lib/language_operator/learning/task_synthesizer.rb +0 -288
- data/lib/language_operator/learning/trace_analyzer.rb +0 -285
- data/lib/language_operator/templates/task_synthesis.tmpl +0 -98
- data/lib/language_operator/ux/base.rb +0 -81
- data/lib/language_operator/ux/concerns/README.md +0 -155
- data/lib/language_operator/ux/concerns/headings.rb +0 -90
- data/lib/language_operator/ux/create_agent.rb +0 -255
- data/lib/language_operator/ux/create_model.rb +0 -267
- data/lib/language_operator/ux/quickstart.rb +0 -594
- data/synth/003/agent.rb +0 -41
- data/synth/003/output.log +0 -68
- /data/docs/{architecture/agent-runtime.md → agent-internals.md} +0 -0
- /data/docs/{dsl/chat-endpoints.md → chat-endpoints.md} +0 -0
- /data/docs/{dsl/SCHEMA_VERSION.md → schema-versioning.md} +0 -0
|
@@ -1,319 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require 'logger'
|
|
4
|
-
|
|
5
|
-
module LanguageOperator
|
|
6
|
-
module Learning
|
|
7
|
-
# Orchestrates the optimization of neural tasks to symbolic implementations
|
|
8
|
-
#
|
|
9
|
-
# The Optimizer analyzes running agents, identifies optimization opportunities,
|
|
10
|
-
# proposes code changes, and applies them with user approval. It integrates
|
|
11
|
-
# TraceAnalyzer (pattern detection) and PatternDetector (code generation) to
|
|
12
|
-
# provide a complete optimization workflow.
|
|
13
|
-
#
|
|
14
|
-
# @example Basic usage
|
|
15
|
-
# optimizer = Optimizer.new(
|
|
16
|
-
# agent_name: 'github-monitor',
|
|
17
|
-
# agent_definition: agent_def,
|
|
18
|
-
# trace_analyzer: TraceAnalyzer.new(endpoint: ENV['OTEL_QUERY_ENDPOINT']),
|
|
19
|
-
# pattern_detector: PatternDetector.new(...)
|
|
20
|
-
# )
|
|
21
|
-
#
|
|
22
|
-
# opportunities = optimizer.analyze
|
|
23
|
-
# opportunities.each do |opp|
|
|
24
|
-
# proposal = optimizer.propose(task_name: opp[:task_name])
|
|
25
|
-
# # Show to user, get approval
|
|
26
|
-
# optimizer.apply(proposal) if approved
|
|
27
|
-
# end
|
|
28
|
-
class Optimizer
|
|
29
|
-
# Minimum consistency score required for optimization
|
|
30
|
-
DEFAULT_MIN_CONSISTENCY = 0.85
|
|
31
|
-
|
|
32
|
-
# Minimum executions required for optimization
|
|
33
|
-
DEFAULT_MIN_EXECUTIONS = 10
|
|
34
|
-
|
|
35
|
-
# Initialize optimizer
|
|
36
|
-
#
|
|
37
|
-
# @param agent_name [String] Name of the agent to optimize
|
|
38
|
-
# @param agent_definition [Dsl::AgentDefinition] Agent definition object
|
|
39
|
-
# @param trace_analyzer [TraceAnalyzer] Analyzer for querying execution traces
|
|
40
|
-
# @param pattern_detector [PatternDetector] Detector for generating symbolic code
|
|
41
|
-
# @param task_synthesizer [TaskSynthesizer, nil] Optional LLM-based synthesizer
|
|
42
|
-
# @param logger [Logger, nil] Logger instance (creates default if nil)
|
|
43
|
-
def initialize(agent_name:, agent_definition:, trace_analyzer:, pattern_detector:, task_synthesizer: nil,
|
|
44
|
-
logger: nil)
|
|
45
|
-
@agent_name = agent_name
|
|
46
|
-
@agent_definition = agent_definition
|
|
47
|
-
@trace_analyzer = trace_analyzer
|
|
48
|
-
@pattern_detector = pattern_detector
|
|
49
|
-
@task_synthesizer = task_synthesizer
|
|
50
|
-
@logger = logger || ::Logger.new($stdout, level: ::Logger::WARN)
|
|
51
|
-
end
|
|
52
|
-
|
|
53
|
-
# Analyze agent for optimization opportunities
|
|
54
|
-
#
|
|
55
|
-
# Queries execution traces for each neural task and determines which tasks
|
|
56
|
-
# are eligible for optimization based on consistency and execution count.
|
|
57
|
-
#
|
|
58
|
-
# @param min_consistency [Float] Minimum consistency threshold (0.0-1.0)
|
|
59
|
-
# @param min_executions [Integer] Minimum execution count required
|
|
60
|
-
# @param time_range [Integer, Range<Time>, nil] Time range for trace queries
|
|
61
|
-
# @return [Array<Hash>] Array of optimization opportunities
|
|
62
|
-
def analyze(min_consistency: DEFAULT_MIN_CONSISTENCY, min_executions: DEFAULT_MIN_EXECUTIONS, time_range: nil)
|
|
63
|
-
opportunities = []
|
|
64
|
-
|
|
65
|
-
# Find all neural tasks in the agent
|
|
66
|
-
neural_tasks = find_neural_tasks
|
|
67
|
-
|
|
68
|
-
if neural_tasks.empty?
|
|
69
|
-
@logger.info("No neural tasks found in agent '#{@agent_name}'")
|
|
70
|
-
return opportunities
|
|
71
|
-
end
|
|
72
|
-
|
|
73
|
-
# Analyze each neural task
|
|
74
|
-
neural_tasks.each do |task|
|
|
75
|
-
analysis = @trace_analyzer.analyze_patterns(
|
|
76
|
-
task_name: task[:name],
|
|
77
|
-
agent_name: @agent_name,
|
|
78
|
-
min_executions: min_executions,
|
|
79
|
-
consistency_threshold: min_consistency,
|
|
80
|
-
time_range: time_range
|
|
81
|
-
)
|
|
82
|
-
|
|
83
|
-
next unless analysis
|
|
84
|
-
|
|
85
|
-
opportunities << {
|
|
86
|
-
task_name: task[:name],
|
|
87
|
-
task_definition: task[:definition],
|
|
88
|
-
execution_count: analysis[:execution_count],
|
|
89
|
-
consistency_score: analysis[:consistency_score],
|
|
90
|
-
ready_for_learning: analysis[:ready_for_learning],
|
|
91
|
-
common_pattern: analysis[:common_pattern],
|
|
92
|
-
reason: analysis[:reason]
|
|
93
|
-
}
|
|
94
|
-
end
|
|
95
|
-
|
|
96
|
-
opportunities
|
|
97
|
-
end
|
|
98
|
-
|
|
99
|
-
# Generate optimization proposal for a specific task
|
|
100
|
-
#
|
|
101
|
-
# Uses PatternDetector to generate symbolic code and calculates
|
|
102
|
-
# the performance impact of the optimization. Falls back to TaskSynthesizer
|
|
103
|
-
# (LLM-based) if pattern detection fails and synthesizer is available.
|
|
104
|
-
#
|
|
105
|
-
# @param task_name [String] Name of task to optimize
|
|
106
|
-
# @param use_synthesis [Boolean] Force use of LLM synthesis instead of pattern detection
|
|
107
|
-
# @return [Hash] Optimization proposal with code, metrics, and metadata
|
|
108
|
-
def propose(task_name:, use_synthesis: false)
|
|
109
|
-
task_def = find_task_definition(task_name)
|
|
110
|
-
raise ArgumentError, "Task '#{task_name}' not found" unless task_def
|
|
111
|
-
|
|
112
|
-
analysis = @trace_analyzer.analyze_patterns(task_name: task_name, agent_name: @agent_name)
|
|
113
|
-
raise ArgumentError, "No execution data found for task '#{task_name}'" unless analysis
|
|
114
|
-
|
|
115
|
-
traces = @trace_analyzer.query_task_traces(task_name: task_name, agent_name: @agent_name, limit: 20)
|
|
116
|
-
detection_result = @pattern_detector.detect_pattern(analysis_result: analysis) unless use_synthesis
|
|
117
|
-
|
|
118
|
-
return propose_via_synthesis(task_name, task_def, analysis, traces) if should_use_synthesis?(use_synthesis, detection_result)
|
|
119
|
-
|
|
120
|
-
unless detection_result&.dig(:success)
|
|
121
|
-
raise ArgumentError, "Cannot optimize task '#{task_name}': #{detection_result&.dig(:reason) || 'No common pattern found'}"
|
|
122
|
-
end
|
|
123
|
-
|
|
124
|
-
build_pattern_proposal(task_name, task_def, analysis, detection_result)
|
|
125
|
-
end
|
|
126
|
-
|
|
127
|
-
# Apply optimization proposal
|
|
128
|
-
#
|
|
129
|
-
# This method would update the agent definition in Kubernetes.
|
|
130
|
-
# For now, it returns the updated agent code that would be applied.
|
|
131
|
-
#
|
|
132
|
-
# @param proposal [Hash] Proposal from #propose
|
|
133
|
-
# @return [Hash] Result with updated agent definition
|
|
134
|
-
def apply(proposal:)
|
|
135
|
-
# In a real implementation, this would:
|
|
136
|
-
# 1. Update the agent CRD with new task definition
|
|
137
|
-
# 2. Create new ConfigMap version
|
|
138
|
-
# 3. Trigger pod restart
|
|
139
|
-
# For now, we return what would be applied
|
|
140
|
-
|
|
141
|
-
{
|
|
142
|
-
success: true,
|
|
143
|
-
task_name: proposal[:task_name],
|
|
144
|
-
updated_code: proposal[:proposed_code],
|
|
145
|
-
action: 'would_update_agent_definition',
|
|
146
|
-
message: "Optimization for '#{proposal[:task_name]}' ready to apply"
|
|
147
|
-
}
|
|
148
|
-
end
|
|
149
|
-
|
|
150
|
-
private
|
|
151
|
-
|
|
152
|
-
def should_use_synthesis?(use_synthesis, detection_result)
|
|
153
|
-
(use_synthesis || !detection_result&.dig(:success)) && @task_synthesizer
|
|
154
|
-
end
|
|
155
|
-
|
|
156
|
-
def propose_via_synthesis(task_name, task_def, analysis, traces)
|
|
157
|
-
@logger.info("Using LLM synthesis for task '#{task_name}'")
|
|
158
|
-
synthesis_result = @task_synthesizer.synthesize(
|
|
159
|
-
task_definition: task_def,
|
|
160
|
-
traces: traces,
|
|
161
|
-
available_tools: detect_available_tools,
|
|
162
|
-
consistency_score: analysis[:consistency_score],
|
|
163
|
-
common_pattern: analysis[:common_pattern]
|
|
164
|
-
)
|
|
165
|
-
|
|
166
|
-
raise ArgumentError, "Cannot optimize task '#{task_name}': #{synthesis_result[:explanation]}" unless synthesis_result[:is_deterministic]
|
|
167
|
-
|
|
168
|
-
build_synthesis_proposal(task_name: task_name, task_def: task_def, analysis: analysis,
|
|
169
|
-
synthesis_result: synthesis_result)
|
|
170
|
-
end
|
|
171
|
-
|
|
172
|
-
def build_pattern_proposal(task_name, task_def, analysis, detection_result)
|
|
173
|
-
impact = calculate_impact(execution_count: analysis[:execution_count],
|
|
174
|
-
consistency_score: analysis[:consistency_score])
|
|
175
|
-
{
|
|
176
|
-
task_name: task_name, current_code: format_current_code(task_def),
|
|
177
|
-
proposed_code: extract_task_code(detection_result[:generated_code]),
|
|
178
|
-
full_generated_code: detection_result[:generated_code],
|
|
179
|
-
consistency_score: analysis[:consistency_score], execution_count: analysis[:execution_count],
|
|
180
|
-
pattern: analysis[:common_pattern], performance_impact: impact,
|
|
181
|
-
validation_violations: detection_result[:validation_violations],
|
|
182
|
-
ready_to_deploy: detection_result[:ready_to_deploy], synthesis_method: :pattern_detection
|
|
183
|
-
}
|
|
184
|
-
end
|
|
185
|
-
|
|
186
|
-
# Find all neural tasks in the agent definition
|
|
187
|
-
#
|
|
188
|
-
# @return [Array<Hash>] Array of neural task info
|
|
189
|
-
def find_neural_tasks
|
|
190
|
-
return [] unless @agent_definition.respond_to?(:tasks)
|
|
191
|
-
|
|
192
|
-
neural_tasks = @agent_definition.tasks.select do |_name, task_def|
|
|
193
|
-
# Neural tasks have instructions but no code block
|
|
194
|
-
task_def.neural?
|
|
195
|
-
end
|
|
196
|
-
|
|
197
|
-
neural_tasks.map do |name, task_def|
|
|
198
|
-
{
|
|
199
|
-
name: name.to_s,
|
|
200
|
-
definition: task_def
|
|
201
|
-
}
|
|
202
|
-
end
|
|
203
|
-
end
|
|
204
|
-
|
|
205
|
-
# Find a specific task definition by name
|
|
206
|
-
#
|
|
207
|
-
# @param task_name [String] Task name
|
|
208
|
-
# @return [Dsl::TaskDefinition, nil] Task definition or nil
|
|
209
|
-
def find_task_definition(task_name)
|
|
210
|
-
return nil unless @agent_definition.respond_to?(:tasks)
|
|
211
|
-
|
|
212
|
-
@agent_definition.tasks[task_name.to_sym]
|
|
213
|
-
end
|
|
214
|
-
|
|
215
|
-
# Format current task code for display
|
|
216
|
-
#
|
|
217
|
-
# @param task_def [Dsl::TaskDefinition] Task definition
|
|
218
|
-
# @return [String] Formatted code
|
|
219
|
-
def format_current_code(task_def)
|
|
220
|
-
inputs_str = (task_def.inputs || {}).map { |k, v| "#{k}: '#{v}'" }.join(', ')
|
|
221
|
-
outputs_str = (task_def.outputs || {}).map { |k, v| "#{k}: '#{v}'" }.join(', ')
|
|
222
|
-
|
|
223
|
-
<<~RUBY
|
|
224
|
-
task :#{task_def.name},
|
|
225
|
-
instructions: "#{task_def.instructions}",
|
|
226
|
-
inputs: { #{inputs_str} },
|
|
227
|
-
outputs: { #{outputs_str} }
|
|
228
|
-
RUBY
|
|
229
|
-
end
|
|
230
|
-
|
|
231
|
-
# Extract task code from full agent definition
|
|
232
|
-
#
|
|
233
|
-
# @param full_code [String] Complete agent definition
|
|
234
|
-
# @return [String] Just the task definition portion
|
|
235
|
-
def extract_task_code(full_code)
|
|
236
|
-
# Extract just the task definition from the full agent code
|
|
237
|
-
lines = full_code.lines
|
|
238
|
-
task_start = lines.index { |l| l.strip.start_with?('task :') }
|
|
239
|
-
task_end = lines.index { |l| l.strip == 'end' && l.start_with?(' end') }
|
|
240
|
-
|
|
241
|
-
return full_code unless task_start && task_end
|
|
242
|
-
|
|
243
|
-
lines[task_start..task_end].join
|
|
244
|
-
end
|
|
245
|
-
|
|
246
|
-
# Calculate performance impact of optimization
|
|
247
|
-
#
|
|
248
|
-
# @param execution_count [Integer] Number of executions observed
|
|
249
|
-
# @param consistency_score [Float] Pattern consistency
|
|
250
|
-
# @return [Hash] Impact metrics
|
|
251
|
-
def calculate_impact(execution_count:, consistency_score:)
|
|
252
|
-
# Estimates based on typical LLM vs symbolic execution
|
|
253
|
-
avg_neural_time = 2.5 # seconds
|
|
254
|
-
avg_neural_cost = 0.003 # dollars
|
|
255
|
-
avg_symbolic_time = 0.1 # seconds
|
|
256
|
-
avg_symbolic_cost = 0.0 # dollars
|
|
257
|
-
|
|
258
|
-
time_saved = avg_neural_time - avg_symbolic_time
|
|
259
|
-
cost_saved = avg_neural_cost - avg_symbolic_cost
|
|
260
|
-
|
|
261
|
-
{
|
|
262
|
-
current_avg_time: avg_neural_time,
|
|
263
|
-
optimized_avg_time: avg_symbolic_time,
|
|
264
|
-
time_reduction_pct: ((time_saved / avg_neural_time) * 100).round(1),
|
|
265
|
-
current_avg_cost: avg_neural_cost,
|
|
266
|
-
optimized_avg_cost: avg_symbolic_cost,
|
|
267
|
-
cost_reduction_pct: ((cost_saved / avg_neural_cost) * 100).round(1),
|
|
268
|
-
projected_monthly_savings: (cost_saved * execution_count * 30).round(2)
|
|
269
|
-
}
|
|
270
|
-
end
|
|
271
|
-
|
|
272
|
-
# Build proposal from synthesis result
|
|
273
|
-
#
|
|
274
|
-
# @param task_name [String] Task name
|
|
275
|
-
# @param task_def [Dsl::TaskDefinition] Task definition
|
|
276
|
-
# @param analysis [Hash] Pattern analysis result
|
|
277
|
-
# @param synthesis_result [Hash] LLM synthesis result
|
|
278
|
-
# @return [Hash] Optimization proposal
|
|
279
|
-
def build_synthesis_proposal(task_name:, task_def:, analysis:, synthesis_result:)
|
|
280
|
-
impact = calculate_impact(
|
|
281
|
-
execution_count: analysis[:execution_count],
|
|
282
|
-
consistency_score: synthesis_result[:confidence]
|
|
283
|
-
)
|
|
284
|
-
|
|
285
|
-
{
|
|
286
|
-
task_name: task_name,
|
|
287
|
-
current_code: format_current_code(task_def),
|
|
288
|
-
proposed_code: synthesis_result[:code],
|
|
289
|
-
full_generated_code: synthesis_result[:code],
|
|
290
|
-
consistency_score: analysis[:consistency_score],
|
|
291
|
-
execution_count: analysis[:execution_count],
|
|
292
|
-
pattern: analysis[:common_pattern],
|
|
293
|
-
performance_impact: impact,
|
|
294
|
-
validation_violations: synthesis_result[:validation_errors] || [],
|
|
295
|
-
ready_to_deploy: synthesis_result[:validation_errors].nil?,
|
|
296
|
-
synthesis_method: :llm_synthesis,
|
|
297
|
-
synthesis_confidence: synthesis_result[:confidence],
|
|
298
|
-
synthesis_explanation: synthesis_result[:explanation]
|
|
299
|
-
}
|
|
300
|
-
end
|
|
301
|
-
|
|
302
|
-
# Detect available tools from agent definition
|
|
303
|
-
#
|
|
304
|
-
# @return [Array<String>] Tool names
|
|
305
|
-
def detect_available_tools
|
|
306
|
-
return [] unless @agent_definition.respond_to?(:mcp_servers)
|
|
307
|
-
|
|
308
|
-
# Extract tool names from MCP server configurations
|
|
309
|
-
tools = []
|
|
310
|
-
@agent_definition.mcp_servers.each_value do |server|
|
|
311
|
-
tools.concat(server[:tools] || []) if server.is_a?(Hash)
|
|
312
|
-
end
|
|
313
|
-
tools.uniq
|
|
314
|
-
rescue StandardError
|
|
315
|
-
[]
|
|
316
|
-
end
|
|
317
|
-
end
|
|
318
|
-
end
|
|
319
|
-
end
|
|
@@ -1,260 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require 'logger'
|
|
4
|
-
|
|
5
|
-
module LanguageOperator
|
|
6
|
-
module Learning
|
|
7
|
-
# Detects patterns in task execution traces and generates symbolic code
|
|
8
|
-
#
|
|
9
|
-
# The PatternDetector analyzes execution patterns from TraceAnalyzer and
|
|
10
|
-
# converts deterministic neural task behavior into symbolic Ruby DSL v1 code.
|
|
11
|
-
# This enables the learning system to automatically optimize neural tasks
|
|
12
|
-
# into faster, cheaper symbolic implementations.
|
|
13
|
-
#
|
|
14
|
-
# @example Basic usage
|
|
15
|
-
# analyzer = TraceAnalyzer.new(endpoint: ENV['OTEL_QUERY_ENDPOINT'])
|
|
16
|
-
# validator = Agent::Safety::ASTValidator.new
|
|
17
|
-
# detector = PatternDetector.new(trace_analyzer: analyzer, validator: validator)
|
|
18
|
-
#
|
|
19
|
-
# analysis = analyzer.analyze_patterns(task_name: 'fetch_user_data')
|
|
20
|
-
# result = detector.detect_pattern(analysis_result: analysis)
|
|
21
|
-
#
|
|
22
|
-
# if result[:success]
|
|
23
|
-
# puts "Generated code:"
|
|
24
|
-
# puts result[:generated_code]
|
|
25
|
-
# end
|
|
26
|
-
class PatternDetector
|
|
27
|
-
# Default minimum consistency threshold for learning
|
|
28
|
-
DEFAULT_CONSISTENCY_THRESHOLD = 0.85
|
|
29
|
-
|
|
30
|
-
# Default minimum executions required before learning
|
|
31
|
-
DEFAULT_MIN_EXECUTIONS = 10
|
|
32
|
-
|
|
33
|
-
# Minimum pattern confidence for code generation
|
|
34
|
-
MIN_PATTERN_CONFIDENCE = 0.75
|
|
35
|
-
|
|
36
|
-
# Initialize pattern detector
|
|
37
|
-
#
|
|
38
|
-
# @param trace_analyzer [TraceAnalyzer] Analyzer for querying execution traces
|
|
39
|
-
# @param validator [Agent::Safety::ASTValidator] Validator for generated code
|
|
40
|
-
# @param logger [Logger, nil] Logger instance (creates default if nil)
|
|
41
|
-
def initialize(trace_analyzer:, validator:, logger: nil)
|
|
42
|
-
@trace_analyzer = trace_analyzer
|
|
43
|
-
@validator = validator
|
|
44
|
-
@logger = logger || ::Logger.new($stdout, level: ::Logger::WARN)
|
|
45
|
-
end
|
|
46
|
-
|
|
47
|
-
# Detect patterns and generate symbolic code
|
|
48
|
-
#
|
|
49
|
-
# Main entry point for pattern detection. Takes analysis results from
|
|
50
|
-
# TraceAnalyzer and generates validated symbolic code if the pattern
|
|
51
|
-
# meets consistency and execution count thresholds.
|
|
52
|
-
#
|
|
53
|
-
# @param analysis_result [Hash] Result from TraceAnalyzer#analyze_patterns
|
|
54
|
-
# @return [Hash] Detection result with generated code and metadata
|
|
55
|
-
def detect_pattern(analysis_result:)
|
|
56
|
-
# Validate that we can generate code from this analysis
|
|
57
|
-
return early_rejection_result(analysis_result) unless can_generate_code?(analysis_result)
|
|
58
|
-
|
|
59
|
-
# Generate symbolic code from the pattern
|
|
60
|
-
code = generate_symbolic_code(
|
|
61
|
-
pattern: analysis_result[:common_pattern],
|
|
62
|
-
task_name: analysis_result[:task_name]
|
|
63
|
-
)
|
|
64
|
-
|
|
65
|
-
# Validate the generated code
|
|
66
|
-
validation_result = validate_generated_code(code: code)
|
|
67
|
-
|
|
68
|
-
# Build result
|
|
69
|
-
{
|
|
70
|
-
success: validation_result[:valid],
|
|
71
|
-
task_name: analysis_result[:task_name],
|
|
72
|
-
generated_code: code,
|
|
73
|
-
validation_violations: validation_result[:violations],
|
|
74
|
-
consistency_score: analysis_result[:consistency_score],
|
|
75
|
-
execution_count: analysis_result[:execution_count],
|
|
76
|
-
pattern: analysis_result[:common_pattern],
|
|
77
|
-
ready_to_deploy: validation_result[:valid] && analysis_result[:consistency_score] >= 0.90,
|
|
78
|
-
generated_at: Time.now.iso8601
|
|
79
|
-
}
|
|
80
|
-
end
|
|
81
|
-
|
|
82
|
-
# Generate symbolic Ruby code from tool call pattern
|
|
83
|
-
#
|
|
84
|
-
# Converts a deterministic tool call sequence into a valid Ruby DSL v1
|
|
85
|
-
# task definition with chained execute_task calls.
|
|
86
|
-
#
|
|
87
|
-
# @param pattern [String] Tool sequence like "db_fetch → cache_get → api"
|
|
88
|
-
# @param task_name [String] Name of the task being learned
|
|
89
|
-
# @return [String] Complete Ruby DSL v1 agent definition
|
|
90
|
-
def generate_symbolic_code(pattern:, task_name:)
|
|
91
|
-
sequence = extract_tool_sequence(pattern)
|
|
92
|
-
|
|
93
|
-
# Generate the task code body with chained execute_task calls
|
|
94
|
-
task_body = generate_task_code(sequence: sequence)
|
|
95
|
-
|
|
96
|
-
# Wrap in complete agent definition
|
|
97
|
-
generate_agent_wrapper(
|
|
98
|
-
task_name: task_name,
|
|
99
|
-
task_body: task_body
|
|
100
|
-
)
|
|
101
|
-
end
|
|
102
|
-
|
|
103
|
-
# Validate generated code with ASTValidator
|
|
104
|
-
#
|
|
105
|
-
# @param code [String] Ruby code to validate
|
|
106
|
-
# @return [Hash] Validation result with violations
|
|
107
|
-
def validate_generated_code(code:)
|
|
108
|
-
violations = @validator.validate(code)
|
|
109
|
-
|
|
110
|
-
{
|
|
111
|
-
valid: violations.empty?,
|
|
112
|
-
violations: violations,
|
|
113
|
-
safe_methods_used: true
|
|
114
|
-
}
|
|
115
|
-
rescue StandardError => e
|
|
116
|
-
@logger.error("Failed to validate generated code: #{e.message}")
|
|
117
|
-
{
|
|
118
|
-
valid: false,
|
|
119
|
-
violations: [{ type: :validation_error, message: e.message }],
|
|
120
|
-
safe_methods_used: false
|
|
121
|
-
}
|
|
122
|
-
end
|
|
123
|
-
|
|
124
|
-
private
|
|
125
|
-
|
|
126
|
-
# Check if pattern analysis meets criteria for code generation
|
|
127
|
-
#
|
|
128
|
-
# @param analysis_result [Hash] Analysis result from TraceAnalyzer
|
|
129
|
-
# @return [Boolean] True if code can be generated
|
|
130
|
-
def can_generate_code?(analysis_result)
|
|
131
|
-
return false unless analysis_result.is_a?(Hash)
|
|
132
|
-
|
|
133
|
-
# Check if ready for learning flag is set
|
|
134
|
-
return false unless analysis_result[:ready_for_learning]
|
|
135
|
-
|
|
136
|
-
# Check execution count
|
|
137
|
-
return false if analysis_result[:execution_count].to_i < DEFAULT_MIN_EXECUTIONS
|
|
138
|
-
|
|
139
|
-
# Check consistency score
|
|
140
|
-
return false if analysis_result[:consistency_score].to_f < DEFAULT_CONSISTENCY_THRESHOLD
|
|
141
|
-
|
|
142
|
-
# Check pattern exists
|
|
143
|
-
return false if analysis_result[:common_pattern].nil? || analysis_result[:common_pattern].empty?
|
|
144
|
-
|
|
145
|
-
true
|
|
146
|
-
end
|
|
147
|
-
|
|
148
|
-
# Build early rejection result when criteria not met
|
|
149
|
-
#
|
|
150
|
-
# @param analysis_result [Hash, nil] Analysis result if available
|
|
151
|
-
# @return [Hash] Rejection result with reason
|
|
152
|
-
def early_rejection_result(analysis_result)
|
|
153
|
-
if analysis_result.nil? || !analysis_result.is_a?(Hash)
|
|
154
|
-
return {
|
|
155
|
-
success: false,
|
|
156
|
-
reason: 'Invalid analysis result'
|
|
157
|
-
}
|
|
158
|
-
end
|
|
159
|
-
|
|
160
|
-
reasons = []
|
|
161
|
-
if analysis_result[:execution_count].to_i < DEFAULT_MIN_EXECUTIONS
|
|
162
|
-
reasons << "Insufficient executions (#{analysis_result[:execution_count]}/#{DEFAULT_MIN_EXECUTIONS})"
|
|
163
|
-
end
|
|
164
|
-
if analysis_result[:consistency_score].to_f < DEFAULT_CONSISTENCY_THRESHOLD
|
|
165
|
-
reasons << "Low consistency (#{analysis_result[:consistency_score]}/#{DEFAULT_CONSISTENCY_THRESHOLD})"
|
|
166
|
-
end
|
|
167
|
-
reasons << 'No common pattern found' if analysis_result[:common_pattern].nil? || analysis_result[:common_pattern].empty?
|
|
168
|
-
|
|
169
|
-
{
|
|
170
|
-
success: false,
|
|
171
|
-
task_name: analysis_result[:task_name],
|
|
172
|
-
execution_count: analysis_result[:execution_count],
|
|
173
|
-
consistency_score: analysis_result[:consistency_score],
|
|
174
|
-
ready_for_learning: false,
|
|
175
|
-
reason: reasons.join('; ')
|
|
176
|
-
}
|
|
177
|
-
end
|
|
178
|
-
|
|
179
|
-
# Extract tool sequence from pattern string
|
|
180
|
-
#
|
|
181
|
-
# @param pattern [String] Pattern like "db_fetch → cache_get → api"
|
|
182
|
-
# @return [Array<Symbol>] Sequence like [:db_fetch, :cache_get, :api]
|
|
183
|
-
def extract_tool_sequence(pattern)
|
|
184
|
-
pattern.split('→').map(&:strip).map(&:to_sym)
|
|
185
|
-
end
|
|
186
|
-
|
|
187
|
-
# Generate task code body with chained execute_task calls
|
|
188
|
-
#
|
|
189
|
-
# Creates Ruby code that executes tools in sequence, passing outputs
|
|
190
|
-
# from each tool to the next one as inputs.
|
|
191
|
-
#
|
|
192
|
-
# @param sequence [Array<Symbol>] Tool sequence
|
|
193
|
-
# @return [String] Ruby code for task body
|
|
194
|
-
def generate_task_code(sequence:)
|
|
195
|
-
return ' { result: {} }' if sequence.empty?
|
|
196
|
-
|
|
197
|
-
lines = []
|
|
198
|
-
|
|
199
|
-
# First call: use original inputs
|
|
200
|
-
first_tool = sequence[0]
|
|
201
|
-
lines << "step1_result = execute_task(:#{first_tool}, inputs: inputs)"
|
|
202
|
-
|
|
203
|
-
# Middle calls: chain outputs from previous step
|
|
204
|
-
if sequence.size > 1
|
|
205
|
-
sequence[1..-2].each_with_index do |tool, index|
|
|
206
|
-
step_num = index + 2
|
|
207
|
-
prev_step = "step#{step_num - 1}_result"
|
|
208
|
-
lines << "step#{step_num}_result = execute_task(:#{tool}, inputs: #{prev_step})"
|
|
209
|
-
end
|
|
210
|
-
|
|
211
|
-
# Final call
|
|
212
|
-
final_tool = sequence[-1]
|
|
213
|
-
last_step = "step#{sequence.size - 1}_result"
|
|
214
|
-
lines << "final_result = execute_task(:#{final_tool}, inputs: #{last_step})"
|
|
215
|
-
else
|
|
216
|
-
lines << 'final_result = step1_result'
|
|
217
|
-
end
|
|
218
|
-
|
|
219
|
-
# Return statement
|
|
220
|
-
lines << '{ result: final_result }'
|
|
221
|
-
|
|
222
|
-
# Indent and join
|
|
223
|
-
lines.map { |line| " #{line}" }.join("\n")
|
|
224
|
-
end
|
|
225
|
-
|
|
226
|
-
# Generate complete agent wrapper with task definition
|
|
227
|
-
#
|
|
228
|
-
# @param task_name [String] Name of the task
|
|
229
|
-
# @param task_body [String] Generated task code body
|
|
230
|
-
# @return [String] Complete Ruby DSL v1 agent definition
|
|
231
|
-
def generate_agent_wrapper(task_name:, task_body:)
|
|
232
|
-
# Convert task name to kebab-case for agent name
|
|
233
|
-
agent_name = task_name.to_s.gsub('_', '-')
|
|
234
|
-
|
|
235
|
-
<<~RUBY
|
|
236
|
-
# frozen_string_literal: true
|
|
237
|
-
|
|
238
|
-
require 'language_operator'
|
|
239
|
-
|
|
240
|
-
LanguageOperator::Dsl.define_agents do
|
|
241
|
-
agent "#{agent_name}-symbolic" do
|
|
242
|
-
description "Symbolic implementation of #{task_name} (learned from execution patterns)"
|
|
243
|
-
|
|
244
|
-
task :core_pattern,
|
|
245
|
-
inputs: { data: 'hash' },
|
|
246
|
-
outputs: { result: 'hash' }
|
|
247
|
-
do |inputs|
|
|
248
|
-
#{task_body}
|
|
249
|
-
end
|
|
250
|
-
|
|
251
|
-
main do |inputs|
|
|
252
|
-
execute_task(:core_pattern, inputs: inputs)
|
|
253
|
-
end
|
|
254
|
-
end
|
|
255
|
-
end
|
|
256
|
-
RUBY
|
|
257
|
-
end
|
|
258
|
-
end
|
|
259
|
-
end
|
|
260
|
-
end
|