language-operator 0.1.58 → 0.1.59

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.
Files changed (29) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile.lock +1 -1
  3. data/lib/language_operator/agent/base.rb +19 -0
  4. data/lib/language_operator/agent/task_executor.rb +60 -21
  5. data/lib/language_operator/agent/telemetry.rb +22 -11
  6. data/lib/language_operator/agent.rb +3 -0
  7. data/lib/language_operator/cli/base_command.rb +7 -1
  8. data/lib/language_operator/cli/commands/agent.rb +575 -0
  9. data/lib/language_operator/cli/formatters/optimization_formatter.rb +226 -0
  10. data/lib/language_operator/cli/formatters/progress_formatter.rb +1 -1
  11. data/lib/language_operator/client/base.rb +72 -2
  12. data/lib/language_operator/client/mcp_connector.rb +4 -6
  13. data/lib/language_operator/learning/adapters/base_adapter.rb +147 -0
  14. data/lib/language_operator/learning/adapters/jaeger_adapter.rb +218 -0
  15. data/lib/language_operator/learning/adapters/signoz_adapter.rb +432 -0
  16. data/lib/language_operator/learning/adapters/tempo_adapter.rb +236 -0
  17. data/lib/language_operator/learning/optimizer.rb +318 -0
  18. data/lib/language_operator/learning/pattern_detector.rb +260 -0
  19. data/lib/language_operator/learning/task_synthesizer.rb +261 -0
  20. data/lib/language_operator/learning/trace_analyzer.rb +280 -0
  21. data/lib/language_operator/templates/schema/agent_dsl_openapi.yaml +1 -1
  22. data/lib/language_operator/templates/schema/agent_dsl_schema.json +1 -1
  23. data/lib/language_operator/templates/task_synthesis.tmpl +97 -0
  24. data/lib/language_operator/ux/concerns/provider_helpers.rb +2 -2
  25. data/lib/language_operator/version.rb +1 -1
  26. data/synth/003/Makefile +8 -1
  27. data/synth/003/output.log +68 -0
  28. data/synth/README.md +1 -3
  29. metadata +12 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: afc74193530a3f556debc6b31b0a43769da67eaf9c956d0a7b4f17477792bf94
4
- data.tar.gz: ffc401b931b0b3bd427c92a36847986442b738f2508acd1206547aa4d14c92eb
3
+ metadata.gz: 478995080aedadd2299a94fb609d3180634abd352440b985f4eece929275bdf8
4
+ data.tar.gz: f0b890c5825447e6ead0ec172d9467afd59138f92350457e733cf269125e6757
5
5
  SHA512:
6
- metadata.gz: 5079fbb3621d2e3d0f00a9381bc7b6fa09c3e28ae029d3e6cde4b7e0ac42643c7881953dd66549f0379414c0a74053dfed301e832812d82902d0de553f791158
7
- data.tar.gz: f24aea2d8cb473e7e9633fe713e663f637391c258695eacdd091b94dc12b206606408162f1d3cf3ae60484aa8097177bd4a858d392b132e07f6aaafadf63f35a
6
+ metadata.gz: 55a51a910de5d8580b741690ca56a2ba489cf5424892b6a9db2b31756936b8c8c11b1fe7d85b9c042cd8c56f04ab6f117af027dc28bc6cd47c6e571cbf53d7e6
7
+ data.tar.gz: fcf2dcad6af25f904c806d0a7b0216a7c6aa4d6d3803000096c33c350cb56050387dcb783394534226d1cc52def72628d57157dc54af60e694c229607c796147
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- language-operator (0.1.58)
4
+ language-operator (0.1.59)
5
5
  faraday (~> 2.0)
6
6
  k8s-ruby (~> 0.17)
7
7
  mcp (~> 0.4)
@@ -63,6 +63,9 @@ module LanguageOperator
63
63
  else
64
64
  raise "Unknown agent mode: #{normalized_mode}"
65
65
  end
66
+ ensure
67
+ # Flush telemetry for short-lived processes (scheduled mode)
68
+ flush_telemetry if normalized_mode == 'scheduled'
66
69
  end
67
70
  end
68
71
 
@@ -112,6 +115,22 @@ module LanguageOperator
112
115
  @web_server = WebServer.new(self)
113
116
  @web_server.start
114
117
  end
118
+
119
+ # Flush OpenTelemetry spans to ensure they're exported before process exits
120
+ #
121
+ # Critical for short-lived processes (CronJobs) that exit quickly.
122
+ # BatchSpanProcessor buffers spans and exports periodically, so without
123
+ # explicit flushing, spans may be lost when the process terminates.
124
+ #
125
+ # @return [void]
126
+ def flush_telemetry
127
+ return unless ENV.fetch('OTEL_EXPORTER_OTLP_ENDPOINT', nil)
128
+
129
+ OpenTelemetry.tracer_provider.force_flush
130
+ logger.info('OpenTelemetry spans flushed to OTLP endpoint')
131
+ rescue StandardError => e
132
+ logger.warn("Failed to flush telemetry: #{e.message}")
133
+ end
115
134
  end
