ruby_llm-agents 0.5.0 → 1.0.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 +189 -31
- data/app/controllers/ruby_llm/agents/agents_controller.rb +136 -16
- data/app/controllers/ruby_llm/agents/dashboard_controller.rb +29 -9
- 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/execution.rb +3 -0
- data/app/models/ruby_llm/agents/tenant_budget.rb +58 -15
- data/app/services/ruby_llm/agents/agent_registry.rb +51 -12
- data/app/views/layouts/ruby_llm/agents/application.html.erb +2 -29
- 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/dashboard/_agent_comparison.html.erb +269 -15
- data/app/views/ruby_llm/agents/executions/show.html.erb +16 -0
- data/app/views/ruby_llm/agents/shared/_agent_type_badge.html.erb +93 -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 +1 -0
- data/lib/generators/ruby_llm_agents/agent_generator.rb +56 -7
- 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/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} +22 -1
- data/lib/ruby_llm/agents/core/llm_tenant.rb +358 -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 -11
- 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 +172 -34
- 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 -366
- 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 -210
- data/lib/ruby_llm/agents/budget_tracker.rb +0 -733
- data/lib/ruby_llm/agents/configuration.rb +0 -394
- /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/{resolved_config.rb → core/resolved_config.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,442 @@
|
|
|
1
|
+
<%= render "ruby_llm/agents/shared/breadcrumbs", items: [
|
|
2
|
+
{ label: "Dashboard", path: ruby_llm_agents.root_path },
|
|
3
|
+
{ label: "Workflows", path: ruby_llm_agents.agents_path(tab: "workflows") },
|
|
4
|
+
{ label: @workflow_type.gsub(/Workflow$|Pipeline$|Parallel$|Router$/, '') }
|
|
5
|
+
] %>
|
|
6
|
+
|
|
7
|
+
<!-- Header -->
|
|
8
|
+
<div class="bg-white dark:bg-gray-800 rounded-lg shadow p-6 mb-6">
|
|
9
|
+
<div class="flex items-start justify-between">
|
|
10
|
+
<div>
|
|
11
|
+
<div class="flex items-center space-x-3">
|
|
12
|
+
<%= render "ruby_llm/agents/shared/workflow_type_badge", workflow_type: @workflow_type_kind, size: :md %>
|
|
13
|
+
<h1 class="text-2xl font-bold text-gray-900 dark:text-gray-100">
|
|
14
|
+
<% name_parts = @workflow_type.gsub(/Workflow$|Pipeline$|Parallel$|Router$/, '').split('::') %>
|
|
15
|
+
<% if name_parts.length > 1 %>
|
|
16
|
+
<span class="text-gray-400 dark:text-gray-500 font-normal"><%= name_parts[0..-2].join('::') %>::</span>
|
|
17
|
+
<% end %>
|
|
18
|
+
<%= name_parts.last %>
|
|
19
|
+
</h1>
|
|
20
|
+
|
|
21
|
+
<% if @workflow_active %>
|
|
22
|
+
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-green-100 dark:bg-green-900/50 text-green-800 dark:text-green-300">
|
|
23
|
+
Active
|
|
24
|
+
</span>
|
|
25
|
+
<% else %>
|
|
26
|
+
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-gray-100 dark:bg-gray-700 text-gray-600 dark:text-gray-400">
|
|
27
|
+
Deleted
|
|
28
|
+
</span>
|
|
29
|
+
<% end %>
|
|
30
|
+
|
|
31
|
+
<% if @config %>
|
|
32
|
+
<span class="text-sm text-gray-500 dark:text-gray-400">
|
|
33
|
+
v<%= @config[:version] %>
|
|
34
|
+
</span>
|
|
35
|
+
<% end %>
|
|
36
|
+
</div>
|
|
37
|
+
|
|
38
|
+
<% if @config && @config[:description].present? %>
|
|
39
|
+
<p class="text-gray-600 dark:text-gray-300 mt-2 max-w-2xl">
|
|
40
|
+
<%= @config[:description] %>
|
|
41
|
+
</p>
|
|
42
|
+
<% end %>
|
|
43
|
+
|
|
44
|
+
<!-- Workflow Summary Line -->
|
|
45
|
+
<div class="flex items-center gap-4 mt-3 text-sm text-gray-500 dark:text-gray-400">
|
|
46
|
+
<% if @workflow_type_kind == "pipeline" && @steps.present? %>
|
|
47
|
+
<span><%= @steps.size %> steps</span>
|
|
48
|
+
<% elsif @workflow_type_kind == "parallel" && @branches.present? %>
|
|
49
|
+
<span><%= @branches.size %> branches</span>
|
|
50
|
+
<% elsif @workflow_type_kind == "router" && @routes.present? %>
|
|
51
|
+
<span><%= @routes.size %> routes</span>
|
|
52
|
+
<% end %>
|
|
53
|
+
|
|
54
|
+
<% if @config && @config[:timeout] %>
|
|
55
|
+
<span class="text-gray-300 dark:text-gray-600">|</span>
|
|
56
|
+
<span>timeout <%= @config[:timeout] %>s</span>
|
|
57
|
+
<% end %>
|
|
58
|
+
|
|
59
|
+
<% if @config && @config[:max_cost] %>
|
|
60
|
+
<span class="text-gray-300 dark:text-gray-600">|</span>
|
|
61
|
+
<span>max cost $<%= @config[:max_cost] %></span>
|
|
62
|
+
<% end %>
|
|
63
|
+
|
|
64
|
+
<% if @workflow_type_kind == "router" && @config && @config[:classifier_model] %>
|
|
65
|
+
<span class="text-gray-300 dark:text-gray-600">|</span>
|
|
66
|
+
<span>classifier: <%= @config[:classifier_model] %></span>
|
|
67
|
+
<% end %>
|
|
68
|
+
</div>
|
|
69
|
+
</div>
|
|
70
|
+
|
|
71
|
+
<div class="text-right">
|
|
72
|
+
<p class="text-sm text-gray-500 dark:text-gray-400">
|
|
73
|
+
<%= number_with_delimiter(@stats[:count]) %> total executions
|
|
74
|
+
</p>
|
|
75
|
+
|
|
76
|
+
<div class="flex items-center justify-end gap-3 mt-1">
|
|
77
|
+
<% status_colors = {
|
|
78
|
+
"success" => "bg-green-500",
|
|
79
|
+
"error" => "bg-red-500",
|
|
80
|
+
"timeout" => "bg-yellow-500",
|
|
81
|
+
"running" => "bg-blue-500"
|
|
82
|
+
} %>
|
|
83
|
+
|
|
84
|
+
<% @status_distribution.each do |status, count| %>
|
|
85
|
+
<div class="flex items-center gap-1">
|
|
86
|
+
<span class="w-2 h-2 rounded-full <%= status_colors[status] || 'bg-gray-400' %> <%= status == 'running' ? 'animate-pulse' : '' %>"></span>
|
|
87
|
+
<span class="text-xs text-gray-600 dark:text-gray-400">
|
|
88
|
+
<%= number_with_delimiter(count) %>
|
|
89
|
+
</span>
|
|
90
|
+
</div>
|
|
91
|
+
<% end %>
|
|
92
|
+
</div>
|
|
93
|
+
</div>
|
|
94
|
+
</div>
|
|
95
|
+
</div>
|
|
96
|
+
|
|
97
|
+
<!-- Workflow Structure -->
|
|
98
|
+
<div class="bg-white dark:bg-gray-800 rounded-lg shadow p-6 mb-6">
|
|
99
|
+
<div class="flex items-center justify-between mb-4">
|
|
100
|
+
<h3 class="text-lg font-semibold text-gray-900 dark:text-gray-100">
|
|
101
|
+
Workflow Structure
|
|
102
|
+
</h3>
|
|
103
|
+
|
|
104
|
+
<% if @workflow_type_kind == "parallel" && @config %>
|
|
105
|
+
<span class="text-sm text-gray-500 dark:text-gray-400">
|
|
106
|
+
fail_fast: <%= @config[:fail_fast] ? 'on' : 'off' %>
|
|
107
|
+
</span>
|
|
108
|
+
<% elsif @workflow_type_kind == "router" && @config && @config[:classifier_model] %>
|
|
109
|
+
<span class="text-sm text-gray-500 dark:text-gray-400">
|
|
110
|
+
classifier: <%= @config[:classifier_model] %> @ <%= @config[:classifier_temperature] || 0.0 %>
|
|
111
|
+
</span>
|
|
112
|
+
<% end %>
|
|
113
|
+
</div>
|
|
114
|
+
|
|
115
|
+
<% case @workflow_type_kind %>
|
|
116
|
+
<% when "pipeline" %>
|
|
117
|
+
<%= render "ruby_llm/agents/workflows/structure_pipeline", steps: @steps || [] %>
|
|
118
|
+
<% when "parallel" %>
|
|
119
|
+
<%= render "ruby_llm/agents/workflows/structure_parallel", branches: @branches || [] %>
|
|
120
|
+
<% when "router" %>
|
|
121
|
+
<%= render "ruby_llm/agents/workflows/structure_router", routes: @routes || [] %>
|
|
122
|
+
<% else %>
|
|
123
|
+
<p class="text-gray-500 dark:text-gray-400 italic">
|
|
124
|
+
Workflow structure unavailable
|
|
125
|
+
</p>
|
|
126
|
+
<% end %>
|
|
127
|
+
</div>
|
|
128
|
+
|
|
129
|
+
<!-- Stats Grid -->
|
|
130
|
+
<% success_rate = @stats[:success_rate] || 0 %>
|
|
131
|
+
<% success_rate_color = success_rate >= 95 ? 'text-green-600' : success_rate >= 80 ? 'text-yellow-600' : 'text-red-600' %>
|
|
132
|
+
|
|
133
|
+
<div class="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-5 gap-4 mb-6">
|
|
134
|
+
<%= render "ruby_llm/agents/shared/stat_card",
|
|
135
|
+
title: "Executions",
|
|
136
|
+
value: number_with_delimiter(@stats[:count]),
|
|
137
|
+
subtitle: "Today: #{@stats_today[:count]}",
|
|
138
|
+
icon: "M13 10V3L4 14h7v7l9-11h-7z",
|
|
139
|
+
icon_color: "text-blue-500" %>
|
|
140
|
+
|
|
141
|
+
<%= render "ruby_llm/agents/shared/stat_card",
|
|
142
|
+
title: "Success Rate",
|
|
143
|
+
value: "#{success_rate}%",
|
|
144
|
+
subtitle: "Error rate: #{@stats[:error_rate] || 0}%",
|
|
145
|
+
icon: "M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z",
|
|
146
|
+
icon_color: "text-green-500",
|
|
147
|
+
value_color: success_rate_color %>
|
|
148
|
+
|
|
149
|
+
<%= render "ruby_llm/agents/shared/stat_card",
|
|
150
|
+
title: "Total Cost",
|
|
151
|
+
value: "$#{number_with_precision(@stats[:total_cost] || 0, precision: 4)}",
|
|
152
|
+
subtitle: "Avg: $#{number_with_precision(@stats[:avg_cost] || 0, precision: 6)}",
|
|
153
|
+
icon: "M12 8c-1.657 0-3 .895-3 2s1.343 2 3 2 3 .895 3 2-1.343 2-3 2m0-8c1.11 0 2.08.402 2.599 1M12 8V7m0 1v8m0 0v1m0-1c-1.11 0-2.08-.402-2.599-1M21 12a9 9 0 11-18 0 9 9 0 0118 0z",
|
|
154
|
+
icon_color: "text-amber-500" %>
|
|
155
|
+
|
|
156
|
+
<%= render "ruby_llm/agents/shared/stat_card",
|
|
157
|
+
title: "Total Tokens",
|
|
158
|
+
value: number_with_delimiter(@stats[:total_tokens] || 0),
|
|
159
|
+
subtitle: "Avg: #{number_with_delimiter(@stats[:avg_tokens] || 0)}",
|
|
160
|
+
icon: "M7 20l4-16m2 16l4-16M6 9h14M4 15h14",
|
|
161
|
+
icon_color: "text-indigo-500" %>
|
|
162
|
+
|
|
163
|
+
<%= render "ruby_llm/agents/shared/stat_card",
|
|
164
|
+
title: "Avg Duration",
|
|
165
|
+
value: "#{number_with_delimiter(@stats[:avg_duration_ms] || 0)} ms",
|
|
166
|
+
icon: "M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z",
|
|
167
|
+
icon_color: "text-purple-500" %>
|
|
168
|
+
</div>
|
|
169
|
+
|
|
170
|
+
<!-- Step/Branch/Route Performance -->
|
|
171
|
+
<% if @step_stats.present? %>
|
|
172
|
+
<%= render "ruby_llm/agents/workflows/step_performance",
|
|
173
|
+
workflow_type: @workflow_type_kind,
|
|
174
|
+
step_stats: @step_stats,
|
|
175
|
+
route_distribution: @route_distribution %>
|
|
176
|
+
<% end %>
|
|
177
|
+
|
|
178
|
+
<!-- Charts Section -->
|
|
179
|
+
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-6">
|
|
180
|
+
<!-- Executions Over Time -->
|
|
181
|
+
<div class="bg-white dark:bg-gray-800 rounded-lg shadow p-6 overflow-hidden">
|
|
182
|
+
<h3 class="text-lg font-semibold text-gray-900 dark:text-gray-100 mb-4">
|
|
183
|
+
Executions (30 days)
|
|
184
|
+
</h3>
|
|
185
|
+
<div id="executions-chart" style="height: 220px;"></div>
|
|
186
|
+
</div>
|
|
187
|
+
|
|
188
|
+
<!-- Cost Over Time -->
|
|
189
|
+
<div class="bg-white dark:bg-gray-800 rounded-lg shadow p-6 overflow-hidden">
|
|
190
|
+
<h3 class="text-lg font-semibold text-gray-900 dark:text-gray-100 mb-4">
|
|
191
|
+
Cost (30 days)
|
|
192
|
+
</h3>
|
|
193
|
+
<div id="cost-chart" style="height: 220px;"></div>
|
|
194
|
+
</div>
|
|
195
|
+
</div>
|
|
196
|
+
|
|
197
|
+
<script>
|
|
198
|
+
(function() {
|
|
199
|
+
const trendData = <%= raw @trend_data.to_json %>;
|
|
200
|
+
const categories = trendData.map(d => d.date.split('-').slice(1).join('/'));
|
|
201
|
+
|
|
202
|
+
// Executions chart
|
|
203
|
+
Highcharts.chart('executions-chart', {
|
|
204
|
+
chart: { type: 'areaspline', backgroundColor: 'transparent' },
|
|
205
|
+
xAxis: { categories: categories, labels: { style: { color: '#9CA3AF' } } },
|
|
206
|
+
yAxis: { min: 0, title: { text: null }, labels: { style: { color: '#9CA3AF' } } },
|
|
207
|
+
legend: { enabled: false },
|
|
208
|
+
plotOptions: { areaspline: { fillOpacity: 0.2, marker: { enabled: false } } },
|
|
209
|
+
series: [
|
|
210
|
+
{ name: 'Success', color: '#10B981', data: trendData.map(d => d.count - (d.error_count || 0)) },
|
|
211
|
+
{ name: 'Failed', color: '#EF4444', data: trendData.map(d => d.error_count || 0) }
|
|
212
|
+
]
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
// Cost chart
|
|
216
|
+
Highcharts.chart('cost-chart', {
|
|
217
|
+
chart: { type: 'spline', backgroundColor: 'transparent' },
|
|
218
|
+
xAxis: { categories: categories, labels: { style: { color: '#9CA3AF' } } },
|
|
219
|
+
yAxis: { min: 0, title: { text: null }, labels: { style: { color: '#9CA3AF' }, format: '${value}' } },
|
|
220
|
+
legend: { enabled: false },
|
|
221
|
+
plotOptions: { spline: { marker: { enabled: false } } },
|
|
222
|
+
tooltip: { valuePrefix: '$' },
|
|
223
|
+
series: [{ name: 'Cost', color: '#10B981', data: trendData.map(d => parseFloat(d.total_cost) || 0) }]
|
|
224
|
+
});
|
|
225
|
+
})();
|
|
226
|
+
</script>
|
|
227
|
+
|
|
228
|
+
<!-- Finish Reason Distribution -->
|
|
229
|
+
<% if @finish_reason_distribution.present? && @finish_reason_distribution.any? %>
|
|
230
|
+
<div class="bg-white dark:bg-gray-800 rounded-lg shadow p-4 mb-6">
|
|
231
|
+
<div class="flex items-center justify-between">
|
|
232
|
+
<p class="text-sm text-gray-500 dark:text-gray-400 uppercase">
|
|
233
|
+
Finish Reasons
|
|
234
|
+
</p>
|
|
235
|
+
|
|
236
|
+
<div class="flex flex-wrap gap-4">
|
|
237
|
+
<% finish_colors = {
|
|
238
|
+
'stop' => '#10B981',
|
|
239
|
+
'length' => '#F59E0B',
|
|
240
|
+
'content_filter' => '#EF4444',
|
|
241
|
+
'tool_calls' => '#3B82F6',
|
|
242
|
+
nil => '#6B7280'
|
|
243
|
+
} %>
|
|
244
|
+
|
|
245
|
+
<% @finish_reason_distribution.each do |reason, count| %>
|
|
246
|
+
<div class="flex items-center">
|
|
247
|
+
<span class="w-2 h-2 rounded-full mr-1.5" style="background-color: <%= finish_colors[reason] || '#6B7280' %>"></span>
|
|
248
|
+
<span class="text-sm text-gray-700 dark:text-gray-300">
|
|
249
|
+
<%= reason || 'unknown' %>
|
|
250
|
+
</span>
|
|
251
|
+
<span class="text-sm font-medium text-gray-900 dark:text-gray-100 ml-1">
|
|
252
|
+
(<%= number_with_delimiter(count) %>)
|
|
253
|
+
</span>
|
|
254
|
+
</div>
|
|
255
|
+
<% end %>
|
|
256
|
+
</div>
|
|
257
|
+
</div>
|
|
258
|
+
</div>
|
|
259
|
+
<% end %>
|
|
260
|
+
|
|
261
|
+
<% if @config %>
|
|
262
|
+
<!-- Configuration -->
|
|
263
|
+
<div class="bg-white dark:bg-gray-800 rounded-lg shadow p-6 mb-6">
|
|
264
|
+
<h3 class="text-lg font-semibold text-gray-900 dark:text-gray-100 mb-4">
|
|
265
|
+
Configuration
|
|
266
|
+
</h3>
|
|
267
|
+
|
|
268
|
+
<!-- Basic Configuration -->
|
|
269
|
+
<p class="text-xs text-gray-500 dark:text-gray-400 uppercase tracking-wider mb-3">
|
|
270
|
+
Basic
|
|
271
|
+
</p>
|
|
272
|
+
|
|
273
|
+
<div class="grid grid-cols-2 md:grid-cols-4 gap-4 mb-6">
|
|
274
|
+
<div>
|
|
275
|
+
<p class="text-sm text-gray-500 dark:text-gray-400">Version</p>
|
|
276
|
+
<p class="font-medium text-gray-900 dark:text-gray-100">
|
|
277
|
+
<%= @config[:version] || 'N/A' %>
|
|
278
|
+
</p>
|
|
279
|
+
</div>
|
|
280
|
+
|
|
281
|
+
<div>
|
|
282
|
+
<p class="text-sm text-gray-500 dark:text-gray-400">Timeout</p>
|
|
283
|
+
<p class="font-medium text-gray-900 dark:text-gray-100">
|
|
284
|
+
<%= @config[:timeout] ? "#{@config[:timeout]}s" : 'N/A' %>
|
|
285
|
+
</p>
|
|
286
|
+
</div>
|
|
287
|
+
|
|
288
|
+
<div>
|
|
289
|
+
<p class="text-sm text-gray-500 dark:text-gray-400">Max Cost</p>
|
|
290
|
+
<p class="font-medium text-gray-900 dark:text-gray-100">
|
|
291
|
+
<%= @config[:max_cost] ? "$#{@config[:max_cost]}" : 'N/A' %>
|
|
292
|
+
</p>
|
|
293
|
+
</div>
|
|
294
|
+
|
|
295
|
+
<% if @workflow_type_kind == "parallel" %>
|
|
296
|
+
<div>
|
|
297
|
+
<p class="text-sm text-gray-500 dark:text-gray-400">Fail Fast</p>
|
|
298
|
+
<p class="font-medium text-gray-900 dark:text-gray-100">
|
|
299
|
+
<%= @config[:fail_fast] ? 'Yes' : 'No' %>
|
|
300
|
+
</p>
|
|
301
|
+
</div>
|
|
302
|
+
<% end %>
|
|
303
|
+
</div>
|
|
304
|
+
|
|
305
|
+
<% if @workflow_type_kind == "router" && @config[:classifier_model] %>
|
|
306
|
+
<!-- Router Settings -->
|
|
307
|
+
<div class="border-t border-gray-100 dark:border-gray-700 pt-4">
|
|
308
|
+
<p class="text-xs text-gray-500 dark:text-gray-400 uppercase tracking-wider mb-3">
|
|
309
|
+
Router Settings
|
|
310
|
+
</p>
|
|
311
|
+
|
|
312
|
+
<div class="grid grid-cols-2 md:grid-cols-4 gap-4">
|
|
313
|
+
<div>
|
|
314
|
+
<p class="text-sm text-gray-500 dark:text-gray-400">Classifier Model</p>
|
|
315
|
+
<p class="font-medium text-gray-900 dark:text-gray-100">
|
|
316
|
+
<%= @config[:classifier_model] %>
|
|
317
|
+
</p>
|
|
318
|
+
</div>
|
|
319
|
+
|
|
320
|
+
<div>
|
|
321
|
+
<p class="text-sm text-gray-500 dark:text-gray-400">Temperature</p>
|
|
322
|
+
<p class="font-medium text-gray-900 dark:text-gray-100">
|
|
323
|
+
<%= @config[:classifier_temperature] || 0.0 %>
|
|
324
|
+
</p>
|
|
325
|
+
</div>
|
|
326
|
+
</div>
|
|
327
|
+
</div>
|
|
328
|
+
<% end %>
|
|
329
|
+
</div>
|
|
330
|
+
<% end %>
|
|
331
|
+
|
|
332
|
+
<!-- Executions -->
|
|
333
|
+
<div class="bg-white dark:bg-gray-800 rounded-lg shadow p-6">
|
|
334
|
+
<h3 class="text-lg font-semibold text-gray-900 dark:text-gray-100 mb-4">
|
|
335
|
+
Executions
|
|
336
|
+
</h3>
|
|
337
|
+
|
|
338
|
+
<div id="executions_table">
|
|
339
|
+
<%
|
|
340
|
+
has_filters = params[:statuses].present? || params[:versions].present? || params[:models].present? || params[:temperatures].present? || params[:days].present?
|
|
341
|
+
selected_statuses = params[:statuses].present? ? (params[:statuses].is_a?(Array) ? params[:statuses] : params[:statuses].split(",")) : []
|
|
342
|
+
selected_versions = params[:versions].present? ? (params[:versions].is_a?(Array) ? params[:versions] : params[:versions].split(",")) : []
|
|
343
|
+
selected_models = params[:models].present? ? (params[:models].is_a?(Array) ? params[:models] : params[:models].split(",")) : []
|
|
344
|
+
selected_temperatures = params[:temperatures].present? ? (params[:temperatures].is_a?(Array) ? params[:temperatures] : params[:temperatures].split(",")).map(&:to_s) : []
|
|
345
|
+
|
|
346
|
+
status_options = [
|
|
347
|
+
{ value: "success", label: "Success", color: "bg-green-500" },
|
|
348
|
+
{ value: "error", label: "Error", color: "bg-red-500" },
|
|
349
|
+
{ value: "running", label: "Running", color: "bg-blue-500" },
|
|
350
|
+
{ value: "timeout", label: "Timeout", color: "bg-yellow-500" }
|
|
351
|
+
]
|
|
352
|
+
version_options = @versions.map { |v| { value: v.to_s, label: "v#{v}" } }
|
|
353
|
+
model_options = @models.map { |m| { value: m, label: m } }
|
|
354
|
+
temperature_options = @temperatures.map { |t| { value: t.to_s, label: t.to_s } }
|
|
355
|
+
days_options = [
|
|
356
|
+
{ value: "", label: "All Time" },
|
|
357
|
+
{ value: "1", label: "Today" },
|
|
358
|
+
{ value: "7", label: "Last 7 Days" },
|
|
359
|
+
{ value: "30", label: "Last 30 Days" }
|
|
360
|
+
]
|
|
361
|
+
%>
|
|
362
|
+
|
|
363
|
+
<%= form_with url: ruby_llm_agents.workflow_path(@workflow_type), method: :get, local: true do |f| %>
|
|
364
|
+
<div class="flex flex-wrap items-center gap-3 mb-4 pb-4 border-b border-gray-100 dark:border-gray-700">
|
|
365
|
+
<%# Status Filter (Multi-select) %>
|
|
366
|
+
<%= render "ruby_llm/agents/shared/filter_dropdown",
|
|
367
|
+
name: "statuses[]",
|
|
368
|
+
filter_id: "statuses",
|
|
369
|
+
label: "Status",
|
|
370
|
+
all_label: "All Statuses",
|
|
371
|
+
options: status_options,
|
|
372
|
+
selected: selected_statuses %>
|
|
373
|
+
|
|
374
|
+
<%# Version Filter (Multi-select) %>
|
|
375
|
+
<% if @versions.any? %>
|
|
376
|
+
<%= render "ruby_llm/agents/shared/filter_dropdown",
|
|
377
|
+
name: "versions[]",
|
|
378
|
+
filter_id: "versions",
|
|
379
|
+
label: "Version",
|
|
380
|
+
all_label: "All Versions",
|
|
381
|
+
options: version_options,
|
|
382
|
+
selected: selected_versions,
|
|
383
|
+
icon: "M7 7h.01M7 3h5c.512 0 1.024.195 1.414.586l7 7a2 2 0 010 2.828l-7 7a2 2 0 01-2.828 0l-7-7A1.994 1.994 0 013 12V7a4 4 0 014-4z" %>
|
|
384
|
+
<% end %>
|
|
385
|
+
|
|
386
|
+
<%# Model Filter (Multi-select) %>
|
|
387
|
+
<% if @models.length > 1 %>
|
|
388
|
+
<%= render "ruby_llm/agents/shared/filter_dropdown",
|
|
389
|
+
name: "models[]",
|
|
390
|
+
filter_id: "models",
|
|
391
|
+
label: "Model",
|
|
392
|
+
all_label: "All Models",
|
|
393
|
+
options: model_options,
|
|
394
|
+
selected: selected_models,
|
|
395
|
+
icon: "M9.75 17L9 20l-1 1h8l-1-1-.75-3M3 13h18M5 17h14a2 2 0 002-2V5a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z" %>
|
|
396
|
+
<% end %>
|
|
397
|
+
|
|
398
|
+
<%# Temperature Filter (Multi-select) %>
|
|
399
|
+
<% if @temperatures.length > 1 %>
|
|
400
|
+
<%= render "ruby_llm/agents/shared/filter_dropdown",
|
|
401
|
+
name: "temperatures[]",
|
|
402
|
+
filter_id: "temperatures",
|
|
403
|
+
label: "Temp",
|
|
404
|
+
all_label: "All Temps",
|
|
405
|
+
options: temperature_options,
|
|
406
|
+
selected: selected_temperatures,
|
|
407
|
+
icon: "M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z" %>
|
|
408
|
+
<% end %>
|
|
409
|
+
|
|
410
|
+
<%# Time Range Filter (Single-select) %>
|
|
411
|
+
<%= render "ruby_llm/agents/shared/select_dropdown",
|
|
412
|
+
name: "days",
|
|
413
|
+
filter_id: "days",
|
|
414
|
+
options: days_options,
|
|
415
|
+
selected: params[:days].to_s,
|
|
416
|
+
icon: "M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" %>
|
|
417
|
+
|
|
418
|
+
<%# Clear Filters %>
|
|
419
|
+
<% if has_filters %>
|
|
420
|
+
<%= link_to ruby_llm_agents.workflow_path(@workflow_type),
|
|
421
|
+
class: "flex items-center gap-1 px-3 py-2 text-sm text-red-500 dark:text-red-400 hover:text-red-600 dark:hover:text-red-300 hover:bg-red-50 dark:hover:bg-red-900/30 rounded-lg transition-colors" do %>
|
|
422
|
+
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
423
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/>
|
|
424
|
+
</svg>
|
|
425
|
+
Clear
|
|
426
|
+
<% end %>
|
|
427
|
+
<% end %>
|
|
428
|
+
|
|
429
|
+
<%# Stats Summary (right aligned) %>
|
|
430
|
+
<div class="ml-auto flex items-center gap-4 text-sm text-gray-500 dark:text-gray-400">
|
|
431
|
+
<span><%= number_with_delimiter(@filter_stats[:total_count]) %> executions</span>
|
|
432
|
+
<span class="text-gray-300 dark:text-gray-600">|</span>
|
|
433
|
+
<span>$<%= number_with_precision(@filter_stats[:total_cost] || 0, precision: 4) %></span>
|
|
434
|
+
<span class="text-gray-300 dark:text-gray-600">|</span>
|
|
435
|
+
<span><%= number_with_delimiter(@filter_stats[:total_tokens] || 0) %> tokens</span>
|
|
436
|
+
</div>
|
|
437
|
+
</div>
|
|
438
|
+
<% end %>
|
|
439
|
+
|
|
440
|
+
<%= render "ruby_llm/agents/shared/executions_table", executions: @executions, pagination: @pagination %>
|
|
441
|
+
</div>
|
|
442
|
+
</div>
|
data/config/routes.rb
CHANGED
|
@@ -7,9 +7,10 @@ module RubyLlmAgents
|
|
|
7
7
|
#
|
|
8
8
|
# Usage:
|
|
9
9
|
# rails generate ruby_llm_agents:agent SearchIntent query:required limit:10
|
|
10
|
+
# rails generate ruby_llm_agents:agent SearchIntent query:required --root=ai
|
|
10
11
|
#
|
|
11
12
|
# This will create:
|
|
12
|
-
# - app/agents/search_intent_agent.rb
|
|
13
|
+
# - app/{root}/agents/search_intent_agent.rb
|
|
13
14
|
#
|
|
14
15
|
# Parameter syntax:
|
|
15
16
|
# name - Optional parameter
|
|
@@ -27,28 +28,76 @@ module RubyLlmAgents
|
|
|
27
28
|
desc: "The temperature setting (0.0-1.0)"
|
|
28
29
|
class_option :cache, type: :string, default: nil,
|
|
29
30
|
desc: "Cache TTL (e.g., '1.hour', '30.minutes')"
|
|
31
|
+
class_option :root,
|
|
32
|
+
type: :string,
|
|
33
|
+
default: nil,
|
|
34
|
+
desc: "Root directory name (default: uses config or 'llm')"
|
|
35
|
+
class_option :namespace,
|
|
36
|
+
type: :string,
|
|
37
|
+
default: nil,
|
|
38
|
+
desc: "Root namespace (default: camelized root or config)"
|
|
39
|
+
|
|
40
|
+
def ensure_base_class_and_skill_file
|
|
41
|
+
@root_namespace = root_namespace
|
|
42
|
+
agents_dir = "app/#{root_directory}/agents"
|
|
43
|
+
|
|
44
|
+
# Create directory if needed
|
|
45
|
+
empty_directory agents_dir
|
|
46
|
+
|
|
47
|
+
# Create base class if it doesn't exist
|
|
48
|
+
base_class_path = "#{agents_dir}/application_agent.rb"
|
|
49
|
+
unless File.exist?(File.join(destination_root, base_class_path))
|
|
50
|
+
template "application_agent.rb.tt", base_class_path
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Create skill file if it doesn't exist
|
|
54
|
+
skill_file_path = "#{agents_dir}/AGENTS.md"
|
|
55
|
+
unless File.exist?(File.join(destination_root, skill_file_path))
|
|
56
|
+
template "skills/AGENTS.md.tt", skill_file_path
|
|
57
|
+
end
|
|
58
|
+
end
|
|
30
59
|
|
|
31
60
|
def create_agent_file
|
|
32
|
-
# Support nested paths: "chat/support" -> "app/agents/chat/support_agent.rb"
|
|
61
|
+
# Support nested paths: "chat/support" -> "app/{root}/agents/chat/support_agent.rb"
|
|
33
62
|
# Rails' class_name handles namespacing: "chat/support" -> "Chat::Support"
|
|
63
|
+
@root_namespace = root_namespace
|
|
34
64
|
agent_path = name.underscore
|
|
35
|
-
template "agent.rb.tt", "app/agents/#{agent_path}_agent.rb"
|
|
65
|
+
template "agent.rb.tt", "app/#{root_directory}/agents/#{agent_path}_agent.rb"
|
|
36
66
|
end
|
|
37
67
|
|
|
38
68
|
def show_usage
|
|
39
69
|
# Build full class name from path (e.g., "chat/support" -> "Chat::Support")
|
|
40
|
-
|
|
70
|
+
agent_class_name = name.split("/").map(&:camelize).join("::")
|
|
71
|
+
full_class_name = "#{root_namespace}::#{agent_class_name}Agent"
|
|
41
72
|
say ""
|
|
42
|
-
say "Agent #{full_class_name}
|
|
73
|
+
say "Agent #{full_class_name} created!", :green
|
|
43
74
|
say ""
|
|
44
75
|
say "Usage:"
|
|
45
|
-
say " #{full_class_name}
|
|
46
|
-
say " #{full_class_name}
|
|
76
|
+
say " #{full_class_name}.call(#{usage_params})"
|
|
77
|
+
say " #{full_class_name}.call(#{usage_params}, dry_run: true)"
|
|
47
78
|
say ""
|
|
48
79
|
end
|
|
49
80
|
|
|
50
81
|
private
|
|
51
82
|
|
|
83
|
+
def root_directory
|
|
84
|
+
@root_directory ||= options[:root] || RubyLLM::Agents.configuration.root_directory
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def root_namespace
|
|
88
|
+
@root_namespace ||= options[:namespace] || camelize(root_directory)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def camelize(str)
|
|
92
|
+
# Handle special cases for common abbreviations
|
|
93
|
+
return "AI" if str.downcase == "ai"
|
|
94
|
+
return "ML" if str.downcase == "ml"
|
|
95
|
+
return "LLM" if str.downcase == "llm"
|
|
96
|
+
|
|
97
|
+
# Standard camelization
|
|
98
|
+
str.split(/[-_]/).map(&:capitalize).join
|
|
99
|
+
end
|
|
100
|
+
|
|
52
101
|
def parsed_params
|
|
53
102
|
@parsed_params ||= params.map do |param|
|
|
54
103
|
name, modifier = param.split(":")
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rails/generators"
|
|
4
|
+
|
|
5
|
+
module RubyLlmAgents
|
|
6
|
+
# BackgroundRemover generator for creating new background removers
|
|
7
|
+
#
|
|
8
|
+
# Usage:
|
|
9
|
+
# rails generate ruby_llm_agents:background_remover Product
|
|
10
|
+
# rails generate ruby_llm_agents:background_remover Portrait --model segment-anything --alpha_matting
|
|
11
|
+
# rails generate ruby_llm_agents:background_remover Photo --refine_edges --return_mask
|
|
12
|
+
# rails generate ruby_llm_agents:background_remover Product --root=ai
|
|
13
|
+
#
|
|
14
|
+
# This will create:
|
|
15
|
+
# - app/{root}/image/background_removers/product_background_remover.rb
|
|
16
|
+
#
|
|
17
|
+
class BackgroundRemoverGenerator < ::Rails::Generators::NamedBase
|
|
18
|
+
source_root File.expand_path("templates", __dir__)
|
|
19
|
+
|
|
20
|
+
class_option :model, type: :string, default: "rembg",
|
|
21
|
+
desc: "The segmentation model to use"
|
|
22
|
+
class_option :output_format, type: :string, default: "png",
|
|
23
|
+
desc: "Output format (png, webp)"
|
|
24
|
+
class_option :refine_edges, type: :boolean, default: false,
|
|
25
|
+
desc: "Enable edge refinement"
|
|
26
|
+
class_option :alpha_matting, type: :boolean, default: false,
|
|
27
|
+
desc: "Enable alpha matting for better edges"
|
|
28
|
+
class_option :return_mask, type: :boolean, default: false,
|
|
29
|
+
desc: "Also return the segmentation mask"
|
|
30
|
+
class_option :cache, type: :string, default: nil,
|
|
31
|
+
desc: "Cache TTL (e.g., '1.hour', '1.day')"
|
|
32
|
+
class_option :root,
|
|
33
|
+
type: :string,
|
|
34
|
+
default: nil,
|
|
35
|
+
desc: "Root directory name (default: uses config or 'llm')"
|
|
36
|
+
class_option :namespace,
|
|
37
|
+
type: :string,
|
|
38
|
+
default: nil,
|
|
39
|
+
desc: "Root namespace (default: camelized root or config)"
|
|
40
|
+
|
|
41
|
+
def ensure_base_class_and_skill_file
|
|
42
|
+
@root_namespace = root_namespace
|
|
43
|
+
@image_namespace = "#{root_namespace}::Image"
|
|
44
|
+
removers_dir = "app/#{root_directory}/image/background_removers"
|
|
45
|
+
|
|
46
|
+
# Create directory if needed
|
|
47
|
+
empty_directory removers_dir
|
|
48
|
+
|
|
49
|
+
# Create base class if it doesn't exist
|
|
50
|
+
base_class_path = "#{removers_dir}/application_background_remover.rb"
|
|
51
|
+
unless File.exist?(File.join(destination_root, base_class_path))
|
|
52
|
+
template "application_background_remover.rb.tt", base_class_path
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Create skill file if it doesn't exist
|
|
56
|
+
skill_file_path = "#{removers_dir}/BACKGROUND_REMOVERS.md"
|
|
57
|
+
unless File.exist?(File.join(destination_root, skill_file_path))
|
|
58
|
+
template "skills/BACKGROUND_REMOVERS.md.tt", skill_file_path
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def create_background_remover_file
|
|
63
|
+
@root_namespace = root_namespace
|
|
64
|
+
@image_namespace = "#{root_namespace}::Image"
|
|
65
|
+
remover_path = name.underscore
|
|
66
|
+
template "background_remover.rb.tt", "app/#{root_directory}/image/background_removers/#{remover_path}_background_remover.rb"
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def show_usage
|
|
70
|
+
remover_class_name = name.split("/").map(&:camelize).join("::")
|
|
71
|
+
full_class_name = "#{root_namespace}::Image::#{remover_class_name}BackgroundRemover"
|
|
72
|
+
say ""
|
|
73
|
+
say "Background remover #{full_class_name} created!", :green
|
|
74
|
+
say ""
|
|
75
|
+
say "Usage:"
|
|
76
|
+
say " # Remove background from an image"
|
|
77
|
+
say " result = #{full_class_name}.call(image: 'photo.jpg')"
|
|
78
|
+
say " result.url # => 'https://...' (transparent PNG)"
|
|
79
|
+
say " result.has_alpha? # => true"
|
|
80
|
+
say ""
|
|
81
|
+
say " # Override settings at runtime"
|
|
82
|
+
say " result = #{full_class_name}.call("
|
|
83
|
+
say " image: 'portrait.jpg',"
|
|
84
|
+
say " alpha_matting: true,"
|
|
85
|
+
say " return_mask: true"
|
|
86
|
+
say " )"
|
|
87
|
+
say ""
|
|
88
|
+
say " # Access the segmentation mask"
|
|
89
|
+
say " result.mask_url # => 'https://...' (if return_mask was enabled)"
|
|
90
|
+
say ""
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
private
|
|
94
|
+
|
|
95
|
+
def root_directory
|
|
96
|
+
@root_directory ||= options[:root] || RubyLLM::Agents.configuration.root_directory
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def root_namespace
|
|
100
|
+
@root_namespace ||= options[:namespace] || camelize(root_directory)
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def camelize(str)
|
|
104
|
+
return "AI" if str.downcase == "ai"
|
|
105
|
+
return "ML" if str.downcase == "ml"
|
|
106
|
+
return "LLM" if str.downcase == "llm"
|
|
107
|
+
str.split(/[-_]/).map(&:capitalize).join
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|