ruby_llm-agents 1.0.0 → 1.2.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.
Files changed (152) hide show
  1. checksums.yaml +4 -4
  2. data/app/controllers/concerns/ruby_llm/agents/paginatable.rb +9 -3
  3. data/app/controllers/concerns/ruby_llm/agents/sortable.rb +58 -0
  4. data/app/controllers/ruby_llm/agents/agents_controller.rb +59 -16
  5. data/app/controllers/ruby_llm/agents/dashboard_controller.rb +144 -20
  6. data/app/controllers/ruby_llm/agents/executions_controller.rb +13 -16
  7. data/app/controllers/ruby_llm/agents/workflows_controller.rb +279 -90
  8. data/app/helpers/ruby_llm/agents/application_helper.rb +100 -0
  9. data/app/mailers/ruby_llm/agents/alert_mailer.rb +84 -0
  10. data/app/mailers/ruby_llm/agents/application_mailer.rb +28 -0
  11. data/app/models/ruby_llm/agents/execution/analytics.rb +170 -20
  12. data/app/models/ruby_llm/agents/execution/scopes.rb +0 -31
  13. data/app/models/ruby_llm/agents/execution/workflow.rb +0 -129
  14. data/app/models/ruby_llm/agents/execution.rb +50 -14
  15. data/app/models/ruby_llm/agents/tenant/budgetable.rb +277 -0
  16. data/app/models/ruby_llm/agents/tenant/configurable.rb +135 -0
  17. data/app/models/ruby_llm/agents/tenant/trackable.rb +310 -0
  18. data/app/models/ruby_llm/agents/tenant.rb +146 -0
  19. data/app/models/ruby_llm/agents/tenant_budget.rb +12 -253
  20. data/app/services/ruby_llm/agents/agent_registry.rb +18 -12
  21. data/app/views/layouts/ruby_llm/agents/application.html.erb +72 -76
  22. data/app/views/ruby_llm/agents/agents/_agent.html.erb +0 -12
  23. data/app/views/ruby_llm/agents/agents/_sortable_header.html.erb +56 -0
  24. data/app/views/ruby_llm/agents/agents/_workflow.html.erb +5 -15
  25. data/app/views/ruby_llm/agents/agents/index.html.erb +271 -100
  26. data/app/views/ruby_llm/agents/agents/show.html.erb +1 -0
  27. data/app/views/ruby_llm/agents/alert_mailer/alert_notification.html.erb +107 -0
  28. data/app/views/ruby_llm/agents/alert_mailer/alert_notification.text.erb +18 -0
  29. data/app/views/ruby_llm/agents/api_configurations/show.html.erb +4 -1
  30. data/app/views/ruby_llm/agents/dashboard/_agent_comparison.html.erb +66 -359
  31. data/app/views/ruby_llm/agents/dashboard/_model_comparison.html.erb +56 -0
  32. data/app/views/ruby_llm/agents/dashboard/_model_cost_breakdown.html.erb +115 -0
  33. data/app/views/ruby_llm/agents/dashboard/_now_strip.html.erb +35 -60
  34. data/app/views/ruby_llm/agents/dashboard/_top_errors.html.erb +17 -6
  35. data/app/views/ruby_llm/agents/dashboard/index.html.erb +373 -72
  36. data/app/views/ruby_llm/agents/executions/_execution.html.erb +0 -1
  37. data/app/views/ruby_llm/agents/executions/_filters.html.erb +51 -39
  38. data/app/views/ruby_llm/agents/executions/_list.html.erb +53 -195
  39. data/app/views/ruby_llm/agents/executions/_workflow_summary.html.erb +5 -20
  40. data/app/views/ruby_llm/agents/executions/index.html.erb +7 -83
  41. data/app/views/ruby_llm/agents/executions/show.html.erb +10 -20
  42. data/app/views/ruby_llm/agents/shared/_agent_type_badge.html.erb +2 -1
  43. data/app/views/ruby_llm/agents/shared/_doc_link.html.erb +12 -0
  44. data/app/views/ruby_llm/agents/shared/_executions_table.html.erb +3 -15
  45. data/app/views/ruby_llm/agents/shared/_filter_dropdown.html.erb +1 -1
  46. data/app/views/ruby_llm/agents/shared/_select_dropdown.html.erb +1 -1
  47. data/app/views/ruby_llm/agents/shared/_sortable_header.html.erb +53 -0
  48. data/app/views/ruby_llm/agents/shared/_status_badge.html.erb +7 -0
  49. data/app/views/ruby_llm/agents/shared/_status_dot.html.erb +1 -1
  50. data/app/views/ruby_llm/agents/shared/_workflow_type_badge.html.erb +9 -35
  51. data/app/views/ruby_llm/agents/system_config/show.html.erb +4 -1
  52. data/app/views/ruby_llm/agents/tenants/index.html.erb +4 -1
  53. data/app/views/ruby_llm/agents/workflows/_step_performance.html.erb +7 -15
  54. data/app/views/ruby_llm/agents/workflows/_structure_dsl.html.erb +539 -0
  55. data/app/views/ruby_llm/agents/workflows/_workflow_diagram.html.erb +920 -0
  56. data/app/views/ruby_llm/agents/workflows/index.html.erb +179 -0
  57. data/app/views/ruby_llm/agents/workflows/show.html.erb +164 -139
  58. data/config/routes.rb +1 -1
  59. data/lib/generators/ruby_llm_agents/agent_generator.rb +6 -36
  60. data/lib/generators/ruby_llm_agents/background_remover_generator.rb +7 -37
  61. data/lib/generators/ruby_llm_agents/embedder_generator.rb +5 -38
  62. data/lib/generators/ruby_llm_agents/image_analyzer_generator.rb +7 -37
  63. data/lib/generators/ruby_llm_agents/image_editor_generator.rb +7 -37
  64. data/lib/generators/ruby_llm_agents/image_generator_generator.rb +8 -41
  65. data/lib/generators/ruby_llm_agents/image_pipeline_generator.rb +18 -46
  66. data/lib/generators/ruby_llm_agents/image_transformer_generator.rb +7 -37
  67. data/lib/generators/ruby_llm_agents/image_upscaler_generator.rb +7 -37
  68. data/lib/generators/ruby_llm_agents/image_variator_generator.rb +7 -37
  69. data/lib/generators/ruby_llm_agents/install_generator.rb +33 -56
  70. data/lib/generators/ruby_llm_agents/migrate_structure_generator.rb +480 -0
  71. data/lib/generators/ruby_llm_agents/multi_tenancy_generator.rb +42 -22
  72. data/lib/generators/ruby_llm_agents/restructure_generator.rb +2 -2
  73. data/lib/generators/ruby_llm_agents/speaker_generator.rb +8 -39
  74. data/lib/generators/ruby_llm_agents/templates/add_tenant_to_executions_migration.rb.tt +13 -2
  75. data/lib/generators/ruby_llm_agents/templates/agent.rb.tt +5 -8
  76. data/lib/generators/ruby_llm_agents/templates/application_agent.rb.tt +40 -42
  77. data/lib/generators/ruby_llm_agents/templates/application_background_remover.rb.tt +20 -22
  78. data/lib/generators/ruby_llm_agents/templates/application_embedder.rb.tt +24 -26
  79. data/lib/generators/ruby_llm_agents/templates/application_image_analyzer.rb.tt +20 -22
  80. data/lib/generators/ruby_llm_agents/templates/application_image_editor.rb.tt +19 -17
  81. data/lib/generators/ruby_llm_agents/templates/application_image_generator.rb.tt +31 -33
  82. data/lib/generators/ruby_llm_agents/templates/application_image_pipeline.rb.tt +125 -127
  83. data/lib/generators/ruby_llm_agents/templates/application_image_transformer.rb.tt +20 -18
  84. data/lib/generators/ruby_llm_agents/templates/application_image_upscaler.rb.tt +19 -17
  85. data/lib/generators/ruby_llm_agents/templates/application_image_variator.rb.tt +19 -17
  86. data/lib/generators/ruby_llm_agents/templates/application_speaker.rb.tt +38 -40
  87. data/lib/generators/ruby_llm_agents/templates/application_transcriber.rb.tt +42 -44
  88. data/lib/generators/ruby_llm_agents/templates/application_workflow.rb.tt +48 -0
  89. data/lib/generators/ruby_llm_agents/templates/background_remover.rb.tt +19 -21
  90. data/lib/generators/ruby_llm_agents/templates/create_tenant_budgets_migration.rb.tt +11 -0
  91. data/lib/generators/ruby_llm_agents/templates/create_tenants_migration.rb.tt +72 -0
  92. data/lib/generators/ruby_llm_agents/templates/embedder.rb.tt +19 -21
  93. data/lib/generators/ruby_llm_agents/templates/image_analyzer.rb.tt +20 -22
  94. data/lib/generators/ruby_llm_agents/templates/image_editor.rb.tt +15 -17
  95. data/lib/generators/ruby_llm_agents/templates/image_generator.rb.tt +25 -27
  96. data/lib/generators/ruby_llm_agents/templates/image_pipeline.rb.tt +19 -21
  97. data/lib/generators/ruby_llm_agents/templates/image_transformer.rb.tt +20 -22
  98. data/lib/generators/ruby_llm_agents/templates/image_upscaler.rb.tt +17 -19
  99. data/lib/generators/ruby_llm_agents/templates/image_variator.rb.tt +15 -17
  100. data/lib/generators/ruby_llm_agents/templates/rename_tenant_budgets_to_tenants_migration.rb.tt +34 -0
  101. data/lib/generators/ruby_llm_agents/templates/skills/AGENTS.md.tt +87 -24
  102. data/lib/generators/ruby_llm_agents/templates/skills/BACKGROUND_REMOVERS.md.tt +21 -27
  103. data/lib/generators/ruby_llm_agents/templates/skills/EMBEDDERS.md.tt +46 -54
  104. data/lib/generators/ruby_llm_agents/templates/skills/IMAGE_ANALYZERS.md.tt +31 -39
  105. data/lib/generators/ruby_llm_agents/templates/skills/IMAGE_EDITORS.md.tt +22 -28
  106. data/lib/generators/ruby_llm_agents/templates/skills/IMAGE_GENERATORS.md.tt +53 -63
  107. data/lib/generators/ruby_llm_agents/templates/skills/IMAGE_PIPELINES.md.tt +46 -56
  108. data/lib/generators/ruby_llm_agents/templates/skills/IMAGE_TRANSFORMERS.md.tt +23 -31
  109. data/lib/generators/ruby_llm_agents/templates/skills/IMAGE_UPSCALERS.md.tt +22 -30
  110. data/lib/generators/ruby_llm_agents/templates/skills/IMAGE_VARIATORS.md.tt +23 -31
  111. data/lib/generators/ruby_llm_agents/templates/skills/SPEAKERS.md.tt +38 -46
  112. data/lib/generators/ruby_llm_agents/templates/skills/TOOLS.md.tt +7 -7
  113. data/lib/generators/ruby_llm_agents/templates/skills/TRANSCRIBERS.md.tt +59 -71
  114. data/lib/generators/ruby_llm_agents/templates/skills/WORKFLOWS.md.tt +274 -23
  115. data/lib/generators/ruby_llm_agents/templates/speaker.rb.tt +29 -31
  116. data/lib/generators/ruby_llm_agents/templates/transcriber.rb.tt +28 -30
  117. data/lib/generators/ruby_llm_agents/transcriber_generator.rb +10 -43
  118. data/lib/generators/ruby_llm_agents/upgrade_generator.rb +26 -0
  119. data/lib/ruby_llm/agents/core/configuration.rb +55 -43
  120. data/lib/ruby_llm/agents/core/llm_tenant.rb +60 -60
  121. data/lib/ruby_llm/agents/core/version.rb +1 -1
  122. data/lib/ruby_llm/agents/infrastructure/alert_manager.rb +26 -0
  123. data/lib/ruby_llm/agents/infrastructure/budget/config_resolver.rb +4 -2
  124. data/lib/ruby_llm/agents/pipeline.rb +69 -0
  125. data/lib/ruby_llm/agents/workflow/approval.rb +205 -0
  126. data/lib/ruby_llm/agents/workflow/approval_store.rb +179 -0
  127. data/lib/ruby_llm/agents/workflow/dsl/executor.rb +467 -0
  128. data/lib/ruby_llm/agents/workflow/dsl/input_schema.rb +244 -0
  129. data/lib/ruby_llm/agents/workflow/dsl/iteration_executor.rb +289 -0
  130. data/lib/ruby_llm/agents/workflow/dsl/parallel_group.rb +107 -0
  131. data/lib/ruby_llm/agents/workflow/dsl/route_builder.rb +150 -0
  132. data/lib/ruby_llm/agents/workflow/dsl/schedule_helpers.rb +187 -0
  133. data/lib/ruby_llm/agents/workflow/dsl/step_config.rb +352 -0
  134. data/lib/ruby_llm/agents/workflow/dsl/step_executor.rb +415 -0
  135. data/lib/ruby_llm/agents/workflow/dsl/wait_config.rb +257 -0
  136. data/lib/ruby_llm/agents/workflow/dsl/wait_executor.rb +317 -0
  137. data/lib/ruby_llm/agents/workflow/dsl.rb +576 -0
  138. data/lib/ruby_llm/agents/workflow/instrumentation.rb +2 -7
  139. data/lib/ruby_llm/agents/workflow/notifiers/base.rb +117 -0
  140. data/lib/ruby_llm/agents/workflow/notifiers/email.rb +117 -0
  141. data/lib/ruby_llm/agents/workflow/notifiers/slack.rb +180 -0
  142. data/lib/ruby_llm/agents/workflow/notifiers/webhook.rb +121 -0
  143. data/lib/ruby_llm/agents/workflow/notifiers.rb +70 -0
  144. data/lib/ruby_llm/agents/workflow/orchestrator.rb +190 -23
  145. data/lib/ruby_llm/agents/workflow/result.rb +202 -0
  146. data/lib/ruby_llm/agents/workflow/throttle_manager.rb +206 -0
  147. data/lib/ruby_llm/agents/workflow/wait_result.rb +213 -0
  148. metadata +43 -6
  149. data/app/views/ruby_llm/agents/dashboard/_execution_item.html.erb +0 -66
  150. data/lib/ruby_llm/agents/workflow/parallel.rb +0 -299
  151. data/lib/ruby_llm/agents/workflow/pipeline.rb +0 -306
  152. data/lib/ruby_llm/agents/workflow/router.rb +0 -429
