ruby_llm-agents 0.4.0 → 1.0.0.beta.1
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 +225 -34
- data/app/controllers/ruby_llm/agents/agents_controller.rb +136 -16
- data/app/controllers/ruby_llm/agents/api_configurations_controller.rb +214 -0
- data/app/controllers/ruby_llm/agents/dashboard_controller.rb +29 -9
- data/app/controllers/ruby_llm/agents/{settings_controller.rb → system_config_controller.rb} +3 -3
- data/app/controllers/ruby_llm/agents/tenants_controller.rb +109 -0
- data/app/controllers/ruby_llm/agents/workflows_controller.rb +355 -0
- data/app/helpers/ruby_llm/agents/application_helper.rb +25 -0
- data/app/models/ruby_llm/agents/api_configuration.rb +386 -0
- data/app/models/ruby_llm/agents/execution.rb +3 -0
- data/app/models/ruby_llm/agents/tenant_budget.rb +112 -14
- data/app/services/ruby_llm/agents/agent_registry.rb +51 -12
- data/app/views/layouts/ruby_llm/agents/application.html.erb +5 -30
- data/app/views/ruby_llm/agents/agents/_agent.html.erb +13 -1
- data/app/views/ruby_llm/agents/agents/_config_agent.html.erb +235 -0
- data/app/views/ruby_llm/agents/agents/_config_embedder.html.erb +70 -0
- data/app/views/ruby_llm/agents/agents/_config_image_generator.html.erb +152 -0
- data/app/views/ruby_llm/agents/agents/_config_moderator.html.erb +63 -0
- data/app/views/ruby_llm/agents/agents/_config_speaker.html.erb +108 -0
- data/app/views/ruby_llm/agents/agents/_config_transcriber.html.erb +91 -0
- data/app/views/ruby_llm/agents/agents/_workflow.html.erb +1 -1
- data/app/views/ruby_llm/agents/agents/index.html.erb +74 -9
- data/app/views/ruby_llm/agents/agents/show.html.erb +18 -378
- data/app/views/ruby_llm/agents/api_configurations/_api_key_field.html.erb +34 -0
- data/app/views/ruby_llm/agents/api_configurations/_form.html.erb +288 -0
- data/app/views/ruby_llm/agents/api_configurations/edit.html.erb +95 -0
- data/app/views/ruby_llm/agents/api_configurations/edit_tenant.html.erb +97 -0
- data/app/views/ruby_llm/agents/api_configurations/show.html.erb +211 -0
- data/app/views/ruby_llm/agents/api_configurations/tenant.html.erb +179 -0
- data/app/views/ruby_llm/agents/dashboard/_action_center.html.erb +1 -1
- data/app/views/ruby_llm/agents/dashboard/_agent_comparison.html.erb +269 -15
- data/app/views/ruby_llm/agents/executions/show.html.erb +98 -0
- data/app/views/ruby_llm/agents/shared/_agent_type_badge.html.erb +93 -0
- data/app/views/ruby_llm/agents/{settings → system_config}/show.html.erb +1 -1
- data/app/views/ruby_llm/agents/tenants/_form.html.erb +150 -0
- data/app/views/ruby_llm/agents/tenants/edit.html.erb +13 -0
- data/app/views/ruby_llm/agents/tenants/index.html.erb +129 -0
- data/app/views/ruby_llm/agents/tenants/show.html.erb +374 -0
- data/app/views/ruby_llm/agents/workflows/_step_performance.html.erb +236 -0
- data/app/views/ruby_llm/agents/workflows/_structure_parallel.html.erb +76 -0
- data/app/views/ruby_llm/agents/workflows/_structure_pipeline.html.erb +74 -0
- data/app/views/ruby_llm/agents/workflows/_structure_router.html.erb +108 -0
- data/app/views/ruby_llm/agents/workflows/show.html.erb +442 -0
- data/config/routes.rb +13 -1
- data/lib/generators/ruby_llm_agents/agent_generator.rb +56 -7
- data/lib/generators/ruby_llm_agents/api_configuration_generator.rb +100 -0
- data/lib/generators/ruby_llm_agents/background_remover_generator.rb +110 -0
- data/lib/generators/ruby_llm_agents/embedder_generator.rb +107 -0
- data/lib/generators/ruby_llm_agents/image_analyzer_generator.rb +115 -0
- data/lib/generators/ruby_llm_agents/image_editor_generator.rb +108 -0
- data/lib/generators/ruby_llm_agents/image_generator_generator.rb +116 -0
- data/lib/generators/ruby_llm_agents/image_pipeline_generator.rb +178 -0
- data/lib/generators/ruby_llm_agents/image_transformer_generator.rb +109 -0
- data/lib/generators/ruby_llm_agents/image_upscaler_generator.rb +103 -0
- data/lib/generators/ruby_llm_agents/image_variator_generator.rb +102 -0
- data/lib/generators/ruby_llm_agents/install_generator.rb +76 -4
- data/lib/generators/ruby_llm_agents/restructure_generator.rb +292 -0
- data/lib/generators/ruby_llm_agents/speaker_generator.rb +121 -0
- data/lib/generators/ruby_llm_agents/templates/add_execution_type_migration.rb.tt +8 -0
- data/lib/generators/ruby_llm_agents/templates/agent.rb.tt +99 -84
- data/lib/generators/ruby_llm_agents/templates/application_agent.rb.tt +42 -40
- data/lib/generators/ruby_llm_agents/templates/application_background_remover.rb.tt +26 -0
- data/lib/generators/ruby_llm_agents/templates/application_embedder.rb.tt +50 -0
- data/lib/generators/ruby_llm_agents/templates/application_image_analyzer.rb.tt +26 -0
- data/lib/generators/ruby_llm_agents/templates/application_image_editor.rb.tt +20 -0
- data/lib/generators/ruby_llm_agents/templates/application_image_generator.rb.tt +38 -0
- data/lib/generators/ruby_llm_agents/templates/application_image_pipeline.rb.tt +139 -0
- data/lib/generators/ruby_llm_agents/templates/application_image_transformer.rb.tt +21 -0
- data/lib/generators/ruby_llm_agents/templates/application_image_upscaler.rb.tt +20 -0
- data/lib/generators/ruby_llm_agents/templates/application_image_variator.rb.tt +20 -0
- data/lib/generators/ruby_llm_agents/templates/application_speaker.rb.tt +49 -0
- data/lib/generators/ruby_llm_agents/templates/application_transcriber.rb.tt +53 -0
- data/lib/generators/ruby_llm_agents/templates/background_remover.rb.tt +44 -0
- data/lib/generators/ruby_llm_agents/templates/create_api_configurations_migration.rb.tt +90 -0
- data/lib/generators/ruby_llm_agents/templates/embedder.rb.tt +41 -0
- data/lib/generators/ruby_llm_agents/templates/image_analyzer.rb.tt +45 -0
- data/lib/generators/ruby_llm_agents/templates/image_editor.rb.tt +35 -0
- data/lib/generators/ruby_llm_agents/templates/image_generator.rb.tt +47 -0
- data/lib/generators/ruby_llm_agents/templates/image_pipeline.rb.tt +50 -0
- data/lib/generators/ruby_llm_agents/templates/image_transformer.rb.tt +44 -0
- data/lib/generators/ruby_llm_agents/templates/image_upscaler.rb.tt +38 -0
- data/lib/generators/ruby_llm_agents/templates/image_variator.rb.tt +33 -0
- data/lib/generators/ruby_llm_agents/templates/skills/AGENTS.md.tt +228 -0
- data/lib/generators/ruby_llm_agents/templates/skills/BACKGROUND_REMOVERS.md.tt +131 -0
- data/lib/generators/ruby_llm_agents/templates/skills/EMBEDDERS.md.tt +255 -0
- data/lib/generators/ruby_llm_agents/templates/skills/IMAGE_ANALYZERS.md.tt +120 -0
- data/lib/generators/ruby_llm_agents/templates/skills/IMAGE_EDITORS.md.tt +102 -0
- data/lib/generators/ruby_llm_agents/templates/skills/IMAGE_GENERATORS.md.tt +282 -0
- data/lib/generators/ruby_llm_agents/templates/skills/IMAGE_PIPELINES.md.tt +228 -0
- data/lib/generators/ruby_llm_agents/templates/skills/IMAGE_TRANSFORMERS.md.tt +120 -0
- data/lib/generators/ruby_llm_agents/templates/skills/IMAGE_UPSCALERS.md.tt +110 -0
- data/lib/generators/ruby_llm_agents/templates/skills/IMAGE_VARIATORS.md.tt +120 -0
- data/lib/generators/ruby_llm_agents/templates/skills/SPEAKERS.md.tt +212 -0
- data/lib/generators/ruby_llm_agents/templates/skills/TOOLS.md.tt +227 -0
- data/lib/generators/ruby_llm_agents/templates/skills/TRANSCRIBERS.md.tt +251 -0
- data/lib/generators/ruby_llm_agents/templates/skills/WORKFLOWS.md.tt +300 -0
- data/lib/generators/ruby_llm_agents/templates/speaker.rb.tt +56 -0
- data/lib/generators/ruby_llm_agents/templates/transcriber.rb.tt +51 -0
- data/lib/generators/ruby_llm_agents/transcriber_generator.rb +107 -0
- data/lib/generators/ruby_llm_agents/upgrade_generator.rb +152 -1
- data/lib/ruby_llm/agents/audio/speaker.rb +553 -0
- data/lib/ruby_llm/agents/audio/transcriber.rb +669 -0
- data/lib/ruby_llm/agents/base_agent.rb +675 -0
- data/lib/ruby_llm/agents/core/base/moderation_dsl.rb +181 -0
- data/lib/ruby_llm/agents/core/base/moderation_execution.rb +274 -0
- data/lib/ruby_llm/agents/core/base.rb +135 -0
- data/lib/ruby_llm/agents/core/configuration.rb +981 -0
- data/lib/ruby_llm/agents/core/errors.rb +150 -0
- data/lib/ruby_llm/agents/{instrumentation.rb → core/instrumentation.rb} +93 -4
- data/lib/ruby_llm/agents/core/llm_tenant.rb +358 -0
- data/lib/ruby_llm/agents/core/resolved_config.rb +348 -0
- data/lib/ruby_llm/agents/{version.rb → core/version.rb} +1 -1
- data/lib/ruby_llm/agents/dsl/base.rb +110 -0
- data/lib/ruby_llm/agents/dsl/caching.rb +142 -0
- data/lib/ruby_llm/agents/dsl/reliability.rb +307 -0
- data/lib/ruby_llm/agents/dsl.rb +41 -0
- data/lib/ruby_llm/agents/image/analyzer/dsl.rb +130 -0
- data/lib/ruby_llm/agents/image/analyzer/execution.rb +402 -0
- data/lib/ruby_llm/agents/image/analyzer.rb +90 -0
- data/lib/ruby_llm/agents/image/background_remover/dsl.rb +154 -0
- data/lib/ruby_llm/agents/image/background_remover/execution.rb +240 -0
- data/lib/ruby_llm/agents/image/background_remover.rb +89 -0
- data/lib/ruby_llm/agents/image/concerns/image_operation_dsl.rb +91 -0
- data/lib/ruby_llm/agents/image/concerns/image_operation_execution.rb +165 -0
- data/lib/ruby_llm/agents/image/editor/dsl.rb +56 -0
- data/lib/ruby_llm/agents/image/editor/execution.rb +207 -0
- data/lib/ruby_llm/agents/image/editor.rb +92 -0
- data/lib/ruby_llm/agents/image/generator/active_storage_support.rb +127 -0
- data/lib/ruby_llm/agents/image/generator/content_policy.rb +95 -0
- data/lib/ruby_llm/agents/image/generator/pricing.rb +353 -0
- data/lib/ruby_llm/agents/image/generator/templates.rb +124 -0
- data/lib/ruby_llm/agents/image/generator.rb +455 -0
- data/lib/ruby_llm/agents/image/pipeline/dsl.rb +213 -0
- data/lib/ruby_llm/agents/image/pipeline/execution.rb +382 -0
- data/lib/ruby_llm/agents/image/pipeline.rb +97 -0
- data/lib/ruby_llm/agents/image/transformer/dsl.rb +148 -0
- data/lib/ruby_llm/agents/image/transformer/execution.rb +223 -0
- data/lib/ruby_llm/agents/image/transformer.rb +95 -0
- data/lib/ruby_llm/agents/image/upscaler/dsl.rb +83 -0
- data/lib/ruby_llm/agents/image/upscaler/execution.rb +219 -0
- data/lib/ruby_llm/agents/image/upscaler.rb +81 -0
- data/lib/ruby_llm/agents/image/variator/dsl.rb +62 -0
- data/lib/ruby_llm/agents/image/variator/execution.rb +189 -0
- data/lib/ruby_llm/agents/image/variator.rb +80 -0
- data/lib/ruby_llm/agents/{alert_manager.rb → infrastructure/alert_manager.rb} +17 -22
- data/lib/ruby_llm/agents/infrastructure/budget/budget_query.rb +145 -0
- data/lib/ruby_llm/agents/infrastructure/budget/config_resolver.rb +149 -0
- data/lib/ruby_llm/agents/infrastructure/budget/forecaster.rb +68 -0
- data/lib/ruby_llm/agents/infrastructure/budget/spend_recorder.rb +279 -0
- data/lib/ruby_llm/agents/infrastructure/budget_tracker.rb +275 -0
- data/lib/ruby_llm/agents/{execution_logger_job.rb → infrastructure/execution_logger_job.rb} +17 -1
- data/lib/ruby_llm/agents/{reliability → infrastructure/reliability}/executor.rb +2 -1
- data/lib/ruby_llm/agents/{reliability → infrastructure/reliability}/retry_strategy.rb +9 -3
- data/lib/ruby_llm/agents/{reliability.rb → infrastructure/reliability.rb} +11 -21
- data/lib/ruby_llm/agents/pipeline/builder.rb +215 -0
- data/lib/ruby_llm/agents/pipeline/context.rb +255 -0
- data/lib/ruby_llm/agents/pipeline/executor.rb +86 -0
- data/lib/ruby_llm/agents/pipeline/middleware/base.rb +124 -0
- data/lib/ruby_llm/agents/pipeline/middleware/budget.rb +95 -0
- data/lib/ruby_llm/agents/pipeline/middleware/cache.rb +171 -0
- data/lib/ruby_llm/agents/pipeline/middleware/instrumentation.rb +415 -0
- data/lib/ruby_llm/agents/pipeline/middleware/reliability.rb +276 -0
- data/lib/ruby_llm/agents/pipeline/middleware/tenant.rb +196 -0
- data/lib/ruby_llm/agents/pipeline.rb +68 -0
- data/lib/ruby_llm/agents/{engine.rb → rails/engine.rb} +79 -10
- data/lib/ruby_llm/agents/results/background_removal_result.rb +286 -0
- data/lib/ruby_llm/agents/{result.rb → results/base.rb} +73 -1
- data/lib/ruby_llm/agents/results/embedding_result.rb +243 -0
- data/lib/ruby_llm/agents/results/image_analysis_result.rb +314 -0
- data/lib/ruby_llm/agents/results/image_edit_result.rb +250 -0
- data/lib/ruby_llm/agents/results/image_generation_result.rb +346 -0
- data/lib/ruby_llm/agents/results/image_pipeline_result.rb +399 -0
- data/lib/ruby_llm/agents/results/image_transform_result.rb +251 -0
- data/lib/ruby_llm/agents/results/image_upscale_result.rb +255 -0
- data/lib/ruby_llm/agents/results/image_variation_result.rb +237 -0
- data/lib/ruby_llm/agents/results/moderation_result.rb +158 -0
- data/lib/ruby_llm/agents/results/speech_result.rb +338 -0
- data/lib/ruby_llm/agents/results/transcription_result.rb +408 -0
- data/lib/ruby_llm/agents/text/embedder.rb +444 -0
- data/lib/ruby_llm/agents/text/moderator.rb +237 -0
- data/lib/ruby_llm/agents/workflow/async.rb +220 -0
- data/lib/ruby_llm/agents/workflow/async_executor.rb +156 -0
- data/lib/ruby_llm/agents/{workflow.rb → workflow/orchestrator.rb} +6 -5
- data/lib/ruby_llm/agents/workflow/parallel.rb +34 -17
- data/lib/ruby_llm/agents/workflow/thread_pool.rb +185 -0
- data/lib/ruby_llm/agents.rb +86 -20
- metadata +189 -35
- data/lib/ruby_llm/agents/base/caching.rb +0 -40
- data/lib/ruby_llm/agents/base/cost_calculation.rb +0 -105
- data/lib/ruby_llm/agents/base/dsl.rb +0 -324
- data/lib/ruby_llm/agents/base/execution.rb +0 -283
- data/lib/ruby_llm/agents/base/reliability_dsl.rb +0 -82
- data/lib/ruby_llm/agents/base/reliability_execution.rb +0 -136
- data/lib/ruby_llm/agents/base/response_building.rb +0 -86
- data/lib/ruby_llm/agents/base/tool_tracking.rb +0 -57
- data/lib/ruby_llm/agents/base.rb +0 -209
- data/lib/ruby_llm/agents/budget_tracker.rb +0 -471
- data/lib/ruby_llm/agents/configuration.rb +0 -357
- /data/lib/ruby_llm/agents/{deprecations.rb → core/deprecations.rb} +0 -0
- /data/lib/ruby_llm/agents/{inflections.rb → core/inflections.rb} +0 -0
- /data/lib/ruby_llm/agents/{attempt_tracker.rb → infrastructure/attempt_tracker.rb} +0 -0
- /data/lib/ruby_llm/agents/{cache_helper.rb → infrastructure/cache_helper.rb} +0 -0
- /data/lib/ruby_llm/agents/{circuit_breaker.rb → infrastructure/circuit_breaker.rb} +0 -0
- /data/lib/ruby_llm/agents/{redactor.rb → infrastructure/redactor.rb} +0 -0
- /data/lib/ruby_llm/agents/{reliability → infrastructure/reliability}/breaker_manager.rb +0 -0
- /data/lib/ruby_llm/agents/{reliability → infrastructure/reliability}/execution_constraints.rb +0 -0
- /data/lib/ruby_llm/agents/{reliability → infrastructure/reliability}/fallback_routing.rb +0 -0
|
@@ -0,0 +1,355 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RubyLLM
|
|
4
|
+
module Agents
|
|
5
|
+
# Controller for viewing workflow details and per-workflow analytics
|
|
6
|
+
#
|
|
7
|
+
# Provides detailed views for individual workflows including structure
|
|
8
|
+
# visualization, per-step/branch performance, route distribution,
|
|
9
|
+
# and execution history.
|
|
10
|
+
#
|
|
11
|
+
# @see AgentRegistry For workflow discovery
|
|
12
|
+
# @see Paginatable For pagination implementation
|
|
13
|
+
# @see Filterable For filter parsing and validation
|
|
14
|
+
# @api private
|
|
15
|
+
class WorkflowsController < ApplicationController
|
|
16
|
+
include Paginatable
|
|
17
|
+
include Filterable
|
|
18
|
+
|
|
19
|
+
# Shows detailed view for a specific workflow
|
|
20
|
+
#
|
|
21
|
+
# Loads workflow configuration (if class exists), statistics,
|
|
22
|
+
# filtered executions, chart data, and step-level analytics.
|
|
23
|
+
# Works for both active workflows and deleted workflows with history.
|
|
24
|
+
#
|
|
25
|
+
# @return [void]
|
|
26
|
+
def show
|
|
27
|
+
@workflow_type = CGI.unescape(params[:id])
|
|
28
|
+
@workflow_class = AgentRegistry.find(@workflow_type)
|
|
29
|
+
@workflow_active = @workflow_class.present?
|
|
30
|
+
|
|
31
|
+
# Determine workflow type from class or execution history
|
|
32
|
+
@workflow_type_kind = detect_workflow_type_kind
|
|
33
|
+
|
|
34
|
+
load_workflow_stats
|
|
35
|
+
load_filter_options
|
|
36
|
+
load_filtered_executions
|
|
37
|
+
load_chart_data
|
|
38
|
+
load_step_stats
|
|
39
|
+
|
|
40
|
+
if @workflow_class
|
|
41
|
+
load_workflow_config
|
|
42
|
+
end
|
|
43
|
+
rescue StandardError => e
|
|
44
|
+
Rails.logger.error("[RubyLLM::Agents] Error loading workflow #{@workflow_type}: #{e.message}")
|
|
45
|
+
redirect_to ruby_llm_agents.agents_path, alert: "Error loading workflow details"
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
private
|
|
49
|
+
|
|
50
|
+
# Detects the workflow type kind (pipeline, parallel, router)
|
|
51
|
+
#
|
|
52
|
+
# @return [String, nil] The workflow type kind
|
|
53
|
+
def detect_workflow_type_kind
|
|
54
|
+
if @workflow_class
|
|
55
|
+
ancestors = @workflow_class.ancestors.map { |a| a.name.to_s }
|
|
56
|
+
if ancestors.include?("RubyLLM::Agents::Workflow::Pipeline")
|
|
57
|
+
"pipeline"
|
|
58
|
+
elsif ancestors.include?("RubyLLM::Agents::Workflow::Parallel")
|
|
59
|
+
"parallel"
|
|
60
|
+
elsif ancestors.include?("RubyLLM::Agents::Workflow::Router")
|
|
61
|
+
"router"
|
|
62
|
+
end
|
|
63
|
+
else
|
|
64
|
+
# Fallback to execution history
|
|
65
|
+
Execution.by_agent(@workflow_type)
|
|
66
|
+
.where.not(workflow_type: nil)
|
|
67
|
+
.pluck(:workflow_type)
|
|
68
|
+
.first
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Loads all-time and today's statistics for the workflow
|
|
73
|
+
#
|
|
74
|
+
# @return [void]
|
|
75
|
+
def load_workflow_stats
|
|
76
|
+
@stats = Execution.stats_for(@workflow_type, period: :all_time)
|
|
77
|
+
@stats_today = Execution.stats_for(@workflow_type, period: :today)
|
|
78
|
+
|
|
79
|
+
# Additional stats for new schema fields
|
|
80
|
+
workflow_scope = Execution.by_agent(@workflow_type)
|
|
81
|
+
@cache_hit_rate = workflow_scope.cache_hit_rate
|
|
82
|
+
@streaming_rate = workflow_scope.streaming_rate
|
|
83
|
+
@avg_ttft = workflow_scope.avg_time_to_first_token
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# Loads available filter options from execution history
|
|
87
|
+
#
|
|
88
|
+
# @return [void]
|
|
89
|
+
def load_filter_options
|
|
90
|
+
filter_data = Execution.by_agent(@workflow_type)
|
|
91
|
+
.where.not(agent_version: nil)
|
|
92
|
+
.or(Execution.by_agent(@workflow_type).where.not(model_id: nil))
|
|
93
|
+
.or(Execution.by_agent(@workflow_type).where.not(temperature: nil))
|
|
94
|
+
.pluck(:agent_version, :model_id, :temperature)
|
|
95
|
+
|
|
96
|
+
@versions = filter_data.map(&:first).compact.uniq.sort.reverse
|
|
97
|
+
@models = filter_data.map { |d| d[1] }.compact.uniq.sort
|
|
98
|
+
@temperatures = filter_data.map(&:last).compact.uniq.sort
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# Loads paginated and filtered executions with statistics
|
|
102
|
+
#
|
|
103
|
+
# @return [void]
|
|
104
|
+
def load_filtered_executions
|
|
105
|
+
base_scope = build_filtered_scope
|
|
106
|
+
result = paginate(base_scope)
|
|
107
|
+
@executions = result[:records]
|
|
108
|
+
@pagination = result[:pagination]
|
|
109
|
+
|
|
110
|
+
@filter_stats = {
|
|
111
|
+
total_count: result[:pagination][:total_count],
|
|
112
|
+
total_cost: base_scope.sum(:total_cost),
|
|
113
|
+
total_tokens: base_scope.sum(:total_tokens)
|
|
114
|
+
}
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# Builds a filtered scope for the current workflow's executions
|
|
118
|
+
#
|
|
119
|
+
# @return [ActiveRecord::Relation] Filtered execution scope
|
|
120
|
+
def build_filtered_scope
|
|
121
|
+
scope = Execution.by_agent(@workflow_type)
|
|
122
|
+
|
|
123
|
+
# Apply status filter with validation
|
|
124
|
+
statuses = parse_array_param(:statuses)
|
|
125
|
+
scope = apply_status_filter(scope, statuses) if statuses.any?
|
|
126
|
+
|
|
127
|
+
# Apply version filter
|
|
128
|
+
versions = parse_array_param(:versions)
|
|
129
|
+
scope = scope.where(agent_version: versions) if versions.any?
|
|
130
|
+
|
|
131
|
+
# Apply model filter
|
|
132
|
+
models = parse_array_param(:models)
|
|
133
|
+
scope = scope.where(model_id: models) if models.any?
|
|
134
|
+
|
|
135
|
+
# Apply temperature filter
|
|
136
|
+
temperatures = parse_array_param(:temperatures)
|
|
137
|
+
scope = scope.where(temperature: temperatures) if temperatures.any?
|
|
138
|
+
|
|
139
|
+
# Apply time range filter with validation
|
|
140
|
+
days = parse_days_param
|
|
141
|
+
scope = apply_time_filter(scope, days)
|
|
142
|
+
|
|
143
|
+
scope
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
# Loads chart data for workflow performance visualization
|
|
147
|
+
#
|
|
148
|
+
# @return [void]
|
|
149
|
+
def load_chart_data
|
|
150
|
+
@trend_data = Execution.trend_analysis(agent_type: @workflow_type, days: 30)
|
|
151
|
+
@status_distribution = Execution.by_agent(@workflow_type).group(:status).count
|
|
152
|
+
@finish_reason_distribution = Execution.by_agent(@workflow_type).finish_reason_distribution
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
# Loads per-step/branch statistics for workflow analytics
|
|
156
|
+
#
|
|
157
|
+
# @return [void]
|
|
158
|
+
def load_step_stats
|
|
159
|
+
@step_stats = calculate_step_stats
|
|
160
|
+
@route_distribution = calculate_route_distribution if @workflow_type_kind == "router"
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
# Calculates per-step/branch performance statistics
|
|
164
|
+
#
|
|
165
|
+
# @return [Array<Hash>] Array of step stats hashes
|
|
166
|
+
def calculate_step_stats
|
|
167
|
+
# Get root workflow executions
|
|
168
|
+
root_executions = Execution.by_agent(@workflow_type)
|
|
169
|
+
.root_executions
|
|
170
|
+
.where("created_at > ?", 30.days.ago)
|
|
171
|
+
.pluck(:id)
|
|
172
|
+
|
|
173
|
+
return [] if root_executions.empty?
|
|
174
|
+
|
|
175
|
+
# Aggregate child execution stats by workflow_step
|
|
176
|
+
child_stats = Execution.where(parent_execution_id: root_executions)
|
|
177
|
+
.group(:workflow_step)
|
|
178
|
+
.select(
|
|
179
|
+
"workflow_step",
|
|
180
|
+
"COUNT(*) as execution_count",
|
|
181
|
+
"AVG(duration_ms) as avg_duration_ms",
|
|
182
|
+
"SUM(total_cost) as total_cost",
|
|
183
|
+
"AVG(total_cost) as avg_cost",
|
|
184
|
+
"SUM(total_tokens) as total_tokens",
|
|
185
|
+
"AVG(total_tokens) as avg_tokens",
|
|
186
|
+
"SUM(CASE WHEN status = 'success' THEN 1 ELSE 0 END) as success_count",
|
|
187
|
+
"SUM(CASE WHEN status = 'error' THEN 1 ELSE 0 END) as error_count"
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
# Get agent type mappings for each step
|
|
191
|
+
step_agent_map = Execution.where(parent_execution_id: root_executions)
|
|
192
|
+
.where.not(workflow_step: nil)
|
|
193
|
+
.group(:workflow_step)
|
|
194
|
+
.pluck(:workflow_step, Arel.sql("MAX(agent_type)"))
|
|
195
|
+
.to_h
|
|
196
|
+
|
|
197
|
+
child_stats.map do |row|
|
|
198
|
+
next if row.workflow_step.blank?
|
|
199
|
+
|
|
200
|
+
execution_count = row.execution_count.to_i
|
|
201
|
+
success_count = row.success_count.to_i
|
|
202
|
+
success_rate = execution_count > 0 ? (success_count.to_f / execution_count * 100).round(1) : 0
|
|
203
|
+
|
|
204
|
+
{
|
|
205
|
+
name: row.workflow_step,
|
|
206
|
+
agent_type: step_agent_map[row.workflow_step],
|
|
207
|
+
execution_count: execution_count,
|
|
208
|
+
success_rate: success_rate,
|
|
209
|
+
avg_duration_ms: row.avg_duration_ms.to_f.round(0),
|
|
210
|
+
total_cost: row.total_cost.to_f.round(4),
|
|
211
|
+
avg_cost: row.avg_cost.to_f.round(6),
|
|
212
|
+
total_tokens: row.total_tokens.to_i,
|
|
213
|
+
avg_tokens: row.avg_tokens.to_f.round(0)
|
|
214
|
+
}
|
|
215
|
+
end.compact
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
# Calculates route distribution for router workflows
|
|
219
|
+
#
|
|
220
|
+
# @return [Hash] Route distribution data
|
|
221
|
+
def calculate_route_distribution
|
|
222
|
+
# Get route distribution from routed_to field
|
|
223
|
+
distribution = Execution.by_agent(@workflow_type)
|
|
224
|
+
.where("created_at > ?", 30.days.ago)
|
|
225
|
+
.where.not(routed_to: nil)
|
|
226
|
+
.group(:routed_to)
|
|
227
|
+
.count
|
|
228
|
+
|
|
229
|
+
total = distribution.values.sum
|
|
230
|
+
return {} if total.zero?
|
|
231
|
+
|
|
232
|
+
# Add percentage and sorting
|
|
233
|
+
distribution.transform_values do |count|
|
|
234
|
+
{
|
|
235
|
+
count: count,
|
|
236
|
+
percentage: (count.to_f / total * 100).round(1)
|
|
237
|
+
}
|
|
238
|
+
end.sort_by { |_k, v| -v[:count] }.to_h
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
# Loads the current workflow class configuration
|
|
242
|
+
#
|
|
243
|
+
# @return [void]
|
|
244
|
+
def load_workflow_config
|
|
245
|
+
@config = {
|
|
246
|
+
# Basic configuration
|
|
247
|
+
version: safe_call(@workflow_class, :version),
|
|
248
|
+
description: safe_call(@workflow_class, :description),
|
|
249
|
+
timeout: safe_call(@workflow_class, :timeout),
|
|
250
|
+
max_cost: safe_call(@workflow_class, :max_cost)
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
# Workflow-specific configuration
|
|
254
|
+
case @workflow_type_kind
|
|
255
|
+
when "pipeline"
|
|
256
|
+
load_pipeline_config
|
|
257
|
+
when "parallel"
|
|
258
|
+
load_parallel_config
|
|
259
|
+
when "router"
|
|
260
|
+
load_router_config
|
|
261
|
+
end
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
# Loads pipeline-specific configuration
|
|
265
|
+
#
|
|
266
|
+
# @return [void]
|
|
267
|
+
def load_pipeline_config
|
|
268
|
+
@steps = extract_steps(@workflow_class)
|
|
269
|
+
@config[:steps_count] = @steps.size
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
# Loads parallel-specific configuration
|
|
273
|
+
#
|
|
274
|
+
# @return [void]
|
|
275
|
+
def load_parallel_config
|
|
276
|
+
@branches = extract_branches(@workflow_class)
|
|
277
|
+
@config[:branches_count] = @branches.size
|
|
278
|
+
@config[:fail_fast] = safe_call(@workflow_class, :fail_fast?)
|
|
279
|
+
end
|
|
280
|
+
|
|
281
|
+
# Loads router-specific configuration
|
|
282
|
+
#
|
|
283
|
+
# @return [void]
|
|
284
|
+
def load_router_config
|
|
285
|
+
@routes = extract_routes(@workflow_class)
|
|
286
|
+
@config[:routes_count] = @routes.size
|
|
287
|
+
@config[:classifier_model] = safe_call(@workflow_class, :classifier_model)
|
|
288
|
+
@config[:classifier_temperature] = safe_call(@workflow_class, :classifier_temperature)
|
|
289
|
+
end
|
|
290
|
+
|
|
291
|
+
# Extracts steps from a pipeline workflow class
|
|
292
|
+
#
|
|
293
|
+
# @param klass [Class] The workflow class
|
|
294
|
+
# @return [Array<Hash>] Array of step hashes
|
|
295
|
+
def extract_steps(klass)
|
|
296
|
+
return [] unless klass.respond_to?(:steps)
|
|
297
|
+
|
|
298
|
+
klass.steps.map do |name, config|
|
|
299
|
+
{
|
|
300
|
+
name: name,
|
|
301
|
+
agent: config[:agent]&.name,
|
|
302
|
+
optional: config[:continue_on_error] || false
|
|
303
|
+
}
|
|
304
|
+
end
|
|
305
|
+
end
|
|
306
|
+
|
|
307
|
+
# Extracts branches from a parallel workflow class
|
|
308
|
+
#
|
|
309
|
+
# @param klass [Class] The workflow class
|
|
310
|
+
# @return [Array<Hash>] Array of branch hashes
|
|
311
|
+
def extract_branches(klass)
|
|
312
|
+
return [] unless klass.respond_to?(:branches)
|
|
313
|
+
|
|
314
|
+
klass.branches.map do |name, config|
|
|
315
|
+
{
|
|
316
|
+
name: name,
|
|
317
|
+
agent: config[:agent]&.name,
|
|
318
|
+
optional: config[:optional] || false
|
|
319
|
+
}
|
|
320
|
+
end
|
|
321
|
+
end
|
|
322
|
+
|
|
323
|
+
# Extracts routes from a router workflow class
|
|
324
|
+
#
|
|
325
|
+
# @param klass [Class] The workflow class
|
|
326
|
+
# @return [Array<Hash>] Array of route hashes
|
|
327
|
+
def extract_routes(klass)
|
|
328
|
+
return [] unless klass.respond_to?(:routes)
|
|
329
|
+
|
|
330
|
+
klass.routes.map do |name, config|
|
|
331
|
+
{
|
|
332
|
+
name: name,
|
|
333
|
+
agent: config[:agent]&.name,
|
|
334
|
+
description: config[:description],
|
|
335
|
+
default: config[:default] || false
|
|
336
|
+
}
|
|
337
|
+
end
|
|
338
|
+
end
|
|
339
|
+
|
|
340
|
+
# Safely calls a method on a class, returning nil if method doesn't exist
|
|
341
|
+
#
|
|
342
|
+
# @param klass [Class, nil] The class to call the method on
|
|
343
|
+
# @param method_name [Symbol] The method to call
|
|
344
|
+
# @return [Object, nil] The result or nil
|
|
345
|
+
def safe_call(klass, method_name)
|
|
346
|
+
return nil unless klass
|
|
347
|
+
return nil unless klass.respond_to?(method_name)
|
|
348
|
+
|
|
349
|
+
klass.public_send(method_name)
|
|
350
|
+
rescue StandardError
|
|
351
|
+
nil
|
|
352
|
+
end
|
|
353
|
+
end
|
|
354
|
+
end
|
|
355
|
+
end
|
|
@@ -24,6 +24,31 @@ module RubyLLM
|
|
|
24
24
|
RubyLLM::Agents::Engine.routes.url_helpers
|
|
25
25
|
end
|
|
26
26
|
|
|
27
|
+
# Returns the URL for "All Tenants" (clears tenant filter)
|
|
28
|
+
#
|
|
29
|
+
# Handles two scenarios:
|
|
30
|
+
# 1. Query param routes - removes tenant_id from query params
|
|
31
|
+
# 2. Path-based tenant routes - navigates to equivalent global route
|
|
32
|
+
#
|
|
33
|
+
# @return [String] URL without tenant filtering
|
|
34
|
+
def all_tenants_url
|
|
35
|
+
# Map tenant-specific path routes to their global equivalents
|
|
36
|
+
tenant_route_mappings = {
|
|
37
|
+
"tenant" => ruby_llm_agents.api_configuration_path,
|
|
38
|
+
"edit_tenant" => ruby_llm_agents.edit_api_configuration_path
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
# Check if current action has a global equivalent
|
|
42
|
+
if tenant_route_mappings.key?(action_name)
|
|
43
|
+
base_path = tenant_route_mappings[action_name]
|
|
44
|
+
query = request.query_parameters.except("tenant_id")
|
|
45
|
+
query.any? ? "#{base_path}?#{query.to_query}" : base_path
|
|
46
|
+
else
|
|
47
|
+
# For query param routes, just remove tenant_id
|
|
48
|
+
url_for(request.query_parameters.except("tenant_id"))
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
27
52
|
# Formats large numbers with human-readable suffixes (K, M, B)
|
|
28
53
|
#
|
|
29
54
|
# @param number [Numeric, nil] The number to format
|