Module: LanguageOperator::CLI::Commands::System::Synthesize
- Included in:
- Base
- Defined in:
- lib/language_operator/cli/commands/system/synthesize.rb
Overview
Agent code synthesis command
Class Method Summary collapse
Class Method Details
.included(base) ⇒ Object
9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 |
# File 'lib/language_operator/cli/commands/system/synthesize.rb', line 9 def self.included(base) base.class_eval do desc 'synthesize [INSTRUCTIONS]', 'Synthesize agent code from natural language instructions' long_desc <<-DESC Synthesize agent code by converting natural language instructions into Ruby DSL code without creating an actual agent. This command uses a LanguageModel resource from your cluster to generate agent code. If --model is not specified, the first available model will be auto-selected. Instructions can be provided either as a command argument or via STDIN. If no argument is provided, the command will read from STDIN. This command helps you validate your instructions and understand how the synthesis engine interprets them. Use --dry-run to see the prompt that would be sent to the LLM, or run without it to generate actual code. Examples: # Test with dry-run (show prompt only) langop system synthesize "Monitor GitHub issues daily" --dry-run # Generate code from instructions (auto-selects first available model) langop system synthesize "Send daily reports to Slack" # Use a specific cluster model langop system synthesize "Process webhooks from GitHub" --model my-claude # Output raw code without formatting (useful for piping to files) langop system synthesize "Monitor logs" --raw > agent.rb # Read instructions from STDIN cat instructions.txt | langop system synthesize > agent.rb # Read from STDIN with pipe echo "Monitor GitHub issues" | langop system synthesize --raw # Specify custom agent name and tools langop system synthesize "Process webhooks from GitHub" \\ --agent-name github-processor \\ --tools github,slack \\ --model my-gpt4 DESC option :agent_name, type: :string, default: 'test-agent', desc: 'Name for the test agent' option :tools, type: :string, desc: 'Comma-separated list of available tools' option :models, type: :string, desc: 'Comma-separated list of available models (from cluster)' option :model, type: :string, desc: 'Model to use for synthesis (defaults to first available in cluster)' option :dry_run, type: :boolean, default: false, desc: 'Show prompt without calling LLM' option :raw, type: :boolean, default: false, desc: 'Output only the raw code without formatting' def synthesize(instructions = nil) handle_command_error('synthesize agent') do # Read instructions from STDIN if not provided as argument if instructions.nil? || instructions.strip.empty? if $stdin.tty? Formatters::ProgressFormatter.error('No instructions provided') puts puts 'Provide instructions either as an argument or via STDIN:' puts ' langop system synthesize "Your instructions here"' puts ' cat instructions.txt | langop system synthesize' exit 1 else instructions = $stdin.read.strip if instructions.empty? Formatters::ProgressFormatter.error('No instructions provided') puts puts 'Provide instructions either as an argument or via STDIN:' puts ' langop system synthesize "Your instructions here"' puts ' cat instructions.txt | langop system synthesize' exit 1 end end end # Select model to use for synthesis selected_model = select_synthesis_model # Load synthesis template template_content = load_bundled_template('agent') # Detect temporal intent from instructions temporal_intent = detect_temporal_intent(instructions) # Prepare template data template_data = { 'Instructions' => instructions, 'AgentName' => [:agent_name], 'ToolsList' => format_tools_list([:tools]), 'ModelsList' => format_models_list([:models]), 'TemporalIntent' => temporal_intent, 'PersonaSection' => '', 'ScheduleSection' => temporal_intent == 'scheduled' ? ' schedule "0 */1 * * *" # Example hourly schedule' : '', 'ScheduleRules' => temporal_intent == 'scheduled' ? "\n2. Include schedule with cron expression\n3. Set mode to :scheduled\n4. " : "\n2. ", 'ConstraintsSection' => '', 'ErrorContext' => nil } # Render template (Go-style template syntax) rendered_prompt = render_go_template(template_content, template_data) if [:dry_run] # Show the prompt that would be sent puts 'Synthesis Prompt Preview' puts '=' * 80 puts puts rendered_prompt puts puts '=' * 80 Formatters::ProgressFormatter.success('Dry-run complete - prompt displayed above') return end # Call LLM to generate code (no output - just do it) llm_response = call_llm_for_synthesis(rendered_prompt, selected_model) # Extract Ruby code from response generated_code = extract_ruby_code(llm_response) if generated_code.nil? Formatters::ProgressFormatter.error('Failed to extract Ruby code from LLM response') puts puts 'LLM Response:' puts llm_response exit 1 end # Handle raw output if [:raw] puts generated_code return end # Display formatted code highlighted_code = highlight_ruby_code(generated_code) puts highlighted_code end end private # Detect temporal intent from instructions (scheduled vs autonomous) def detect_temporal_intent(instructions) temporal_keywords = { scheduled: %w[daily weekly hourly monthly schedule cron every day week hour minute], autonomous: %w[monitor watch continuously constantly always loop] } instructions_lower = instructions.downcase # Check for scheduled keywords scheduled_matches = temporal_keywords[:scheduled].count { |keyword| instructions_lower.include?(keyword) } autonomous_matches = temporal_keywords[:autonomous].count { |keyword| instructions_lower.include?(keyword) } scheduled_matches > autonomous_matches ? 'scheduled' : 'autonomous' end # Format tools list for template def format_tools_list(tools_str) return 'No tools specified' if tools_str.nil? || tools_str.strip.empty? tools = tools_str.split(',').map(&:strip) tools.map { |tool| "- #{tool}" }.join("\n") end # Format models list for template def format_models_list(models_str) # If not specified, try to detect from cluster if models_str.nil? || models_str.strip.empty? models = detect_available_models return models.map { |model| "- #{model}" }.join("\n") unless models.empty? return 'No models available (run: langop model list)' end models = models_str.split(',').map(&:strip) models.map { |model| "- #{model}" }.join("\n") end # Detect available models from cluster def detect_available_models models = ctx.client.list_resources('LanguageModel', namespace: ctx.namespace) models.map { |m| m.dig('metadata', 'name') } rescue StandardError => e Formatters::ProgressFormatter.error("Failed to list models from cluster: #{e.}") [] end # Select model to use for synthesis def select_synthesis_model # If --model option specified, use it return [:model] if [:model] # Otherwise, auto-select from available cluster models available_models = detect_available_models if available_models.empty? Formatters::ProgressFormatter.error('No models available in cluster') puts puts 'Please create a model first:' puts ' langop model create' puts puts 'Or list existing models:' puts ' langop model list' exit 1 end # Auto-select first available model (silently) available_models.first end end end |