@@ -1,4 +1,4 @@
1
- # <%= @root_namespace %> Workflows
1
+ # Workflows
2
2
 
3
3
  This directory contains workflow orchestration classes that compose multiple agents. Workflows provide patterns for sequential, parallel, and conditional agent execution.
4
4
 
@@ -6,18 +6,254 @@ This directory contains workflow orchestration classes that compose multiple age
6
6
 
7
7
  | Type | Class | Description |
8
8
  |------|-------|-------------|
9
- | Pipeline | `Workflow::Pipeline` | Sequential execution, data flows between steps |
10
- | Parallel | `Workflow::Parallel` | Concurrent execution with aggregation |
11
- | Router | `Workflow::Router` | Conditional dispatch based on classification |
9
+ | **DSL Workflow** | `Workflow` | Declarative DSL for mixed sequential/parallel/routing |
10
+ | Pipeline | `Workflow::Pipeline` | Legacy: Sequential execution, data flows between steps |
11
+ | Parallel | `Workflow::Parallel` | Legacy: Concurrent execution with aggregation |
12
+ | Router | `Workflow::Router` | Legacy: Conditional dispatch based on classification |
12
13
 
13
- ## Creating Workflows
14
+ ## Declarative DSL Workflows (Recommended)
15
+
16
+ The new declarative DSL provides a clean, expressive syntax for defining workflows with minimal boilerplate. It supports sequential steps, parallel execution, conditional routing, and input validation all in one workflow class.
17
+
18
+ ### Minimal Workflow
19
+
20
+ ```ruby
21
+ module Workflows
22
+ class SimpleWorkflow < RubyLLM::Agents::Workflow
23
+ step :fetch, FetcherAgent
24
+ step :process, ProcessorAgent
25
+ step :save, SaverAgent
26
+ end
27
+ end
28
+
29
+ # Usage
30
+ result = Workflows::SimpleWorkflow.call(order_id: "ORD-123")
31
+ result.success? # => true
32
+ result.content # => final step output
33
+ result.steps[:process] # => ProcessorAgent result
34
+ ```
35
+
36
+ ### Full-Featured Workflow
37
+
38
+ ```ruby
39
+ module Workflows
40
+ class OrderWorkflow < RubyLLM::Agents::Workflow
41
+ description "Process customer orders end-to-end"
42
+ version "2.0"
43
+ timeout 300
44
+ max_cost 2.00
45
+
46
+ # Input validation with defaults
47
+ input do
48
+ required :order_id, String
49
+ required :user_id, Integer
50
+ optional :priority, String, default: "normal"
51
+ optional :expedited, :boolean, default: false
52
+ end
53
+
54
+ # Sequential steps
55
+ step :fetch, FetcherAgent, timeout: 30
56
+ step :validate, ValidatorAgent
57
+
58
+ # Conditional routing based on previous step result
59
+ step :process, on: -> { validate.tier } do |route|
60
+ route.premium PremiumProcessorAgent
61
+ route.standard StandardProcessorAgent
62
+ route.default BasicProcessorAgent
63
+ end
64
+
65
+ # Parallel execution block
66
+ parallel do
67
+ step :analyze, AnalyzerAgent
68
+ step :summarize, SummarizerAgent
69
+ step :notify, NotifierAgent
70
+ end
71
+
72
+ # Conditional step execution
73
+ step :expedite, ExpediteAgent, if: :expedited?
74
+
75
+ private
76
+
77
+ def expedited?
78
+ input.expedited == true
79
+ end
80
+ end
81
+ end
82
+ ```
83
+
84
+ ### DSL Reference
85
+
86
+ #### Input Schema
87
+
88
+ Define required and optional inputs with type validation:
89
+
90
+ ```ruby
91
+ input do
92
+ required :order_id, String
93
+ required :amount, Numeric
94
+ optional :priority, String, default: "normal"
95
+ optional :debug, :boolean, default: false
96
+
97
+ # With enum validation
98
+ optional :status, String, in: %w[pending active completed]
99
+ end
100
+ ```
101
+
102
+ #### Step Options
103
+
104
+ ```ruby
105
+ # Basic step
106
+ step :name, AgentClass
107
+
108
+ # With description
109
+ step :validate, ValidatorAgent, "Validates order data"
110
+
111
+ # With timeout (seconds)
112
+ step :fetch, FetcherAgent, timeout: 30
113
+
114
+ # Optional step (workflow continues on failure)
115
+ step :enrich, EnricherAgent, optional: true
116
+
117
+ # With default value on failure
118
+ step :lookup, LookupAgent, optional: true, default: { found: false }
119
+
120
+ # Conditional execution
121
+ step :notify, NotifierAgent, if: :should_notify?
122
+ step :skip_this, SkipAgent, unless: :condition_met?
123
+
124
+ # With retry
125
+ step :api_call, ApiAgent, retry: 3
126
+ step :api_call, ApiAgent, retry: { max: 3, delay: 2, backoff: :exponential }
127
+ ```
128
+
129
+ #### Conditional Routing
130
+
131
+ Route to different agents based on a value:
132
+
133
+ ```ruby
134
+ # Route based on lambda returning a value
135
+ step :process, on: -> { validate.tier } do |route|
136
+ route.premium PremiumAgent
137
+ route.standard StandardAgent
138
+ route.default FallbackAgent # Required fallback
139
+ end
140
+
141
+ # With custom input mapping per route
142
+ step :handle, on: -> { classify.type } do |route|
143
+ route.billing BillingAgent, input: -> { { account: input.account_id } }
144
+ route.support SupportAgent, input: -> { { ticket: input.ticket_id } }
145
+ route.default GeneralAgent
146
+ end
147
+ ```
148
+
149
+ #### Parallel Execution
150
+
151
+ Execute steps concurrently:
152
+
153
+ ```ruby
154
+ # Anonymous parallel group
155
+ parallel do
156
+ step :sentiment, SentimentAgent
157
+ step :keywords, KeywordAgent
158
+ step :entities, EntityAgent
159
+ end
160
+
161
+ # Named parallel group with options
162
+ parallel :analysis, fail_fast: true, concurrency: 2 do
163
+ step :deep_analyze, DeepAnalyzer
164
+ step :quick_scan, QuickScanner
165
+ end
166
+ ```
167
+
168
+ #### Lifecycle Hooks
169
+
170
+ ```ruby
171
+ class MyWorkflow < RubyLLM::Agents::Workflow
172
+ step :process, ProcessorAgent
173
+ step :save, SaverAgent
174
+
175
+ # Workflow-level hooks
176
+ before_workflow { Rails.logger.info("Starting workflow") }
177
+ after_workflow { Rails.logger.info("Workflow complete") }
178
+
179
+ # Step-level hooks
180
+ before_step(:process) { |context| log_step_start(:process) }
181
+ after_step(:process) { |result, duration_ms| log_step_end(:process, duration_ms) }
182
+
183
+ # Error hooks
184
+ on_step_failure(:process) { |error, context| handle_error(error) }
185
+ end
186
+ ```
187
+
188
+ #### Accessing Step Results
189
+
190
+ Within the workflow, access previous step results:
191
+
192
+ ```ruby
193
+ step :validate, ValidatorAgent
194
+
195
+ # In routing lambda
196
+ step :process, on: -> { validate.tier } do |route|
197
+ # validate.tier returns validate step's result[:tier]
198
+ end
199
+
200
+ # In condition methods
201
+ def should_notify?
202
+ validate.valid? && input.callback_url.present?
203
+ end
204
+ ```
205
+
206
+ ### Workflow Result API
207
+
208
+ ```ruby
209
+ result = MyWorkflow.call(order_id: "123")
210
+
211
+ # Status
212
+ result.success? # All steps succeeded
213
+ result.partial? # Some optional steps failed
214
+ result.error? # Required step failed
215
+ result.status # "success", "partial", or "error"
216
+
217
+ # Content
218
+ result.content # Final step output
219
+
220
+ # Step results
221
+ result.steps # Hash of all step results
222
+ result.steps[:validate] # Specific step result
223
+ result.steps[:validate].content # Step output content
224
+
225
+ # Metrics
226
+ result.total_cost # Combined cost of all steps
227
+ result.input_tokens # Total input tokens
228
+ result.output_tokens # Total output tokens
229
+ result.duration_ms # Total workflow duration
230
+
231
+ # Errors
232
+ result.errors # Hash of step errors
233
+ ```
234
+
235
+ ### Dry Run / Validation
236
+
237
+ Validate workflow configuration without executing:
238
+
239
+ ```ruby
240
+ validation = MyWorkflow.dry_run(order_id: "123")
241
+
242
+ validation[:valid] # true/false
243
+ validation[:input_errors] # Array of validation errors
244
+ validation[:steps] # List of step names
245
+ validation[:agents] # List of agent classes
246
+ validation[:parallel_groups] # Parallel group info
247
+ ```
248
+
249
+ ## Creating Workflows (Legacy Patterns)
14
250
 