116
135
  end
117
136
  end
@@ -117,7 +117,8 @@ module LanguageOperator
117
117
  task: task_name,
118
118
  type: task_type,
119
119
  timeout: timeout,
120
- max_retries: max_retries)
120
+ max_retries: max_retries,
121
+ inputs: summarize_values(inputs))
121
122
 
122
123
  # Add timeout to span attributes after it's determined
123
124
  OpenTelemetry::Trace.current_span&.set_attribute('task.timeout', timeout)
@@ -214,21 +215,38 @@ module LanguageOperator
214
215
 
215
216
  # Helper method for symbolic tasks to execute tools
216
217
  #
217
- # This is a simplified interface - symbolic tasks should primarily use
218
- # execute_llm to leverage tools through the LLM interface, or call tools
219
- # directly through the MCP client if needed.
218
+ # Executes an MCP tool directly through the agent's MCP clients.
220
219
  #
221
- # @param tool_name [String] Name of the tool
222
- # @param action [String] Tool action/method
220
+ # @param tool_name [Symbol, String] Name of the tool to execute
223
221
  # @param params [Hash] Tool parameters
224
- # @return [Object] Tool response
225
- # @note For DSL v1, tools are accessed via LLM tool calling, not direct invocation
226
- def execute_tool(tool_name, action, params = {})
227
- # Build prompt to use the tool via LLM
228
- prompt = "Use the #{tool_name} tool to perform #{action} with parameters: #{params.inspect}"
229
- execute_llm(prompt)
230
- # Parse response - for now just return the text
231
- # TODO: More sophisticated tool result extraction
222
+ # @return [Object] Tool response (parsed from tool result)
223
+ def execute_tool(tool_name, params = {})
224
+ tool_name_str = tool_name.to_s
225
+
226
+ logger.info('Tool call initiated by symbolic task',
227
+ tool: tool_name_str,
228
+ params: summarize_values(params))
229
+
230
+ # Find the tool across all MCP clients
231
+ tool = @agent.tools.find { |t| t.name == tool_name_str }
232
+ raise ArgumentError, "Tool '#{tool_name_str}' not found" unless tool
233
+
234
+ # Execute the tool (it's a Proc/lambda wrapped by RubyLLM)
235
+ result = tool.call(**params)
236
+
237
+ logger.debug('Tool call completed',
238
+ tool: tool_name_str,
239
+ result_preview: result.is_a?(String) ? result[0..200] : result.class.name)
240
+
241
+ # Try to parse JSON response if it looks like JSON
242
+ if result.is_a?(String) && (result.strip.start_with?('{') || result.strip.start_with?('['))
243
+ JSON.parse(result, symbolize_names: true)
244
+ else
245
+ result
246
+ end
247
+ rescue JSON::ParserError
248
+ # Not JSON, return as-is
249
+ result
232
250
  end
233
251
 
234
252
  # Helper method for symbolic tasks to call LLM directly
@@ -291,6 +309,25 @@ module LanguageOperator
291
309
  'Agent::TaskExecutor'
292
310
  end
293
311
 
312
+ # Summarize hash values for logging (truncate long strings)
313
+ #
314
+ # @param hash [Hash] Hash to summarize
315
+ # @return [Hash] Summarized hash with truncated values
316
+ def summarize_values(hash)
317
+ return {} unless hash.is_a?(Hash)
318
+
319
+ hash.transform_values do |v|
320
+ case v
321
+ when String
322
+ v.length > 100 ? "#{v[0..97]}... (#{v.length} chars)" : v
323
+ when Array
324
+ v.length > 5 ? "#{v.first(3).inspect}... (#{v.length} items)" : v.inspect
325
+ else
326
+ v.inspect
327
+ end
328
+ end
329
+ end
330
+
294
331
  # Build prompt for neural task execution
295
332
  #
296
333
  # @param task [TaskDefinition] The task definition
@@ -316,10 +353,11 @@ module LanguageOperator
316
353
  prompt += "\n"
317
354
 
318
355
  prompt += "## Response Format\n"
319
- prompt += "Return ONLY valid JSON matching the output schema above.\n"
320
- prompt += "Do NOT include any explanations, thinking, or text before or after the JSON.\n"
321
- prompt += "Do NOT use [THINK] tags or any other markup.\n"
356
+ prompt += "You may include your reasoning in [THINK]...[/THINK] tags if helpful.\n"
322
357
  prompt += "Use available tools as needed to complete the task.\n"
