ruby_llm-agents 1.3.4 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (191) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +112 -336
  3. data/app/controllers/concerns/ruby_llm/agents/sortable.rb +0 -1
  4. data/app/controllers/ruby_llm/agents/agents_controller.rb +5 -56
  5. data/app/controllers/ruby_llm/agents/dashboard_controller.rb +22 -106
  6. data/app/controllers/ruby_llm/agents/executions_controller.rb +4 -114
  7. data/app/controllers/ruby_llm/agents/tenants_controller.rb +30 -2
  8. data/app/helpers/ruby_llm/agents/application_helper.rb +19 -53
  9. data/app/models/ruby_llm/agents/execution/analytics.rb +13 -54
  10. data/app/models/ruby_llm/agents/execution/scopes.rb +61 -14
  11. data/app/models/ruby_llm/agents/execution.rb +52 -12
  12. data/app/models/ruby_llm/agents/execution_detail.rb +18 -0
  13. data/app/models/ruby_llm/agents/tenant/budgetable.rb +132 -24
  14. data/app/models/ruby_llm/agents/tenant/incrementable.rb +117 -0
  15. data/app/models/ruby_llm/agents/tenant/resettable.rb +128 -0
  16. data/app/models/ruby_llm/agents/tenant/trackable.rb +46 -12
  17. data/app/models/ruby_llm/agents/tenant.rb +2 -3
  18. data/app/models/ruby_llm/agents/tenant_budget.rb +6 -3
  19. data/app/services/ruby_llm/agents/agent_registry.rb +6 -112
  20. data/app/views/layouts/ruby_llm/agents/application.html.erb +89 -252
  21. data/app/views/ruby_llm/agents/agents/_config_agent.html.erb +71 -218
  22. data/app/views/ruby_llm/agents/agents/_config_embedder.html.erb +20 -63
  23. data/app/views/ruby_llm/agents/agents/_config_image_generator.html.erb +44 -131
  24. data/app/views/ruby_llm/agents/agents/_config_moderator.html.erb +16 -57
  25. data/app/views/ruby_llm/agents/agents/_config_speaker.html.erb +39 -104
  26. data/app/views/ruby_llm/agents/agents/_config_transcriber.html.erb +29 -82
  27. data/app/views/ruby_llm/agents/agents/_empty_state.html.erb +4 -14
  28. data/app/views/ruby_llm/agents/agents/index.html.erb +105 -274
  29. data/app/views/ruby_llm/agents/agents/show.html.erb +248 -378
  30. data/app/views/ruby_llm/agents/dashboard/_action_center.html.erb +29 -52
  31. data/app/views/ruby_llm/agents/dashboard/_tenant_budget.html.erb +73 -99
  32. data/app/views/ruby_llm/agents/dashboard/index.html.erb +228 -433
  33. data/app/views/ruby_llm/agents/executions/_execution.html.erb +1 -1
  34. data/app/views/ruby_llm/agents/executions/_filters.html.erb +4 -25
  35. data/app/views/ruby_llm/agents/executions/_list.html.erb +111 -152
  36. data/app/views/ruby_llm/agents/executions/index.html.erb +5 -7
  37. data/app/views/ruby_llm/agents/executions/show.html.erb +526 -1037
  38. data/app/views/ruby_llm/agents/shared/_agent_type_badge.html.erb +5 -21
  39. data/app/views/ruby_llm/agents/shared/_executions_table.html.erb +70 -191
  40. data/app/views/ruby_llm/agents/shared/_filter_dropdown.html.erb +16 -44
  41. data/app/views/ruby_llm/agents/shared/_select_dropdown.html.erb +12 -41
  42. data/app/views/ruby_llm/agents/shared/_status_badge.html.erb +11 -65
  43. data/app/views/ruby_llm/agents/shared/_tenant_filter.html.erb +6 -5
  44. data/app/views/ruby_llm/agents/system_config/show.html.erb +240 -351
  45. data/app/views/ruby_llm/agents/tenants/_form.html.erb +67 -77
  46. data/app/views/ruby_llm/agents/tenants/edit.html.erb +7 -9
  47. data/app/views/ruby_llm/agents/tenants/index.html.erb +100 -122
  48. data/app/views/ruby_llm/agents/tenants/show.html.erb +146 -336
  49. data/config/routes.rb +0 -13
  50. data/lib/generators/ruby_llm_agents/install_generator.rb +13 -17
  51. data/lib/generators/ruby_llm_agents/migrate_structure_generator.rb +2 -12
  52. data/lib/generators/ruby_llm_agents/restructure_generator.rb +0 -2
  53. data/lib/generators/ruby_llm_agents/templates/add_usage_counters_to_tenants_migration.rb.tt +37 -0
  54. data/lib/generators/ruby_llm_agents/templates/agent.rb.tt +1 -2
  55. data/lib/generators/ruby_llm_agents/templates/application_agent.rb.tt +1 -1
  56. data/lib/generators/ruby_llm_agents/templates/application_image_pipeline.rb.tt +0 -1
  57. data/lib/generators/ruby_llm_agents/templates/create_execution_details_migration.rb.tt +27 -0
  58. data/lib/generators/ruby_llm_agents/templates/create_tenants_migration.rb.tt +25 -0
  59. data/lib/generators/ruby_llm_agents/templates/image_pipeline.rb.tt +0 -1
  60. data/lib/generators/ruby_llm_agents/templates/initializer.rb.tt +33 -12
  61. data/lib/generators/ruby_llm_agents/templates/migration.rb.tt +40 -71
  62. data/lib/generators/ruby_llm_agents/templates/remove_agent_version_migration.rb.tt +13 -0
  63. data/lib/generators/ruby_llm_agents/templates/remove_workflow_columns_migration.rb.tt +19 -0
  64. data/lib/generators/ruby_llm_agents/templates/skills/AGENTS.md.tt +2 -4
  65. data/lib/generators/ruby_llm_agents/templates/skills/IMAGE_PIPELINES.md.tt +0 -1
  66. data/lib/generators/ruby_llm_agents/templates/split_execution_details_migration.rb.tt +232 -0
  67. data/lib/generators/ruby_llm_agents/upgrade_generator.rb +77 -259
  68. data/lib/ruby_llm/agents/audio/speaker.rb +0 -1
  69. data/lib/ruby_llm/agents/audio/transcriber.rb +0 -1
  70. data/lib/ruby_llm/agents/base_agent.rb +54 -23
  71. data/lib/ruby_llm/agents/core/base/callbacks.rb +142 -0
  72. data/lib/ruby_llm/agents/core/base.rb +23 -55
  73. data/lib/ruby_llm/agents/core/configuration.rb +97 -117
  74. data/lib/ruby_llm/agents/core/errors.rb +0 -58
  75. data/lib/ruby_llm/agents/core/instrumentation.rb +157 -110
  76. data/lib/ruby_llm/agents/core/llm_tenant.rb +8 -7
  77. data/lib/ruby_llm/agents/core/version.rb +1 -1
  78. data/lib/ruby_llm/agents/dsl/base.rb +157 -17
  79. data/lib/ruby_llm/agents/dsl/caching.rb +33 -2
  80. data/lib/ruby_llm/agents/dsl/reliability.rb +148 -0
  81. data/lib/ruby_llm/agents/dsl.rb +1 -2
  82. data/lib/ruby_llm/agents/image/analyzer/execution.rb +1 -2
  83. data/lib/ruby_llm/agents/image/background_remover/execution.rb +1 -2
  84. data/lib/ruby_llm/agents/image/concerns/image_operation_dsl.rb +1 -13
  85. data/lib/ruby_llm/agents/image/concerns/image_operation_execution.rb +2 -2
  86. data/lib/ruby_llm/agents/image/editor/dsl.rb +0 -14
  87. data/lib/ruby_llm/agents/image/editor/execution.rb +1 -10
  88. data/lib/ruby_llm/agents/image/editor.rb +0 -1
  89. data/lib/ruby_llm/agents/image/generator.rb +0 -21
  90. data/lib/ruby_llm/agents/image/pipeline/dsl.rb +0 -13
  91. data/lib/ruby_llm/agents/image/pipeline/execution.rb +0 -1
  92. data/lib/ruby_llm/agents/image/transformer/dsl.rb +0 -13
  93. data/lib/ruby_llm/agents/image/transformer/execution.rb +1 -10
  94. data/lib/ruby_llm/agents/image/transformer.rb +0 -1
  95. data/lib/ruby_llm/agents/image/upscaler/execution.rb +1 -2
  96. data/lib/ruby_llm/agents/image/variator/execution.rb +1 -2
  97. data/lib/ruby_llm/agents/infrastructure/alert_manager.rb +78 -173
  98. data/lib/ruby_llm/agents/infrastructure/budget/budget_query.rb +66 -2
  99. data/lib/ruby_llm/agents/infrastructure/budget/spend_recorder.rb +0 -12
  100. data/lib/ruby_llm/agents/infrastructure/circuit_breaker.rb +10 -13
  101. data/lib/ruby_llm/agents/infrastructure/execution_logger_job.rb +8 -0
  102. data/lib/ruby_llm/agents/pipeline/context.rb +0 -1
  103. data/lib/ruby_llm/agents/pipeline/middleware/budget.rb +28 -4
  104. data/lib/ruby_llm/agents/pipeline/middleware/cache.rb +3 -10
  105. data/lib/ruby_llm/agents/pipeline/middleware/instrumentation.rb +88 -55
  106. data/lib/ruby_llm/agents/pipeline/middleware/tenant.rb +5 -41
  107. data/lib/ruby_llm/agents/rails/engine.rb +6 -6
  108. data/lib/ruby_llm/agents/results/base.rb +1 -49
  109. data/lib/ruby_llm/agents/text/embedder.rb +0 -1
  110. data/lib/ruby_llm/agents.rb +1 -9
  111. data/lib/tasks/ruby_llm_agents.rake +34 -0
  112. metadata +14 -83
  113. data/app/controllers/ruby_llm/agents/api_configurations_controller.rb +0 -214
  114. data/app/controllers/ruby_llm/agents/workflows_controller.rb +0 -544
  115. data/app/mailers/ruby_llm/agents/alert_mailer.rb +0 -84
  116. data/app/mailers/ruby_llm/agents/application_mailer.rb +0 -28
  117. data/app/models/ruby_llm/agents/api_configuration.rb +0 -386
  118. data/app/models/ruby_llm/agents/execution/workflow.rb +0 -170
  119. data/app/models/ruby_llm/agents/tenant/configurable.rb +0 -135
  120. data/app/views/ruby_llm/agents/agents/_agent.html.erb +0 -98
  121. data/app/views/ruby_llm/agents/agents/_version_comparison.html.erb +0 -186
  122. data/app/views/ruby_llm/agents/agents/_workflow.html.erb +0 -126
  123. data/app/views/ruby_llm/agents/alert_mailer/alert_notification.html.erb +0 -107
  124. data/app/views/ruby_llm/agents/alert_mailer/alert_notification.text.erb +0 -18
  125. data/app/views/ruby_llm/agents/api_configurations/_api_key_field.html.erb +0 -34
  126. data/app/views/ruby_llm/agents/api_configurations/_form.html.erb +0 -288
  127. data/app/views/ruby_llm/agents/api_configurations/edit.html.erb +0 -95
  128. data/app/views/ruby_llm/agents/api_configurations/edit_tenant.html.erb +0 -97
  129. data/app/views/ruby_llm/agents/api_configurations/show.html.erb +0 -214
  130. data/app/views/ruby_llm/agents/api_configurations/tenant.html.erb +0 -179
  131. data/app/views/ruby_llm/agents/dashboard/_agent_comparison.html.erb +0 -73
  132. data/app/views/ruby_llm/agents/dashboard/_alerts_feed.html.erb +0 -62
  133. data/app/views/ruby_llm/agents/dashboard/_breaker_strip.html.erb +0 -47
  134. data/app/views/ruby_llm/agents/dashboard/_budgets_bar.html.erb +0 -75
  135. data/app/views/ruby_llm/agents/dashboard/_model_comparison.html.erb +0 -56
  136. data/app/views/ruby_llm/agents/dashboard/_model_cost_breakdown.html.erb +0 -115
  137. data/app/views/ruby_llm/agents/dashboard/_now_strip.html.erb +0 -59
  138. data/app/views/ruby_llm/agents/dashboard/_top_errors.html.erb +0 -60
  139. data/app/views/ruby_llm/agents/executions/_workflow_summary.html.erb +0 -86
  140. data/app/views/ruby_llm/agents/executions/dry_run.html.erb +0 -149
  141. data/app/views/ruby_llm/agents/shared/_breadcrumbs.html.erb +0 -48
  142. data/app/views/ruby_llm/agents/shared/_nav_link.html.erb +0 -27
  143. data/app/views/ruby_llm/agents/shared/_stat_card.html.erb +0 -14
  144. data/app/views/ruby_llm/agents/shared/_workflow_type_badge.html.erb +0 -35
  145. data/app/views/ruby_llm/agents/workflows/_empty_state.html.erb +0 -22
  146. data/app/views/ruby_llm/agents/workflows/_step_performance.html.erb +0 -228
  147. data/app/views/ruby_llm/agents/workflows/_structure_dsl.html.erb +0 -539
  148. data/app/views/ruby_llm/agents/workflows/_structure_parallel.html.erb +0 -76
  149. data/app/views/ruby_llm/agents/workflows/_structure_pipeline.html.erb +0 -74
  150. data/app/views/ruby_llm/agents/workflows/_structure_router.html.erb +0 -108
  151. data/app/views/ruby_llm/agents/workflows/_workflow_diagram.html.erb +0 -920
  152. data/app/views/ruby_llm/agents/workflows/index.html.erb +0 -179
  153. data/app/views/ruby_llm/agents/workflows/show.html.erb +0 -467
  154. data/lib/generators/ruby_llm_agents/api_configuration_generator.rb +0 -100
  155. data/lib/generators/ruby_llm_agents/templates/add_workflow_migration.rb.tt +0 -38
  156. data/lib/generators/ruby_llm_agents/templates/application_workflow.rb.tt +0 -48
  157. data/lib/generators/ruby_llm_agents/templates/create_api_configurations_migration.rb.tt +0 -90
  158. data/lib/generators/ruby_llm_agents/templates/skills/WORKFLOWS.md.tt +0 -551
  159. data/lib/ruby_llm/agents/core/base/moderation_dsl.rb +0 -181
  160. data/lib/ruby_llm/agents/core/base/moderation_execution.rb +0 -274
  161. data/lib/ruby_llm/agents/core/resolved_config.rb +0 -348
  162. data/lib/ruby_llm/agents/image/generator/content_policy.rb +0 -95
  163. data/lib/ruby_llm/agents/infrastructure/redactor.rb +0 -130
  164. data/lib/ruby_llm/agents/results/moderation_result.rb +0 -158
  165. data/lib/ruby_llm/agents/text/moderator.rb +0 -237
  166. data/lib/ruby_llm/agents/workflow/approval.rb +0 -205
  167. data/lib/ruby_llm/agents/workflow/approval_store.rb +0 -179
  168. data/lib/ruby_llm/agents/workflow/async.rb +0 -220
  169. data/lib/ruby_llm/agents/workflow/async_executor.rb +0 -156
  170. data/lib/ruby_llm/agents/workflow/dsl/executor.rb +0 -467
  171. data/lib/ruby_llm/agents/workflow/dsl/input_schema.rb +0 -244
  172. data/lib/ruby_llm/agents/workflow/dsl/iteration_executor.rb +0 -289
  173. data/lib/ruby_llm/agents/workflow/dsl/parallel_group.rb +0 -107
  174. data/lib/ruby_llm/agents/workflow/dsl/route_builder.rb +0 -150
  175. data/lib/ruby_llm/agents/workflow/dsl/schedule_helpers.rb +0 -187
  176. data/lib/ruby_llm/agents/workflow/dsl/step_config.rb +0 -352
  177. data/lib/ruby_llm/agents/workflow/dsl/step_executor.rb +0 -415
  178. data/lib/ruby_llm/agents/workflow/dsl/wait_config.rb +0 -257
  179. data/lib/ruby_llm/agents/workflow/dsl/wait_executor.rb +0 -317
  180. data/lib/ruby_llm/agents/workflow/dsl.rb +0 -576
  181. data/lib/ruby_llm/agents/workflow/instrumentation.rb +0 -249
  182. data/lib/ruby_llm/agents/workflow/notifiers/base.rb +0 -117
  183. data/lib/ruby_llm/agents/workflow/notifiers/email.rb +0 -117
  184. data/lib/ruby_llm/agents/workflow/notifiers/slack.rb +0 -180
  185. data/lib/ruby_llm/agents/workflow/notifiers/webhook.rb +0 -121
  186. data/lib/ruby_llm/agents/workflow/notifiers.rb +0 -70
  187. data/lib/ruby_llm/agents/workflow/orchestrator.rb +0 -416
  188. data/lib/ruby_llm/agents/workflow/result.rb +0 -592
  189. data/lib/ruby_llm/agents/workflow/thread_pool.rb +0 -185
  190. data/lib/ruby_llm/agents/workflow/throttle_manager.rb +0 -206
  191. data/lib/ruby_llm/agents/workflow/wait_result.rb +0 -213