15
251
  ### Pipeline (Sequential)
16
252
 
17
253
  Execute agents in order, passing each step's output to the next:
18
254
 
19
255
  ```ruby
20
- module <%= @root_namespace %>
256
+ module Workflows
21
257
  class ContentPipeline < RubyLLM::Agents::Workflow::Pipeline
22
258
  version "1.0"
23
259
  timeout 120
@@ -35,7 +271,7 @@ end
35
271
  Execute multiple agents simultaneously:
36
272
 
37
273
  ```ruby
38
- module <%= @root_namespace %>
274
+ module Workflows
39
275
  class ReviewAnalyzer < RubyLLM::Agents::Workflow::Parallel
40
276
  version "1.0"
41
277
 
@@ -59,7 +295,7 @@ end
59
295
  Route to different agents based on classification:
60
296
 
61
297
  ```ruby
62
- module <%= @root_namespace %>
298
+ module Workflows
63
299
  class SupportRouter < RubyLLM::Agents::Workflow::Router
64
300
  version "1.0"
65
301
  classifier ClassificationAgent
@@ -145,7 +381,7 @@ end
145
381
  ### Basic Execution
146
382
 
147
383
  ```ruby
148
- result = <%= @root_namespace %>::ContentPipeline.call(text: "raw input")
384
+ result = Workflows::ContentPipeline.call(text: "raw input")
149
385
 
