ruby_llm-agents 3.5.4 → 3.6.0
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/README.md +4 -0
- data/app/controllers/ruby_llm/agents/dashboard_controller.rb +155 -10
- data/app/helpers/ruby_llm/agents/application_helper.rb +15 -1
- data/app/models/ruby_llm/agents/execution/replayable.rb +124 -0
- data/app/models/ruby_llm/agents/execution/scopes.rb +42 -1
- data/app/models/ruby_llm/agents/execution.rb +50 -1
- data/app/models/ruby_llm/agents/tenant/budgetable.rb +28 -4
- data/app/views/layouts/ruby_llm/agents/application.html.erb +41 -28
- data/app/views/ruby_llm/agents/agents/show.html.erb +16 -1
- data/app/views/ruby_llm/agents/dashboard/_top_tenants.html.erb +47 -0
- data/app/views/ruby_llm/agents/dashboard/index.html.erb +397 -100
- data/lib/generators/ruby_llm_agents/rename_agent_generator.rb +53 -0
- data/lib/generators/ruby_llm_agents/templates/rename_agent_migration.rb.tt +19 -0
- data/lib/ruby_llm/agents/agent_tool.rb +125 -0
- data/lib/ruby_llm/agents/audio/speaker.rb +5 -3
- data/lib/ruby_llm/agents/audio/speech_pricing.rb +63 -187
- data/lib/ruby_llm/agents/audio/transcriber.rb +5 -3
- data/lib/ruby_llm/agents/audio/transcription_pricing.rb +5 -7
- data/lib/ruby_llm/agents/base_agent.rb +144 -5
- data/lib/ruby_llm/agents/core/configuration.rb +178 -53
- data/lib/ruby_llm/agents/core/errors.rb +3 -77
- data/lib/ruby_llm/agents/core/instrumentation.rb +0 -17
- data/lib/ruby_llm/agents/core/version.rb +1 -1
- data/lib/ruby_llm/agents/dsl/base.rb +0 -8
- data/lib/ruby_llm/agents/dsl/queryable.rb +124 -0
- data/lib/ruby_llm/agents/dsl.rb +1 -0
- data/lib/ruby_llm/agents/image/concerns/image_operation_execution.rb +2 -1
- data/lib/ruby_llm/agents/image/generator/pricing.rb +75 -217
- data/lib/ruby_llm/agents/image/generator.rb +5 -3
- data/lib/ruby_llm/agents/infrastructure/attempt_tracker.rb +8 -0
- data/lib/ruby_llm/agents/infrastructure/circuit_breaker.rb +4 -2
- data/lib/ruby_llm/agents/pipeline/builder.rb +43 -0
- data/lib/ruby_llm/agents/pipeline/context.rb +11 -1
- data/lib/ruby_llm/agents/pipeline/executor.rb +1 -25
- data/lib/ruby_llm/agents/pipeline/middleware/budget.rb +26 -1
- data/lib/ruby_llm/agents/pipeline/middleware/cache.rb +18 -0
- data/lib/ruby_llm/agents/pipeline/middleware/instrumentation.rb +130 -3
- data/lib/ruby_llm/agents/pipeline/middleware/reliability.rb +29 -0
- data/lib/ruby_llm/agents/pipeline/middleware/tenant.rb +11 -4
- data/lib/ruby_llm/agents/pipeline.rb +0 -92
- data/lib/ruby_llm/agents/results/background_removal_result.rb +11 -1
- data/lib/ruby_llm/agents/results/base.rb +23 -1
- data/lib/ruby_llm/agents/results/embedding_result.rb +14 -1
- data/lib/ruby_llm/agents/results/image_analysis_result.rb +11 -1
- data/lib/ruby_llm/agents/results/image_edit_result.rb +11 -1
- data/lib/ruby_llm/agents/results/image_generation_result.rb +12 -3
- data/lib/ruby_llm/agents/results/image_pipeline_result.rb +11 -1
- data/lib/ruby_llm/agents/results/image_transform_result.rb +11 -1
- data/lib/ruby_llm/agents/results/image_upscale_result.rb +11 -1
- data/lib/ruby_llm/agents/results/image_variation_result.rb +11 -1
- data/lib/ruby_llm/agents/results/speech_result.rb +20 -1
- data/lib/ruby_llm/agents/results/transcription_result.rb +20 -1
- data/lib/ruby_llm/agents/text/embedder.rb +23 -18
- data/lib/ruby_llm/agents.rb +70 -5
- data/lib/tasks/ruby_llm_agents.rake +21 -0
- metadata +7 -6
- data/lib/ruby_llm/agents/infrastructure/reliability/breaker_manager.rb +0 -80
- data/lib/ruby_llm/agents/infrastructure/reliability/execution_constraints.rb +0 -69
- data/lib/ruby_llm/agents/infrastructure/reliability/executor.rb +0 -125
- data/lib/ruby_llm/agents/infrastructure/reliability/fallback_routing.rb +0 -72
- data/lib/ruby_llm/agents/infrastructure/reliability/retry_strategy.rb +0 -82
|
@@ -51,36 +51,12 @@ module RubyLLM
|
|
|
51
51
|
# @param context [Context] The execution context
|
|
52
52
|
# @return [Context] The context with output set
|
|
53
53
|
def call(context)
|
|
54
|
-
@agent.execute
|
|
54
|
+
@agent.send(:execute, context)
|
|
55
55
|
context
|
|
56
56
|
end
|
|
57
57
|
end
|
|
58
58
|
|
|
59
59
|
# Lambda-based executor for simple cases
|
|
60
|
-
#
|
|
61
|
-
# Allows wrapping a lambda/proc as the core executor,
|
|
62
|
-
# useful for testing or simple agents.
|
|
63
|
-
#
|
|
64
|
-
# @example
|
|
65
|
-
# executor = LambdaExecutor.new(->(ctx) {
|
|
66
|
-
# ctx.output = "Hello, #{ctx.input}!"
|
|
67
|
-
# })
|
|
68
|
-
#
|
|
69
|
-
class LambdaExecutor
|
|
70
|
-
# @param callable [#call] A lambda/proc that takes a context
|
|
71
|
-
def initialize(callable)
|
|
72
|
-
@callable = callable
|
|
73
|
-
end
|
|
74
|
-
|
|
75
|
-
# Execute the lambda with the context
|
|
76
|
-
#
|
|
77
|
-
# @param context [Context] The execution context
|
|
78
|
-
# @return [Context] The context (possibly modified by the lambda)
|
|
79
|
-
def call(context)
|
|
80
|
-
@callable.call(context)
|
|
81
|
-
context
|
|
82
|
-
end
|
|
83
|
-
end
|
|
84
60
|
end
|
|
85
61
|
end
|
|
86
62
|
end
|
|
@@ -39,13 +39,35 @@ module RubyLLM
|
|
|
39
39
|
@app.call(context)
|
|
40
40
|
|
|
41
41
|
# Record spend after successful execution (if not cached)
|
|
42
|
-
|
|
42
|
+
if context.success? && !context.cached?
|
|
43
|
+
record_spend!(context)
|
|
44
|
+
emit_budget_notification("ruby_llm_agents.budget.record", context,
|
|
45
|
+
total_cost: context.total_cost,
|
|
46
|
+
total_tokens: context.total_tokens)
|
|
47
|
+
end
|
|
43
48
|
|
|
44
49
|
context
|
|
45
50
|
end
|
|
46
51
|
|
|
47
52
|
private
|
|
48
53
|
|
|
54
|
+
# Emits an AS::Notification for budget events
|
|
55
|
+
#
|
|
56
|
+
# @param event [String] The notification event name
|
|
57
|
+
# @param context [Context] The execution context
|
|
58
|
+
# @param extras [Hash] Additional payload fields
|
|
59
|
+
def emit_budget_notification(event, context, **extras)
|
|
60
|
+
ActiveSupport::Notifications.instrument(
|
|
61
|
+
event,
|
|
62
|
+
{
|
|
63
|
+
agent_type: context.agent_class&.name,
|
|
64
|
+
tenant_id: context.tenant_id
|
|
65
|
+
}.merge(extras)
|
|
66
|
+
)
|
|
67
|
+
rescue
|
|
68
|
+
# Never let notifications break execution
|
|
69
|
+
end
|
|
70
|
+
|
|
49
71
|
# Returns whether budgets are enabled globally
|
|
50
72
|
#
|
|
51
73
|
# @return [Boolean]
|
|
@@ -63,6 +85,8 @@ module RubyLLM
|
|
|
63
85
|
# @param context [Context] The execution context
|
|
64
86
|
# @raise [BudgetExceededError] If budget exceeded with hard enforcement
|
|
65
87
|
def check_budget!(context)
|
|
88
|
+
emit_budget_notification("ruby_llm_agents.budget.check", context)
|
|
89
|
+
|
|
66
90
|
if context.tenant_id.present?
|
|
67
91
|
tenant = RubyLLM::Agents::Tenant.find_by(tenant_id: context.tenant_id)
|
|
68
92
|
if tenant
|
|
@@ -77,6 +101,7 @@ module RubyLLM
|
|
|
77
101
|
tenant_id: context.tenant_id
|
|
78
102
|
)
|
|
79
103
|
rescue RubyLLM::Agents::Reliability::BudgetExceededError
|
|
104
|
+
emit_budget_notification("ruby_llm_agents.budget.exceeded", context)
|
|
80
105
|
raise
|
|
81
106
|
rescue => e
|
|
82
107
|
error("Budget check failed: #{e.message}")
|
|
@@ -41,10 +41,13 @@ module RubyLLM
|
|
|
41
41
|
context.cached = true
|
|
42
42
|
context[:cache_key] = cache_key
|
|
43
43
|
debug("Cache hit for #{cache_key}")
|
|
44
|
+
emit_cache_notification("ruby_llm_agents.cache.hit", cache_key)
|
|
44
45
|
return context
|
|
45
46
|
end
|
|
46
47
|
end
|
|
47
48
|
|
|
49
|
+
emit_cache_notification("ruby_llm_agents.cache.miss", cache_key)
|
|
50
|
+
|
|
48
51
|
# Execute the chain
|
|
49
52
|
@app.call(context)
|
|
50
53
|
|
|
@@ -52,6 +55,7 @@ module RubyLLM
|
|
|
52
55
|
if context.success?
|
|
53
56
|
cache_write(cache_key, context.output)
|
|
54
57
|
debug("Cache write for #{cache_key}")
|
|
58
|
+
emit_cache_notification("ruby_llm_agents.cache.write", cache_key)
|
|
55
59
|
end
|
|
56
60
|
|
|
57
61
|
context
|
|
@@ -59,6 +63,20 @@ module RubyLLM
|
|
|
59
63
|
|
|
60
64
|
private
|
|
61
65
|
|
|
66
|
+
# Emits an AS::Notification for cache events
|
|
67
|
+
#
|
|
68
|
+
# @param event [String] The notification event name
|
|
69
|
+
# @param cache_key [String] The cache key involved
|
|
70
|
+
def emit_cache_notification(event, cache_key)
|
|
71
|
+
ActiveSupport::Notifications.instrument(
|
|
72
|
+
event,
|
|
73
|
+
agent_type: @agent_class&.name,
|
|
74
|
+
cache_key: cache_key
|
|
75
|
+
)
|
|
76
|
+
rescue
|
|
77
|
+
# Never let notifications break execution
|
|
78
|
+
end
|
|
79
|
+
|
|
62
80
|
# Returns whether caching is enabled for this agent
|
|
63
81
|
#
|
|
64
82
|
# @return [Boolean]
|
|
@@ -42,6 +42,7 @@ module RubyLLM
|
|
|
42
42
|
# Create "running" record immediately (SYNC - must appear on dashboard)
|
|
43
43
|
execution = create_running_execution(context)
|
|
44
44
|
context.execution_id = execution&.id
|
|
45
|
+
emit_start_notification(context)
|
|
45
46
|
status_update_completed = false
|
|
46
47
|
raised_exception = nil
|
|
47
48
|
|
|
@@ -55,6 +56,8 @@ module RubyLLM
|
|
|
55
56
|
rescue
|
|
56
57
|
# Let ensure block handle via mark_execution_failed!
|
|
57
58
|
end
|
|
59
|
+
|
|
60
|
+
emit_complete_notification(context, "success")
|
|
58
61
|
rescue => e
|
|
59
62
|
context.completed_at = Time.current
|
|
60
63
|
context.error = e
|
|
@@ -67,6 +70,7 @@ module RubyLLM
|
|
|
67
70
|
# Let ensure block handle via mark_execution_failed!
|
|
68
71
|
end
|
|
69
72
|
|
|
73
|
+
emit_complete_notification(context, determine_error_status(e))
|
|
70
74
|
raise
|
|
71
75
|
ensure
|
|
72
76
|
# Emergency fallback if update failed
|
|
@@ -93,6 +97,12 @@ module RubyLLM
|
|
|
93
97
|
data = build_running_execution_data(context)
|
|
94
98
|
execution = Execution.create!(data)
|
|
95
99
|
|
|
100
|
+
# Root executions point root_execution_id at themselves
|
|
101
|
+
if execution.parent_execution_id.nil? && execution.root_execution_id.nil?
|
|
102
|
+
execution.update_column(:root_execution_id, execution.id)
|
|
103
|
+
end
|
|
104
|
+
context.root_execution_id = execution.root_execution_id || execution.id
|
|
105
|
+
|
|
96
106
|
# Create detail record with parameters
|
|
97
107
|
params = sanitize_parameters(context)
|
|
98
108
|
if params.present? && params != {}
|
|
@@ -181,6 +191,61 @@ module RubyLLM
|
|
|
181
191
|
error.is_a?(Timeout::Error) ? "timeout" : "error"
|
|
182
192
|
end
|
|
183
193
|
|
|
194
|
+
# Emits an AS::Notification for execution start
|
|
195
|
+
#
|
|
196
|
+
# Fires even when DB tracking is disabled — observability should
|
|
197
|
+
# work independently of persistence.
|
|
198
|
+
#
|
|
199
|
+
# @param context [Context] The execution context
|
|
200
|
+
def emit_start_notification(context)
|
|
201
|
+
ActiveSupport::Notifications.instrument(
|
|
202
|
+
"ruby_llm_agents.execution.start",
|
|
203
|
+
agent_type: context.agent_class&.name,
|
|
204
|
+
model: context.model,
|
|
205
|
+
tenant_id: context.tenant_id,
|
|
206
|
+
execution_id: context.execution_id
|
|
207
|
+
)
|
|
208
|
+
rescue
|
|
209
|
+
# Never let notifications break execution
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
# Emits an AS::Notification for execution completion or error
|
|
213
|
+
#
|
|
214
|
+
# Uses execution.complete for success, execution.error for failures.
|
|
215
|
+
# Fires even when DB tracking is disabled.
|
|
216
|
+
#
|
|
217
|
+
# @param context [Context] The execution context
|
|
218
|
+
# @param status [String] "success", "error", or "timeout"
|
|
219
|
+
def emit_complete_notification(context, status)
|
|
220
|
+
event = (status == "success") ? "ruby_llm_agents.execution.complete" : "ruby_llm_agents.execution.error"
|
|
221
|
+
|
|
222
|
+
ActiveSupport::Notifications.instrument(
|
|
223
|
+
event,
|
|
224
|
+
agent_type: context.agent_class&.name,
|
|
225
|
+
agent_type_symbol: context.agent_type,
|
|
226
|
+
execution_id: context.execution_id,
|
|
227
|
+
model: context.model,
|
|
228
|
+
model_used: context.model_used,
|
|
229
|
+
tenant_id: context.tenant_id,
|
|
230
|
+
status: status,
|
|
231
|
+
duration_ms: context.duration_ms,
|
|
232
|
+
input_tokens: context.input_tokens,
|
|
233
|
+
output_tokens: context.output_tokens,
|
|
234
|
+
total_tokens: context.total_tokens,
|
|
235
|
+
input_cost: context.input_cost,
|
|
236
|
+
output_cost: context.output_cost,
|
|
237
|
+
total_cost: context.total_cost,
|
|
238
|
+
cached: context.cached?,
|
|
239
|
+
attempts_made: context.attempts_made,
|
|
240
|
+
finish_reason: context.finish_reason,
|
|
241
|
+
time_to_first_token_ms: context.time_to_first_token_ms,
|
|
242
|
+
error_class: context.error&.class&.name,
|
|
243
|
+
error_message: context.error&.message
|
|
244
|
+
)
|
|
245
|
+
rescue
|
|
246
|
+
# Never let notifications break execution
|
|
247
|
+
end
|
|
248
|
+
|
|
184
249
|
# Builds data for initial running execution record
|
|
185
250
|
#
|
|
186
251
|
# @param context [Context] The execution context
|
|
@@ -202,6 +267,28 @@ module RubyLLM
|
|
|
202
267
|
data[:tenant_id] = context.tenant_id
|
|
203
268
|
end
|
|
204
269
|
|
|
270
|
+
# Include agent-defined metadata so it appears on the dashboard immediately
|
|
271
|
+
agent_meta = safe_agent_metadata(context)
|
|
272
|
+
if agent_meta.any?
|
|
273
|
+
data[:metadata] = agent_meta.transform_keys(&:to_s)
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
# Track replay source if this is a replayed execution
|
|
277
|
+
replay_source_id = begin
|
|
278
|
+
context.agent_instance&.send(:options)&.dig(:_replay_source_id)
|
|
279
|
+
rescue
|
|
280
|
+
nil
|
|
281
|
+
end
|
|
282
|
+
if replay_source_id
|
|
283
|
+
data[:metadata] = (data[:metadata] || {}).merge("replay_source_id" => replay_source_id.to_s)
|
|
284
|
+
end
|
|
285
|
+
|
|
286
|
+
# Execution hierarchy (agent-as-tool)
|
|
287
|
+
if context.parent_execution_id.present?
|
|
288
|
+
data[:parent_execution_id] = context.parent_execution_id
|
|
289
|
+
data[:root_execution_id] = context.root_execution_id || context.parent_execution_id
|
|
290
|
+
end
|
|
291
|
+
|
|
205
292
|
data
|
|
206
293
|
end
|
|
207
294
|
|
|
@@ -222,12 +309,18 @@ module RubyLLM
|
|
|
222
309
|
attempts_count: context.attempts_made
|
|
223
310
|
}
|
|
224
311
|
|
|
225
|
-
#
|
|
226
|
-
|
|
312
|
+
# Merge metadata: agent metadata (base) < middleware metadata (overlay)
|
|
313
|
+
agent_meta = safe_agent_metadata(context)
|
|
314
|
+
merged_metadata = agent_meta.transform_keys(&:to_s)
|
|
315
|
+
|
|
316
|
+
context_meta = begin
|
|
227
317
|
context.metadata.dup
|
|
228
318
|
rescue
|
|
229
319
|
{}
|
|
230
320
|
end
|
|
321
|
+
context_meta.transform_keys!(&:to_s)
|
|
322
|
+
merged_metadata.merge!(context_meta)
|
|
323
|
+
|
|
231
324
|
if context.cached? && context[:cache_key]
|
|
232
325
|
merged_metadata["response_cache_key"] = context[:cache_key]
|
|
233
326
|
end
|
|
@@ -321,11 +414,18 @@ module RubyLLM
|
|
|
321
414
|
# @param status [String] "success" or "error"
|
|
322
415
|
# @return [Hash] Execution data
|
|
323
416
|
def build_execution_data(context, status)
|
|
324
|
-
|
|
417
|
+
# Merge metadata: agent metadata (base) < middleware metadata (overlay)
|
|
418
|
+
agent_meta = safe_agent_metadata(context)
|
|
419
|
+
merged_metadata = agent_meta.transform_keys(&:to_s)
|
|
420
|
+
|
|
421
|
+
context_meta = begin
|
|
325
422
|
context.metadata.dup
|
|
326
423
|
rescue
|
|
327
424
|
{}
|
|
328
425
|
end
|
|
426
|
+
context_meta.transform_keys!(&:to_s)
|
|
427
|
+
merged_metadata.merge!(context_meta)
|
|
428
|
+
|
|
329
429
|
if context.cached? && context[:cache_key]
|
|
330
430
|
merged_metadata["response_cache_key"] = context[:cache_key]
|
|
331
431
|
end
|
|
@@ -423,15 +523,42 @@ module RubyLLM
|
|
|
423
523
|
params[key] = "[REDACTED]" if params.key?(key)
|
|
424
524
|
end
|
|
425
525
|
|
|
526
|
+
INTERNAL_KEYS.each do |key|
|
|
527
|
+
params.delete(key)
|
|
528
|
+
end
|
|
529
|
+
|
|
426
530
|
params
|
|
427
531
|
end
|
|
428
532
|
|
|
533
|
+
# Safely retrieves custom metadata from the agent instance
|
|
534
|
+
#
|
|
535
|
+
# Returns an empty hash if the agent doesn't define metadata,
|
|
536
|
+
# the method raises, or the result isn't a Hash.
|
|
537
|
+
#
|
|
538
|
+
# @param context [Context] The execution context
|
|
539
|
+
# @return [Hash] Agent-defined metadata, or empty hash
|
|
540
|
+
def safe_agent_metadata(context)
|
|
541
|
+
return {} unless context.agent_instance
|
|
542
|
+
return {} unless context.agent_instance.respond_to?(:metadata)
|
|
543
|
+
|
|
544
|
+
result = context.agent_instance.metadata
|
|
545
|
+
result.is_a?(Hash) ? result : {}
|
|
546
|
+
rescue => e
|
|
547
|
+
debug("Failed to retrieve agent metadata: #{e.message}")
|
|
548
|
+
{}
|
|
549
|
+
end
|
|
550
|
+
|
|
429
551
|
# Sensitive parameter keys that should be redacted
|
|
430
552
|
SENSITIVE_KEYS = %w[
|
|
431
553
|
password token api_key secret credential auth key
|
|
432
554
|
access_token refresh_token private_key secret_key
|
|
433
555
|
].freeze
|
|
434
556
|
|
|
557
|
+
# Internal keys that should be stripped from persisted parameters
|
|
558
|
+
INTERNAL_KEYS = %w[
|
|
559
|
+
_replay_source_id _ask_message _parent_execution_id _root_execution_id
|
|
560
|
+
].freeze
|
|
561
|
+
|
|
435
562
|
# Truncates error message to prevent database issues
|
|
436
563
|
#
|
|
437
564
|
# @param message [String] The error message
|
|
@@ -120,6 +120,17 @@ module RubyLLM
|
|
|
120
120
|
|
|
121
121
|
if result
|
|
122
122
|
context[:reliability_attempts] = tracker.to_json_array
|
|
123
|
+
|
|
124
|
+
# Emit fallback_used if a non-primary model succeeded
|
|
125
|
+
if current_model != models_to_try.first
|
|
126
|
+
emit_reliability_notification(
|
|
127
|
+
"ruby_llm_agents.reliability.fallback_used",
|
|
128
|
+
primary_model: models_to_try.first,
|
|
129
|
+
used_model: current_model,
|
|
130
|
+
attempts_made: context.attempts_made
|
|
131
|
+
)
|
|
132
|
+
end
|
|
133
|
+
|
|
123
134
|
return result
|
|
124
135
|
end
|
|
125
136
|
|
|
@@ -131,6 +142,11 @@ module RubyLLM
|
|
|
131
142
|
context[:reliability_attempts] = tracker.to_json_array
|
|
132
143
|
|
|
133
144
|
# All models exhausted
|
|
145
|
+
emit_reliability_notification(
|
|
146
|
+
"ruby_llm_agents.reliability.all_models_exhausted",
|
|
147
|
+
models_tried: models_to_try
|
|
148
|
+
)
|
|
149
|
+
|
|
134
150
|
raise Agents::Reliability::AllModelsExhaustedError.new(
|
|
135
151
|
models_to_try, last_error,
|
|
136
152
|
attempts: tracker.to_json_array
|
|
@@ -292,6 +308,19 @@ module RubyLLM
|
|
|
292
308
|
)
|
|
293
309
|
end
|
|
294
310
|
|
|
311
|
+
# Emits an AS::Notification for reliability events
|
|
312
|
+
#
|
|
313
|
+
# @param event [String] The notification event name
|
|
314
|
+
# @param extras [Hash] Additional payload fields
|
|
315
|
+
def emit_reliability_notification(event, **extras)
|
|
316
|
+
ActiveSupport::Notifications.instrument(
|
|
317
|
+
event,
|
|
318
|
+
{agent_type: @agent_class&.name}.merge(extras)
|
|
319
|
+
)
|
|
320
|
+
rescue
|
|
321
|
+
# Never let notifications break execution
|
|
322
|
+
end
|
|
323
|
+
|
|
295
324
|
# Sleeps without blocking other fibers when in async context
|
|
296
325
|
#
|
|
297
326
|
# @param seconds [Numeric] Duration to sleep
|
|
@@ -59,10 +59,17 @@ module RubyLLM
|
|
|
59
59
|
case tenant_value
|
|
60
60
|
when nil
|
|
61
61
|
# No explicit tenant - fall back to configured tenant_resolver
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
62
|
+
resolved_value = RubyLLM::Agents.configuration.current_tenant_id
|
|
63
|
+
|
|
64
|
+
if resolved_value.respond_to?(:llm_tenant_id)
|
|
65
|
+
context.tenant_id = resolved_value.llm_tenant_id&.to_s
|
|
66
|
+
context.tenant_object = resolved_value
|
|
67
|
+
context.tenant_config = extract_tenant_config(resolved_value)
|
|
68
|
+
else
|
|
69
|
+
context.tenant_id = resolved_value&.to_s
|
|
70
|
+
context.tenant_object = nil
|
|
71
|
+
context.tenant_config = nil
|
|
72
|
+
end
|
|
66
73
|
|
|
67
74
|
when Hash
|
|
68
75
|
# Hash format: { id: "tenant_id", object: tenant, ... }
|
|
@@ -40,98 +40,6 @@ require_relative "pipeline/middleware/reliability"
|
|
|
40
40
|
module RubyLLM
|
|
41
41
|
module Agents
|
|
42
42
|
module Pipeline
|
|
43
|
-
# Represents an error result from a failed step
|
|
44
|
-
#
|
|
45
|
-
# Used to track errors that occurred during step execution while
|
|
46
|
-
# allowing the workflow to continue (for optional steps).
|
|
47
|
-
#
|
|
48
|
-
# @api public
|
|
49
|
-
class ErrorResult
|
|
50
|
-
attr_reader :step_name, :error_class, :error_message
|
|
51
|
-
|
|
52
|
-
def initialize(step_name:, error_class:, error_message:)
|
|
53
|
-
@step_name = step_name
|
|
54
|
-
@error_class = error_class
|
|
55
|
-
@error_message = error_message
|
|
56
|
-
end
|
|
57
|
-
|
|
58
|
-
def content
|
|
59
|
-
nil
|
|
60
|
-
end
|
|
61
|
-
|
|
62
|
-
def success?
|
|
63
|
-
false
|
|
64
|
-
end
|
|
65
|
-
|
|
66
|
-
def error?
|
|
67
|
-
true
|
|
68
|
-
end
|
|
69
|
-
|
|
70
|
-
def skipped?
|
|
71
|
-
false
|
|
72
|
-
end
|
|
73
|
-
|
|
74
|
-
def input_tokens
|
|
75
|
-
0
|
|
76
|
-
end
|
|
77
|
-
|
|
78
|
-
def output_tokens
|
|
79
|
-
0
|
|
80
|
-
end
|
|
81
|
-
|
|
82
|
-
def total_tokens
|
|
83
|
-
0
|
|
84
|
-
end
|
|
85
|
-
|
|
86
|
-
def cached_tokens
|
|
87
|
-
0
|
|
88
|
-
end
|
|
89
|
-
|
|
90
|
-
def input_cost
|
|
91
|
-
0.0
|
|
92
|
-
end
|
|
93
|
-
|
|
94
|
-
def output_cost
|
|
95
|
-
0.0
|
|
96
|
-
end
|
|
97
|
-
|
|
98
|
-
def total_cost
|
|
99
|
-
0.0
|
|
100
|
-
end
|
|
101
|
-
|
|
102
|
-
def to_h
|
|
103
|
-
{
|
|
104
|
-
error: true,
|
|
105
|
-
step_name: step_name,
|
|
106
|
-
error_class: error_class,
|
|
107
|
-
error_message: error_message
|
|
108
|
-
}
|
|
109
|
-
end
|
|
110
|
-
end
|
|
111
|
-
|
|
112
|
-
class << self
|
|
113
|
-
# Build a pipeline for an agent class with default middleware
|
|
114
|
-
#
|
|
115
|
-
# This is a convenience method that combines Builder.for with build.
|
|
116
|
-
#
|
|
117
|
-
# @param agent_class [Class] The agent class
|
|
118
|
-
# @param executor [#call] The core executor
|
|
119
|
-
# @return [#call] The complete pipeline
|
|
120
|
-
def build(agent_class, executor)
|
|
121
|
-
Builder.for(agent_class).build(executor)
|
|
122
|
-
end
|
|
123
|
-
|
|
124
|
-
# Build an empty pipeline (no middleware)
|
|
125
|
-
#
|
|
126
|
-
# Useful for testing or when you want direct execution.
|
|
127
|
-
#
|
|
128
|
-
# @param agent_class [Class] The agent class
|
|
129
|
-
# @param executor [#call] The core executor
|
|
130
|
-
# @return [#call] The executor (no middleware wrapping)
|
|
131
|
-
def build_empty(agent_class, executor)
|
|
132
|
-
Builder.empty(agent_class).build(executor)
|
|
133
|
-
end
|
|
134
|
-
end
|
|
135
43
|
end
|
|
136
44
|
end
|
|
137
45
|
end
|
|
@@ -19,6 +19,7 @@ module RubyLLM
|
|
|
19
19
|
:alpha_matting, :refine_edges,
|
|
20
20
|
:started_at, :completed_at, :tenant_id, :remover_class,
|
|
21
21
|
:error_class, :error_message
|
|
22
|
+
attr_accessor :execution_id
|
|
22
23
|
|
|
23
24
|
# Initialize a new result
|
|
24
25
|
#
|
|
@@ -51,6 +52,14 @@ module RubyLLM
|
|
|
51
52
|
@remover_class = remover_class
|
|
52
53
|
@error_class = error_class
|
|
53
54
|
@error_message = error_message
|
|
55
|
+
@execution_id = nil
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Loads the associated Execution record from the database
|
|
59
|
+
#
|
|
60
|
+
# @return [RubyLLM::Agents::Execution, nil] The execution record, or nil
|
|
61
|
+
def execution
|
|
62
|
+
@execution ||= RubyLLM::Agents::Execution.find_by(id: execution_id) if execution_id
|
|
54
63
|
end
|
|
55
64
|
|
|
56
65
|
# Status helpers
|
|
@@ -196,7 +205,8 @@ module RubyLLM
|
|
|
196
205
|
tenant_id: tenant_id,
|
|
197
206
|
remover_class: remover_class,
|
|
198
207
|
error_class: error_class,
|
|
199
|
-
error_message: error_message
|
|
208
|
+
error_message: error_message,
|
|
209
|
+
execution_id: execution_id
|
|
200
210
|
}
|
|
201
211
|
end
|
|
202
212
|
|
|
@@ -102,6 +102,11 @@ module RubyLLM
|
|
|
102
102
|
# @return [Integer, nil] Number of tokens used for thinking
|
|
103
103
|
attr_reader :thinking_text, :thinking_signature, :thinking_tokens
|
|
104
104
|
|
|
105
|
+
# @!group Execution Record
|
|
106
|
+
# @!attribute [r] execution_id
|
|
107
|
+
# @return [Integer, nil] Database ID of the associated Execution record
|
|
108
|
+
attr_reader :execution_id
|
|
109
|
+
|
|
105
110
|
# Creates a new Result instance
|
|
106
111
|
#
|
|
107
112
|
# @param content [Hash, String] The processed response content
|
|
@@ -151,6 +156,22 @@ module RubyLLM
|
|
|
151
156
|
@thinking_text = options[:thinking_text]
|
|
152
157
|
@thinking_signature = options[:thinking_signature]
|
|
153
158
|
@thinking_tokens = options[:thinking_tokens]
|
|
159
|
+
|
|
160
|
+
# Execution record
|
|
161
|
+
@execution_id = options[:execution_id]
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
# Loads the associated Execution record from the database
|
|
165
|
+
#
|
|
166
|
+
# Useful for debugging in the Rails console to inspect the full
|
|
167
|
+
# execution record after an agent call.
|
|
168
|
+
#
|
|
169
|
+
# @return [RubyLLM::Agents::Execution, nil] The execution record, or nil
|
|
170
|
+
# @example
|
|
171
|
+
# result = MyAgent.call(query: "test")
|
|
172
|
+
# result.execution # => #<RubyLLM::Agents::Execution id: 42, ...>
|
|
173
|
+
def execution
|
|
174
|
+
@execution ||= Execution.find_by(id: execution_id) if execution_id
|
|
154
175
|
end
|
|
155
176
|
|
|
156
177
|
# Returns total tokens (input + output)
|
|
@@ -240,7 +261,8 @@ module RubyLLM
|
|
|
240
261
|
tool_calls_count: tool_calls_count,
|
|
241
262
|
thinking_text: thinking_text,
|
|
242
263
|
thinking_signature: thinking_signature,
|
|
243
|
-
thinking_tokens: thinking_tokens
|
|
264
|
+
thinking_tokens: thinking_tokens,
|
|
265
|
+
execution_id: execution_id
|
|
244
266
|
}
|
|
245
267
|
end
|
|
246
268
|
|
|
@@ -73,6 +73,10 @@ module RubyLLM
|
|
|
73
73
|
# @return [String, nil] Exception message if failed
|
|
74
74
|
attr_reader :error_message
|
|
75
75
|
|
|
76
|
+
# @!attribute [r] execution_id
|
|
77
|
+
# @return [Integer, nil] Database ID of the associated Execution record
|
|
78
|
+
attr_reader :execution_id
|
|
79
|
+
|
|
76
80
|
# Creates a new EmbeddingResult instance
|
|
77
81
|
#
|
|
78
82
|
# @param attributes [Hash] Result attributes
|
|
@@ -101,6 +105,14 @@ module RubyLLM
|
|
|
101
105
|
@tenant_id = attributes[:tenant_id]
|
|
102
106
|
@error_class = attributes[:error_class]
|
|
103
107
|
@error_message = attributes[:error_message]
|
|
108
|
+
@execution_id = attributes[:execution_id]
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# Loads the associated Execution record from the database
|
|
112
|
+
#
|
|
113
|
+
# @return [RubyLLM::Agents::Execution, nil] The execution record, or nil
|
|
114
|
+
def execution
|
|
115
|
+
@execution ||= RubyLLM::Agents::Execution.find_by(id: execution_id) if execution_id
|
|
104
116
|
end
|
|
105
117
|
|
|
106
118
|
# Returns whether this result contains a single embedding
|
|
@@ -215,7 +227,8 @@ module RubyLLM
|
|
|
215
227
|
completed_at: completed_at,
|
|
216
228
|
tenant_id: tenant_id,
|
|
217
229
|
error_class: error_class,
|
|
218
|
-
error_message: error_message
|
|
230
|
+
error_message: error_message,
|
|
231
|
+
execution_id: execution_id
|
|
219
232
|
}
|
|
220
233
|
end
|
|
221
234
|
|
|
@@ -21,6 +21,7 @@ module RubyLLM
|
|
|
21
21
|
:caption, :description, :tags, :objects, :colors, :text,
|
|
22
22
|
:raw_response, :started_at, :completed_at, :tenant_id, :analyzer_class,
|
|
23
23
|
:error_class, :error_message
|
|
24
|
+
attr_accessor :execution_id
|
|
24
25
|
|
|
25
26
|
# Initialize a new result
|
|
26
27
|
#
|
|
@@ -59,6 +60,14 @@ module RubyLLM
|
|
|
59
60
|
@analyzer_class = analyzer_class
|
|
60
61
|
@error_class = error_class
|
|
61
62
|
@error_message = error_message
|
|
63
|
+
@execution_id = nil
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Loads the associated Execution record from the database
|
|
67
|
+
#
|
|
68
|
+
# @return [RubyLLM::Agents::Execution, nil] The execution record, or nil
|
|
69
|
+
def execution
|
|
70
|
+
@execution ||= RubyLLM::Agents::Execution.find_by(id: execution_id) if execution_id
|
|
62
71
|
end
|
|
63
72
|
|
|
64
73
|
# Status helpers
|
|
@@ -205,7 +214,8 @@ module RubyLLM
|
|
|
205
214
|
tenant_id: tenant_id,
|
|
206
215
|
analyzer_class: analyzer_class,
|
|
207
216
|
error_class: error_class,
|
|
208
|
-
error_message: error_message
|
|
217
|
+
error_message: error_message,
|
|
218
|
+
execution_id: execution_id
|
|
209
219
|
}
|
|
210
220
|
end
|
|
211
221
|
|
|
@@ -20,6 +20,7 @@ module RubyLLM
|
|
|
20
20
|
attr_reader :images, :source_image, :mask, :prompt, :model_id, :size,
|
|
21
21
|
:started_at, :completed_at, :tenant_id, :editor_class,
|
|
22
22
|
:error_class, :error_message
|
|
23
|
+
attr_accessor :execution_id
|
|
23
24
|
|
|
24
25
|
# Initialize a new result
|
|
25
26
|
#
|
|
@@ -50,6 +51,14 @@ module RubyLLM
|
|
|
50
51
|
@editor_class = editor_class
|
|
51
52
|
@error_class = error_class
|
|
52
53
|
@error_message = error_message
|
|
54
|
+
@execution_id = nil
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Loads the associated Execution record from the database
|
|
58
|
+
#
|
|
59
|
+
# @return [RubyLLM::Agents::Execution, nil] The execution record, or nil
|
|
60
|
+
def execution
|
|
61
|
+
@execution ||= RubyLLM::Agents::Execution.find_by(id: execution_id) if execution_id
|
|
53
62
|
end
|
|
54
63
|
|
|
55
64
|
# Status helpers
|
|
@@ -175,7 +184,8 @@ module RubyLLM
|
|
|
175
184
|
tenant_id: tenant_id,
|
|
176
185
|
editor_class: editor_class,
|
|
177
186
|
error_class: error_class,
|
|
178
|
-
error_message: error_message
|
|
187
|
+
error_message: error_message,
|
|
188
|
+
execution_id: execution_id
|
|
179
189
|
}
|
|
180
190
|
end
|
|
181
191
|
|