358
+ prompt += "After using tools (if needed), return your final answer as valid JSON matching the output schema above.\n"
359
+ prompt += "Your final JSON response should come after any tool calls and thinking.\n"
360
+ prompt += "Do not include explanations outside of [THINK] tags - only the JSON output.\n"
323
361
 
324
362
  prompt
325
363
  end
@@ -503,10 +541,11 @@ module LanguageOperator
503
541
  end
504
542
 
505
543
  execution_time = Time.now - attempt_start
506
- logger.debug('Task execution completed',
507
- task: task_name,
508
- attempt: attempt + 1,
509
- execution_time: execution_time.round(3))
544
+ logger.info('Task completed',
545
+ task: task_name,
546
+ attempt: attempt + 1,
547
+ execution_time: execution_time.round(3),
548
+ outputs: summarize_values(result))
510
549
 
511
550
  result
512
551
  rescue Timeout::Error => e
@@ -26,29 +26,40 @@ module LanguageOperator
26
26
  #
27
27
  # @return [void]
28
28
  def configure
29
- endpoint = ENV.fetch('OTEL_EXPORTER_OTLP_ENDPOINT', nil)
30
- return unless endpoint
29
+ return unless ENV.fetch('OTEL_EXPORTER_OTLP_ENDPOINT', nil)
30
+
31
+ # Configure custom error handler for detailed logging
32
+ OpenTelemetry.error_handler = lambda do |exception: nil, message: nil|
33
+ if exception
34
+ warn "OpenTelemetry error: #{message} - #{exception.class}: #{exception.message}"
35
+ warn exception.backtrace.first(5).join("\n") if exception.backtrace
36
+ else
37
+ warn "OpenTelemetry error: #{message}"
38
+ end
39
+ end
31
40
 
41
+ # Initialize OpenTelemetry SDK with OTLP exporter
42
+ # Uses environment variables set by the operator:
43
+ # - OTEL_EXPORTER_OTLP_ENDPOINT: http://host:port
44
+ # - OTEL_SERVICE_NAME: service name
32
45
  OpenTelemetry::SDK.configure do |c|
33
- c.service_name = 'language-operator-agent'
34
- c.service_version = LanguageOperator::VERSION
46
+ c.service_name = ENV.fetch('OTEL_SERVICE_NAME', 'language-operator-agent')
35
47
 
36
- # Configure resource attributes
37
- c.resource = OpenTelemetry::SDK::Resources::Resource.create(
38
- build_resource_attributes
39
- )
48
+ # Add resource attributes
49
+ c.resource = OpenTelemetry::SDK::Resources::Resource.create(build_resource_attributes)
40
50
 
41
- # Configure OTLP exporter
51
+ # Use OTLP HTTP exporter (reads endpoint from OTEL_EXPORTER_OTLP_ENDPOINT env var)
42
52
  c.add_span_processor(
43
53
  OpenTelemetry::SDK::Trace::Export::BatchSpanProcessor.new(
44
54
  OpenTelemetry::Exporter::OTLP::Exporter.new(
45
- endpoint: endpoint
55
+ endpoint: "#{ENV.fetch('OTEL_EXPORTER_OTLP_ENDPOINT')}/v1/traces",
56
+ headers: {}
46
57
  )
47
58
  )
48
59
  )
49
60
  end
50
61
 
51
- # Restore trace context from TRACEPARENT if present
62
+ # Restore trace context from TRACEPARENT if present for distributed tracing
52
63
  restore_trace_context if ENV['TRACEPARENT']
53
64
  rescue StandardError => e
54
65
  warn "Failed to configure OpenTelemetry: #{e.message}"
@@ -185,6 +185,9 @@ module LanguageOperator
185
185
 
186
186
  logger.info('Scheduled execution completed - exiting',
187
187
  agent_name: agent_def.name)
188
+
189
+ # Flush telemetry for short-lived processes
190
+ agent.send(:flush_telemetry)
188
191
  when 'reactive', 'http', 'webhook'
189
192
  # Start web server with webhooks, MCP tools, and chat endpoint
190
193
  web_server = LanguageOperator::Agent::WebServer.new(agent)
@@ -20,7 +20,13 @@ module LanguageOperator
20
20
  yield
21
21
  rescue StandardError => e
22
22
  Formatters::ProgressFormatter.error("Failed to #{operation}: #{e.message}")
23
- raise if ENV['DEBUG']
23
+
24
+ # Show backtrace for debugging
25
+ if ENV['DEBUG']
26
+ puts "\nBacktrace:"
27
+ puts e.backtrace.join("\n")
28
+ raise
29
+ end
24
30
 
25
31
  exit 1
26
32
  end