150
386
  result.success? # All steps succeeded
151
387
  result.partial? # Some steps succeeded
@@ -158,7 +394,7 @@ result.duration_ms # Total duration
158
394
  ### Pipeline Results
159
395
 
160
396
  ```ruby
161
- result = <%= @root_namespace %>::ContentPipeline.call(text: "input")
397
+ result = Workflows::ContentPipeline.call(text: "input")
162
398
 
163
399
  result.steps # Hash of all step results
164
400
  result.steps[:extract].content # Specific step output
@@ -168,7 +404,7 @@ result.errors # Hash of step errors
168
404
  ### Parallel Results
169
405
 
170
406
  ```ruby
171
- result = <%= @root_namespace %>::ReviewAnalyzer.call(text: "Great product!")
407
+ result = Workflows::ReviewAnalyzer.call(text: "Great product!")
172
408
 
173
409
  result.branches # Hash of branch results
174
410
  result.branches[:sentiment].content # Specific branch output
@@ -177,7 +413,7 @@ result.branches[:sentiment].content # Specific branch output
177
413
  ### Router Results
178
414
 
179
415
  ```ruby
180
- result = <%= @root_namespace %>::SupportRouter.call(question: "How do I pay?")
416
+ result = Workflows::SupportRouter.call(question: "How do I pay?")
181
417
 
182
418
  result.classification # What route was selected
183
419
  result.route # Which agent handled it
@@ -191,7 +427,7 @@ result.content # Response from routed agent
191
427
  Transform data between pipeline steps:
192
428
 
193
429
  ```ruby
