ruby_llm-agents 1.3.4 → 2.1.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 +112 -336
- data/app/controllers/concerns/ruby_llm/agents/sortable.rb +0 -1
- data/app/controllers/ruby_llm/agents/agents_controller.rb +5 -56
- data/app/controllers/ruby_llm/agents/dashboard_controller.rb +22 -106
- data/app/controllers/ruby_llm/agents/executions_controller.rb +4 -114
- data/app/controllers/ruby_llm/agents/tenants_controller.rb +30 -2
- data/app/helpers/ruby_llm/agents/application_helper.rb +19 -53
- data/app/models/ruby_llm/agents/execution/analytics.rb +13 -54
- data/app/models/ruby_llm/agents/execution/scopes.rb +61 -14
- data/app/models/ruby_llm/agents/execution.rb +52 -12
- data/app/models/ruby_llm/agents/execution_detail.rb +18 -0
- data/app/models/ruby_llm/agents/tenant/budgetable.rb +132 -24
- data/app/models/ruby_llm/agents/tenant/incrementable.rb +117 -0
- data/app/models/ruby_llm/agents/tenant/resettable.rb +128 -0
- data/app/models/ruby_llm/agents/tenant/trackable.rb +46 -12
- data/app/models/ruby_llm/agents/tenant.rb +2 -3
- data/app/models/ruby_llm/agents/tenant_budget.rb +6 -3
- data/app/services/ruby_llm/agents/agent_registry.rb +6 -112
- data/app/views/layouts/ruby_llm/agents/application.html.erb +89 -252
- data/app/views/ruby_llm/agents/agents/_config_agent.html.erb +71 -218
- data/app/views/ruby_llm/agents/agents/_config_embedder.html.erb +20 -63
- data/app/views/ruby_llm/agents/agents/_config_image_generator.html.erb +44 -131
- data/app/views/ruby_llm/agents/agents/_config_moderator.html.erb +16 -57
- data/app/views/ruby_llm/agents/agents/_config_speaker.html.erb +39 -104
- data/app/views/ruby_llm/agents/agents/_config_transcriber.html.erb +29 -82
- data/app/views/ruby_llm/agents/agents/_empty_state.html.erb +4 -14
- data/app/views/ruby_llm/agents/agents/index.html.erb +105 -274
- data/app/views/ruby_llm/agents/agents/show.html.erb +248 -378
- data/app/views/ruby_llm/agents/dashboard/_action_center.html.erb +29 -52
- data/app/views/ruby_llm/agents/dashboard/_tenant_budget.html.erb +73 -99
- data/app/views/ruby_llm/agents/dashboard/index.html.erb +228 -433
- data/app/views/ruby_llm/agents/executions/_execution.html.erb +1 -1
- data/app/views/ruby_llm/agents/executions/_filters.html.erb +4 -25
- data/app/views/ruby_llm/agents/executions/_list.html.erb +111 -152
- data/app/views/ruby_llm/agents/executions/index.html.erb +5 -7
- data/app/views/ruby_llm/agents/executions/show.html.erb +526 -1037
- data/app/views/ruby_llm/agents/shared/_agent_type_badge.html.erb +5 -21
- data/app/views/ruby_llm/agents/shared/_executions_table.html.erb +70 -191
- data/app/views/ruby_llm/agents/shared/_filter_dropdown.html.erb +16 -44
- data/app/views/ruby_llm/agents/shared/_select_dropdown.html.erb +12 -41
- data/app/views/ruby_llm/agents/shared/_status_badge.html.erb +11 -65
- data/app/views/ruby_llm/agents/shared/_tenant_filter.html.erb +6 -5
- data/app/views/ruby_llm/agents/system_config/show.html.erb +240 -351
- data/app/views/ruby_llm/agents/tenants/_form.html.erb +67 -77
- data/app/views/ruby_llm/agents/tenants/edit.html.erb +7 -9
- data/app/views/ruby_llm/agents/tenants/index.html.erb +100 -122
- data/app/views/ruby_llm/agents/tenants/show.html.erb +146 -336
- data/config/routes.rb +0 -13
- data/lib/generators/ruby_llm_agents/install_generator.rb +13 -17
- data/lib/generators/ruby_llm_agents/migrate_structure_generator.rb +2 -12
- data/lib/generators/ruby_llm_agents/restructure_generator.rb +0 -2
- data/lib/generators/ruby_llm_agents/templates/add_usage_counters_to_tenants_migration.rb.tt +37 -0
- data/lib/generators/ruby_llm_agents/templates/agent.rb.tt +1 -2
- data/lib/generators/ruby_llm_agents/templates/application_agent.rb.tt +1 -1
- data/lib/generators/ruby_llm_agents/templates/application_image_pipeline.rb.tt +0 -1
- data/lib/generators/ruby_llm_agents/templates/create_execution_details_migration.rb.tt +27 -0
- data/lib/generators/ruby_llm_agents/templates/create_tenants_migration.rb.tt +25 -0
- data/lib/generators/ruby_llm_agents/templates/image_pipeline.rb.tt +0 -1
- data/lib/generators/ruby_llm_agents/templates/initializer.rb.tt +33 -12
- data/lib/generators/ruby_llm_agents/templates/migration.rb.tt +40 -71
- data/lib/generators/ruby_llm_agents/templates/remove_agent_version_migration.rb.tt +13 -0
- data/lib/generators/ruby_llm_agents/templates/remove_workflow_columns_migration.rb.tt +19 -0
- data/lib/generators/ruby_llm_agents/templates/skills/AGENTS.md.tt +2 -4
- data/lib/generators/ruby_llm_agents/templates/skills/IMAGE_PIPELINES.md.tt +0 -1
- data/lib/generators/ruby_llm_agents/templates/split_execution_details_migration.rb.tt +232 -0
- data/lib/generators/ruby_llm_agents/upgrade_generator.rb +77 -259
- data/lib/ruby_llm/agents/audio/speaker.rb +0 -1
- data/lib/ruby_llm/agents/audio/transcriber.rb +0 -1
- data/lib/ruby_llm/agents/base_agent.rb +54 -23
- data/lib/ruby_llm/agents/core/base/callbacks.rb +142 -0
- data/lib/ruby_llm/agents/core/base.rb +23 -55
- data/lib/ruby_llm/agents/core/configuration.rb +97 -117
- data/lib/ruby_llm/agents/core/errors.rb +0 -58
- data/lib/ruby_llm/agents/core/instrumentation.rb +157 -110
- data/lib/ruby_llm/agents/core/llm_tenant.rb +8 -7
- data/lib/ruby_llm/agents/core/version.rb +1 -1
- data/lib/ruby_llm/agents/dsl/base.rb +157 -17
- data/lib/ruby_llm/agents/dsl/caching.rb +33 -2
- data/lib/ruby_llm/agents/dsl/reliability.rb +148 -0
- data/lib/ruby_llm/agents/dsl.rb +1 -2
- data/lib/ruby_llm/agents/image/analyzer/execution.rb +1 -2
- data/lib/ruby_llm/agents/image/background_remover/execution.rb +1 -2
- data/lib/ruby_llm/agents/image/concerns/image_operation_dsl.rb +1 -13
- data/lib/ruby_llm/agents/image/concerns/image_operation_execution.rb +2 -2
- data/lib/ruby_llm/agents/image/editor/dsl.rb +0 -14
- data/lib/ruby_llm/agents/image/editor/execution.rb +1 -10
- data/lib/ruby_llm/agents/image/editor.rb +0 -1
- data/lib/ruby_llm/agents/image/generator.rb +0 -21
- data/lib/ruby_llm/agents/image/pipeline/dsl.rb +0 -13
- data/lib/ruby_llm/agents/image/pipeline/execution.rb +0 -1
- data/lib/ruby_llm/agents/image/transformer/dsl.rb +0 -13
- data/lib/ruby_llm/agents/image/transformer/execution.rb +1 -10
- data/lib/ruby_llm/agents/image/transformer.rb +0 -1
- data/lib/ruby_llm/agents/image/upscaler/execution.rb +1 -2
- data/lib/ruby_llm/agents/image/variator/execution.rb +1 -2
- data/lib/ruby_llm/agents/infrastructure/alert_manager.rb +78 -173
- data/lib/ruby_llm/agents/infrastructure/budget/budget_query.rb +66 -2
- data/lib/ruby_llm/agents/infrastructure/budget/spend_recorder.rb +0 -12
- data/lib/ruby_llm/agents/infrastructure/circuit_breaker.rb +10 -13
- data/lib/ruby_llm/agents/infrastructure/execution_logger_job.rb +8 -0
- data/lib/ruby_llm/agents/pipeline/context.rb +0 -1
- data/lib/ruby_llm/agents/pipeline/middleware/budget.rb +28 -4
- data/lib/ruby_llm/agents/pipeline/middleware/cache.rb +3 -10
- data/lib/ruby_llm/agents/pipeline/middleware/instrumentation.rb +88 -55
- data/lib/ruby_llm/agents/pipeline/middleware/tenant.rb +5 -41
- data/lib/ruby_llm/agents/rails/engine.rb +6 -6
- data/lib/ruby_llm/agents/results/base.rb +1 -49
- data/lib/ruby_llm/agents/text/embedder.rb +0 -1
- data/lib/ruby_llm/agents.rb +1 -9
- data/lib/tasks/ruby_llm_agents.rake +34 -0
- metadata +14 -83
- data/app/controllers/ruby_llm/agents/api_configurations_controller.rb +0 -214
- data/app/controllers/ruby_llm/agents/workflows_controller.rb +0 -544
- data/app/mailers/ruby_llm/agents/alert_mailer.rb +0 -84
- data/app/mailers/ruby_llm/agents/application_mailer.rb +0 -28
- data/app/models/ruby_llm/agents/api_configuration.rb +0 -386
- data/app/models/ruby_llm/agents/execution/workflow.rb +0 -170
- data/app/models/ruby_llm/agents/tenant/configurable.rb +0 -135
- data/app/views/ruby_llm/agents/agents/_agent.html.erb +0 -98
- data/app/views/ruby_llm/agents/agents/_version_comparison.html.erb +0 -186
- data/app/views/ruby_llm/agents/agents/_workflow.html.erb +0 -126
- data/app/views/ruby_llm/agents/alert_mailer/alert_notification.html.erb +0 -107
- data/app/views/ruby_llm/agents/alert_mailer/alert_notification.text.erb +0 -18
- data/app/views/ruby_llm/agents/api_configurations/_api_key_field.html.erb +0 -34
- data/app/views/ruby_llm/agents/api_configurations/_form.html.erb +0 -288
- data/app/views/ruby_llm/agents/api_configurations/edit.html.erb +0 -95
- data/app/views/ruby_llm/agents/api_configurations/edit_tenant.html.erb +0 -97
- data/app/views/ruby_llm/agents/api_configurations/show.html.erb +0 -214
- data/app/views/ruby_llm/agents/api_configurations/tenant.html.erb +0 -179
- data/app/views/ruby_llm/agents/dashboard/_agent_comparison.html.erb +0 -73
- data/app/views/ruby_llm/agents/dashboard/_alerts_feed.html.erb +0 -62
- data/app/views/ruby_llm/agents/dashboard/_breaker_strip.html.erb +0 -47
- data/app/views/ruby_llm/agents/dashboard/_budgets_bar.html.erb +0 -75
- data/app/views/ruby_llm/agents/dashboard/_model_comparison.html.erb +0 -56
- data/app/views/ruby_llm/agents/dashboard/_model_cost_breakdown.html.erb +0 -115
- data/app/views/ruby_llm/agents/dashboard/_now_strip.html.erb +0 -59
- data/app/views/ruby_llm/agents/dashboard/_top_errors.html.erb +0 -60
- data/app/views/ruby_llm/agents/executions/_workflow_summary.html.erb +0 -86
- data/app/views/ruby_llm/agents/executions/dry_run.html.erb +0 -149
- data/app/views/ruby_llm/agents/shared/_breadcrumbs.html.erb +0 -48
- data/app/views/ruby_llm/agents/shared/_nav_link.html.erb +0 -27
- data/app/views/ruby_llm/agents/shared/_stat_card.html.erb +0 -14
- data/app/views/ruby_llm/agents/shared/_workflow_type_badge.html.erb +0 -35
- data/app/views/ruby_llm/agents/workflows/_empty_state.html.erb +0 -22
- data/app/views/ruby_llm/agents/workflows/_step_performance.html.erb +0 -228
- data/app/views/ruby_llm/agents/workflows/_structure_dsl.html.erb +0 -539
- data/app/views/ruby_llm/agents/workflows/_structure_parallel.html.erb +0 -76
- data/app/views/ruby_llm/agents/workflows/_structure_pipeline.html.erb +0 -74
- data/app/views/ruby_llm/agents/workflows/_structure_router.html.erb +0 -108
- data/app/views/ruby_llm/agents/workflows/_workflow_diagram.html.erb +0 -920
- data/app/views/ruby_llm/agents/workflows/index.html.erb +0 -179
- data/app/views/ruby_llm/agents/workflows/show.html.erb +0 -467
- data/lib/generators/ruby_llm_agents/api_configuration_generator.rb +0 -100
- data/lib/generators/ruby_llm_agents/templates/add_workflow_migration.rb.tt +0 -38
- data/lib/generators/ruby_llm_agents/templates/application_workflow.rb.tt +0 -48
- data/lib/generators/ruby_llm_agents/templates/create_api_configurations_migration.rb.tt +0 -90
- data/lib/generators/ruby_llm_agents/templates/skills/WORKFLOWS.md.tt +0 -551
- data/lib/ruby_llm/agents/core/base/moderation_dsl.rb +0 -181
- data/lib/ruby_llm/agents/core/base/moderation_execution.rb +0 -274
- data/lib/ruby_llm/agents/core/resolved_config.rb +0 -348
- data/lib/ruby_llm/agents/image/generator/content_policy.rb +0 -95
- data/lib/ruby_llm/agents/infrastructure/redactor.rb +0 -130
- data/lib/ruby_llm/agents/results/moderation_result.rb +0 -158
- data/lib/ruby_llm/agents/text/moderator.rb +0 -237
- data/lib/ruby_llm/agents/workflow/approval.rb +0 -205
- data/lib/ruby_llm/agents/workflow/approval_store.rb +0 -179
- data/lib/ruby_llm/agents/workflow/async.rb +0 -220
- data/lib/ruby_llm/agents/workflow/async_executor.rb +0 -156
- data/lib/ruby_llm/agents/workflow/dsl/executor.rb +0 -467
- data/lib/ruby_llm/agents/workflow/dsl/input_schema.rb +0 -244
- data/lib/ruby_llm/agents/workflow/dsl/iteration_executor.rb +0 -289
- data/lib/ruby_llm/agents/workflow/dsl/parallel_group.rb +0 -107
- data/lib/ruby_llm/agents/workflow/dsl/route_builder.rb +0 -150
- data/lib/ruby_llm/agents/workflow/dsl/schedule_helpers.rb +0 -187
- data/lib/ruby_llm/agents/workflow/dsl/step_config.rb +0 -352
- data/lib/ruby_llm/agents/workflow/dsl/step_executor.rb +0 -415
- data/lib/ruby_llm/agents/workflow/dsl/wait_config.rb +0 -257
- data/lib/ruby_llm/agents/workflow/dsl/wait_executor.rb +0 -317
- data/lib/ruby_llm/agents/workflow/dsl.rb +0 -576
- data/lib/ruby_llm/agents/workflow/instrumentation.rb +0 -249
- data/lib/ruby_llm/agents/workflow/notifiers/base.rb +0 -117
- data/lib/ruby_llm/agents/workflow/notifiers/email.rb +0 -117
- data/lib/ruby_llm/agents/workflow/notifiers/slack.rb +0 -180
- data/lib/ruby_llm/agents/workflow/notifiers/webhook.rb +0 -121
- data/lib/ruby_llm/agents/workflow/notifiers.rb +0 -70
- data/lib/ruby_llm/agents/workflow/orchestrator.rb +0 -416
- data/lib/ruby_llm/agents/workflow/result.rb +0 -592
- data/lib/ruby_llm/agents/workflow/thread_pool.rb +0 -185
- data/lib/ruby_llm/agents/workflow/throttle_manager.rb +0 -206
- data/lib/ruby_llm/agents/workflow/wait_result.rb +0 -213
|
@@ -19,13 +19,10 @@ module RubyLLM
|
|
|
19
19
|
"dashboard/index" => "Dashboard",
|
|
20
20
|
"agents/index" => "Agent-DSL",
|
|
21
21
|
"agents/show" => "Agent-DSL",
|
|
22
|
-
"workflows/index" => "Workflows",
|
|
23
|
-
"workflows/show" => "Workflows",
|
|
24
22
|
"executions/index" => "Execution-Tracking",
|
|
25
23
|
"executions/show" => "Execution-Tracking",
|
|
26
24
|
"tenants/index" => "Multi-Tenancy",
|
|
27
|
-
"system_config/show" => "Configuration"
|
|
28
|
-
"api_configurations/show" => "Configuration"
|
|
25
|
+
"system_config/show" => "Configuration"
|
|
29
26
|
}.freeze
|
|
30
27
|
|
|
31
28
|
# Returns the documentation URL for the current page or a specific page key
|
|
@@ -59,27 +56,11 @@ module RubyLLM
|
|
|
59
56
|
|
|
60
57
|
# Returns the URL for "All Tenants" (clears tenant filter)
|
|
61
58
|
#
|
|
62
|
-
#
|
|
63
|
-
# 1. Query param routes - removes tenant_id from query params
|
|
64
|
-
# 2. Path-based tenant routes - navigates to equivalent global route
|
|
59
|
+
# Removes tenant_id from query params to show unfiltered results.
|
|
65
60
|
#
|
|
66
61
|
# @return [String] URL without tenant filtering
|
|
67
62
|
def all_tenants_url
|
|
68
|
-
|
|
69
|
-
tenant_route_mappings = {
|
|
70
|
-
"tenant" => ruby_llm_agents.api_configuration_path,
|
|
71
|
-
"edit_tenant" => ruby_llm_agents.edit_api_configuration_path
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
# Check if current action has a global equivalent
|
|
75
|
-
if tenant_route_mappings.key?(action_name)
|
|
76
|
-
base_path = tenant_route_mappings[action_name]
|
|
77
|
-
query = request.query_parameters.except("tenant_id")
|
|
78
|
-
query.any? ? "#{base_path}?#{query.to_query}" : base_path
|
|
79
|
-
else
|
|
80
|
-
# For query param routes, just remove tenant_id
|
|
81
|
-
url_for(request.query_parameters.except("tenant_id"))
|
|
82
|
-
end
|
|
63
|
+
url_for(request.query_parameters.except("tenant_id"))
|
|
83
64
|
end
|
|
84
65
|
|
|
85
66
|
# Formats large numbers with human-readable suffixes (K, M, B)
|
|
@@ -121,7 +102,7 @@ module RubyLLM
|
|
|
121
102
|
# @return [ActiveSupport::SafeBuffer] HTML badge element
|
|
122
103
|
def render_enabled_badge(enabled)
|
|
123
104
|
if enabled
|
|
124
|
-
'<span class="inline-flex items-center px-2 py-1 rounded text-xs font-medium bg-green-100 dark:bg-green-
|
|
105
|
+
'<span class="inline-flex items-center px-2 py-1 rounded text-xs font-medium bg-green-100 dark:bg-green-500/20 text-green-700 dark:text-green-300">Enabled</span>'.html_safe
|
|
125
106
|
else
|
|
126
107
|
'<span class="inline-flex items-center px-2 py-1 rounded text-xs font-medium bg-gray-100 dark:bg-gray-700 text-gray-500 dark:text-gray-400">Disabled</span>'.html_safe
|
|
127
108
|
end
|
|
@@ -133,7 +114,7 @@ module RubyLLM
|
|
|
133
114
|
# @return [ActiveSupport::SafeBuffer] HTML badge element
|
|
134
115
|
def render_configured_badge(configured)
|
|
135
116
|
if configured
|
|
136
|
-
'<span class="inline-flex items-center px-2 py-1 rounded text-xs font-medium bg-green-100 dark:bg-green-
|
|
117
|
+
'<span class="inline-flex items-center px-2 py-1 rounded text-xs font-medium bg-green-100 dark:bg-green-500/20 text-green-700 dark:text-green-300">Configured</span>'.html_safe
|
|
137
118
|
else
|
|
138
119
|
'<span class="inline-flex items-center px-2 py-1 rounded text-xs font-medium bg-gray-100 dark:bg-gray-700 text-gray-500 dark:text-gray-400">Not configured</span>'.html_safe
|
|
139
120
|
end
|
|
@@ -254,7 +235,7 @@ module RubyLLM
|
|
|
254
235
|
end
|
|
255
236
|
|
|
256
237
|
if is_improvement
|
|
257
|
-
content_tag(:span, class: "inline-flex items-center gap-1 px-2 py-0.5 text-xs font-medium text-green-700 dark:text-green-300 bg-green-100 dark:bg-green-
|
|
238
|
+
content_tag(:span, class: "inline-flex items-center gap-1 px-2 py-0.5 text-xs font-medium text-green-700 dark:text-green-300 bg-green-100 dark:bg-green-500/20 rounded-full") do
|
|
258
239
|
safe_join([
|
|
259
240
|
content_tag(:svg, class: "w-3 h-3", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24") do
|
|
260
241
|
content_tag(:path, nil, "stroke-linecap": "round", "stroke-linejoin": "round", "stroke-width": "2", d: "M5 10l7-7m0 0l7 7m-7-7v18")
|
|
@@ -263,7 +244,7 @@ module RubyLLM
|
|
|
263
244
|
])
|
|
264
245
|
end
|
|
265
246
|
elsif is_regression
|
|
266
|
-
content_tag(:span, class: "inline-flex items-center gap-1 px-2 py-0.5 text-xs font-medium text-red-700 dark:text-red-300 bg-red-100 dark:bg-red-
|
|
247
|
+
content_tag(:span, class: "inline-flex items-center gap-1 px-2 py-0.5 text-xs font-medium text-red-700 dark:text-red-300 bg-red-100 dark:bg-red-500/20 rounded-full") do
|
|
267
248
|
safe_join([
|
|
268
249
|
content_tag(:svg, class: "w-3 h-3", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24") do
|
|
269
250
|
content_tag(:path, nil, "stroke-linecap": "round", "stroke-linejoin": "round", "stroke-width": "2", d: "M19 14l-7 7m0 0l-7-7m7 7V3")
|
|
@@ -306,31 +287,16 @@ module RubyLLM
|
|
|
306
287
|
|
|
307
288
|
# Returns human-readable display name for time range
|
|
308
289
|
#
|
|
309
|
-
# @param range [String] Range parameter (today, 7d, 30d
|
|
290
|
+
# @param range [String] Range parameter (today, 7d, 30d)
|
|
310
291
|
# @return [String] Human-readable range name
|
|
311
292
|
# @example
|
|
312
|
-
# range_display_name("7d") #=> "
|
|
313
|
-
# range_display_name("2024-01-01_2024-01-15") #=> "Jan 1 - Jan 15"
|
|
293
|
+
# range_display_name("7d") #=> "7 Days"
|
|
314
294
|
def range_display_name(range)
|
|
315
295
|
case range
|
|
316
296
|
when "today" then "Today"
|
|
317
|
-
when "7d" then "
|
|
318
|
-
when "30d" then "
|
|
319
|
-
|
|
320
|
-
when "90d" then "Last 90 Days"
|
|
321
|
-
else
|
|
322
|
-
if range&.include?("_")
|
|
323
|
-
from_str, to_str = range.split("_")
|
|
324
|
-
from_date = Date.parse(from_str) rescue nil
|
|
325
|
-
to_date = Date.parse(to_str) rescue nil
|
|
326
|
-
if from_date && to_date
|
|
327
|
-
"#{from_date.strftime('%b %-d')} - #{to_date.strftime('%b %-d')}"
|
|
328
|
-
else
|
|
329
|
-
"Custom Range"
|
|
330
|
-
end
|
|
331
|
-
else
|
|
332
|
-
"Today"
|
|
333
|
-
end
|
|
297
|
+
when "7d" then "7 Days"
|
|
298
|
+
when "30d" then "30 Days"
|
|
299
|
+
else "Today"
|
|
334
300
|
end
|
|
335
301
|
end
|
|
336
302
|
|
|
@@ -391,7 +357,7 @@ module RubyLLM
|
|
|
391
357
|
# @return [ActiveSupport::SafeBuffer] HTML summary banner
|
|
392
358
|
def comparison_summary_badge(improvements_count, regressions_count, v2_label)
|
|
393
359
|
if improvements_count >= 3 && regressions_count == 0
|
|
394
|
-
content_tag(:span, class: "inline-flex items-center gap-1 px-3 py-1 text-sm font-medium text-green-700 dark:text-green-300 bg-green-100 dark:bg-green-
|
|
360
|
+
content_tag(:span, class: "inline-flex items-center gap-1 px-3 py-1 text-sm font-medium text-green-700 dark:text-green-300 bg-green-100 dark:bg-green-500/20 rounded-lg") do
|
|
395
361
|
safe_join([
|
|
396
362
|
content_tag(:svg, class: "w-4 h-4", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24") do
|
|
397
363
|
content_tag(:path, nil, "stroke-linecap": "round", "stroke-linejoin": "round", "stroke-width": "2", d: "M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z")
|
|
@@ -400,7 +366,7 @@ module RubyLLM
|
|
|
400
366
|
])
|
|
401
367
|
end
|
|
402
368
|
elsif regressions_count >= 3 && improvements_count == 0
|
|
403
|
-
content_tag(:span, class: "inline-flex items-center gap-1 px-3 py-1 text-sm font-medium text-red-700 dark:text-red-300 bg-red-100 dark:bg-red-
|
|
369
|
+
content_tag(:span, class: "inline-flex items-center gap-1 px-3 py-1 text-sm font-medium text-red-700 dark:text-red-300 bg-red-100 dark:bg-red-500/20 rounded-lg") do
|
|
404
370
|
safe_join([
|
|
405
371
|
content_tag(:svg, class: "w-4 h-4", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24") do
|
|
406
372
|
content_tag(:path, nil, "stroke-linecap": "round", "stroke-linejoin": "round", "stroke-width": "2", d: "M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z")
|
|
@@ -409,7 +375,7 @@ module RubyLLM
|
|
|
409
375
|
])
|
|
410
376
|
end
|
|
411
377
|
elsif improvements_count > 0 || regressions_count > 0
|
|
412
|
-
content_tag(:span, class: "inline-flex items-center gap-1 px-3 py-1 text-sm font-medium text-amber-700 dark:text-amber-300 bg-amber-100 dark:bg-amber-
|
|
378
|
+
content_tag(:span, class: "inline-flex items-center gap-1 px-3 py-1 text-sm font-medium text-amber-700 dark:text-amber-300 bg-amber-100 dark:bg-amber-500/20 rounded-lg") do
|
|
413
379
|
safe_join([
|
|
414
380
|
content_tag(:svg, class: "w-4 h-4", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24") do
|
|
415
381
|
content_tag(:path, nil, "stroke-linecap": "round", "stroke-linejoin": "round", "stroke-width": "2", d: "M8 7h12m0 0l-4-4m4 4l-4 4m0 6H4m0 0l4 4m-4-4l4-4")
|
|
@@ -552,14 +518,14 @@ module RubyLLM
|
|
|
552
518
|
|
|
553
519
|
escaped_value = ERB::Util.html_escape(token[:value])
|
|
554
520
|
if is_key
|
|
555
|
-
result << %(<span class="text-purple-600">#{escaped_value}</span>)
|
|
521
|
+
result << %(<span class="text-purple-600 dark:text-purple-400">#{escaped_value}</span>)
|
|
556
522
|
else
|
|
557
|
-
result << %(<span class="text-green-600">#{escaped_value}</span>)
|
|
523
|
+
result << %(<span class="text-green-600 dark:text-green-400">#{escaped_value}</span>)
|
|
558
524
|
end
|
|
559
525
|
when :number
|
|
560
|
-
result << %(<span class="text-blue-600">#{token[:value]}</span>)
|
|
526
|
+
result << %(<span class="text-blue-600 dark:text-blue-400">#{token[:value]}</span>)
|
|
561
527
|
when :boolean
|
|
562
|
-
result << %(<span class="text-amber-600">#{token[:value]}</span>)
|
|
528
|
+
result << %(<span class="text-amber-600 dark:text-amber-400">#{token[:value]}</span>)
|
|
563
529
|
when :null
|
|
564
530
|
result << %(<span class="text-gray-400">#{token[:value]}</span>)
|
|
565
531
|
else
|
|
@@ -82,58 +82,6 @@ module RubyLLM
|
|
|
82
82
|
end
|
|
83
83
|
|
|
84
84
|
# Compares performance between two agent versions
|
|
85
|
-
#
|
|
86
|
-
# @param agent_type [String] The agent class name
|
|
87
|
-
# @param version1 [String] First version to compare (baseline)
|
|
88
|
-
# @param version2 [String] Second version to compare
|
|
89
|
-
# @param period [Symbol] Time scope for comparison
|
|
90
|
-
# @return [Hash] Comparison data with stats for each version and improvement percentages
|
|
91
|
-
def compare_versions(agent_type, version1, version2, period: :this_week)
|
|
92
|
-
base_scope = by_agent(agent_type).public_send(period)
|
|
93
|
-
|
|
94
|
-
v1_stats = stats_for_scope(base_scope.by_version(version1))
|
|
95
|
-
v2_stats = stats_for_scope(base_scope.by_version(version2))
|
|
96
|
-
|
|
97
|
-
{
|
|
98
|
-
agent_type: agent_type,
|
|
99
|
-
period: period,
|
|
100
|
-
version1: { version: version1, **v1_stats },
|
|
101
|
-
version2: { version: version2, **v2_stats },
|
|
102
|
-
improvements: {
|
|
103
|
-
cost_change_pct: percent_change(v1_stats[:avg_cost], v2_stats[:avg_cost]),
|
|
104
|
-
token_change_pct: percent_change(v1_stats[:avg_tokens], v2_stats[:avg_tokens]),
|
|
105
|
-
speed_change_pct: percent_change(v1_stats[:avg_duration_ms], v2_stats[:avg_duration_ms])
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
end
|
|
109
|
-
|
|
110
|
-
# Returns daily trend data for a specific agent version
|
|
111
|
-
#
|
|
112
|
-
# Used for sparkline charts in version comparison.
|
|
113
|
-
#
|
|
114
|
-
# @param agent_type [String] The agent class name
|
|
115
|
-
# @param version [String] The version to analyze
|
|
116
|
-
# @param days [Integer] Number of days to analyze
|
|
117
|
-
# @return [Array<Hash>] Daily metrics sorted oldest to newest
|
|
118
|
-
def version_trend_data(agent_type, version, days: 14)
|
|
119
|
-
scope = by_agent(agent_type).by_version(version)
|
|
120
|
-
|
|
121
|
-
(0...days).map do |days_ago|
|
|
122
|
-
date = days_ago.days.ago.to_date
|
|
123
|
-
day_scope = scope.where(created_at: date.beginning_of_day..date.end_of_day)
|
|
124
|
-
count = day_scope.count
|
|
125
|
-
|
|
126
|
-
{
|
|
127
|
-
date: date,
|
|
128
|
-
count: count,
|
|
129
|
-
success_rate: calculate_success_rate(day_scope),
|
|
130
|
-
avg_cost: count > 0 ? ((day_scope.total_cost_sum || 0) / count).round(6) : 0,
|
|
131
|
-
avg_duration_ms: day_scope.avg_duration&.round || 0,
|
|
132
|
-
avg_tokens: day_scope.avg_tokens&.round || 0
|
|
133
|
-
}
|
|
134
|
-
end.reverse
|
|
135
|
-
end
|
|
136
|
-
|
|
137
85
|
# Analyzes trends over a time period
|
|
138
86
|
#
|
|
139
87
|
# @param agent_type [String, nil] Filter to specific agent, or nil for all
|
|
@@ -526,9 +474,18 @@ module RubyLLM
|
|
|
526
474
|
|
|
527
475
|
# Average time to first token for streaming executions
|
|
528
476
|
#
|
|
477
|
+
# time_to_first_token_ms is stored in metadata JSON, so we use
|
|
478
|
+
# Ruby-level calculation instead of SQL aggregation.
|
|
479
|
+
#
|
|
529
480
|
# @return [Integer, nil] Average TTFT in milliseconds, or nil if no data
|
|
530
481
|
def avg_time_to_first_token
|
|
531
|
-
streaming
|
|
482
|
+
ttft_values = streaming
|
|
483
|
+
.where("metadata IS NOT NULL")
|
|
484
|
+
.pluck(:metadata)
|
|
485
|
+
.filter_map { |m| m&.dig("time_to_first_token_ms") }
|
|
486
|
+
return nil if ttft_values.empty?
|
|
487
|
+
|
|
488
|
+
(ttft_values.sum.to_f / ttft_values.size).round(0)
|
|
532
489
|
end
|
|
533
490
|
|
|
534
491
|
# Finish reason distribution
|
|
@@ -540,9 +497,11 @@ module RubyLLM
|
|
|
540
497
|
|
|
541
498
|
# Rate limited execution count
|
|
542
499
|
#
|
|
500
|
+
# rate_limited is stored in metadata JSON
|
|
501
|
+
#
|
|
543
502
|
# @return [Integer] Number of executions that were rate limited
|
|
544
503
|
def rate_limited_count
|
|
545
|
-
|
|
504
|
+
metadata_true("rate_limited").count
|
|
546
505
|
end
|
|
547
506
|
|
|
548
507
|
# Rate limited rate percentage
|
|
@@ -79,17 +79,11 @@ module RubyLLM
|
|
|
79
79
|
# @param agent_type [String] The agent class name
|
|
80
80
|
# @return [ActiveRecord::Relation]
|
|
81
81
|
|
|
82
|
-
# @!method by_version(version)
|
|
83
|
-
# Filters to a specific agent version
|
|
84
|
-
# @param version [String] The version string
|
|
85
|
-
# @return [ActiveRecord::Relation]
|
|
86
|
-
|
|
87
82
|
# @!method by_model(model_id)
|
|
88
83
|
# Filters to a specific LLM model
|
|
89
84
|
# @param model_id [String] The model identifier
|
|
90
85
|
# @return [ActiveRecord::Relation]
|
|
91
86
|
scope :by_agent, ->(agent_type) { where(agent_type: agent_type.to_s) }
|
|
92
|
-
scope :by_version, ->(version) { where(agent_version: version.to_s) }
|
|
93
87
|
scope :by_model, ->(model_id) { where(model_id: model_id.to_s) }
|
|
94
88
|
|
|
95
89
|
# @!endgroup
|
|
@@ -142,15 +136,25 @@ module RubyLLM
|
|
|
142
136
|
# @!group Parameter Scopes
|
|
143
137
|
|
|
144
138
|
# @!method with_parameter(key, value = nil)
|
|
145
|
-
# Filters by
|
|
139
|
+
# Filters by parameter key/value in the execution_details table
|
|
146
140
|
# @param key [String, Symbol] Parameter key to check
|
|
147
141
|
# @param value [Object, nil] Optional value to match
|
|
148
142
|
# @return [ActiveRecord::Relation]
|
|
149
143
|
scope :with_parameter, ->(key, value = nil) do
|
|
150
|
-
|
|
151
|
-
|
|
144
|
+
detail_table = RubyLLM::Agents::ExecutionDetail.table_name
|
|
145
|
+
joined = joins(:detail)
|
|
146
|
+
if connection.adapter_name.downcase.include?("sqlite")
|
|
147
|
+
if value
|
|
148
|
+
joined.where("json_extract(#{detail_table}.parameters, ?) = ?", "$.#{key}", value.to_s)
|
|
149
|
+
else
|
|
150
|
+
joined.where("json_extract(#{detail_table}.parameters, ?) IS NOT NULL", "$.#{key}")
|
|
151
|
+
end
|
|
152
152
|
else
|
|
153
|
-
|
|
153
|
+
if value
|
|
154
|
+
joined.where("#{detail_table}.parameters @> ?", { key => value }.to_json)
|
|
155
|
+
else
|
|
156
|
+
joined.where("#{detail_table}.parameters ? :key", key: key.to_s)
|
|
157
|
+
end
|
|
154
158
|
end
|
|
155
159
|
end
|
|
156
160
|
|
|
@@ -196,10 +200,12 @@ module RubyLLM
|
|
|
196
200
|
# @!method rate_limited
|
|
197
201
|
# Returns executions that were rate limited
|
|
198
202
|
# @return [ActiveRecord::Relation]
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
scope :
|
|
202
|
-
scope :
|
|
203
|
+
#
|
|
204
|
+
# Note: fallback_reason, retryable, and rate_limited are stored in metadata JSON
|
|
205
|
+
scope :with_fallback, -> { metadata_present("fallback_reason") }
|
|
206
|
+
scope :retryable_errors, -> { metadata_true("retryable") }
|
|
207
|
+
scope :rate_limited, -> { metadata_true("rate_limited") }
|
|
208
|
+
scope :by_fallback_reason, ->(reason) { metadata_value("fallback_reason", reason) }
|
|
203
209
|
|
|
204
210
|
# @!endgroup
|
|
205
211
|
|
|
@@ -269,6 +275,47 @@ module RubyLLM
|
|
|
269
275
|
# They can be called on scoped relations.
|
|
270
276
|
|
|
271
277
|
class_methods do
|
|
278
|
+
# Database-agnostic JSON metadata queries
|
|
279
|
+
# These fields (fallback_reason, retryable, rate_limited, etc.) are stored
|
|
280
|
+
# in the metadata JSON column rather than as direct columns.
|
|
281
|
+
|
|
282
|
+
# Queries for metadata key presence (IS NOT NULL)
|
|
283
|
+
#
|
|
284
|
+
# @param key [String] The metadata key
|
|
285
|
+
# @return [ActiveRecord::Relation]
|
|
286
|
+
def metadata_present(key)
|
|
287
|
+
if connection.adapter_name.downcase.include?("sqlite")
|
|
288
|
+
where("json_extract(metadata, ?) IS NOT NULL", "$.#{key}")
|
|
289
|
+
else
|
|
290
|
+
where("metadata->>? IS NOT NULL", key.to_s)
|
|
291
|
+
end
|
|
292
|
+
end
|
|
293
|
+
|
|
294
|
+
# Queries for metadata boolean value being true
|
|
295
|
+
#
|
|
296
|
+
# @param key [String] The metadata key
|
|
297
|
+
# @return [ActiveRecord::Relation]
|
|
298
|
+
def metadata_true(key)
|
|
299
|
+
if connection.adapter_name.downcase.include?("sqlite")
|
|
300
|
+
where("json_extract(metadata, ?) = 1", "$.#{key}")
|
|
301
|
+
else
|
|
302
|
+
where("metadata @> ?", { key.to_s => true }.to_json)
|
|
303
|
+
end
|
|
304
|
+
end
|
|
305
|
+
|
|
306
|
+
# Queries for metadata key matching a specific value
|
|
307
|
+
#
|
|
308
|
+
# @param key [String] The metadata key
|
|
309
|
+
# @param value [Object] The value to match
|
|
310
|
+
# @return [ActiveRecord::Relation]
|
|
311
|
+
def metadata_value(key, value)
|
|
312
|
+
if connection.adapter_name.downcase.include?("sqlite")
|
|
313
|
+
where("json_extract(metadata, ?) = ?", "$.#{key}", value.to_s)
|
|
314
|
+
else
|
|
315
|
+
where("metadata->>? = ?", key.to_s, value.to_s)
|
|
316
|
+
end
|
|
317
|
+
end
|
|
318
|
+
|
|
272
319
|
# Returns sum of total_cost for the current scope
|
|
273
320
|
#
|
|
274
321
|
# @return [Float, nil] Total cost in USD
|
|
@@ -8,8 +8,6 @@ module RubyLLM
|
|
|
8
8
|
#
|
|
9
9
|
# @!attribute [rw] agent_type
|
|
10
10
|
# @return [String] Full class name of the agent (e.g., "SearchAgent")
|
|
11
|
-
# @!attribute [rw] agent_version
|
|
12
|
-
# @return [String] Version string for cache invalidation
|
|
13
11
|
# @!attribute [rw] model_id
|
|
14
12
|
# @return [String] LLM model identifier used
|
|
15
13
|
# @!attribute [rw] temperature
|
|
@@ -37,7 +35,7 @@ module RubyLLM
|
|
|
37
35
|
# @!attribute [rw] parameters
|
|
38
36
|
# @return [Hash] Sanitized parameters passed to the agent
|
|
39
37
|
# @!attribute [rw] metadata
|
|
40
|
-
# @return [Hash] Custom metadata from
|
|
38
|
+
# @return [Hash] Custom metadata from metadata hook
|
|
41
39
|
# @!attribute [rw] error_class
|
|
42
40
|
# @return [String, nil] Exception class name if failed
|
|
43
41
|
# @!attribute [rw] error_message
|
|
@@ -51,7 +49,6 @@ module RubyLLM
|
|
|
51
49
|
include Execution::Metrics
|
|
52
50
|
include Execution::Scopes
|
|
53
51
|
include Execution::Analytics
|
|
54
|
-
include Execution::Workflow
|
|
55
52
|
|
|
56
53
|
# Status enum
|
|
57
54
|
# - running: execution in progress
|
|
@@ -72,20 +69,25 @@ module RubyLLM
|
|
|
72
69
|
has_many :child_executions, class_name: "RubyLLM::Agents::Execution",
|
|
73
70
|
foreign_key: :parent_execution_id, dependent: :nullify, inverse_of: :parent_execution
|
|
74
71
|
|
|
75
|
-
#
|
|
76
|
-
|
|
72
|
+
# Detail record for large payloads (prompts, responses, tool calls, etc.)
|
|
73
|
+
has_one :detail, class_name: "RubyLLM::Agents::ExecutionDetail",
|
|
74
|
+
foreign_key: :execution_id, dependent: :destroy
|
|
75
|
+
|
|
76
|
+
# Delegations so existing code keeps working transparently
|
|
77
|
+
delegate :system_prompt, :user_prompt, :response, :error_message,
|
|
78
|
+
:messages_summary, :tool_calls, :attempts, :fallback_chain,
|
|
79
|
+
:parameters, :routed_to, :classification_result,
|
|
80
|
+
:cached_at, :cache_creation_tokens,
|
|
81
|
+
to: :detail, prefix: false, allow_nil: true
|
|
77
82
|
|
|
78
83
|
# Validations
|
|
79
84
|
validates :agent_type, :model_id, :started_at, presence: true
|
|
80
85
|
validates :status, inclusion: { in: statuses.keys }
|
|
81
|
-
validates :agent_version, presence: true
|
|
82
86
|
validates :temperature, numericality: { greater_than_or_equal_to: 0, less_than_or_equal_to: 2 }, allow_nil: true
|
|
83
87
|
validates :input_tokens, :output_tokens, numericality: { greater_than_or_equal_to: 0 }, allow_nil: true
|
|
84
88
|
validates :duration_ms, numericality: { greater_than_or_equal_to: 0 }, allow_nil: true
|
|
85
89
|
validates :input_cost, :output_cost, :total_cost, numericality: { greater_than_or_equal_to: 0 }, allow_nil: true
|
|
86
90
|
validates :finish_reason, inclusion: { in: FINISH_REASONS }, allow_nil: true
|
|
87
|
-
validates :fallback_reason, inclusion: { in: FALLBACK_REASONS }, allow_nil: true
|
|
88
|
-
validates :time_to_first_token_ms, numericality: { greater_than_or_equal_to: 0 }, allow_nil: true
|
|
89
91
|
|
|
90
92
|
before_save :calculate_total_tokens, if: -> { input_tokens_changed? || output_tokens_changed? }
|
|
91
93
|
before_save :calculate_total_cost, if: -> { input_cost_changed? || output_cost_changed? }
|
|
@@ -205,7 +207,41 @@ module RubyLLM
|
|
|
205
207
|
#
|
|
206
208
|
# @return [Boolean] true if rate limiting occurred
|
|
207
209
|
def rate_limited?
|
|
208
|
-
rate_limited == true
|
|
210
|
+
metadata&.dig("rate_limited") == true
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
# Convenience accessors for niche fields stored in metadata JSON
|
|
214
|
+
%w[span_id response_cache_key fallback_reason].each do |field|
|
|
215
|
+
define_method(field) { metadata&.dig(field) }
|
|
216
|
+
define_method(:"#{field}=") { |val| self.metadata = (metadata || {}).merge(field => val) }
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
%w[time_to_first_token_ms].each do |field|
|
|
220
|
+
define_method(field) { metadata&.dig(field)&.to_i }
|
|
221
|
+
define_method(:"#{field}=") { |val| self.metadata = (metadata || {}).merge(field => val) }
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
def retryable
|
|
225
|
+
metadata&.dig("retryable")
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
def retryable=(val)
|
|
229
|
+
self.metadata = (metadata || {}).merge("retryable" => val)
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
def rate_limited
|
|
233
|
+
metadata&.dig("rate_limited")
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
def rate_limited=(val)
|
|
237
|
+
self.metadata = (metadata || {}).merge("rate_limited" => val)
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
# Convenience method to access tenant_record through the tenant
|
|
241
|
+
def tenant_record
|
|
242
|
+
return nil unless tenant_id.present?
|
|
243
|
+
|
|
244
|
+
Tenant.find_by(tenant_id: tenant_id)&.tenant_record
|
|
209
245
|
end
|
|
210
246
|
|
|
211
247
|
# Returns whether this execution used streaming
|
|
@@ -323,14 +359,18 @@ module RubyLLM
|
|
|
323
359
|
|
|
324
360
|
# Resolves model info for cost calculation
|
|
325
361
|
#
|
|
362
|
+
# Uses Models.find (local registry lookup) rather than Models.resolve
|
|
363
|
+
# because cost calculation only needs pricing data, not a provider instance.
|
|
364
|
+
# Models.resolve requires API keys to instantiate the provider, which may
|
|
365
|
+
# not be available in background jobs or instrumentation contexts.
|
|
366
|
+
#
|
|
326
367
|
# @param lookup_model_id [String, nil] The model identifier (defaults to self.model_id)
|
|
327
368
|
# @return [Object, nil] Model info or nil
|
|
328
369
|
def resolve_model_info(lookup_model_id = nil)
|
|
329
370
|
lookup_model_id ||= model_id
|
|
330
371
|
return nil unless lookup_model_id
|
|
331
372
|
|
|
332
|
-
|
|
333
|
-
model
|
|
373
|
+
RubyLLM::Models.find(lookup_model_id)
|
|
334
374
|
rescue StandardError
|
|
335
375
|
nil
|
|
336
376
|
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RubyLLM
|
|
4
|
+
module Agents
|
|
5
|
+
# Stores large payload data for an execution (prompts, responses, tool calls, etc.)
|
|
6
|
+
#
|
|
7
|
+
# Separated from {Execution} to keep the main table lean for analytics queries.
|
|
8
|
+
# Only created when there is detail data to store.
|
|
9
|
+
#
|
|
10
|
+
# @see Execution
|
|
11
|
+
# @api public
|
|
12
|
+
class ExecutionDetail < ::ActiveRecord::Base
|
|
13
|
+
self.table_name = "ruby_llm_agents_execution_details"
|
|
14
|
+
|
|
15
|
+
belongs_to :execution, class_name: "RubyLLM::Agents::Execution"
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|