language-operator 0.1.57 → 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.
- checksums.yaml +4 -4
- data/Gemfile.lock +1 -1
- data/lib/language_operator/agent/base.rb +19 -0
- data/lib/language_operator/agent/executor.rb +11 -0
- data/lib/language_operator/agent/task_executor.rb +77 -22
- data/lib/language_operator/agent/telemetry.rb +22 -11
- data/lib/language_operator/agent.rb +3 -0
- data/lib/language_operator/cli/base_command.rb +7 -1
- data/lib/language_operator/cli/commands/agent.rb +578 -1
- data/lib/language_operator/cli/formatters/optimization_formatter.rb +226 -0
- data/lib/language_operator/cli/formatters/progress_formatter.rb +1 -1
- data/lib/language_operator/client/base.rb +72 -2
- data/lib/language_operator/client/mcp_connector.rb +28 -6
- data/lib/language_operator/instrumentation/task_tracer.rb +64 -2
- data/lib/language_operator/kubernetes/resource_builder.rb +3 -1
- data/lib/language_operator/learning/adapters/base_adapter.rb +147 -0
- data/lib/language_operator/learning/adapters/jaeger_adapter.rb +218 -0
- data/lib/language_operator/learning/adapters/signoz_adapter.rb +432 -0
- data/lib/language_operator/learning/adapters/tempo_adapter.rb +236 -0
- data/lib/language_operator/learning/optimizer.rb +318 -0
- data/lib/language_operator/learning/pattern_detector.rb +260 -0
- data/lib/language_operator/learning/task_synthesizer.rb +261 -0
- data/lib/language_operator/learning/trace_analyzer.rb +280 -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/templates/task_synthesis.tmpl +97 -0
- data/lib/language_operator/tool_loader.rb +5 -3
- data/lib/language_operator/ux/concerns/provider_helpers.rb +2 -2
- data/lib/language_operator/version.rb +1 -1
- data/synth/003/Makefile +10 -0
- data/synth/003/output.log +68 -0
- data/synth/README.md +1 -3
- metadata +12 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 478995080aedadd2299a94fb609d3180634abd352440b985f4eece929275bdf8
|
|
4
|
+
data.tar.gz: f0b890c5825447e6ead0ec172d9467afd59138f92350457e733cf269125e6757
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 55a51a910de5d8580b741690ca56a2ba489cf5424892b6a9db2b31756936b8c8c11b1fe7d85b9c042cd8c56f04ab6f117af027dc28bc6cd47c6e571cbf53d7e6
|
|
7
|
+
data.tar.gz: fcf2dcad6af25f904c806d0a7b0216a7c6aa4d6d3803000096c33c350cb56050387dcb783394534226d1cc52def72628d57157dc54af60e694c229607c796147
|
data/Gemfile.lock
CHANGED
|
@@ -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
|
|
@@ -107,6 +107,17 @@ module LanguageOperator
|
|
|
107
107
|
)
|
|
108
108
|
end
|
|
109
109
|
|
|
110
|
+
# Capture thinking blocks before stripping (for observability)
|
|
111
|
+
thinking_blocks = result_text.scan(%r{\[THINK\](.*?)\[/THINK\]}m).flatten
|
|
112
|
+
if thinking_blocks.any?
|
|
113
|
+
logger.info('LLM thinking captured',
|
|
114
|
+
event: 'llm_thinking',
|
|
115
|
+
iteration: @iteration_count,
|
|
116
|
+
thinking_steps: thinking_blocks.length,
|
|
117
|
+
thinking: thinking_blocks,
|
|
118
|
+
thinking_preview: thinking_blocks.first&.[](0..500))
|
|
119
|
+
end
|
|
120
|
+
|
|
110
121
|
# Log the actual LLM response content (strip [THINK] blocks)
|
|
111
122
|
cleaned_response = result_text.gsub(%r{\[THINK\].*?\[/THINK\]}m, '').strip
|
|
112
123
|
response_preview = cleaned_response.length > 500 ? "#{cleaned_response[0..500]}..." : cleaned_response
|
|
@@ -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)
|
|
@@ -164,10 +165,15 @@ module LanguageOperator
|
|
|
164
165
|
logger.debug('Calling LLM with prompt', task: task.name, prompt_preview: prompt[0..200])
|
|
165
166
|
response = @agent.send_message(prompt)
|
|
166
167
|
|
|
168
|
+
# Check for tool calls and log details
|
|
169
|
+
has_tool_calls = response.respond_to?(:tool_calls) && response.tool_calls&.any?
|
|
170
|
+
tool_call_count = has_tool_calls ? response.tool_calls.length : 0
|
|
171
|
+
|
|
167
172
|
logger.info('LLM response received, extracting content',
|
|
168
173
|
task: task.name,
|
|
169
174
|
response_class: response.class.name,
|
|
170
|
-
has_tool_calls:
|
|
175
|
+
has_tool_calls: has_tool_calls,
|
|
176
|
+
tool_call_count: tool_call_count)
|
|
171
177
|
|
|
172
178
|
response_text = response.is_a?(String) ? response : response.content
|
|
173
179
|
|
|
@@ -209,21 +215,38 @@ module LanguageOperator
|
|
|
209
215
|
|
|
210
216
|
# Helper method for symbolic tasks to execute tools
|
|
211
217
|
#
|
|
212
|
-
#
|
|
213
|
-
# execute_llm to leverage tools through the LLM interface, or call tools
|
|
214
|
-
# directly through the MCP client if needed.
|
|
218
|
+
# Executes an MCP tool directly through the agent's MCP clients.
|
|
215
219
|
#
|
|
216
|
-
# @param tool_name [String] Name of the tool
|
|
217
|
-
# @param action [String] Tool action/method
|
|
220
|
+
# @param tool_name [Symbol, String] Name of the tool to execute
|
|
218
221
|
# @param params [Hash] Tool parameters
|
|
219
|
-
# @return [Object] Tool response
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
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
|
|
227
250
|
end
|
|
228
251
|
|
|
229
252
|
# Helper method for symbolic tasks to call LLM directly
|
|
@@ -286,6 +309,25 @@ module LanguageOperator
|
|
|
286
309
|
'Agent::TaskExecutor'
|
|
287
310
|
end
|
|
288
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
|
+
|
|
289
331
|
# Build prompt for neural task execution
|
|
290
332
|
#
|
|
291
333
|
# @param task [TaskDefinition] The task definition
|
|
@@ -311,10 +353,11 @@ module LanguageOperator
|
|
|
311
353
|
prompt += "\n"
|
|
312
354
|
|
|
313
355
|
prompt += "## Response Format\n"
|
|
314
|
-
prompt += "
|
|
315
|
-
prompt += "Do NOT include any explanations, thinking, or text before or after the JSON.\n"
|
|
316
|
-
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"
|
|
317
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"
|
|
318
361
|
|
|
319
362
|
prompt
|
|
320
363
|
end
|
|
@@ -326,6 +369,17 @@ module LanguageOperator
|
|
|
326
369
|
# @return [Hash] Parsed outputs
|
|
327
370
|
# @raise [RuntimeError] If parsing fails
|
|
328
371
|
def parse_neural_response(response_text, task)
|
|
372
|
+
# Capture thinking blocks before stripping (for observability)
|
|
373
|
+
thinking_blocks = response_text.scan(%r{\[THINK\](.*?)\[/THINK\]}m).flatten
|
|
374
|
+
if thinking_blocks.any?
|
|
375
|
+
logger.info('LLM thinking captured',
|
|
376
|
+
event: 'llm_thinking',
|
|
377
|
+
task: task.name,
|
|
378
|
+
thinking_steps: thinking_blocks.length,
|
|
379
|
+
thinking: thinking_blocks,
|
|
380
|
+
thinking_preview: thinking_blocks.first&.[](0..500))
|
|
381
|
+
end
|
|
382
|
+
|
|
329
383
|
# Strip thinking tags that some models add (e.g., [THINK]...[/THINK])
|
|
330
384
|
cleaned_text = response_text.gsub(%r{\[THINK\].*?\[/THINK\]}m, '').strip
|
|
331
385
|
|
|
@@ -487,10 +541,11 @@ module LanguageOperator
|
|
|
487
541
|
end
|
|
488
542
|
|
|
489
543
|
execution_time = Time.now - attempt_start
|
|
490
|
-
logger.
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
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))
|
|
494
549
|
|
|
495
550
|
result
|
|
496
551
|
rescue Timeout::Error => e
|
|
@@ -26,29 +26,40 @@ module LanguageOperator
|
|
|
26
26
|
#
|
|
27
27
|
# @return [void]
|
|
28
28
|
def configure
|
|
29
|
-
|
|
30
|
-
|
|
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
|
-
#
|
|
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
|
-
#
|
|
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:
|
|
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
|
-
|
|
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
|