194
- module <%= @root_namespace %>
430
+ module Workflows
195
431
  class TransformPipeline < RubyLLM::Agents::Workflow::Pipeline
196
432
  step :analyze, agent: AnalyzerAgent
197
433
  step :enrich, agent: EnricherAgent
@@ -212,7 +448,7 @@ end
212
448
  Combine parallel results:
213
449
 
214
450
  ```ruby
215
- module <%= @root_namespace %>
451
+ module Workflows
216
452
  class SafetyChecker < RubyLLM::Agents::Workflow::Parallel
217
453
  branch :toxicity, agent: ToxicityAgent
218
454
  branch :spam, agent: SpamAgent
@@ -233,7 +469,7 @@ end
233
469
  Handle step failures:
234
470
 
235
471
  ```ruby
236
- module <%= @root_namespace %>
472
+ module Workflows
237
473
  class ResilientPipeline < RubyLLM::Agents::Workflow::Pipeline
238
474
  step :primary, agent: PrimaryAgent
239
475
  step :backup, agent: BackupAgent, optional: true
@@ -253,7 +489,7 @@ end
253
489
  Stop workflow if cost exceeds threshold:
254
490
 
255
491
  ```ruby
256
- module <%= @root_namespace %>
492
+ module Workflows
257
493
  class BudgetedWorkflow < RubyLLM::Agents::Workflow::Pipeline
258
494
  max_cost 1.00 # Stop if workflow exceeds $1.00
259
495
 
@@ -266,7 +502,7 @@ end
266
502
  ## Testing Workflows
267
503
 
268
504
  ```ruby
