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,592 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module RubyLLM
4
- module Agents
5
- class Workflow
6
- # Result wrapper for workflow executions with aggregate metrics
7
- #
8
- # Extends the base Result class with workflow-specific data including
9
- # step results, branch results, routing information, and aggregated
10
- # token/cost metrics across all child executions.
11
- #
12
- # @example Pipeline result
13
- # result = ContentPipeline.call(text: "input")
14
- # result.content # Final output
15
- # result.steps[:extract] # Individual step result
16
- # result.total_cost # Sum of all steps
17
- #
18
- # @example Parallel result
19
- # result = ReviewAnalyzer.call(text: "review")
20
- # result.branches[:sentiment] # Branch result
21
- # result.failed_branches # [:toxicity] if it failed
22
- #
23
- # @example Router result
24
- # result = SupportRouter.call(message: "billing issue")
25
- # result.routed_to # :billing
26
- # result.classification # Classification details
27
- #
28
- # @api public
29
- class Result
30
- extend ActiveSupport::Delegation
31
-
32
- # @!attribute [r] content
33
- # @return [Object] The final processed content
34
- attr_reader :content
35
-
36
- # @!attribute [r] workflow_type
37
- # @return [String] The workflow class name
38
- attr_reader :workflow_type
39
-
40
- # @!attribute [r] workflow_id
41
- # @return [String] Unique identifier for this workflow execution
42
- attr_reader :workflow_id
43
-
44
- # @!group Step/Branch Results
45
-
46
- # @!attribute [r] steps
47
- # @return [Hash<Symbol, Result>] Results from pipeline steps
48
- attr_reader :steps
49
-
50
- # @!attribute [r] branches
51
- # @return [Hash<Symbol, Result>] Results from parallel branches
52
- attr_reader :branches
53
-
54
- # @!endgroup
55
-
56
- # @!group Router Results
57
-
58
- # @!attribute [r] routed_to
59
- # @return [Symbol, nil] The route that was selected
60
- attr_reader :routed_to
61
-
62
- # @!attribute [r] classification
63
- # @return [Hash, nil] Classification details from router
64
- attr_reader :classification
65
-
66
- # @!attribute [r] classifier_result
67
- # @return [Result, nil] The classifier agent's result
68
- attr_reader :classifier_result
69
-
70
- # @!endgroup
71
-
72
- # @!group Timing
73
-
74
- # @!attribute [r] started_at
75
- # @return [Time] When the workflow started
76
- attr_reader :started_at
77
-
78
- # @!attribute [r] completed_at
79
- # @return [Time] When the workflow completed
80
- attr_reader :completed_at
81
-
82
- # @!attribute [r] duration_ms
83
- # @return [Integer] Total workflow duration in milliseconds
84
- attr_reader :duration_ms
85
-
86
- # @!endgroup
87
-
88
- # @!group Status
89
-
90
- # @!attribute [r] status
91
- # @return [String] Workflow status: "success", "error", "partial"
92
- attr_reader :status
93
-
94
- # @!attribute [r] error_class
95
- # @return [String, nil] Error class if failed
96
- attr_reader :error_class
97
-
98
- # @!attribute [r] error_message
99
- # @return [String, nil] Error message if failed
100
- attr_reader :error_message
101
-
102
- # @!attribute [r] errors
103
- # @return [Hash<Symbol, Exception>] Errors by step/branch name
104
- attr_reader :errors
105
-
106
- # @!endgroup
107
-
108
- # Creates a new WorkflowResult
109
- #
110
- # @param content [Object] The final processed content
111
- # @param options [Hash] Additional result metadata
112
- def initialize(content:, **options)
113
- @content = content
114
- @workflow_type = options[:workflow_type]
115
- @workflow_id = options[:workflow_id]
116
-
117
- # Step/branch results
118
- @steps = options[:steps] || {}
119
- @branches = options[:branches] || {}
120
-
121
- # Router results
122
- @routed_to = options[:routed_to]
123
- @classification = options[:classification]
124
- @classifier_result = options[:classifier_result]
125
-
126
- # Timing
127
- @started_at = options[:started_at]
128
- @completed_at = options[:completed_at]
129
- @duration_ms = options[:duration_ms]
130
-
131
- # Status
132
- @status = options[:status] || "success"
133
- @error_class = options[:error_class]
134
- @error_message = options[:error_message]
135
- @errors = options[:errors] || {}
136
- end
137
-
138
- # Returns all child results (steps + branches + classifier)
139
- #
140
- # @return [Array<Result>] All child results
141
- def child_results
142
- results = []
143
- results.concat(steps.values) if steps.any?
144
- results.concat(branches.values) if branches.any?
145
- results << classifier_result if classifier_result
146
- results.compact
147
- end
148
-
149
- # @!group Aggregate Metrics
150
-
151
- # Returns total input tokens across all child executions
152
- #
153
- # @return [Integer] Total input tokens
154
- def input_tokens
155
- child_results.sum { |r| r.input_tokens || 0 }
156
- end
157
-
158
- # Returns total output tokens across all child executions
159
- #
160
- # @return [Integer] Total output tokens
161
- def output_tokens
162
- child_results.sum { |r| r.output_tokens || 0 }
163
- end
164
-
165
- # Returns total tokens across all child executions
166
- #
167
- # @return [Integer] Total tokens
168
- def total_tokens
169
- input_tokens + output_tokens
170
- end
171
-
172
- # Returns total cached tokens across all child executions
173
- #
174
- # @return [Integer] Total cached tokens
175
- def cached_tokens
176
- child_results.sum { |r| r.cached_tokens || 0 }
177
- end
178
-
179
- # Returns total input cost across all child executions
180
- #
181
- # @return [Float] Total input cost in USD
182
- def input_cost
183
- child_results.sum { |r| r.input_cost || 0.0 }
184
- end
185
-
186
- # Returns total output cost across all child executions
187
- #
188
- # @return [Float] Total output cost in USD
189
- def output_cost
190
- child_results.sum { |r| r.output_cost || 0.0 }
191
- end
192
-
193
- # Returns total cost across all child executions
194
- #
195
- # @return [Float] Total cost in USD
196
- def total_cost
197
- child_results.sum { |r| r.total_cost || 0.0 }
198
- end
199
-
200
- # Returns classification cost (router workflows only)
201
- #
202
- # @return [Float] Classification cost in USD
203
- def classification_cost
204
- classifier_result&.total_cost || 0.0
205
- end
206
-
207
- # @!endgroup
208
-
209
- # @!group Status Helpers
210
-
211
- # Returns whether the workflow succeeded
212
- #
213
- # @return [Boolean] true if status is "success"
214
- def success?
215
- status == "success"
216
- end
217
-
218
- # Returns whether the workflow failed
219
- #
220
- # @return [Boolean] true if status is "error"
221
- def error?
222
- status == "error"
223
- end
224
-
225
- # Returns whether the workflow partially succeeded
226
- #
227
- # @return [Boolean] true if status is "partial"
228
- def partial?
229
- status == "partial"
230
- end
231
-
232
- # @!endgroup
233
-
234
- # @!group Pipeline Helpers
235
-
236
- # Returns whether all pipeline steps succeeded
237
- #
238
- # @return [Boolean] true if all steps successful
239
- def all_steps_successful?
240
- return true if steps.empty?
241
-
242
- steps.values.all? { |r| r.respond_to?(:success?) ? r.success? : true }
243
- end
244
-
245
- # Returns the names of failed steps
246
- #
247
- # @return [Array<Symbol>] Failed step names
248
- def failed_steps
249
- steps.select { |_, r| r.respond_to?(:error?) && r.error? }.keys
250
- end
251
-
252
- # Returns the names of skipped steps
253
- #
254
- # @return [Array<Symbol>] Skipped step names
255
- def skipped_steps
256
- steps.select { |_, r| r.respond_to?(:skipped?) && r.skipped? }.keys
257
- end
258
-
259
- # @!endgroup
260
-
261
- # @!group Parallel Helpers
262
-
263
- # Returns whether all parallel branches succeeded
264
- #
265
- # @return [Boolean] true if all branches successful
266
- def all_branches_successful?
267
- return true if branches.empty?
268
-
269
- branches.values.all? { |r| r.nil? || (r.respond_to?(:success?) ? r.success? : true) }
270
- end
271
-
272
- # Returns the names of failed branches
273
- #
274
- # @return [Array<Symbol>] Failed branch names
275
- def failed_branches
276
- failed = branches.select { |_, r| r.respond_to?(:error?) && r.error? }.keys
277
- failed += errors.keys
278
- failed.uniq
279
- end
280
-
281
- # Returns the names of successful branches
282
- #
283
- # @return [Array<Symbol>] Successful branch names
284
- def successful_branches
285
- branches.select { |_, r| r.respond_to?(:success?) && r.success? }.keys
286
- end
287
-
288
- # @!endgroup
289
-
290
- # Converts the result to a hash
291
- #
292
- # @return [Hash] All result data
293
- def to_h
294
- {
295
- content: content,
296
- workflow_type: workflow_type,
297
- workflow_id: workflow_id,
298
- status: status,
299
- steps: steps.transform_values { |r| r.respond_to?(:to_h) ? r.to_h : r },
300
- branches: branches.transform_values { |r| r.respond_to?(:to_h) ? r.to_h : r },
301
- routed_to: routed_to,
302
- classification: classification,
303
- input_tokens: input_tokens,
304
- output_tokens: output_tokens,
305
- total_tokens: total_tokens,
306
- cached_tokens: cached_tokens,
307
- input_cost: input_cost,
308
- output_cost: output_cost,
309
- total_cost: total_cost,
310
- started_at: started_at,
311
- completed_at: completed_at,
312
- duration_ms: duration_ms,
313
- error_class: error_class,
314
- error_message: error_message,
315
- errors: errors.transform_values { |e| { class: e.class.name, message: e.message } }
316
- }
317
- end
318
-
319
- # Delegate hash methods to content for convenience
320
- delegate :[], :dig, :keys, :values, :each, :map, to: :content, allow_nil: true
321
-
322
- # Custom to_json that includes workflow metadata
323
- #
324
- # @param args [Array] Arguments passed to to_json
325
- # @return [String] JSON representation
326
- def to_json(*args)
327
- to_h.to_json(*args)
328
- end
329
- end
330
-
331
- # Represents a skipped step result
332
- class SkippedResult
333
- attr_reader :step_name, :reason
334
-
335
- def initialize(step_name, reason: nil)
336
- @step_name = step_name
337
- @reason = reason
338
- end
339
-
340
- def content
341
- nil
342
- end
343
-
344
- def success?
345
- true
346
- end
347
-
348
- def error?
349
- false
350
- end
351
-
352
- def skipped?
353
- true
354
- end
355
-
356
- def input_tokens
357
- 0
358
- end
359
-
360
- def output_tokens
361
- 0
362
- end
363
-
364
- def total_tokens
365
- 0
366
- end
367
-
368
- def cached_tokens
369
- 0
370
- end
371
-
372
- def input_cost
373
- 0.0
374
- end
375
-
376
- def output_cost
377
- 0.0
378
- end
379
-
380
- def total_cost
381
- 0.0
382
- end
383
-
384
- def to_h
385
- { skipped: true, step_name: step_name, reason: reason }
386
- end
387
- end
388
-
389
- # Result wrapper for sub-workflow execution
390
- #
391
- # Wraps a nested workflow result while providing access to
392
- # aggregate metrics and the underlying workflow result.
393
- #
394
- # @api public
395
- class SubWorkflowResult
396
- attr_reader :content, :sub_workflow_result, :workflow_type, :step_name
397
-
398
- def initialize(content:, sub_workflow_result:, workflow_type:, step_name:)
399
- @content = content
400
- @sub_workflow_result = sub_workflow_result
401
- @workflow_type = workflow_type
402
- @step_name = step_name
403
- end
404
-
405
- def success?
406
- sub_workflow_result.respond_to?(:success?) ? sub_workflow_result.success? : true
407
- end
408
-
409
- def error?
410
- sub_workflow_result.respond_to?(:error?) ? sub_workflow_result.error? : false
411
- end
412
-
413
- def skipped?
414
- false
415
- end
416
-
417
- # Delegate metrics to sub-workflow result
418
- def input_tokens
419
- sub_workflow_result.respond_to?(:input_tokens) ? sub_workflow_result.input_tokens : 0
420
- end
421
-
422
- def output_tokens
423
- sub_workflow_result.respond_to?(:output_tokens) ? sub_workflow_result.output_tokens : 0
424
- end
425
-
426
- def total_tokens
427
- input_tokens + output_tokens
428
- end
429
-
430
- def cached_tokens
431
- sub_workflow_result.respond_to?(:cached_tokens) ? sub_workflow_result.cached_tokens : 0
432
- end
433
-
434
- def input_cost
435
- sub_workflow_result.respond_to?(:input_cost) ? sub_workflow_result.input_cost : 0.0
436
- end
437
-
438
- def output_cost
439
- sub_workflow_result.respond_to?(:output_cost) ? sub_workflow_result.output_cost : 0.0
440
- end
441
-
442
- def total_cost
443
- sub_workflow_result.respond_to?(:total_cost) ? sub_workflow_result.total_cost : 0.0
444
- end
445
-
446
- # Access sub-workflow steps
447
- def steps
448
- sub_workflow_result.respond_to?(:steps) ? sub_workflow_result.steps : {}
449
- end
450
-
451
- def to_h
452
- {
453
- content: content,
454
- workflow_type: workflow_type,
455
- step_name: step_name,
456
- sub_workflow: sub_workflow_result.respond_to?(:to_h) ? sub_workflow_result.to_h : sub_workflow_result,
457
- input_tokens: input_tokens,
458
- output_tokens: output_tokens,
459
- total_cost: total_cost
460
- }
461
- end
462
-
463
- # Delegate hash access to content
464
- def [](key)
465
- content.is_a?(Hash) ? content[key] : nil
466
- end
467
-
468
- def dig(*keys)
469
- content.is_a?(Hash) ? content.dig(*keys) : nil
470
- end
471
- end
472
-
473
- # Result wrapper for iteration execution
474
- #
475
- # Tracks results for each item in an iteration with
476
- # aggregate success/failure counts and metrics.
477
- #
478
- # @api public
479
- class IterationResult
480
- attr_reader :step_name, :item_results, :errors
481
-
482
- def initialize(step_name:, item_results: [], errors: {})
483
- @step_name = step_name
484
- @item_results = item_results
485
- @errors = errors
486
- end
487
-
488
- def content
489
- item_results.map do |result|
490
- result.respond_to?(:content) ? result.content : result
491
- end
492
- end
493
-
494
- def success?
495
- errors.empty? && item_results.all? do |r|
496
- !r.respond_to?(:error?) || !r.error?
497
- end
498
- end
499
-
500
- def error?
501
- !success?
502
- end
503
-
504
- def partial?
505
- errors.any? && item_results.any? do |r|
506
- !r.respond_to?(:error?) || !r.error?
507
- end
508
- end
509
-
510
- def skipped?
511
- false
512
- end
513
-
514
- def successful_count
515
- item_results.count { |r| !r.respond_to?(:error?) || !r.error? }
516
- end
517
-
518
- def failed_count
519
- errors.size + item_results.count { |r| r.respond_to?(:error?) && r.error? }
520
- end
521
-
522
- def total_count
523
- item_results.size + errors.size
524
- end
525
-
526
- # Aggregate metrics across all items
527
- def input_tokens
528
- item_results.sum { |r| r.respond_to?(:input_tokens) ? r.input_tokens : 0 }
529
- end
530
-
531
- def output_tokens
532
- item_results.sum { |r| r.respond_to?(:output_tokens) ? r.output_tokens : 0 }
533
- end
534
-
535
- def total_tokens
536
- input_tokens + output_tokens
537
- end
538
-
539
- def cached_tokens
540
- item_results.sum { |r| r.respond_to?(:cached_tokens) ? r.cached_tokens : 0 }
541
- end
542
-
543
- def input_cost
544
- item_results.sum { |r| r.respond_to?(:input_cost) ? r.input_cost : 0.0 }
545
- end
546
-
547
- def output_cost
548
- item_results.sum { |r| r.respond_to?(:output_cost) ? r.output_cost : 0.0 }
549
- end
550
-
551
- def total_cost
552
- item_results.sum { |r| r.respond_to?(:total_cost) ? r.total_cost : 0.0 }
553
- end
554
-
555
- def to_h
556
- {
557
- step_name: step_name,
558
- total_count: total_count,
559
- successful_count: successful_count,
560
- failed_count: failed_count,
561
- success: success?,
562
- items: item_results.map { |r| r.respond_to?(:to_h) ? r.to_h : r },
563
- errors: errors.transform_values { |e| { class: e.class.name, message: e.message } },
564
- input_tokens: input_tokens,
565
- output_tokens: output_tokens,
566
- total_cost: total_cost
567
- }
568
- end
569
-
570
- # Access individual item results by index
571
- def [](index)
572
- item_results[index]
573
- end
574
-
575
- def each(&block)
576
- item_results.each(&block)
577
- end
578
-
579
- def map(&block)
580
- item_results.map(&block)
581
- end
582
-
583
- include Enumerable
584
-
585
- # Empty iteration result factory
586
- def self.empty(step_name)
587
- new(step_name: step_name, item_results: [], errors: {})
588
- end
589
- end
590
- end
591
- end
592
- end