language-operator 0.1.36 → 0.1.37
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/Gemfile.lock +2 -1
- data/lib/language_operator/cli/commands/system.rb +230 -90
- data/lib/language_operator/templates/agent_synthesis.tmpl +243 -0
- 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/version.rb +1 -1
- metadata +17 -4
- data/CI_STATUS.md +0 -56
- data/lib/language_operator/templates/examples/agent_synthesis.tmpl +0 -133
- /data/lib/language_operator/templates/{examples/persona_distillation.tmpl → persona_distillation.tmpl} +0 -0
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 8b57f326a81a7683900ef0700d54b97c296019f74e044dc886cc8aa02703e5a3
|
|
4
|
+
data.tar.gz: 7ba35359cc513dd5d53df60269f7052449453032c6ce749631e5dd6d80983a66
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 62327e75b4357c55c1de51fbb56eebe4a16faede2b3ead5591a0dd2b8dc8760116130f9e9820ca47499aaa5ad3e285048862db7fef788d96416c4cc9fe123560
|
|
7
|
+
data.tar.gz: 0f63c9ca8e9523f6825a053cbd02a21ccd4103067b36ad190fb78316df051d0b6972a36917fd2a4ef9f26e449deaaaac487a83ae63e9f562fd9c317a8df2f490
|
data/Gemfile.lock
CHANGED
|
@@ -198,49 +198,58 @@ module LanguageOperator
|
|
|
198
198
|
end
|
|
199
199
|
end
|
|
200
200
|
|
|
201
|
-
desc '
|
|
201
|
+
desc 'synthesize INSTRUCTIONS', 'Synthesize agent code from natural language instructions'
|
|
202
202
|
long_desc <<-DESC
|
|
203
|
-
|
|
203
|
+
Synthesize agent code by converting natural language instructions
|
|
204
204
|
into Ruby DSL code without creating an actual agent.
|
|
205
205
|
|
|
206
|
+
This command uses a LanguageModel resource from your cluster to generate
|
|
207
|
+
agent code. If --model is not specified, the first available model will
|
|
208
|
+
be auto-selected.
|
|
209
|
+
|
|
206
210
|
This command helps you validate your instructions and understand how the
|
|
207
211
|
synthesis engine interprets them. Use --dry-run to see the prompt that
|
|
208
212
|
would be sent to the LLM, or run without it to generate actual code.
|
|
209
213
|
|
|
210
214
|
Examples:
|
|
211
215
|
# Test with dry-run (show prompt only)
|
|
212
|
-
aictl system
|
|
216
|
+
aictl system synthesize "Monitor GitHub issues daily" --dry-run
|
|
217
|
+
|
|
218
|
+
# Generate code from instructions (auto-selects first available model)
|
|
219
|
+
aictl system synthesize "Send daily reports to Slack"
|
|
220
|
+
|
|
221
|
+
# Use a specific cluster model
|
|
222
|
+
aictl system synthesize "Process webhooks from GitHub" --model my-claude
|
|
213
223
|
|
|
214
|
-
#
|
|
215
|
-
aictl system
|
|
224
|
+
# Output raw code without formatting (useful for piping to files)
|
|
225
|
+
aictl system synthesize "Monitor logs" --raw > agent.rb
|
|
216
226
|
|
|
217
227
|
# Specify custom agent name and tools
|
|
218
|
-
aictl system
|
|
219
|
-
--instructions "Process webhooks from GitHub" \\
|
|
228
|
+
aictl system synthesize "Process webhooks from GitHub" \\
|
|
220
229
|
--agent-name github-processor \\
|
|
221
|
-
--tools github,slack
|
|
222
|
-
|
|
223
|
-
# Specify available models
|
|
224
|
-
aictl system test-synthesis \\
|
|
225
|
-
--instructions "Analyze logs every hour" \\
|
|
226
|
-
--models gpt-4,claude-3-5-sonnet
|
|
230
|
+
--tools github,slack \\
|
|
231
|
+
--model my-gpt4
|
|
227
232
|
DESC
|
|
228
|
-
option :instructions, type: :string, required: true, desc: 'Natural language instructions for the agent'
|
|
229
233
|
option :agent_name, type: :string, default: 'test-agent', desc: 'Name for the test agent'
|
|
230
234
|
option :tools, type: :string, desc: 'Comma-separated list of available tools'
|
|
231
|
-
option :models, type: :string, desc: 'Comma-separated list of available models'
|
|
235
|
+
option :models, type: :string, desc: 'Comma-separated list of available models (from cluster)'
|
|
236
|
+
option :model, type: :string, desc: 'Model to use for synthesis (defaults to first available in cluster)'
|
|
232
237
|
option :dry_run, type: :boolean, default: false, desc: 'Show prompt without calling LLM'
|
|
233
|
-
|
|
234
|
-
|
|
238
|
+
option :raw, type: :boolean, default: false, desc: 'Output only the raw code without formatting'
|
|
239
|
+
def synthesize(instructions)
|
|
240
|
+
handle_command_error('synthesize agent') do
|
|
241
|
+
# Select model to use for synthesis
|
|
242
|
+
selected_model = select_synthesis_model
|
|
243
|
+
|
|
235
244
|
# Load synthesis template
|
|
236
245
|
template_content = load_bundled_template('agent')
|
|
237
246
|
|
|
238
247
|
# Detect temporal intent from instructions
|
|
239
|
-
temporal_intent = detect_temporal_intent(
|
|
248
|
+
temporal_intent = detect_temporal_intent(instructions)
|
|
240
249
|
|
|
241
250
|
# Prepare template data
|
|
242
251
|
template_data = {
|
|
243
|
-
'Instructions' =>
|
|
252
|
+
'Instructions' => instructions,
|
|
244
253
|
'AgentName' => options[:agent_name],
|
|
245
254
|
'ToolsList' => format_tools_list(options[:tools]),
|
|
246
255
|
'ModelsList' => format_models_list(options[:models]),
|
|
@@ -267,11 +276,8 @@ module LanguageOperator
|
|
|
267
276
|
return
|
|
268
277
|
end
|
|
269
278
|
|
|
270
|
-
# Call LLM to generate code
|
|
271
|
-
|
|
272
|
-
puts
|
|
273
|
-
|
|
274
|
-
llm_response = call_llm_for_synthesis(rendered_prompt)
|
|
279
|
+
# Call LLM to generate code (no output - just do it)
|
|
280
|
+
llm_response = call_llm_for_synthesis(rendered_prompt, selected_model)
|
|
275
281
|
|
|
276
282
|
# Extract Ruby code from response
|
|
277
283
|
generated_code = extract_ruby_code(llm_response)
|
|
@@ -284,34 +290,19 @@ module LanguageOperator
|
|
|
284
290
|
exit 1
|
|
285
291
|
end
|
|
286
292
|
|
|
287
|
-
#
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
puts '=' * 80
|
|
292
|
-
puts
|
|
293
|
-
|
|
294
|
-
# Validate generated code
|
|
295
|
-
puts 'Validating generated code...'
|
|
296
|
-
validation_result = validate_code_against_schema(generated_code)
|
|
297
|
-
|
|
298
|
-
if validation_result[:valid] && validation_result[:warnings].empty?
|
|
299
|
-
Formatters::ProgressFormatter.success('✅ Code is valid - No issues found')
|
|
300
|
-
elsif validation_result[:valid]
|
|
301
|
-
Formatters::ProgressFormatter.success('✅ Code is valid - With warnings')
|
|
302
|
-
puts
|
|
303
|
-
validation_result[:warnings].each do |warn|
|
|
304
|
-
puts " ⚠ #{warn[:message]}"
|
|
305
|
-
end
|
|
306
|
-
else
|
|
307
|
-
Formatters::ProgressFormatter.error('❌ Code validation failed')
|
|
308
|
-
puts
|
|
309
|
-
validation_result[:errors].each do |err|
|
|
310
|
-
puts " ✗ #{err[:message]}"
|
|
311
|
-
end
|
|
293
|
+
# Handle raw output
|
|
294
|
+
if options[:raw]
|
|
295
|
+
puts generated_code
|
|
296
|
+
return
|
|
312
297
|
end
|
|
313
298
|
|
|
314
|
-
|
|
299
|
+
# Display formatted code
|
|
300
|
+
require 'rouge'
|
|
301
|
+
formatter = Rouge::Formatters::Terminal256.new
|
|
302
|
+
lexer = Rouge::Lexers::Ruby.new
|
|
303
|
+
highlighted_code = formatter.format(lexer.lex(generated_code))
|
|
304
|
+
|
|
305
|
+
puts highlighted_code
|
|
315
306
|
end
|
|
316
307
|
end
|
|
317
308
|
|
|
@@ -449,68 +440,217 @@ module LanguageOperator
|
|
|
449
440
|
|
|
450
441
|
# Format models list for template
|
|
451
442
|
def format_models_list(models_str)
|
|
452
|
-
# If not specified, try to detect from
|
|
443
|
+
# If not specified, try to detect from cluster
|
|
453
444
|
if models_str.nil? || models_str.strip.empty?
|
|
454
445
|
models = detect_available_models
|
|
455
446
|
return models.map { |model| "- #{model}" }.join("\n") unless models.empty?
|
|
456
447
|
|
|
457
|
-
return 'No models
|
|
448
|
+
return 'No models available (run: aictl model list)'
|
|
458
449
|
end
|
|
459
450
|
|
|
460
451
|
models = models_str.split(',').map(&:strip)
|
|
461
452
|
models.map { |model| "- #{model}" }.join("\n")
|
|
462
453
|
end
|
|
463
454
|
|
|
464
|
-
# Detect available models from
|
|
455
|
+
# Detect available models from cluster
|
|
465
456
|
def detect_available_models
|
|
466
|
-
models =
|
|
467
|
-
models
|
|
468
|
-
|
|
469
|
-
models
|
|
457
|
+
models = ctx.client.list_resources('LanguageModel', namespace: ctx.namespace)
|
|
458
|
+
models.map { |m| m.dig('metadata', 'name') }
|
|
459
|
+
rescue StandardError => e
|
|
460
|
+
Formatters::ProgressFormatter.error("Failed to list models from cluster: #{e.message}")
|
|
461
|
+
[]
|
|
470
462
|
end
|
|
471
463
|
|
|
472
|
-
#
|
|
473
|
-
def
|
|
474
|
-
|
|
464
|
+
# Select model to use for synthesis
|
|
465
|
+
def select_synthesis_model
|
|
466
|
+
# If --model option specified, use it
|
|
467
|
+
return options[:model] if options[:model]
|
|
468
|
+
|
|
469
|
+
# Otherwise, auto-select from available cluster models
|
|
470
|
+
available_models = detect_available_models
|
|
475
471
|
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
Formatters::ProgressFormatter.error('No LLM credentials found')
|
|
472
|
+
if available_models.empty?
|
|
473
|
+
Formatters::ProgressFormatter.error('No models available in cluster')
|
|
479
474
|
puts
|
|
480
|
-
puts 'Please
|
|
481
|
-
puts '
|
|
482
|
-
puts
|
|
475
|
+
puts 'Please create a model first:'
|
|
476
|
+
puts ' aictl model create'
|
|
477
|
+
puts
|
|
478
|
+
puts 'Or list existing models:'
|
|
479
|
+
puts ' aictl model list'
|
|
483
480
|
exit 1
|
|
484
481
|
end
|
|
485
482
|
|
|
486
|
-
#
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
483
|
+
# Auto-select first available model (silently)
|
|
484
|
+
available_models.first
|
|
485
|
+
end
|
|
486
|
+
|
|
487
|
+
# Get endpoint for a cluster model
|
|
488
|
+
def get_model_endpoint(model_name)
|
|
489
|
+
# For cluster models, we use the service endpoint
|
|
490
|
+
# The service is typically named the same as the model and listens on port 4000
|
|
491
|
+
"http://#{model_name}.#{ctx.namespace}.svc.cluster.local:4000/v1"
|
|
492
|
+
end
|
|
493
|
+
|
|
494
|
+
# Call LLM to generate code from synthesis prompt using cluster model
|
|
495
|
+
def call_llm_for_synthesis(prompt, model_name)
|
|
496
|
+
require 'json'
|
|
497
|
+
require 'faraday'
|
|
498
|
+
|
|
499
|
+
# Get model resource
|
|
500
|
+
model = get_resource_or_exit('LanguageModel', model_name)
|
|
501
|
+
model_id = model.dig('spec', 'modelName')
|
|
502
|
+
|
|
503
|
+
# Get the model's pod
|
|
504
|
+
pod = get_model_pod(model_name)
|
|
505
|
+
pod_name = pod.dig('metadata', 'name')
|
|
506
|
+
|
|
507
|
+
# Set up port-forward to access the model pod
|
|
508
|
+
port_forward_pid = nil
|
|
509
|
+
local_port = find_available_port
|
|
510
|
+
|
|
511
|
+
begin
|
|
512
|
+
# Start kubectl port-forward in background
|
|
513
|
+
port_forward_pid = start_port_forward(pod_name, local_port, 4000)
|
|
514
|
+
|
|
515
|
+
# Wait for port-forward to be ready
|
|
516
|
+
wait_for_port(local_port)
|
|
517
|
+
|
|
518
|
+
# Build the JSON payload for the chat completion request
|
|
519
|
+
payload = {
|
|
520
|
+
model: model_id,
|
|
521
|
+
messages: [{ role: 'user', content: prompt }],
|
|
522
|
+
max_tokens: 4000,
|
|
523
|
+
temperature: 0.3
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
# Make HTTP request using Faraday
|
|
527
|
+
conn = Faraday.new(url: "http://localhost:#{local_port}") do |f|
|
|
528
|
+
f.request :json
|
|
529
|
+
f.response :json
|
|
530
|
+
f.adapter Faraday.default_adapter
|
|
531
|
+
f.options.timeout = 120
|
|
532
|
+
f.options.open_timeout = 10
|
|
533
|
+
end
|
|
534
|
+
|
|
535
|
+
response = conn.post('/v1/chat/completions', payload)
|
|
536
|
+
|
|
537
|
+
# Parse response
|
|
538
|
+
result = response.body
|
|
539
|
+
|
|
540
|
+
if result['error']
|
|
541
|
+
error_msg = result['error']['message'] || result['error']
|
|
542
|
+
raise "Model error: #{error_msg}"
|
|
543
|
+
elsif !result['choices'] || result['choices'].empty?
|
|
544
|
+
raise "Unexpected response format: #{result.inspect}"
|
|
545
|
+
end
|
|
546
|
+
|
|
547
|
+
# Extract the content from the first choice
|
|
548
|
+
result.dig('choices', 0, 'message', 'content')
|
|
549
|
+
rescue Faraday::TimeoutError
|
|
550
|
+
raise 'LLM request timed out after 120 seconds'
|
|
551
|
+
rescue Faraday::ConnectionFailed => e
|
|
552
|
+
raise "Failed to connect to model: #{e.message}"
|
|
553
|
+
rescue StandardError => e
|
|
554
|
+
Formatters::ProgressFormatter.error("LLM call failed: #{e.message}")
|
|
555
|
+
puts
|
|
556
|
+
puts "Make sure the model '#{model_name}' is running: kubectl get pods -n #{ctx.namespace}"
|
|
557
|
+
exit 1
|
|
558
|
+
ensure
|
|
559
|
+
# Clean up port-forward process
|
|
560
|
+
cleanup_port_forward(port_forward_pid) if port_forward_pid
|
|
495
561
|
end
|
|
562
|
+
end
|
|
563
|
+
|
|
564
|
+
# Get the pod for a model
|
|
565
|
+
def get_model_pod(model_name)
|
|
566
|
+
# Get the deployment for the model
|
|
567
|
+
deployment = ctx.client.get_resource('Deployment', model_name, ctx.namespace)
|
|
568
|
+
labels = deployment.dig('spec', 'selector', 'matchLabels')
|
|
496
569
|
|
|
497
|
-
#
|
|
498
|
-
client = RubyLLM.new(provider: provider, api_key: api_key)
|
|
499
|
-
messages = [{ role: 'user', content: prompt }]
|
|
570
|
+
raise "Deployment '#{model_name}' has no selector labels" if labels.nil?
|
|
500
571
|
|
|
501
|
-
|
|
572
|
+
# Convert to hash if needed
|
|
573
|
+
labels_hash = labels.respond_to?(:to_h) ? labels.to_h : labels
|
|
574
|
+
raise "Deployment '#{model_name}' has empty selector labels" if labels_hash.empty?
|
|
502
575
|
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
576
|
+
label_selector = labels_hash.map { |k, v| "#{k}=#{v}" }.join(',')
|
|
577
|
+
|
|
578
|
+
# Find a running pod
|
|
579
|
+
pods = ctx.client.list_resources('Pod', namespace: ctx.namespace, label_selector: label_selector)
|
|
580
|
+
raise "No pods found for model '#{model_name}'" if pods.empty?
|
|
581
|
+
|
|
582
|
+
running_pod = pods.find do |pod|
|
|
583
|
+
pod.dig('status', 'phase') == 'Running' &&
|
|
584
|
+
pod.dig('status', 'conditions')&.any? { |c| c['type'] == 'Ready' && c['status'] == 'True' }
|
|
585
|
+
end
|
|
586
|
+
|
|
587
|
+
if running_pod.nil?
|
|
588
|
+
pod_phases = pods.map { |p| p.dig('status', 'phase') }.join(', ')
|
|
589
|
+
raise "No running pods found. Pod phases: #{pod_phases}"
|
|
590
|
+
end
|
|
591
|
+
|
|
592
|
+
running_pod
|
|
593
|
+
rescue K8s::Error::NotFound
|
|
594
|
+
raise "Model deployment '#{model_name}' not found"
|
|
595
|
+
end
|
|
596
|
+
|
|
597
|
+
# Find an available local port for port-forwarding
|
|
598
|
+
def find_available_port
|
|
599
|
+
require 'socket'
|
|
600
|
+
|
|
601
|
+
# Try ports in the range 14000-14999
|
|
602
|
+
(14_000..14_999).each do |port|
|
|
603
|
+
server = TCPServer.new('127.0.0.1', port)
|
|
604
|
+
server.close
|
|
605
|
+
return port
|
|
606
|
+
rescue Errno::EADDRINUSE
|
|
607
|
+
# Port in use, try next
|
|
608
|
+
next
|
|
609
|
+
end
|
|
610
|
+
|
|
611
|
+
raise 'No available ports found in range 14000-14999'
|
|
612
|
+
end
|
|
613
|
+
|
|
614
|
+
# Start kubectl port-forward in background
|
|
615
|
+
def start_port_forward(pod_name, local_port, remote_port)
|
|
616
|
+
require 'English'
|
|
617
|
+
|
|
618
|
+
cmd = "kubectl port-forward -n #{ctx.namespace} #{pod_name} #{local_port}:#{remote_port}"
|
|
619
|
+
pid = spawn(cmd, out: '/dev/null', err: '/dev/null')
|
|
620
|
+
|
|
621
|
+
# Detach so it runs in background
|
|
622
|
+
Process.detach(pid)
|
|
623
|
+
|
|
624
|
+
pid
|
|
625
|
+
end
|
|
626
|
+
|
|
627
|
+
# Wait for port-forward to be ready
|
|
628
|
+
def wait_for_port(port, max_attempts: 30)
|
|
629
|
+
require 'socket'
|
|
630
|
+
|
|
631
|
+
max_attempts.times do
|
|
632
|
+
socket = TCPSocket.new('127.0.0.1', port)
|
|
633
|
+
socket.close
|
|
634
|
+
return true
|
|
635
|
+
rescue Errno::ECONNREFUSED, Errno::EHOSTUNREACH
|
|
636
|
+
sleep 0.1
|
|
637
|
+
end
|
|
638
|
+
|
|
639
|
+
raise "Port-forward to localhost:#{port} failed to become ready after #{max_attempts} attempts"
|
|
640
|
+
end
|
|
641
|
+
|
|
642
|
+
# Clean up port-forward process
|
|
643
|
+
def cleanup_port_forward(pid)
|
|
644
|
+
return unless pid
|
|
645
|
+
|
|
646
|
+
begin
|
|
647
|
+
Process.kill('TERM', pid)
|
|
648
|
+
Process.wait(pid, Process::WNOHANG)
|
|
649
|
+
rescue Errno::ESRCH
|
|
650
|
+
# Process already gone
|
|
651
|
+
rescue Errno::ECHILD
|
|
652
|
+
# Process already reaped
|
|
510
653
|
end
|
|
511
|
-
rescue StandardError => e
|
|
512
|
-
Formatters::ProgressFormatter.error("LLM call failed: #{e.message}")
|
|
513
|
-
exit 1
|
|
514
654
|
end
|
|
515
655
|
|
|
516
656
|
# Extract Ruby code from LLM response
|
|
@@ -550,7 +690,7 @@ module LanguageOperator
|
|
|
550
690
|
# Load bundled template from gem
|
|
551
691
|
def load_bundled_template(type)
|
|
552
692
|
filename = type == 'agent' ? 'agent_synthesis.tmpl' : 'persona_distillation.tmpl'
|
|
553
|
-
template_path = File.join(__dir__, '..', '..', 'templates',
|
|
693
|
+
template_path = File.join(__dir__, '..', '..', 'templates', filename)
|
|
554
694
|
File.read(template_path)
|
|
555
695
|
end
|
|
556
696
|
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
You are generating Ruby DSL code for an autonomous agent in a Kubernetes operator.
|
|
2
|
+
|
|
3
|
+
{{if .ErrorContext}}
|
|
4
|
+
## IMPORTANT: Self-Healing Synthesis - Attempt {{.AttemptNumber}}
|
|
5
|
+
|
|
6
|
+
The previous code synthesis encountered errors. Please analyze the errors below and generate CORRECTED code.
|
|
7
|
+
|
|
8
|
+
### Previous Synthesis Failures
|
|
9
|
+
|
|
10
|
+
{{if .ErrorContext.ValidationErrors}}
|
|
11
|
+
**Validation Errors** (detected during code generation):
|
|
12
|
+
{{range .ErrorContext.ValidationErrors}}
|
|
13
|
+
- {{.}}
|
|
14
|
+
{{end}}
|
|
15
|
+
{{end}}
|
|
16
|
+
|
|
17
|
+
{{if .ErrorContext.RuntimeErrors}}
|
|
18
|
+
**Runtime Errors** (detected during execution):
|
|
19
|
+
{{range .ErrorContext.RuntimeErrors}}
|
|
20
|
+
- Time: {{.Timestamp}}
|
|
21
|
+
- Type: {{.ErrorType}}
|
|
22
|
+
- Message: {{.ErrorMessage}}
|
|
23
|
+
{{if .StackTrace}}
|
|
24
|
+
- Stack Trace:
|
|
25
|
+
{{range .StackTrace}}
|
|
26
|
+
{{.}}
|
|
27
|
+
{{end}}
|
|
28
|
+
{{end}}
|
|
29
|
+
- Exit Code: {{.ContainerExitCode}}
|
|
30
|
+
{{end}}
|
|
31
|
+
{{end}}
|
|
32
|
+
|
|
33
|
+
{{if .ErrorContext.LastCrashLog}}
|
|
34
|
+
**Last Container Logs** (before crash):
|
|
35
|
+
```
|
|
36
|
+
{{.ErrorContext.LastCrashLog}}
|
|
37
|
+
```
|
|
38
|
+
{{end}}
|
|
39
|
+
|
|
40
|
+
### Your Task
|
|
41
|
+
|
|
42
|
+
1. Carefully analyze each error above
|
|
43
|
+
2. Identify the root cause of the failure
|
|
44
|
+
3. Generate CORRECTED Ruby DSL code that addresses ALL errors
|
|
45
|
+
4. Ensure the code:
|
|
46
|
+
- Fixes the specific errors mentioned
|
|
47
|
+
- Uses only available tools: {{.ToolsList}}
|
|
48
|
+
- Uses only available models: {{.ModelsList}}
|
|
49
|
+
- Follows the Language Operator DSL syntax exactly
|
|
50
|
+
- Does NOT use any dangerous Ruby methods (system, eval, etc.)
|
|
51
|
+
|
|
52
|
+
This is attempt {{.AttemptNumber}} of {{.MaxAttempts}}. The user is counting on you to get it right!
|
|
53
|
+
|
|
54
|
+
{{if .LastKnownGoodCode}}
|
|
55
|
+
### Last Known Working Code (for reference)
|
|
56
|
+
```ruby
|
|
57
|
+
{{.LastKnownGoodCode}}
|
|
58
|
+
```
|
|
59
|
+
{{end}}
|
|
60
|
+
|
|
61
|
+
{{else}}
|
|
62
|
+
## Agent Synthesis Request
|
|
63
|
+
{{end}}
|
|
64
|
+
|
|
65
|
+
**User Instructions:**
|
|
66
|
+
{{.Instructions}}
|
|
67
|
+
|
|
68
|
+
**Available Tools:**
|
|
69
|
+
{{.ToolsList}}
|
|
70
|
+
|
|
71
|
+
**Available Models:**
|
|
72
|
+
{{.ModelsList}}
|
|
73
|
+
|
|
74
|
+
**Agent Name:** {{.AgentName}}
|
|
75
|
+
|
|
76
|
+
**Detected Temporal Intent:** {{.TemporalIntent}}
|
|
77
|
+
|
|
78
|
+
**Runtime Context:**
|
|
79
|
+
- All agent messages and output are automatically logged to stdout
|
|
80
|
+
- Agents have access to a workspace directory for file operations
|
|
81
|
+
- LLM responses are captured and available in agent execution context
|
|
82
|
+
|
|
83
|
+
## DSL v1 Reference Examples
|
|
84
|
+
|
|
85
|
+
Study these examples to understand the task/main model with organic functions:
|
|
86
|
+
|
|
87
|
+
### Example 1: Simple Scheduled Agent (Fully Neural)
|
|
88
|
+
```ruby
|
|
89
|
+
agent "daily-report" do
|
|
90
|
+
description "Generate daily sales report"
|
|
91
|
+
mode :scheduled
|
|
92
|
+
schedule "0 9 * * *"
|
|
93
|
+
|
|
94
|
+
task :fetch_sales,
|
|
95
|
+
instructions: "fetch yesterday's sales data from database",
|
|
96
|
+
inputs: {},
|
|
97
|
+
outputs: { sales: 'array', total: 'number' }
|
|
98
|
+
|
|
99
|
+
task :generate_report,
|
|
100
|
+
instructions: "create a markdown report summarizing the sales",
|
|
101
|
+
inputs: { sales: 'array', total: 'number' },
|
|
102
|
+
outputs: { report: 'string' }
|
|
103
|
+
|
|
104
|
+
main do |inputs|
|
|
105
|
+
sales_data = execute_task(:fetch_sales)
|
|
106
|
+
report = execute_task(:generate_report, inputs: sales_data)
|
|
107
|
+
report
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
output do |outputs|
|
|
111
|
+
puts outputs[:report]
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### Example 2: Hybrid Neural-Symbolic Agent
|
|
117
|
+
```ruby
|
|
118
|
+
agent "code-reviewer" do
|
|
119
|
+
description "Review pull requests"
|
|
120
|
+
|
|
121
|
+
# Symbolic task - deterministic tool call
|
|
122
|
+
task :fetch_pr_diff do |inputs|
|
|
123
|
+
{
|
|
124
|
+
diff: execute_tool('github', 'get_pr_diff', pr_number: inputs[:pr_number])
|
|
125
|
+
}
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
# Neural task - creative analysis
|
|
129
|
+
task :analyze_code,
|
|
130
|
+
instructions: "review code changes for bugs and improvements",
|
|
131
|
+
inputs: { diff: 'string' },
|
|
132
|
+
outputs: { issues: 'array', severity: 'string' }
|
|
133
|
+
|
|
134
|
+
# Symbolic task - deterministic logic
|
|
135
|
+
task :should_approve do |inputs|
|
|
136
|
+
critical = inputs[:issues].select { |i| i['severity'] == 'critical' }
|
|
137
|
+
{ approve: critical.empty? }
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
main do |inputs|
|
|
141
|
+
pr_data = execute_task(:fetch_pr_diff, inputs: inputs)
|
|
142
|
+
analysis = execute_task(:analyze_code, inputs: pr_data)
|
|
143
|
+
decision = execute_task(:should_approve, inputs: analysis)
|
|
144
|
+
analysis.merge(decision)
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
### Example 3: Multi-Step Agent with Tools
|
|
150
|
+
```ruby
|
|
151
|
+
agent "data-pipeline" do
|
|
152
|
+
description "ETL pipeline"
|
|
153
|
+
|
|
154
|
+
task :extract_data,
|
|
155
|
+
instructions: "extract data from the source database",
|
|
156
|
+
inputs: { source: 'string' },
|
|
157
|
+
outputs: { records: 'array', count: 'integer' }
|
|
158
|
+
|
|
159
|
+
task :transform_data,
|
|
160
|
+
instructions: "clean and normalize the records",
|
|
161
|
+
inputs: { records: 'array' },
|
|
162
|
+
outputs: { cleaned_records: 'array' }
|
|
163
|
+
|
|
164
|
+
task :load_data,
|
|
165
|
+
instructions: "load cleaned records into warehouse",
|
|
166
|
+
inputs: { cleaned_records: 'array' },
|
|
167
|
+
outputs: { success: 'boolean', loaded_count: 'integer' }
|
|
168
|
+
|
|
169
|
+
main do |inputs|
|
|
170
|
+
extracted = execute_task(:extract_data, inputs: inputs)
|
|
171
|
+
transformed = execute_task(:transform_data, inputs: extracted)
|
|
172
|
+
result = execute_task(:load_data, inputs: transformed)
|
|
173
|
+
result
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
## Your Task: Generate DSL v1 Agent
|
|
179
|
+
|
|
180
|
+
Using the examples above as reference, generate Ruby DSL code in this format:
|
|
181
|
+
|
|
182
|
+
```ruby
|
|
183
|
+
require 'language_operator'
|
|
184
|
+
|
|
185
|
+
agent "{{.AgentName}}" do
|
|
186
|
+
description "Brief description extracted from instructions"
|
|
187
|
+
{{.PersonaSection}}{{.ScheduleSection}}
|
|
188
|
+
# Break down instructions into tasks
|
|
189
|
+
# Each task needs:
|
|
190
|
+
# - instructions: what to do (for neural tasks)
|
|
191
|
+
# - inputs: hash with parameter types
|
|
192
|
+
# - outputs: hash with result types
|
|
193
|
+
|
|
194
|
+
task :task_name,
|
|
195
|
+
instructions: "clear description of what this task does",
|
|
196
|
+
inputs: { param_name: 'type' },
|
|
197
|
+
outputs: { result_name: 'type' }
|
|
198
|
+
|
|
199
|
+
# For symbolic tasks (when logic is simple/deterministic), use code blocks:
|
|
200
|
+
# task :task_name do |inputs|
|
|
201
|
+
# { result: inputs[:param] * 2 }
|
|
202
|
+
# end
|
|
203
|
+
|
|
204
|
+
# REQUIRED: main block defines execution flow
|
|
205
|
+
main do |inputs|
|
|
206
|
+
result1 = execute_task(:task_name, inputs: { param_name: value })
|
|
207
|
+
# Chain tasks by passing outputs as inputs
|
|
208
|
+
result2 = execute_task(:another_task, inputs: result1)
|
|
209
|
+
result2 # Return final result
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
{{.ConstraintsSection}}
|
|
213
|
+
|
|
214
|
+
# Output handling
|
|
215
|
+
output do |outputs|
|
|
216
|
+
# Save results, send notifications, etc.
|
|
217
|
+
puts "Agent completed: #{outputs.inspect}"
|
|
218
|
+
end
|
|
219
|
+
end
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
**Type System:**
|
|
223
|
+
- `'string'` - text values
|
|
224
|
+
- `'integer'` - whole numbers
|
|
225
|
+
- `'number'` - decimal numbers
|
|
226
|
+
- `'boolean'` - true/false
|
|
227
|
+
- `'array'` - lists
|
|
228
|
+
- `'hash'` - key-value objects
|
|
229
|
+
- `'any'` - any type
|
|
230
|
+
|
|
231
|
+
**Rules:**
|
|
232
|
+
1. Generate ONLY the Ruby code within triple-backticks, no explanations before or after
|
|
233
|
+
{{.ScheduleRules}}
|
|
234
|
+
3. Break down instructions into clear tasks with type-safe contracts
|
|
235
|
+
4. REQUIRED: Always include a `main` block that calls tasks via `execute_task()`
|
|
236
|
+
5. For simple deterministic tasks, use symbolic code blocks (do |inputs| ... end)
|
|
237
|
+
6. For complex/creative tasks, use neural tasks with instructions
|
|
238
|
+
7. Chain tasks by passing outputs as inputs
|
|
239
|
+
8. Use available tools: {{.ToolsList}}
|
|
240
|
+
9. Type schemas are REQUIRED for all tasks (inputs/outputs hashes)
|
|
241
|
+
10. Use the agent name: "{{.AgentName}}"
|
|
242
|
+
|
|
243
|
+
Generate the code now:
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"$id": "https://github.com/language-operator/language-operator-gem/schema/agent-dsl.json",
|
|
4
4
|
"title": "Language Operator Agent DSL",
|
|
5
5
|
"description": "Schema for defining autonomous AI agents using the Language Operator DSL",
|
|
6
|
-
"version": "0.1.
|
|
6
|
+
"version": "0.1.37",
|
|
7
7
|
"type": "object",
|
|
8
8
|
"properties": {
|
|
9
9
|
"name": {
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: language-operator
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.37
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- James Ryan
|
|
@@ -135,6 +135,20 @@ dependencies:
|
|
|
135
135
|
- - "~>"
|
|
136
136
|
- !ruby/object:Gem::Version
|
|
137
137
|
version: '3.9'
|
|
138
|
+
- !ruby/object:Gem::Dependency
|
|
139
|
+
name: faraday
|
|
140
|
+
requirement: !ruby/object:Gem::Requirement
|
|
141
|
+
requirements:
|
|
142
|
+
- - "~>"
|
|
143
|
+
- !ruby/object:Gem::Version
|
|
144
|
+
version: '2.0'
|
|
145
|
+
type: :runtime
|
|
146
|
+
prerelease: false
|
|
147
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
148
|
+
requirements:
|
|
149
|
+
- - "~>"
|
|
150
|
+
- !ruby/object:Gem::Version
|
|
151
|
+
version: '2.0'
|
|
138
152
|
- !ruby/object:Gem::Dependency
|
|
139
153
|
name: k8s-ruby
|
|
140
154
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -397,7 +411,6 @@ extra_rdoc_files: []
|
|
|
397
411
|
files:
|
|
398
412
|
- ".rubocop.yml"
|
|
399
413
|
- CHANGELOG.md
|
|
400
|
-
- CI_STATUS.md
|
|
401
414
|
- Gemfile
|
|
402
415
|
- Gemfile.lock
|
|
403
416
|
- LICENSE
|
|
@@ -506,8 +519,8 @@ files:
|
|
|
506
519
|
- lib/language_operator/retryable.rb
|
|
507
520
|
- lib/language_operator/synthesis_test_harness.rb
|
|
508
521
|
- lib/language_operator/templates/README.md
|
|
509
|
-
- lib/language_operator/templates/
|
|
510
|
-
- lib/language_operator/templates/
|
|
522
|
+
- lib/language_operator/templates/agent_synthesis.tmpl
|
|
523
|
+
- lib/language_operator/templates/persona_distillation.tmpl
|
|
511
524
|
- lib/language_operator/templates/schema/.gitkeep
|
|
512
525
|
- lib/language_operator/templates/schema/CHANGELOG.md
|
|
513
526
|
- lib/language_operator/templates/schema/agent_dsl_openapi.yaml
|
data/CI_STATUS.md
DELETED
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
# CI Integration Test Status
|
|
2
|
-
|
|
3
|
-
## Summary
|
|
4
|
-
|
|
5
|
-
The CI integration tests are significantly improved from their previous completely broken state.
|
|
6
|
-
|
|
7
|
-
### Fixed Issues
|
|
8
|
-
|
|
9
|
-
1. **Numeric Constant Error** ✅
|
|
10
|
-
- **Problem**: SafeExecutor sandbox was blocking access to Ruby type constants (Numeric, Integer, Float, etc.)
|
|
11
|
-
- **Solution**: Inject type constants into the evaluated code scope in SafeExecutor#eval
|
|
12
|
-
- **Impact**: All symbolic tasks using type checking now work correctly
|
|
13
|
-
|
|
14
|
-
2. **Neural Task Connection Errors** ✅
|
|
15
|
-
- **Problem**: Agent tried to connect to real LLM when INTEGRATION_MOCK_LLM=true, failing with "Not connected"
|
|
16
|
-
- **Solution**: Create mock chat object in create_test_agent when mocking is enabled
|
|
17
|
-
- **Impact**: Neural tasks can now execute without real LLM connection
|
|
18
|
-
|
|
19
|
-
3. **Deep Symbol Keys** ✅
|
|
20
|
-
- **Problem**: Nested hashes in neural task outputs had string keys, tests expected symbol keys
|
|
21
|
-
- **Solution**: Implement deep_symbolize_keys in TaskExecutor#parse_neural_response
|
|
22
|
-
- **Impact**: Nested hash structures now match test expectations
|
|
23
|
-
|
|
24
|
-
4. **Multi-Provider LLM Support** ✅
|
|
25
|
-
- **Problem**: Tests only supported OpenAI
|
|
26
|
-
- **Solution**: Added support for SYNTHESIS_*, ANTHROPIC_*, and OPENAI_API_KEY env vars
|
|
27
|
-
- **Impact**: Tests can use local models, Claude, or OpenAI
|
|
28
|
-
|
|
29
|
-
### Current Test Status
|
|
30
|
-
|
|
31
|
-
**Passing Tests** (28/72, 39%):
|
|
32
|
-
- ✅ Comprehensive DSL v1 Integration (all 4 scenarios)
|
|
33
|
-
- ✅ Symbolic Task Execution (complete)
|
|
34
|
-
- ✅ Error Handling (skipped DSL syntax issues)
|
|
35
|
-
- ✅ Type Coercion (partial)
|
|
36
|
-
|
|
37
|
-
**Failing Tests** (44/72, 61%):
|
|
38
|
-
- ❌ Neural Task Execution - individual mocks don't match all output schemas
|
|
39
|
-
- ❌ Hybrid Agent Execution - some neural tasks failing
|
|
40
|
-
- ❌ Parallel Execution - some neural tasks failing
|
|
41
|
-
|
|
42
|
-
**Pending Tests**: 20 (performance benchmarks disabled)
|
|
43
|
-
|
|
44
|
-
### Recommendations
|
|
45
|
-
|
|
46
|
-
For full CI coverage with mocked LLMs, consider:
|
|
47
|
-
1. Use real LLM in CI (with API key secrets) instead of mocking
|
|
48
|
-
2. Add schema-aware mock generation based on task output definitions
|
|
49
|
-
3. Add individual mocks for each failing neural task (tedious but thorough)
|
|
50
|
-
|
|
51
|
-
### Bottom Line
|
|
52
|
-
|
|
53
|
-
**Before**: 100% failure rate - all tests broken
|
|
54
|
-
**After**: 39% pass rate with core functionality working
|
|
55
|
-
|
|
56
|
-
The most critical tests (comprehensive integration) now pass. The CI is in a MUCH better state than before.
|
|
@@ -1,133 +0,0 @@
|
|
|
1
|
-
You are generating Ruby DSL code for an autonomous agent in a Kubernetes operator.
|
|
2
|
-
|
|
3
|
-
{{if .ErrorContext}}
|
|
4
|
-
## IMPORTANT: Self-Healing Synthesis - Attempt {{.AttemptNumber}}
|
|
5
|
-
|
|
6
|
-
The previous code synthesis encountered errors. Please analyze the errors below and generate CORRECTED code.
|
|
7
|
-
|
|
8
|
-
### Previous Synthesis Failures
|
|
9
|
-
|
|
10
|
-
{{if .ErrorContext.ValidationErrors}}
|
|
11
|
-
**Validation Errors** (detected during code generation):
|
|
12
|
-
{{range .ErrorContext.ValidationErrors}}
|
|
13
|
-
- {{.}}
|
|
14
|
-
{{end}}
|
|
15
|
-
{{end}}
|
|
16
|
-
|
|
17
|
-
{{if .ErrorContext.RuntimeErrors}}
|
|
18
|
-
**Runtime Errors** (detected during execution):
|
|
19
|
-
{{range .ErrorContext.RuntimeErrors}}
|
|
20
|
-
- Time: {{.Timestamp}}
|
|
21
|
-
- Type: {{.ErrorType}}
|
|
22
|
-
- Message: {{.ErrorMessage}}
|
|
23
|
-
{{if .StackTrace}}
|
|
24
|
-
- Stack Trace:
|
|
25
|
-
{{range .StackTrace}}
|
|
26
|
-
{{.}}
|
|
27
|
-
{{end}}
|
|
28
|
-
{{end}}
|
|
29
|
-
- Exit Code: {{.ContainerExitCode}}
|
|
30
|
-
{{end}}
|
|
31
|
-
{{end}}
|
|
32
|
-
|
|
33
|
-
{{if .ErrorContext.LastCrashLog}}
|
|
34
|
-
**Last Container Logs** (before crash):
|
|
35
|
-
```
|
|
36
|
-
{{.ErrorContext.LastCrashLog}}
|
|
37
|
-
```
|
|
38
|
-
{{end}}
|
|
39
|
-
|
|
40
|
-
### Your Task
|
|
41
|
-
|
|
42
|
-
1. Carefully analyze each error above
|
|
43
|
-
2. Identify the root cause of the failure
|
|
44
|
-
3. Generate CORRECTED Ruby DSL code that addresses ALL errors
|
|
45
|
-
4. Ensure the code:
|
|
46
|
-
- Fixes the specific errors mentioned
|
|
47
|
-
- Uses only available tools: {{.ToolsList}}
|
|
48
|
-
- Uses only available models: {{.ModelsList}}
|
|
49
|
-
- Follows the Language Operator DSL syntax exactly
|
|
50
|
-
- Does NOT use any dangerous Ruby methods (system, eval, etc.)
|
|
51
|
-
|
|
52
|
-
This is attempt {{.AttemptNumber}} of {{.MaxAttempts}}. The user is counting on you to get it right!
|
|
53
|
-
|
|
54
|
-
{{if .LastKnownGoodCode}}
|
|
55
|
-
### Last Known Working Code (for reference)
|
|
56
|
-
```ruby
|
|
57
|
-
{{.LastKnownGoodCode}}
|
|
58
|
-
```
|
|
59
|
-
{{end}}
|
|
60
|
-
|
|
61
|
-
{{else}}
|
|
62
|
-
## Agent Synthesis Request
|
|
63
|
-
{{end}}
|
|
64
|
-
|
|
65
|
-
**User Instructions:**
|
|
66
|
-
{{.Instructions}}
|
|
67
|
-
|
|
68
|
-
**Available Tools:**
|
|
69
|
-
{{.ToolsList}}
|
|
70
|
-
|
|
71
|
-
**Available Models:**
|
|
72
|
-
{{.ModelsList}}
|
|
73
|
-
|
|
74
|
-
**Agent Name:** {{.AgentName}}
|
|
75
|
-
|
|
76
|
-
**Detected Temporal Intent:** {{.TemporalIntent}}
|
|
77
|
-
|
|
78
|
-
**Runtime Context:**
|
|
79
|
-
- All agent messages and output are automatically logged to stdout
|
|
80
|
-
- Agents have access to a workspace directory for file operations
|
|
81
|
-
- LLM responses are captured and available in agent execution context
|
|
82
|
-
|
|
83
|
-
Generate Ruby DSL code using this exact format (wrapped in triple-backticks with ruby):
|
|
84
|
-
|
|
85
|
-
```ruby
|
|
86
|
-
require 'language_operator'
|
|
87
|
-
|
|
88
|
-
agent "{{.AgentName}}" do
|
|
89
|
-
description "Brief description extracted from instructions"
|
|
90
|
-
{{.PersonaSection}}{{.ScheduleSection}}
|
|
91
|
-
# Extract objectives from instructions
|
|
92
|
-
objectives [
|
|
93
|
-
"First objective",
|
|
94
|
-
"Second objective"
|
|
95
|
-
]
|
|
96
|
-
|
|
97
|
-
# REQUIRED: Define workflow with at least one step
|
|
98
|
-
workflow do
|
|
99
|
-
# Use tools when available
|
|
100
|
-
step :step_name, tool: "tool_name", params: {key: "value"}
|
|
101
|
-
|
|
102
|
-
# Or use execute blocks for custom Ruby code (simple logging, calculations, etc.)
|
|
103
|
-
step :custom_step do
|
|
104
|
-
execute do
|
|
105
|
-
puts "Custom output from Ruby code"
|
|
106
|
-
{ result: "done" }
|
|
107
|
-
end
|
|
108
|
-
end
|
|
109
|
-
|
|
110
|
-
# Chain steps with dependencies if needed
|
|
111
|
-
step :another_step, depends_on: :step_name
|
|
112
|
-
end
|
|
113
|
-
|
|
114
|
-
{{.ConstraintsSection}}
|
|
115
|
-
|
|
116
|
-
# Output configuration (if workspace enabled)
|
|
117
|
-
output do
|
|
118
|
-
workspace "results/output.txt"
|
|
119
|
-
end
|
|
120
|
-
end
|
|
121
|
-
```
|
|
122
|
-
|
|
123
|
-
**Rules:**
|
|
124
|
-
1. Generate ONLY the Ruby code within triple-backticks, no explanations before or after
|
|
125
|
-
{{.ScheduleRules}}
|
|
126
|
-
5. Break down instructions into clear, actionable objectives
|
|
127
|
-
6. REQUIRED: Always include a workflow block with at least one step (even for simple single-action agents)
|
|
128
|
-
7. For simple tasks (logging, calculations), use a single step with an execute block containing Ruby code
|
|
129
|
-
8. For complex tasks, use multiple steps with tools or execute blocks
|
|
130
|
-
9. Use available tools in workflow steps when tools are provided
|
|
131
|
-
10. Use the agent name: "{{.AgentName}}"
|
|
132
|
-
|
|
133
|
-
Generate the code now:
|
|
File without changes
|