269
- RSpec.describe <%= @root_namespace %>::ContentPipeline do
505
+ RSpec.describe Workflows::ContentPipeline do
270
506
  describe ".call" do
271
507
  it "processes content through all steps" do
272
508
  result = described_class.call(text: "test input")
@@ -289,12 +525,27 @@ end
289
525
 
290
526
  ## Best Practices
291
527
 
292
- 1. **Version your workflows** - Track changes for debugging
293
- 2. **Set timeouts** - Prevent runaway workflows
528
+ ### General
529
+
530
+ 1. **Version your workflows** - Track changes for debugging and rollback
531
+ 2. **Set timeouts** - Prevent runaway workflows at both workflow and step level
294
532
  3. **Use max_cost** - Control spending on expensive operations
295
- 4. **Handle errors gracefully** - Use optional steps and error handlers
533
+ 4. **Handle errors gracefully** - Use optional steps, defaults, and error handlers
296
534
  5. **Keep workflows focused** - One workflow, one purpose
297
535
  6. **Test step interactions** - Unit test agents, integration test workflows
298
- 7. **Log workflow execution** - Track step timing and costs
299
- 8. **Use parallel for independent operations** - Improve throughput
300
- 9. **Use router for classification** - Keep routing logic centralized
536
+
537
+ ### DSL Workflows (Recommended)
538
+
539
+ 1. **Use input schemas** - Validate inputs early with `required`/`optional` declarations
540
+ 2. **Prefer DSL over legacy patterns** - The new DSL is more flexible and composable
541
+ 3. **Use parallel for independent operations** - Group unrelated steps for concurrent execution
542
+ 4. **Use routing for classification** - Clean syntax for conditional agent selection
543
+ 5. **Leverage step result access** - Use `step_name.field` syntax in conditions/routing
544
+ 6. **Add lifecycle hooks** - Log, track, and handle errors at appropriate points
545
+ 7. **Use dry_run for validation** - Check workflow configuration before execution
546
+
547
+ ### Legacy Patterns
548
+
549
+ 1. **Use Pipeline for sequential data processing** - Data flows naturally between steps
550
+ 2. **Use Parallel for fan-out operations** - Independent operations with aggregation
551
+ 3. **Use Router for classification-based dispatch** - When route selection depends on LLM classification
@@ -1,56 +1,54 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module <%= @root_namespace %>
4
- module Audio
3
+ module Audio
5
4
  <%- if class_name.include?("::") -%>
6
5
  <%- class_name.split("::")[0..-2].each_with_index do |mod, i| -%>
7
- <%= " " * (i + 2) %>module <%= mod %>
6
+ <%= " " * (i + 1) %>module <%= mod %>
8
7
  <%- end -%>
9
- <%= " " * (class_name.split("::").length + 1) %>class <%= class_name.split("::").last %>Speaker < ApplicationSpeaker
8
+ <%= " " * class_name.split("::").length %>class <%= class_name.split("::").last %>Speaker < ApplicationSpeaker
10
9
  <%- else -%>
11
- class <%= class_name %>Speaker < ApplicationSpeaker
10
+ class <%= class_name %>Speaker < ApplicationSpeaker
12
11
  <%- end -%>