@@ -1,551 +0,0 @@
1
- # Workflows
2
-
3
- This directory contains workflow orchestration classes that compose multiple agents. Workflows provide patterns for sequential, parallel, and conditional agent execution.
4
-
5
- ## Workflow Types
6
-
7
- | Type | Class | Description |
8
- |------|-------|-------------|
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 |
13
-
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)
250
-
251
- ### Pipeline (Sequential)
252
-
253
- Execute agents in order, passing each step's output to the next:
254
-
255
- ```ruby
256
- module Workflows
257
- class ContentPipeline < RubyLLM::Agents::Workflow::Pipeline
258
- version "1.0"
259
- timeout 120
260
- max_cost 0.50
261
-
262
- step :extract, agent: ExtractorAgent
263
- step :validate, agent: ValidatorAgent
264
- step :format, agent: FormatterAgent
265
- end
266
- end
267
- ```
268
-
269
- ### Parallel (Concurrent)
270
-
271
- Execute multiple agents simultaneously:
272
-
273
- ```ruby
274
- module Workflows
275
- class ReviewAnalyzer < RubyLLM::Agents::Workflow::Parallel
276
- version "1.0"
277
-
278
- branch :sentiment, agent: SentimentAgent
279
- branch :summary, agent: SummaryAgent
280
- branch :categories, agent: CategoryAgent
281
-
282
- def aggregate(results)
283
- {
284
- sentiment: results[:sentiment]&.content,
285
- summary: results[:summary]&.content,
286
- categories: results[:categories]&.content
287
- }
288
- end
289
- end
290
- end
291
- ```
292
-
293
- ### Router (Conditional)
294
-
295
- Route to different agents based on classification:
296
-
297
- ```ruby
298
- module Workflows
299
- class SupportRouter < RubyLLM::Agents::Workflow::Router
300
- version "1.0"
301
- classifier ClassificationAgent
302
-
303
- route :billing, agent: BillingAgent
304
- route :technical, agent: TechnicalAgent
305
- route :general, agent: GeneralAgent
306
-
307
- default_route :general
308
- end
309
- end
310
- ```
311
-
312
- ## DSL Reference
313
-
314
- ### Shared Options
315
-
316
- ```ruby
317
- version "1.0" # Version for tracking changes
318
- timeout 120 # Total workflow timeout in seconds
319
- max_cost 0.50 # Maximum allowed cost in USD
320
- description "..." # Human-readable description
321
- ```
322
-
323
- ### Pipeline DSL
324
-
325
- ```ruby
326
- # Define steps
327
- step :name, agent: AgentClass
328
-
329
- # Conditional skipping
330
- step :validate, agent: Validator, skip_on: ->(ctx) { ctx[:skip_validation] }
331
-
332
- # Optional steps (failures won't stop pipeline)
333
- step :enrich, agent: Enricher, optional: true
334
-
335
- # Continue on error
336
- step :notify, agent: Notifier, continue_on_error: true
337
- ```
338
-
339
- ### Parallel DSL
340
-
341
- ```ruby
342
- # Define branches
343
- branch :name, agent: AgentClass
344
-
345
- # Optional branches
346
- branch :extra, agent: ExtraAgent, optional: true
347
-
348
- # Custom input transformation
349
- branch :process, agent: Processor, input: ->(opts) { { text: opts[:content] } }
350
-
351
- # Fail-fast (stop all on first required failure)
352
- fail_fast true
353
-
354
- # Limit concurrency
355
- concurrency 3
356
- ```
357
-
358
- ### Router DSL
359
-
360
- ```ruby
361
- # Set classifier
362
- classifier ClassificationAgent
363
-
364
- # Define routes
365
- route :category, agent: AgentClass
366
-
367
- # Default route
368
- default_route :fallback
369
-
370
- # Custom routing logic
371
- def select_route(classification)
372
- case classification[:type]
373
- when "urgent" then :priority
374
- else :standard
375
- end
376
- end
377
- ```
378
-
379
- ## Using Workflows
380
-
381
- ### Basic Execution
382
-
383
- ```ruby
384
- result = Workflows::ContentPipeline.call(text: "raw input")
385
-
386
- result.success? # All steps succeeded
387
- result.partial? # Some steps succeeded
388
- result.error? # Workflow failed
389
- result.content # Final output
390
- result.total_cost # Combined cost
391
- result.duration_ms # Total duration
392
- ```
393
-
394
- ### Pipeline Results
395
-
396
- ```ruby
397
- result = Workflows::ContentPipeline.call(text: "input")
398
-
399
- result.steps # Hash of all step results
400
- result.steps[:extract].content # Specific step output
401
- result.errors # Hash of step errors
402
- ```
403
-
404
- ### Parallel Results
405
-
406
- ```ruby
407
- result = Workflows::ReviewAnalyzer.call(text: "Great product!")
408
-
409
- result.branches # Hash of branch results
410
- result.branches[:sentiment].content # Specific branch output
411
- ```
412
-
413
- ### Router Results
414
-
415
- ```ruby
416
- result = Workflows::SupportRouter.call(question: "How do I pay?")
417
-
418
- result.classification # What route was selected
419
- result.route # Which agent handled it
420
- result.content # Response from routed agent
421
- ```
422
-
423
- ## Advanced Patterns
424
-
425
- ### Input Transformation
426
-
427
- Transform data between pipeline steps:
428
-
429
- ```ruby
430
- module Workflows
431
- class TransformPipeline < RubyLLM::Agents::Workflow::Pipeline
432
- step :analyze, agent: AnalyzerAgent
433
- step :enrich, agent: EnricherAgent
434
-
435
- # Called before :enrich step
436
- def before_enrich(context)
437
- {
438
- data: context[:analyze].content,
439
- extra_field: "additional context"
440
- }
441
- end
442
- end
443
- end
444
- ```
445
-
446
- ### Custom Aggregation
447
-
448
- Combine parallel results:
449
-
450
- ```ruby
451
- module Workflows
452
- class SafetyChecker < RubyLLM::Agents::Workflow::Parallel
453
- branch :toxicity, agent: ToxicityAgent
454
- branch :spam, agent: SpamAgent
455
- branch :pii, agent: PiiAgent
456
-
457
- def aggregate(results)
458
- {
459
- is_safe: results.values.none? { |r| r&.content == "flagged" },
460
- flags: results.select { |_, r| r&.content == "flagged" }.keys
461
- }
462
- end
463
- end
464
- end
465
- ```
466
-
467
- ### Error Handling
468
-
469
- Handle step failures:
470
-
471
- ```ruby
472
- module Workflows
473
- class ResilientPipeline < RubyLLM::Agents::Workflow::Pipeline
474
- step :primary, agent: PrimaryAgent
475
- step :backup, agent: BackupAgent, optional: true
476
-
477
- # Called when :primary fails
478
- def on_primary_failure(error, context)
479
- Rails.logger.warn("Primary failed: #{error.message}")
480
- :skip # Continue to backup
481
- # :abort would stop the pipeline
482
- end
483
- end
484
- end
485
- ```
486
-
487
- ### Cost Limits
488
-
489
- Stop workflow if cost exceeds threshold:
490
-
491
- ```ruby
492
- module Workflows
493
- class BudgetedWorkflow < RubyLLM::Agents::Workflow::Pipeline
494
- max_cost 1.00 # Stop if workflow exceeds $1.00
495
-
496
- step :expensive_analysis, agent: DeepAnalysisAgent
497
- step :synthesis, agent: SynthesisAgent
498
- end
499
- end
500
- ```
501
-
502
- ## Testing Workflows
503
-
504
- ```ruby
505
- RSpec.describe Workflows::ContentPipeline do
506
- describe ".call" do
507
- it "processes content through all steps" do
508
- result = described_class.call(text: "test input")
509
-
510
- expect(result.success?).to be true
511
- expect(result.steps.keys).to eq([:extract, :validate, :format])
512
- end
513
-
514
- it "handles step failures" do
515
- allow(ExtractorAgent).to receive(:call).and_raise("Network error")
516
-
517
- result = described_class.call(text: "test")
518
-
519
- expect(result.error?).to be true
520
- expect(result.errors[:extract]).to be_present
521
- end
522
- end
523
- end
524
- ```
525
-
526
- ## Best Practices
527
-
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
532
- 3. **Use max_cost** - Control spending on expensive operations
533
- 4. **Handle errors gracefully** - Use optional steps, defaults, and error handlers
534
- 5. **Keep workflows focused** - One workflow, one purpose
535
- 6. **Test step interactions** - Unit test agents, integration test workflows
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,181 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module RubyLLM
4
- module Agents
5
- # DSL for configuring content moderation on agents
6
- #
7
- # Provides declarative configuration for moderating user input
8
- # and/or LLM output against safety policies.
9
- #
10
- # @example Basic input moderation
11
- # class MyAgent < ApplicationAgent
12
- # moderation :input
13
- # end
14
- #
15
- # @example Moderate both input and output
16
- # class MyAgent < ApplicationAgent
17
- # moderation :both
18
- # end
19
- #
20
- # @example With configuration options
21
- # class MyAgent < ApplicationAgent
22
- # moderation :input,
23
- # model: 'omni-moderation-latest',
24
- # threshold: 0.8,
25
- # categories: [:hate, :violence],
26
- # on_flagged: :raise
27
- # end
28
- #
29
- # @example Block-based DSL
30
- # class MyAgent < ApplicationAgent
31
- # moderation do
32
- # input enabled: true, threshold: 0.7
33
- # output enabled: true, threshold: 0.9
34
- # model 'omni-moderation-latest'
35
- # categories :hate, :violence
36
- # on_flagged :block
37
- # end
38
- # end
39
- #
40
- # @api public
41
- module ModerationDSL
42
- # Configures content moderation for this agent
43
- #
44
- # @param phases [Array<Symbol>] Phases to moderate (:input, :output, :both)
45
- # @param model [String] Moderation model to use
46
- # @param threshold [Float] Score threshold (0.0-1.0) for flagging
47
- # @param categories [Array<Symbol>] Categories to check
48
- # @param on_flagged [Symbol] Action when flagged (:block, :raise, :warn, :log)
49
- # @param custom_handler [Symbol] Method name for custom handling
50
- # @yield Block for advanced configuration
51
- # @return [Hash, nil] The moderation configuration
52
- #
53
- # @example Simple input moderation
54
- # moderation :input
55
- #
56
- # @example Input and output moderation
57
- # moderation :input, :output
58
- # # or
59
- # moderation :both
60
- #
61
- # @example With options
62
- # moderation :input, threshold: 0.8, on_flagged: :raise
63
- def moderation(*phases, **options, &block)
64
- if block_given?
65
- builder = ModerationBuilder.new
66
- builder.instance_eval(&block)
67
- @moderation_config = builder.config
68
- else
69
- # Handle :both shorthand
70
- phases = [:input, :output] if phases.include?(:both)
71
- phases = [:input] if phases.empty?
72
-
73
- @moderation_config = {
74
- phases: phases.flatten.map(&:to_sym),
75
- model: options[:model],
76
- threshold: options[:threshold],
77
- categories: options[:categories],
78
- on_flagged: options[:on_flagged] || :block,
79
- custom_handler: options[:custom_handler]
80
- }
81
- end
82
- end
83
-
84
- # Returns the moderation configuration for this agent
85
- #
86
- # @return [Hash, nil] The moderation configuration or nil if not configured
87
- def moderation_config
88
- @moderation_config || inherited_or_default(:moderation_config, nil)
89
- end
90
-
91
- # Returns whether moderation is enabled for this agent
92
- #
93
- # @return [Boolean] true if moderation is configured
94
- def moderation_enabled?
95
- !!moderation_config
96
- end
97
-
98
- private
99
-
100
- def inherited_or_default(method, default)
101
- return default unless superclass.respond_to?(method)
102
-
103
- superclass.send(method)
104
- end
105
- end
106
-
107
- # Builder class for block-based moderation configuration
108
- #
109
- # @api private
110
- class ModerationBuilder
111
- attr_reader :config
112
-
113
- def initialize
114
- @config = {
115
- phases: [],
116
- on_flagged: :block
117
- }
118
- end
119
-
120
- # Enables input moderation
121
- #
122
- # @param enabled [Boolean] Whether to enable input moderation
123
- # @param threshold [Float, nil] Score threshold for input phase
124
- # @return [void]
125
- def input(enabled: true, threshold: nil)
126
- @config[:phases] << :input if enabled
127
- @config[:input_threshold] = threshold if threshold
128
- end
129
-
130
- # Enables output moderation
131
- #
132
- # @param enabled [Boolean] Whether to enable output moderation
133
- # @param threshold [Float, nil] Score threshold for output phase
134
- # @return [void]
135
- def output(enabled: true, threshold: nil)
136
- @config[:phases] << :output if enabled
137
- @config[:output_threshold] = threshold if threshold
138
- end
139
-
140
- # Sets the moderation model
141
- #
142
- # @param model_name [String] Model identifier
143
- # @return [void]
144
- def model(model_name)
145
- @config[:model] = model_name
146
- end
147
-
148
- # Sets the global threshold
149
- #
150
- # @param value [Float] Score threshold (0.0-1.0)
151
- # @return [void]
152
- def threshold(value)
153
- @config[:threshold] = value
154
- end
155
-
156
- # Sets categories to check
157
- #
158
- # @param cats [Array<Symbol>] Category symbols
159
- # @return [void]
160
- def categories(*cats)
161
- @config[:categories] = cats.flatten.map(&:to_sym)
162
- end
163
-
164
- # Sets the action when content is flagged
165
- #
166
- # @param action [Symbol] :block, :raise, :warn, or :log
167
- # @return [void]
168
- def on_flagged(action)
169
- @config[:on_flagged] = action
170
- end
171
-
172
- # Sets a custom handler method
173
- #
174
- # @param method_name [Symbol] Method name to call on the agent
175
- # @return [void]
176
- def custom_handler(method_name)
177
- @config[:custom_handler] = method_name
178
- end
179
- end
180
- end
181
- end