language-operator 0.1.58 → 0.1.61
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/components/agent/Gemfile +1 -1
- data/lib/language_operator/agent/base.rb +22 -0
- data/lib/language_operator/agent/task_executor.rb +80 -23
- 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 +575 -0
- 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 +74 -2
- data/lib/language_operator/client/mcp_connector.rb +4 -6
- data/lib/language_operator/dsl/task_definition.rb +7 -6
- data/lib/language_operator/learning/adapters/base_adapter.rb +149 -0
- data/lib/language_operator/learning/adapters/jaeger_adapter.rb +221 -0
- data/lib/language_operator/learning/adapters/signoz_adapter.rb +435 -0
- data/lib/language_operator/learning/adapters/tempo_adapter.rb +239 -0
- data/lib/language_operator/learning/optimizer.rb +319 -0
- data/lib/language_operator/learning/pattern_detector.rb +260 -0
- data/lib/language_operator/learning/task_synthesizer.rb +288 -0
- data/lib/language_operator/learning/trace_analyzer.rb +285 -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 +98 -0
- data/lib/language_operator/ux/concerns/provider_helpers.rb +2 -2
- data/lib/language_operator/version.rb +1 -1
- data/synth/003/Makefile +10 -3
- 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: 614dadae712dd0b6c1b72314497c73cdbcba8aa247a63cf3d01dbd789b0becaf
|
|
4
|
+
data.tar.gz: 72ad29bec20d5fdc6da1f235cd054617567c2516522e1a45bbd52f09c15bdb65
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: cd062a5b0420dd602f411dd30b1e6fd7aa3dd7bce77ddbf7e94af95eb6991b609f57aa797c59f519150e518e89ba0839b43ca5be9a216a576b26ad9bb3b05ba0
|
|
7
|
+
data.tar.gz: cfdaa40cc2ced23bd59343b6c995499e9c718c1782f8c8c50766d1180399adc2b40c6fafd21865bb0c9130f1e9c46713639faa7985afa739f28bc47d4e66f993
|
data/Gemfile.lock
CHANGED
data/components/agent/Gemfile
CHANGED
|
@@ -29,6 +29,9 @@ module LanguageOperator
|
|
|
29
29
|
def initialize(config)
|
|
30
30
|
super
|
|
31
31
|
|
|
32
|
+
# Log version
|
|
33
|
+
logger.info "Language Operator v#{LanguageOperator::VERSION}"
|
|
34
|
+
|
|
32
35
|
# Initialize OpenTelemetry
|
|
33
36
|
LanguageOperator::Agent::Telemetry.configure
|
|
34
37
|
otel_enabled = !ENV.fetch('OTEL_EXPORTER_OTLP_ENDPOINT', nil).nil?
|
|
@@ -63,6 +66,9 @@ module LanguageOperator
|
|
|
63
66
|
else
|
|
64
67
|
raise "Unknown agent mode: #{normalized_mode}"
|
|
65
68
|
end
|
|
69
|
+
ensure
|
|
70
|
+
# Flush telemetry for short-lived processes (scheduled mode)
|
|
71
|
+
flush_telemetry if normalized_mode == 'scheduled'
|
|
66
72
|
end
|
|
67
73
|
end
|
|
68
74
|
|
|
@@ -112,6 +118,22 @@ module LanguageOperator
|
|
|
112
118
|
@web_server = WebServer.new(self)
|
|
113
119
|
@web_server.start
|
|
114
120
|
end
|
|
121
|
+
|
|
122
|
+
# Flush OpenTelemetry spans to ensure they're exported before process exits
|
|
123
|
+
#
|
|
124
|
+
# Critical for short-lived processes (CronJobs) that exit quickly.
|
|
125
|
+
# BatchSpanProcessor buffers spans and exports periodically, so without
|
|
126
|
+
# explicit flushing, spans may be lost when the process terminates.
|
|
127
|
+
#
|
|
128
|
+
# @return [void]
|
|
129
|
+
def flush_telemetry
|
|
130
|
+
return unless ENV.fetch('OTEL_EXPORTER_OTLP_ENDPOINT', nil)
|
|
131
|
+
|
|
132
|
+
OpenTelemetry.tracer_provider.force_flush
|
|
133
|
+
logger.info('OpenTelemetry spans flushed to OTLP endpoint')
|
|
134
|
+
rescue StandardError => e
|
|
135
|
+
logger.warn("Failed to flush telemetry: #{e.message}")
|
|
136
|
+
end
|
|
115
137
|
end
|
|
116
138
|
end
|
|
117
139
|
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,47 @@ module LanguageOperator
|
|
|
214
215
|
|
|
215
216
|
# Helper method for symbolic tasks to execute tools
|
|
216
217
|
#
|
|
217
|
-
#
|
|
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
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
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
|
+
# Extract text from MCP Content objects
|
|
238
|
+
text_result = if result.is_a?(RubyLLM::MCP::Content)
|
|
239
|
+
result.text
|
|
240
|
+
elsif result.respond_to?(:map) && result.first.is_a?(RubyLLM::MCP::Content)
|
|
241
|
+
result.map(&:text).join
|
|
242
|
+
else
|
|
243
|
+
result
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
logger.debug('Tool call completed',
|
|
247
|
+
tool: tool_name_str,
|
|
248
|
+
result_preview: text_result.is_a?(String) ? text_result[0..200] : text_result.class.name)
|
|
249
|
+
|
|
250
|
+
# Try to parse JSON response if it looks like JSON
|
|
251
|
+
if text_result.is_a?(String) && (text_result.strip.start_with?('{') || text_result.strip.start_with?('['))
|
|
252
|
+
JSON.parse(text_result, symbolize_names: true)
|
|
253
|
+
else
|
|
254
|
+
text_result
|
|
255
|
+
end
|
|
256
|
+
rescue JSON::ParserError
|
|
257
|
+
# Not JSON, return as-is
|
|
258
|
+
text_result
|
|
232
259
|
end
|
|
233
260
|
|
|
234
261
|
# Helper method for symbolic tasks to call LLM directly
|
|
@@ -291,6 +318,25 @@ module LanguageOperator
|
|
|
291
318
|
'Agent::TaskExecutor'
|
|
292
319
|
end
|
|
293
320
|
|
|
321
|
+
# Summarize hash values for logging (truncate long strings)
|
|
322
|
+
#
|
|
323
|
+
# @param hash [Hash] Hash to summarize
|
|
324
|
+
# @return [Hash] Summarized hash with truncated values
|
|
325
|
+
def summarize_values(hash)
|
|
326
|
+
return {} unless hash.is_a?(Hash)
|
|
327
|
+
|
|
328
|
+
hash.transform_values do |v|
|
|
329
|
+
case v
|
|
330
|
+
when String
|
|
331
|
+
v.length > 100 ? "#{v[0..97]}... (#{v.length} chars)" : v
|
|
332
|
+
when Array
|
|
333
|
+
v.length > 5 ? "#{v.first(3).inspect}... (#{v.length} items)" : v.inspect
|
|
334
|
+
else
|
|
335
|
+
v.inspect
|
|
336
|
+
end
|
|
337
|
+
end
|
|
338
|
+
end
|
|
339
|
+
|
|
294
340
|
# Build prompt for neural task execution
|
|
295
341
|
#
|
|
296
342
|
# @param task [TaskDefinition] The task definition
|
|
@@ -316,10 +362,11 @@ module LanguageOperator
|
|
|
316
362
|
prompt += "\n"
|
|
317
363
|
|
|
318
364
|
prompt += "## Response Format\n"
|
|
319
|
-
prompt += "
|
|
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"
|
|
365
|
+
prompt += "You may include your reasoning in [THINK]...[/THINK] tags if helpful.\n"
|
|
322
366
|
prompt += "Use available tools as needed to complete the task.\n"
|
|
367
|
+
prompt += "After using tools (if needed), return your final answer as valid JSON matching the output schema above.\n"
|
|
368
|
+
prompt += "Your final JSON response should come after any tool calls and thinking.\n"
|
|
369
|
+
prompt += "Do not include explanations outside of [THINK] tags - only the JSON output.\n"
|
|
323
370
|
|
|
324
371
|
prompt
|
|
325
372
|
end
|
|
@@ -342,8 +389,15 @@ module LanguageOperator
|
|
|
342
389
|
thinking_preview: thinking_blocks.first&.[](0..500))
|
|
343
390
|
end
|
|
344
391
|
|
|
345
|
-
# Strip thinking tags that some models add (e.g., [THINK]...[/THINK])
|
|
346
|
-
|
|
392
|
+
# Strip thinking tags that some models add (e.g., [THINK]...[/THINK] or unclosed [THINK]...)
|
|
393
|
+
# First try to strip matched pairs, then strip any remaining unclosed [THINK] content
|
|
394
|
+
logger.debug('Parsing neural response', task: task.name, response_length: response_text.length, response_start: response_text[0..100])
|
|
395
|
+
|
|
396
|
+
cleaned_text = response_text.gsub(%r{\[THINK\].*?\[/THINK\]}m, '')
|
|
397
|
+
.gsub(/\[THINK\].*?(?=\{|$)/m, '')
|
|
398
|
+
.strip
|
|
399
|
+
|
|
400
|
+
logger.debug('After stripping THINK tags', cleaned_length: cleaned_text.length, cleaned_start: cleaned_text[0..100])
|
|
347
401
|
|
|
348
402
|
# Try to extract JSON from response
|
|
349
403
|
# Look for JSON code blocks first
|
|
@@ -356,6 +410,8 @@ module LanguageOperator
|
|
|
356
410
|
json_object_match ? json_object_match[0] : cleaned_text
|
|
357
411
|
end
|
|
358
412
|
|
|
413
|
+
logger.debug('Extracted JSON text', json_length: json_text.length, json_start: json_text[0..100])
|
|
414
|
+
|
|
359
415
|
# Parse JSON
|
|
360
416
|
parsed = JSON.parse(json_text)
|
|
361
417
|
|
|
@@ -503,10 +559,11 @@ module LanguageOperator
|
|
|
503
559
|
end
|
|
504
560
|
|
|
505
561
|
execution_time = Time.now - attempt_start
|
|
506
|
-
logger.
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
562
|
+
logger.info('Task completed',
|
|
563
|
+
task: task_name,
|
|
564
|
+
attempt: attempt + 1,
|
|
565
|
+
execution_time: execution_time.round(3),
|
|
566
|
+
outputs: summarize_values(result))
|
|
510
567
|
|
|
511
568
|
result
|
|
512
569
|
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
|