13
- # Provider configuration
14
- provider :<%= options[:provider] %>
12
+ # Provider configuration
13
+ provider :<%= options[:provider] %>
15
14
  <% if options[:model] -%>
16
- model "<%= options[:model] %>"
15
+ model "<%= options[:model] %>"
17
16
  <% end -%>
18
- voice "<%= options[:voice] %>"
17
+ voice "<%= options[:voice] %>"
19
18
  <% if options[:speed] != 1.0 -%>
20
- speed <%= options[:speed] %>
19
+ speed <%= options[:speed] %>
21
20
  <% end -%>
22
21
  <% if options[:format] != "mp3" -%>
23
- output_format :<%= options[:format] %>
22
+ output_format :<%= options[:format] %>
24
23
  <% end -%>
25
24
  <% if options[:cache] -%>
26
25
 
27
- # Caching
28
- cache_for <%= options[:cache] %>
26
+ # Caching
27
+ cache_for <%= options[:cache] %>
29
28
  <% end -%>
30
29
 
31
- # Optional: Custom pronunciation lexicon
32
- # Define how specific words should be pronounced
33
- #
34
- # lexicon do
35
- # pronounce 'API', 'A P I'
36
- # pronounce 'SQL', 'sequel'
37
- # end
30
+ # Optional: Custom pronunciation lexicon
31
+ # Define how specific words should be pronounced
32
+ #
33
+ # lexicon do
34
+ # pronounce 'API', 'A P I'
35
+ # pronounce 'SQL', 'sequel'
36
+ # end
38
37
  <% if options[:provider].to_s == "elevenlabs" -%>
39
38
 
40
- # ElevenLabs voice settings
41
- # voice_settings do
42
- # stability 0.5
43
- # similarity_boost 0.75
44
- # style 0.5
45
- # speaker_boost true
46
- # end
39
+ # ElevenLabs voice settings
40
+ # voice_settings do
41
+ # stability 0.5
42
+ # similarity_boost 0.75
43
+ # style 0.5
44
+ # speaker_boost true
45
+ # end
47
46
  <% end -%>
48
47
  <%- if class_name.include?("::") -%>
49
- <%- (class_name.split("::").length + 1).times do |i| -%>
50
- <%= " " * (class_name.split("::").length + 1 - i) %>end
48
+ <%- class_name.split("::").length.times do |i| -%>
49
+ <%= " " * (class_name.split("::").length - i) %>end
51
50
  <%- end -%>
52
51
  <%- else -%>
53
- end
54
- <%- end -%>
55
52
  end
53
+ <%- end -%>
56
54
  end
@@ -1,51 +1,49 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module <%= @root_namespace %>
4
- module Audio
3
+ module Audio
5
4
  <%- if class_name.include?("::") -%>
6
5
  <%- class_name.split("::")[0..-2].each_with_index do |mod, i| -%>
7
- <%= " " * (i + 2) %>module <%= mod %>
6
+ <%= " " * (i + 1) %>module <%= mod %>
8
7
  <%- end -%>
9
- <%= " " * (class_name.split("::").length + 1) %>class <%= class_name.split("::").last %>Transcriber < ApplicationTranscriber
8
+ <%= " " * class_name.split("::").length %>class <%= class_name.split("::").last %>Transcriber < ApplicationTranscriber
10
9
  <%- else -%>
11
- class <%= class_name %>Transcriber < ApplicationTranscriber
10
+ class <%= class_name %>Transcriber < ApplicationTranscriber
12
11
  <%- end -%>
13
- # Model configuration
14
- model "<%= options[:model] %>"
12
+ # Model configuration
13
+ model "<%= options[:model] %>"
15
14
  <% if options[:language] -%>
16
- language "<%= options[:language] %>"
15
+ language "<%= options[:language] %>"
17
16
  <% end -%>
18
17
  <% if options[:output_format] != "text" -%>
19
- output_format :<%= options[:output_format] %>
18
+ output_format :<%= options[:output_format] %>
20
19
  <% end -%>
21
20
  <% if options[:cache] -%>
22
21
 
23
- # Caching
24
- cache_for <%= options[:cache] %>
22
+ # Caching
23
+ cache_for <%= options[:cache] %>
25
24
  <% end -%>
26
25
 
27
- # Optional: Provide context to improve accuracy
28
- # Override this method to give the model hints about the audio content
29
- #
30
- # def prompt
31
- # "Technical discussion about Ruby programming"
32
- # end
26
+ # Optional: Provide context to improve accuracy
27
+ # Override this method to give the model hints about the audio content
28
+ #
29
+ # def prompt
30
+ # "Technical discussion about Ruby programming"
31
+ # end
33
32
 
34
- # Optional: Clean up transcription text
35
- # Override this method to post-process the transcribed text
36
- #
37
- # def postprocess_text(text)
38
- # text
39
- # .gsub(/\bum\b/i, '') # Remove filler words
40
- # .gsub(/\buh\b/i, '')
41
- # .squeeze(' ') # Remove extra spaces
42
- # end
33
+ # Optional: Clean up transcription text
34
+ # Override this method to post-process the transcribed text
35
+ #
36
+ # def postprocess_text(text)
37
+ # text
38
+ # .gsub(/\bum\b/i, '') # Remove filler words
39
+ # .gsub(/\buh\b/i, '')
40
+ # .squeeze(' ') # Remove extra spaces
41
+ # end
43
42
  <%- if class_name.include?("::") -%>
44
- <%- (class_name.split("::").length + 1).times do |i| -%>
45
- <%= " " * (class_name.split("::").length + 1 - i) %>end
43
+ <%- class_name.split("::").length.times do |i| -%>
44
+ <%= " " * (class_name.split("::").length - i) %>end
46
45
  <%- end -%>
47
46
  <%- else -%>
48
- end
49
- <%- end -%>
50
47
  end
48
+ <%- end -%>
51
49
  end
@@ -7,12 +7,11 @@ module RubyLlmAgents
7
7
  #
8
8
  # Usage:
9
9
  # rails generate ruby_llm_agents:transcriber Meeting
10
- # rails generate ruby_llm_agents:transcriber Meeting --model gpt-4o-transcribe
11
- # rails generate ruby_llm_agents:transcriber Meeting --language es
12
- # rails generate ruby_llm_agents:transcriber Meeting --root=ai
10
+ # rails generate ruby_llm_agents:transcriber Interview --model whisper-1
11
+ # rails generate ruby_llm_agents:transcriber Podcast --language en
13
12
  #
14
13
  # This will create:
15
- # - app/{root}/audio/transcribers/meeting_transcriber.rb
14
+ # - app/agents/audio/meeting_transcriber.rb
16
15
  #
17
16
  class TranscriberGenerator < ::Rails::Generators::NamedBase
18
17
  source_root File.expand_path("templates", __dir__)
@@ -25,48 +24,36 @@ module RubyLlmAgents
25
24
  desc: "Output format (text, srt, vtt, json)"
26
25
  class_option :cache, type: :string, default: nil,
27
26
  desc: "Cache TTL (e.g., '30.days')"
28
- class_option :root,
29
- type: :string,
30
- default: nil,
31
- desc: "Root directory name (default: uses config or 'llm')"
32
- class_option :namespace,
33
- type: :string,
34
- default: nil,
35
- desc: "Root namespace (default: camelized root or config)"
36
27
 
37
28
  def ensure_base_class_and_skill_file
38
- @root_namespace = root_namespace
39
- @audio_namespace = "#{root_namespace}::Audio"
40
- transcribers_dir = "app/#{root_directory}/audio/transcribers"
29
+ audio_dir = "app/agents/audio"
41
30
 
42
31
  # Create directory if needed
43
- empty_directory transcribers_dir
32
+ empty_directory audio_dir
44
33
 
45
34
  # Create base class if it doesn't exist
46
- base_class_path = "#{transcribers_dir}/application_transcriber.rb"
35
+ base_class_path = "#{audio_dir}/application_transcriber.rb"
47
36
  unless File.exist?(File.join(destination_root, base_class_path))
48
37
  template "application_transcriber.rb.tt", base_class_path
49
38
  end
50
39
 
51
40
  # Create skill file if it doesn't exist
52
- skill_file_path = "#{transcribers_dir}/TRANSCRIBERS.md"
41
+ skill_file_path = "#{audio_dir}/TRANSCRIBERS.md"
53
42
  unless File.exist?(File.join(destination_root, skill_file_path))
54
43
  template "skills/TRANSCRIBERS.md.tt", skill_file_path
55
44
  end
56
45
  end
57
46
 
58
47
  def create_transcriber_file
59
- # Support nested paths: "interview/meeting" -> "app/{root}/audio/transcribers/interview/meeting_transcriber.rb"
60
- @root_namespace = root_namespace
61
- @audio_namespace = "#{root_namespace}::Audio"
48
+ # Support nested paths: "interview/meeting" -> "app/agents/audio/interview/meeting_transcriber.rb"
62
49
  transcriber_path = name.underscore
63
- template "transcriber.rb.tt", "app/#{root_directory}/audio/transcribers/#{transcriber_path}_transcriber.rb"
50
+ template "transcriber.rb.tt", "app/agents/audio/#{transcriber_path}_transcriber.rb"
64
51
  end
65
52
 
66
53
  def show_usage
67
54
  # Build full class name from path
68
55
  transcriber_class_name = name.split("/").map(&:camelize).join("::")
69
- full_class_name = "#{root_namespace}::Audio::#{transcriber_class_name}Transcriber"
56
+ full_class_name = "Audio::#{transcriber_class_name}Transcriber"
70
57
  say ""
71
58
  say "Transcriber #{full_class_name} created!", :green
72
59
  say ""
@@ -83,25 +70,5 @@ module RubyLlmAgents
83
70
  say " result.vtt # VTT format"
84
71
  say ""
85
72
  end
86
-
87
- private
88
-
89
- def root_directory
90
- @root_directory ||= options[:root] || RubyLLM::Agents.configuration.root_directory
91
- end
92
-
93
- def root_namespace
94
- @root_namespace ||= options[:namespace] || camelize(root_directory)
95
- end
96
-
97
- def camelize(str)
98
- # Handle special cases for common abbreviations
99
- return "AI" if str.downcase == "ai"
100
- return "ML" if str.downcase == "ml"
101
- return "LLM" if str.downcase == "llm"
102
-
103
- # Standard camelization
104
- str.split(/[-_]/).map(&:capitalize).join
105
- end
106
73
  end
107
74
  end