ruby_llm-agents 1.3.3 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +101 -334
- data/app/controllers/concerns/ruby_llm/agents/sortable.rb +0 -1
- data/app/controllers/ruby_llm/agents/agents_controller.rb +5 -56
- data/app/controllers/ruby_llm/agents/dashboard_controller.rb +22 -106
- data/app/controllers/ruby_llm/agents/executions_controller.rb +4 -114
- data/app/controllers/ruby_llm/agents/tenants_controller.rb +30 -2
- data/app/helpers/ruby_llm/agents/application_helper.rb +19 -53
- data/app/models/ruby_llm/agents/execution/analytics.rb +13 -54
- data/app/models/ruby_llm/agents/execution/scopes.rb +61 -14
- data/app/models/ruby_llm/agents/execution.rb +46 -10
- data/app/models/ruby_llm/agents/execution_detail.rb +18 -0
- data/app/models/ruby_llm/agents/tenant/budgetable.rb +132 -24
- data/app/models/ruby_llm/agents/tenant/incrementable.rb +117 -0
- data/app/models/ruby_llm/agents/tenant/resettable.rb +128 -0
- data/app/models/ruby_llm/agents/tenant/trackable.rb +46 -12
- data/app/models/ruby_llm/agents/tenant.rb +2 -3
- data/app/models/ruby_llm/agents/tenant_budget.rb +6 -3
- data/app/services/ruby_llm/agents/agent_registry.rb +6 -112
- data/app/views/layouts/ruby_llm/agents/application.html.erb +87 -252
- data/app/views/ruby_llm/agents/agents/_config_agent.html.erb +71 -218
- data/app/views/ruby_llm/agents/agents/_config_embedder.html.erb +20 -63
- data/app/views/ruby_llm/agents/agents/_config_image_generator.html.erb +44 -131
- data/app/views/ruby_llm/agents/agents/_config_moderator.html.erb +16 -57
- data/app/views/ruby_llm/agents/agents/_config_speaker.html.erb +39 -104
- data/app/views/ruby_llm/agents/agents/_config_transcriber.html.erb +29 -82
- data/app/views/ruby_llm/agents/agents/_empty_state.html.erb +4 -14
- data/app/views/ruby_llm/agents/agents/index.html.erb +105 -274
- data/app/views/ruby_llm/agents/agents/show.html.erb +248 -378
- data/app/views/ruby_llm/agents/dashboard/_action_center.html.erb +29 -52
- data/app/views/ruby_llm/agents/dashboard/_tenant_budget.html.erb +73 -99
- data/app/views/ruby_llm/agents/dashboard/index.html.erb +228 -433
- data/app/views/ruby_llm/agents/executions/_execution.html.erb +1 -1
- data/app/views/ruby_llm/agents/executions/_filters.html.erb +4 -25
- data/app/views/ruby_llm/agents/executions/_list.html.erb +111 -152
- data/app/views/ruby_llm/agents/executions/index.html.erb +5 -7
- data/app/views/ruby_llm/agents/executions/show.html.erb +528 -989
- data/app/views/ruby_llm/agents/shared/_agent_type_badge.html.erb +5 -21
- data/app/views/ruby_llm/agents/shared/_executions_table.html.erb +70 -191
- data/app/views/ruby_llm/agents/shared/_filter_dropdown.html.erb +16 -44
- data/app/views/ruby_llm/agents/shared/_select_dropdown.html.erb +12 -41
- data/app/views/ruby_llm/agents/shared/_status_badge.html.erb +11 -65
- data/app/views/ruby_llm/agents/shared/_tenant_filter.html.erb +6 -5
- data/app/views/ruby_llm/agents/system_config/show.html.erb +240 -351
- data/app/views/ruby_llm/agents/tenants/_form.html.erb +67 -77
- data/app/views/ruby_llm/agents/tenants/edit.html.erb +7 -9
- data/app/views/ruby_llm/agents/tenants/index.html.erb +100 -122
- data/app/views/ruby_llm/agents/tenants/show.html.erb +146 -336
- data/config/routes.rb +0 -13
- data/lib/generators/ruby_llm_agents/install_generator.rb +9 -14
- data/lib/generators/ruby_llm_agents/migrate_structure_generator.rb +2 -12
- data/lib/generators/ruby_llm_agents/restructure_generator.rb +0 -2
- data/lib/generators/ruby_llm_agents/templates/add_usage_counters_to_tenants_migration.rb.tt +37 -0
- data/lib/generators/ruby_llm_agents/templates/agent.rb.tt +1 -2
- data/lib/generators/ruby_llm_agents/templates/application_agent.rb.tt +1 -1
- data/lib/generators/ruby_llm_agents/templates/application_image_pipeline.rb.tt +0 -1
- data/lib/generators/ruby_llm_agents/templates/create_execution_details_migration.rb.tt +27 -0
- data/lib/generators/ruby_llm_agents/templates/create_tenants_migration.rb.tt +25 -0
- data/lib/generators/ruby_llm_agents/templates/image_pipeline.rb.tt +0 -1
- data/lib/generators/ruby_llm_agents/templates/initializer.rb.tt +9 -12
- data/lib/generators/ruby_llm_agents/templates/migration.rb.tt +40 -71
- data/lib/generators/ruby_llm_agents/templates/remove_agent_version_migration.rb.tt +13 -0
- data/lib/generators/ruby_llm_agents/templates/remove_workflow_columns_migration.rb.tt +19 -0
- data/lib/generators/ruby_llm_agents/templates/skills/AGENTS.md.tt +2 -4
- data/lib/generators/ruby_llm_agents/templates/skills/IMAGE_PIPELINES.md.tt +0 -1
- data/lib/generators/ruby_llm_agents/templates/split_execution_details_migration.rb.tt +232 -0
- data/lib/generators/ruby_llm_agents/upgrade_generator.rb +58 -262
- data/lib/ruby_llm/agents/audio/speaker.rb +0 -1
- data/lib/ruby_llm/agents/audio/transcriber.rb +0 -1
- data/lib/ruby_llm/agents/base_agent.rb +52 -6
- data/lib/ruby_llm/agents/core/base/callbacks.rb +142 -0
- data/lib/ruby_llm/agents/core/base.rb +23 -55
- data/lib/ruby_llm/agents/core/configuration.rb +58 -117
- data/lib/ruby_llm/agents/core/errors.rb +0 -58
- data/lib/ruby_llm/agents/core/instrumentation.rb +157 -110
- data/lib/ruby_llm/agents/core/llm_tenant.rb +8 -7
- data/lib/ruby_llm/agents/core/version.rb +1 -1
- data/lib/ruby_llm/agents/dsl/base.rb +157 -17
- data/lib/ruby_llm/agents/dsl/caching.rb +33 -2
- data/lib/ruby_llm/agents/dsl/reliability.rb +148 -0
- data/lib/ruby_llm/agents/dsl.rb +1 -2
- data/lib/ruby_llm/agents/image/analyzer/execution.rb +1 -2
- data/lib/ruby_llm/agents/image/background_remover/execution.rb +1 -2
- data/lib/ruby_llm/agents/image/concerns/image_operation_dsl.rb +1 -13
- data/lib/ruby_llm/agents/image/concerns/image_operation_execution.rb +2 -2
- data/lib/ruby_llm/agents/image/editor/dsl.rb +0 -14
- data/lib/ruby_llm/agents/image/editor/execution.rb +1 -10
- data/lib/ruby_llm/agents/image/editor.rb +0 -1
- data/lib/ruby_llm/agents/image/generator.rb +0 -21
- data/lib/ruby_llm/agents/image/pipeline/dsl.rb +0 -13
- data/lib/ruby_llm/agents/image/pipeline/execution.rb +0 -1
- data/lib/ruby_llm/agents/image/transformer/dsl.rb +0 -13
- data/lib/ruby_llm/agents/image/transformer/execution.rb +1 -10
- data/lib/ruby_llm/agents/image/transformer.rb +0 -1
- data/lib/ruby_llm/agents/image/upscaler/execution.rb +1 -2
- data/lib/ruby_llm/agents/image/variator/execution.rb +1 -2
- data/lib/ruby_llm/agents/infrastructure/alert_manager.rb +78 -173
- data/lib/ruby_llm/agents/infrastructure/attempt_tracker.rb +1 -0
- data/lib/ruby_llm/agents/infrastructure/budget/budget_query.rb +66 -2
- data/lib/ruby_llm/agents/infrastructure/budget/spend_recorder.rb +0 -12
- data/lib/ruby_llm/agents/infrastructure/circuit_breaker.rb +10 -13
- data/lib/ruby_llm/agents/infrastructure/reliability.rb +37 -2
- data/lib/ruby_llm/agents/pipeline/context.rb +0 -1
- data/lib/ruby_llm/agents/pipeline/middleware/budget.rb +28 -4
- data/lib/ruby_llm/agents/pipeline/middleware/cache.rb +3 -10
- data/lib/ruby_llm/agents/pipeline/middleware/instrumentation.rb +88 -55
- data/lib/ruby_llm/agents/pipeline/middleware/tenant.rb +5 -41
- data/lib/ruby_llm/agents/rails/engine.rb +6 -6
- data/lib/ruby_llm/agents/results/base.rb +1 -49
- data/lib/ruby_llm/agents/text/embedder.rb +0 -1
- data/lib/ruby_llm/agents.rb +1 -9
- data/lib/tasks/ruby_llm_agents.rake +34 -0
- metadata +12 -81
- data/app/controllers/ruby_llm/agents/api_configurations_controller.rb +0 -214
- data/app/controllers/ruby_llm/agents/workflows_controller.rb +0 -544
- data/app/mailers/ruby_llm/agents/alert_mailer.rb +0 -84
- data/app/mailers/ruby_llm/agents/application_mailer.rb +0 -28
- data/app/models/ruby_llm/agents/api_configuration.rb +0 -386
- data/app/models/ruby_llm/agents/execution/workflow.rb +0 -170
- data/app/models/ruby_llm/agents/tenant/configurable.rb +0 -135
- data/app/views/ruby_llm/agents/agents/_agent.html.erb +0 -98
- data/app/views/ruby_llm/agents/agents/_version_comparison.html.erb +0 -186
- data/app/views/ruby_llm/agents/agents/_workflow.html.erb +0 -126
- data/app/views/ruby_llm/agents/alert_mailer/alert_notification.html.erb +0 -107
- data/app/views/ruby_llm/agents/alert_mailer/alert_notification.text.erb +0 -18
- data/app/views/ruby_llm/agents/api_configurations/_api_key_field.html.erb +0 -34
- data/app/views/ruby_llm/agents/api_configurations/_form.html.erb +0 -288
- data/app/views/ruby_llm/agents/api_configurations/edit.html.erb +0 -95
- data/app/views/ruby_llm/agents/api_configurations/edit_tenant.html.erb +0 -97
- data/app/views/ruby_llm/agents/api_configurations/show.html.erb +0 -214
- data/app/views/ruby_llm/agents/api_configurations/tenant.html.erb +0 -179
- data/app/views/ruby_llm/agents/dashboard/_agent_comparison.html.erb +0 -73
- data/app/views/ruby_llm/agents/dashboard/_alerts_feed.html.erb +0 -62
- data/app/views/ruby_llm/agents/dashboard/_breaker_strip.html.erb +0 -47
- data/app/views/ruby_llm/agents/dashboard/_budgets_bar.html.erb +0 -75
- data/app/views/ruby_llm/agents/dashboard/_model_comparison.html.erb +0 -56
- data/app/views/ruby_llm/agents/dashboard/_model_cost_breakdown.html.erb +0 -115
- data/app/views/ruby_llm/agents/dashboard/_now_strip.html.erb +0 -59
- data/app/views/ruby_llm/agents/dashboard/_top_errors.html.erb +0 -60
- data/app/views/ruby_llm/agents/executions/_workflow_summary.html.erb +0 -86
- data/app/views/ruby_llm/agents/executions/dry_run.html.erb +0 -149
- data/app/views/ruby_llm/agents/shared/_breadcrumbs.html.erb +0 -48
- data/app/views/ruby_llm/agents/shared/_nav_link.html.erb +0 -27
- data/app/views/ruby_llm/agents/shared/_stat_card.html.erb +0 -14
- data/app/views/ruby_llm/agents/shared/_workflow_type_badge.html.erb +0 -35
- data/app/views/ruby_llm/agents/workflows/_empty_state.html.erb +0 -22
- data/app/views/ruby_llm/agents/workflows/_step_performance.html.erb +0 -228
- data/app/views/ruby_llm/agents/workflows/_structure_dsl.html.erb +0 -539
- data/app/views/ruby_llm/agents/workflows/_structure_parallel.html.erb +0 -76
- data/app/views/ruby_llm/agents/workflows/_structure_pipeline.html.erb +0 -74
- data/app/views/ruby_llm/agents/workflows/_structure_router.html.erb +0 -108
- data/app/views/ruby_llm/agents/workflows/_workflow_diagram.html.erb +0 -920
- data/app/views/ruby_llm/agents/workflows/index.html.erb +0 -179
- data/app/views/ruby_llm/agents/workflows/show.html.erb +0 -467
- data/lib/generators/ruby_llm_agents/api_configuration_generator.rb +0 -100
- data/lib/generators/ruby_llm_agents/templates/add_workflow_migration.rb.tt +0 -38
- data/lib/generators/ruby_llm_agents/templates/application_workflow.rb.tt +0 -48
- data/lib/generators/ruby_llm_agents/templates/create_api_configurations_migration.rb.tt +0 -90
- data/lib/generators/ruby_llm_agents/templates/skills/WORKFLOWS.md.tt +0 -551
- data/lib/ruby_llm/agents/core/base/moderation_dsl.rb +0 -181
- data/lib/ruby_llm/agents/core/base/moderation_execution.rb +0 -274
- data/lib/ruby_llm/agents/core/resolved_config.rb +0 -348
- data/lib/ruby_llm/agents/image/generator/content_policy.rb +0 -95
- data/lib/ruby_llm/agents/infrastructure/redactor.rb +0 -130
- data/lib/ruby_llm/agents/results/moderation_result.rb +0 -158
- data/lib/ruby_llm/agents/text/moderator.rb +0 -237
- data/lib/ruby_llm/agents/workflow/approval.rb +0 -205
- data/lib/ruby_llm/agents/workflow/approval_store.rb +0 -179
- data/lib/ruby_llm/agents/workflow/async.rb +0 -220
- data/lib/ruby_llm/agents/workflow/async_executor.rb +0 -156
- data/lib/ruby_llm/agents/workflow/dsl/executor.rb +0 -467
- data/lib/ruby_llm/agents/workflow/dsl/input_schema.rb +0 -244
- data/lib/ruby_llm/agents/workflow/dsl/iteration_executor.rb +0 -289
- data/lib/ruby_llm/agents/workflow/dsl/parallel_group.rb +0 -107
- data/lib/ruby_llm/agents/workflow/dsl/route_builder.rb +0 -150
- data/lib/ruby_llm/agents/workflow/dsl/schedule_helpers.rb +0 -187
- data/lib/ruby_llm/agents/workflow/dsl/step_config.rb +0 -352
- data/lib/ruby_llm/agents/workflow/dsl/step_executor.rb +0 -415
- data/lib/ruby_llm/agents/workflow/dsl/wait_config.rb +0 -257
- data/lib/ruby_llm/agents/workflow/dsl/wait_executor.rb +0 -317
- data/lib/ruby_llm/agents/workflow/dsl.rb +0 -576
- data/lib/ruby_llm/agents/workflow/instrumentation.rb +0 -249
- data/lib/ruby_llm/agents/workflow/notifiers/base.rb +0 -117
- data/lib/ruby_llm/agents/workflow/notifiers/email.rb +0 -117
- data/lib/ruby_llm/agents/workflow/notifiers/slack.rb +0 -180
- data/lib/ruby_llm/agents/workflow/notifiers/webhook.rb +0 -121
- data/lib/ruby_llm/agents/workflow/notifiers.rb +0 -70
- data/lib/ruby_llm/agents/workflow/orchestrator.rb +0 -416
- data/lib/ruby_llm/agents/workflow/result.rb +0 -592
- data/lib/ruby_llm/agents/workflow/thread_pool.rb +0 -185
- data/lib/ruby_llm/agents/workflow/throttle_manager.rb +0 -206
- data/lib/ruby_llm/agents/workflow/wait_result.rb +0 -213
|
@@ -1,576 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require "ostruct"
|
|
4
|
-
require_relative "dsl/step_config"
|
|
5
|
-
require_relative "dsl/route_builder"
|
|
6
|
-
require_relative "dsl/parallel_group"
|
|
7
|
-
require_relative "dsl/input_schema"
|
|
8
|
-
require_relative "dsl/step_executor"
|
|
9
|
-
require_relative "dsl/iteration_executor"
|
|
10
|
-
require_relative "dsl/wait_config"
|
|
11
|
-
require_relative "dsl/wait_executor"
|
|
12
|
-
require_relative "dsl/schedule_helpers"
|
|
13
|
-
|
|
14
|
-
module RubyLLM
|
|
15
|
-
module Agents
|
|
16
|
-
class Workflow
|
|
17
|
-
# Refined DSL for declarative workflow definition
|
|
18
|
-
#
|
|
19
|
-
# This module provides a clean, expressive syntax for defining workflows
|
|
20
|
-
# with minimal boilerplate for common patterns while maintaining full
|
|
21
|
-
# flexibility for complex scenarios.
|
|
22
|
-
#
|
|
23
|
-
# @example Minimal workflow
|
|
24
|
-
# class SimpleWorkflow < RubyLLM::Agents::Workflow
|
|
25
|
-
# step :fetch, FetcherAgent
|
|
26
|
-
# step :process, ProcessorAgent
|
|
27
|
-
# step :save, SaverAgent
|
|
28
|
-
# end
|
|
29
|
-
#
|
|
30
|
-
# @example Full-featured workflow
|
|
31
|
-
# class OrderWorkflow < RubyLLM::Agents::Workflow
|
|
32
|
-
# description "Process customer orders end-to-end"
|
|
33
|
-
#
|
|
34
|
-
# input do
|
|
35
|
-
# required :order_id, String
|
|
36
|
-
# optional :priority, String, default: "normal"
|
|
37
|
-
# end
|
|
38
|
-
#
|
|
39
|
-
# step :fetch, FetcherAgent, timeout: 1.minute
|
|
40
|
-
# step :validate, ValidatorAgent
|
|
41
|
-
#
|
|
42
|
-
# step :process, on: -> { validate.tier } do |route|
|
|
43
|
-
# route.premium PremiumAgent
|
|
44
|
-
# route.standard StandardAgent
|
|
45
|
-
# route.default DefaultAgent
|
|
46
|
-
# end
|
|
47
|
-
#
|
|
48
|
-
# parallel do
|
|
49
|
-
# step :analyze, AnalyzerAgent
|
|
50
|
-
# step :summarize, SummarizerAgent
|
|
51
|
-
# end
|
|
52
|
-
#
|
|
53
|
-
# step :notify, NotifierAgent, if: :should_notify?
|
|
54
|
-
#
|
|
55
|
-
# private
|
|
56
|
-
#
|
|
57
|
-
# def should_notify?
|
|
58
|
-
# input.callback_url.present?
|
|
59
|
-
# end
|
|
60
|
-
# end
|
|
61
|
-
#
|
|
62
|
-
# @api public
|
|
63
|
-
module DSL
|
|
64
|
-
def self.included(base)
|
|
65
|
-
base.extend(ClassMethods)
|
|
66
|
-
base.include(InstanceMethods)
|
|
67
|
-
end
|
|
68
|
-
|
|
69
|
-
# Class-level DSL methods
|
|
70
|
-
module ClassMethods
|
|
71
|
-
# Returns the ordered list of steps/groups
|
|
72
|
-
#
|
|
73
|
-
# @return [Array<Symbol, ParallelGroup>]
|
|
74
|
-
def step_order
|
|
75
|
-
@step_order ||= []
|
|
76
|
-
end
|
|
77
|
-
|
|
78
|
-
# Returns step configurations
|
|
79
|
-
#
|
|
80
|
-
# @return [Hash<Symbol, StepConfig>]
|
|
81
|
-
def step_configs
|
|
82
|
-
@step_configs ||= {}
|
|
83
|
-
end
|
|
84
|
-
|
|
85
|
-
# Returns parallel groups
|
|
86
|
-
#
|
|
87
|
-
# @return [Array<ParallelGroup>]
|
|
88
|
-
def parallel_groups
|
|
89
|
-
@parallel_groups ||= []
|
|
90
|
-
end
|
|
91
|
-
|
|
92
|
-
# Returns wait step configurations
|
|
93
|
-
#
|
|
94
|
-
# @return [Array<WaitConfig>]
|
|
95
|
-
def wait_configs
|
|
96
|
-
@wait_configs ||= []
|
|
97
|
-
end
|
|
98
|
-
|
|
99
|
-
# Returns the input schema
|
|
100
|
-
#
|
|
101
|
-
# @return [InputSchema, nil]
|
|
102
|
-
def input_schema
|
|
103
|
-
@input_schema
|
|
104
|
-
end
|
|
105
|
-
|
|
106
|
-
# Returns the output schema
|
|
107
|
-
#
|
|
108
|
-
# @return [OutputSchema, nil]
|
|
109
|
-
def output_schema
|
|
110
|
-
@output_schema
|
|
111
|
-
end
|
|
112
|
-
|
|
113
|
-
# Inherits DSL configuration from parent class
|
|
114
|
-
def inherited(subclass)
|
|
115
|
-
super
|
|
116
|
-
subclass.instance_variable_set(:@step_order, step_order.dup)
|
|
117
|
-
subclass.instance_variable_set(:@step_configs, step_configs.dup)
|
|
118
|
-
subclass.instance_variable_set(:@parallel_groups, parallel_groups.dup)
|
|
119
|
-
subclass.instance_variable_set(:@wait_configs, wait_configs.dup)
|
|
120
|
-
subclass.instance_variable_set(:@input_schema, input_schema&.dup)
|
|
121
|
-
subclass.instance_variable_set(:@output_schema, output_schema&.dup)
|
|
122
|
-
subclass.instance_variable_set(:@lifecycle_hooks, @lifecycle_hooks&.dup || {})
|
|
123
|
-
end
|
|
124
|
-
|
|
125
|
-
# Defines a workflow step
|
|
126
|
-
#
|
|
127
|
-
# @param name [Symbol] Step identifier
|
|
128
|
-
# @param agent [Class, nil] Agent class to execute (optional if using block)
|
|
129
|
-
# @param desc [String, nil] Human-readable description
|
|
130
|
-
# @param options [Hash] Step options (timeout, retry, if, unless, etc.)
|
|
131
|
-
# @yield [route] Block for routing or custom logic
|
|
132
|
-
# @return [void]
|
|
133
|
-
#
|
|
134
|
-
# @example Minimal step
|
|
135
|
-
# step :validate, ValidatorAgent
|
|
136
|
-
#
|
|
137
|
-
# @example With options
|
|
138
|
-
# step :fetch, FetcherAgent, timeout: 30.seconds, retry: 3
|
|
139
|
-
#
|
|
140
|
-
# @example With routing
|
|
141
|
-
# step :process, on: -> { classify.type } do |r|
|
|
142
|
-
# r.typeA AgentA
|
|
143
|
-
# r.typeB AgentB
|
|
144
|
-
# r.default DefaultAgent
|
|
145
|
-
# end
|
|
146
|
-
#
|
|
147
|
-
# @example With custom block
|
|
148
|
-
# step :custom do
|
|
149
|
-
# skip! "No data" if input.data.empty?
|
|
150
|
-
# agent CustomAgent, data: transform(input.data)
|
|
151
|
-
# end
|
|
152
|
-
def step(name, agent = nil, desc = nil, **options, &block)
|
|
153
|
-
# Handle positional description
|
|
154
|
-
description = desc.is_a?(String) ? desc : nil
|
|
155
|
-
if desc.is_a?(Hash)
|
|
156
|
-
options = desc.merge(options)
|
|
157
|
-
end
|
|
158
|
-
|
|
159
|
-
config = StepConfig.new(
|
|
160
|
-
name: name,
|
|
161
|
-
agent: agent,
|
|
162
|
-
description: description,
|
|
163
|
-
options: options,
|
|
164
|
-
block: block
|
|
165
|
-
)
|
|
166
|
-
|
|
167
|
-
step_configs[name] = config
|
|
168
|
-
|
|
169
|
-
# Add to order if not in a parallel block
|
|
170
|
-
unless @_defining_parallel
|
|
171
|
-
step_order << name
|
|
172
|
-
end
|
|
173
|
-
end
|
|
174
|
-
|
|
175
|
-
# Defines a group of steps that execute in parallel
|
|
176
|
-
#
|
|
177
|
-
# @param name [Symbol, nil] Optional name for the group
|
|
178
|
-
# @param options [Hash] Group options (fail_fast, concurrency, timeout)
|
|
179
|
-
# @yield Block defining the parallel steps
|
|
180
|
-
# @return [void]
|
|
181
|
-
#
|
|
182
|
-
# @example Unnamed parallel group
|
|
183
|
-
# parallel do
|
|
184
|
-
# step :sentiment, SentimentAgent
|
|
185
|
-
# step :keywords, KeywordAgent
|
|
186
|
-
# end
|
|
187
|
-
#
|
|
188
|
-
# @example Named parallel group
|
|
189
|
-
# parallel :analysis do
|
|
190
|
-
# step :sentiment, SentimentAgent
|
|
191
|
-
# step :keywords, KeywordAgent
|
|
192
|
-
# end
|
|
193
|
-
def parallel(name = nil, **options, &block)
|
|
194
|
-
@_defining_parallel = true
|
|
195
|
-
previous_step_count = step_configs.size
|
|
196
|
-
|
|
197
|
-
# Execute the block to collect step definitions
|
|
198
|
-
instance_eval(&block)
|
|
199
|
-
|
|
200
|
-
# Find newly added steps
|
|
201
|
-
new_steps = step_configs.keys.last(step_configs.size - previous_step_count)
|
|
202
|
-
|
|
203
|
-
group = ParallelGroup.new(
|
|
204
|
-
name: name,
|
|
205
|
-
step_names: new_steps,
|
|
206
|
-
options: options
|
|
207
|
-
)
|
|
208
|
-
|
|
209
|
-
parallel_groups << group
|
|
210
|
-
step_order << group
|
|
211
|
-
|
|
212
|
-
@_defining_parallel = false
|
|
213
|
-
end
|
|
214
|
-
|
|
215
|
-
# Defines a simple delay wait step
|
|
216
|
-
#
|
|
217
|
-
# @param duration [ActiveSupport::Duration, Integer, Float] Duration to wait
|
|
218
|
-
# @param options [Hash] Wait options (if:, unless:)
|
|
219
|
-
# @return [void]
|
|
220
|
-
#
|
|
221
|
-
# @example Simple delay
|
|
222
|
-
# wait 5.seconds
|
|
223
|
-
#
|
|
224
|
-
# @example Conditional delay
|
|
225
|
-
# wait 5.seconds, if: :needs_cooldown?
|
|
226
|
-
def wait(duration, **options)
|
|
227
|
-
config = WaitConfig.new(type: :delay, duration: duration, **options)
|
|
228
|
-
wait_configs << config
|
|
229
|
-
step_order << config
|
|
230
|
-
end
|
|
231
|
-
|
|
232
|
-
# Defines a conditional wait step that polls until a condition is met
|
|
233
|
-
#
|
|
234
|
-
# @param condition [Proc, nil] Lambda that returns true when ready to proceed
|
|
235
|
-
# @param time [Proc, Time, nil] Time to wait until (for scheduled waits)
|
|
236
|
-
# @param options [Hash] Wait options (poll_interval:, timeout:, on_timeout:, backoff:)
|
|
237
|
-
# @yield Block as condition (alternative to condition param)
|
|
238
|
-
# @return [void]
|
|
239
|
-
#
|
|
240
|
-
# @example Wait until condition
|
|
241
|
-
# wait_until -> { payment.confirmed? }, poll_interval: 5.seconds, timeout: 10.minutes
|
|
242
|
-
#
|
|
243
|
-
# @example With block
|
|
244
|
-
# wait_until(poll_interval: 1.second) { order.ready? }
|
|
245
|
-
#
|
|
246
|
-
# @example Wait until specific time
|
|
247
|
-
# wait_until time: -> { next_weekday_at(9, 0) }
|
|
248
|
-
def wait_until(condition = nil, time: nil, **options, &block)
|
|
249
|
-
condition ||= block
|
|
250
|
-
|
|
251
|
-
if time
|
|
252
|
-
config = WaitConfig.new(type: :schedule, condition: time, **options)
|
|
253
|
-
else
|
|
254
|
-
config = WaitConfig.new(type: :until, condition: condition, **options)
|
|
255
|
-
end
|
|
256
|
-
|
|
257
|
-
wait_configs << config
|
|
258
|
-
step_order << config
|
|
259
|
-
end
|
|
260
|
-
|
|
261
|
-
# Defines a human-in-the-loop approval wait step
|
|
262
|
-
#
|
|
263
|
-
# @param name [Symbol] Identifier for the approval point
|
|
264
|
-
# @param options [Hash] Approval options (notify:, message:, timeout:, approvers:, etc.)
|
|
265
|
-
# @return [void]
|
|
266
|
-
#
|
|
267
|
-
# @example Simple approval
|
|
268
|
-
# wait_for :manager_approval
|
|
269
|
-
#
|
|
270
|
-
# @example With notifications and timeout
|
|
271
|
-
# wait_for :review,
|
|
272
|
-
# notify: [:email, :slack],
|
|
273
|
-
# message: -> { "Please review: #{draft.title}" },
|
|
274
|
-
# timeout: 24.hours,
|
|
275
|
-
# reminder_after: 4.hours
|
|
276
|
-
def wait_for(name, **options)
|
|
277
|
-
config = WaitConfig.new(type: :approval, name: name, **options)
|
|
278
|
-
wait_configs << config
|
|
279
|
-
step_order << config
|
|
280
|
-
end
|
|
281
|
-
|
|
282
|
-
# Defines the input schema
|
|
283
|
-
#
|
|
284
|
-
# @yield Block defining required and optional fields
|
|
285
|
-
# @return [void]
|
|
286
|
-
#
|
|
287
|
-
# @example
|
|
288
|
-
# input do
|
|
289
|
-
# required :order_id, String
|
|
290
|
-
# optional :priority, String, default: "normal"
|
|
291
|
-
# end
|
|
292
|
-
def input(&block)
|
|
293
|
-
@input_schema = InputSchema.new
|
|
294
|
-
@input_schema.instance_eval(&block)
|
|
295
|
-
end
|
|
296
|
-
|
|
297
|
-
# Defines the output schema
|
|
298
|
-
#
|
|
299
|
-
# @yield Block defining required and optional fields
|
|
300
|
-
# @return [void]
|
|
301
|
-
def output(&block)
|
|
302
|
-
@output_schema = OutputSchema.new
|
|
303
|
-
@output_schema.instance_eval(&block)
|
|
304
|
-
end
|
|
305
|
-
|
|
306
|
-
# Registers a lifecycle hook
|
|
307
|
-
#
|
|
308
|
-
# @param hook_name [Symbol] Hook type
|
|
309
|
-
# @param step_name [Symbol, nil] Specific step (nil for all)
|
|
310
|
-
# @param method_name [Symbol, nil] Method to call
|
|
311
|
-
# @yield Block to execute
|
|
312
|
-
# @return [void]
|
|
313
|
-
def register_hook(hook_name, step_name = nil, method_name = nil, &block)
|
|
314
|
-
@lifecycle_hooks ||= {}
|
|
315
|
-
@lifecycle_hooks[hook_name] ||= []
|
|
316
|
-
@lifecycle_hooks[hook_name] << {
|
|
317
|
-
step: step_name,
|
|
318
|
-
method: method_name,
|
|
319
|
-
block: block
|
|
320
|
-
}
|
|
321
|
-
end
|
|
322
|
-
|
|
323
|
-
# Hooks that run before the workflow starts
|
|
324
|
-
def before_workflow(method_name = nil, &block)
|
|
325
|
-
register_hook(:before_workflow, nil, method_name, &block)
|
|
326
|
-
end
|
|
327
|
-
|
|
328
|
-
# Hooks that run after the workflow completes
|
|
329
|
-
def after_workflow(method_name = nil, &block)
|
|
330
|
-
register_hook(:after_workflow, nil, method_name, &block)
|
|
331
|
-
end
|
|
332
|
-
|
|
333
|
-
# Hooks that run before each step
|
|
334
|
-
def before_step(step_name = nil, method_name = nil, &block)
|
|
335
|
-
register_hook(:before_step, step_name, method_name, &block)
|
|
336
|
-
end
|
|
337
|
-
|
|
338
|
-
# Hooks that run after each step
|
|
339
|
-
def after_step(step_name = nil, method_name = nil, &block)
|
|
340
|
-
register_hook(:after_step, step_name, method_name, &block)
|
|
341
|
-
end
|
|
342
|
-
|
|
343
|
-
# Hooks that run when a step fails
|
|
344
|
-
def on_step_failure(step_name = nil, method_name = nil, &block)
|
|
345
|
-
register_hook(:on_step_failure, step_name, method_name, &block)
|
|
346
|
-
end
|
|
347
|
-
|
|
348
|
-
# Hooks that run when a step starts
|
|
349
|
-
def on_step_start(method_name = nil, &block)
|
|
350
|
-
register_hook(:on_step_start, nil, method_name, &block)
|
|
351
|
-
end
|
|
352
|
-
|
|
353
|
-
# Hooks that run when a step completes
|
|
354
|
-
def on_step_complete(method_name = nil, &block)
|
|
355
|
-
register_hook(:on_step_complete, nil, method_name, &block)
|
|
356
|
-
end
|
|
357
|
-
|
|
358
|
-
# Hooks that run when a step errors
|
|
359
|
-
def on_step_error(method_name = nil, &block)
|
|
360
|
-
register_hook(:on_step_error, nil, method_name, &block)
|
|
361
|
-
end
|
|
362
|
-
|
|
363
|
-
# Returns lifecycle hooks
|
|
364
|
-
#
|
|
365
|
-
# @return [Hash]
|
|
366
|
-
def lifecycle_hooks
|
|
367
|
-
@lifecycle_hooks ||= {}
|
|
368
|
-
end
|
|
369
|
-
|
|
370
|
-
# Returns step metadata for UI display
|
|
371
|
-
#
|
|
372
|
-
# @return [Array<Hash>]
|
|
373
|
-
def step_metadata
|
|
374
|
-
step_order.flat_map do |item|
|
|
375
|
-
case item
|
|
376
|
-
when Symbol
|
|
377
|
-
config = step_configs[item]
|
|
378
|
-
[{
|
|
379
|
-
name: item,
|
|
380
|
-
agent: config.agent&.name,
|
|
381
|
-
description: config.description,
|
|
382
|
-
ui_label: config.ui_label || item.to_s.humanize,
|
|
383
|
-
optional: config.optional?,
|
|
384
|
-
timeout: config.timeout,
|
|
385
|
-
routing: config.routing?,
|
|
386
|
-
parallel: false,
|
|
387
|
-
workflow: config.workflow?,
|
|
388
|
-
iteration: config.iteration?,
|
|
389
|
-
iteration_concurrency: config.iteration_concurrency,
|
|
390
|
-
throttle: config.throttle,
|
|
391
|
-
rate_limit: config.rate_limit
|
|
392
|
-
}.compact]
|
|
393
|
-
when ParallelGroup
|
|
394
|
-
item.step_names.map do |step_name|
|
|
395
|
-
config = step_configs[step_name]
|
|
396
|
-
{
|
|
397
|
-
name: step_name,
|
|
398
|
-
agent: config.agent&.name,
|
|
399
|
-
description: config.description,
|
|
400
|
-
ui_label: config.ui_label || step_name.to_s.humanize,
|
|
401
|
-
optional: config.optional?,
|
|
402
|
-
timeout: config.timeout,
|
|
403
|
-
routing: config.routing?,
|
|
404
|
-
parallel: true,
|
|
405
|
-
parallel_group: item.name,
|
|
406
|
-
workflow: config.workflow?,
|
|
407
|
-
iteration: config.iteration?,
|
|
408
|
-
iteration_concurrency: config.iteration_concurrency,
|
|
409
|
-
throttle: config.throttle,
|
|
410
|
-
rate_limit: config.rate_limit
|
|
411
|
-
}.compact
|
|
412
|
-
end
|
|
413
|
-
when WaitConfig
|
|
414
|
-
[{
|
|
415
|
-
name: item.name || "wait_#{item.type}",
|
|
416
|
-
type: :wait,
|
|
417
|
-
wait_type: item.type,
|
|
418
|
-
ui_label: item.ui_label,
|
|
419
|
-
timeout: item.timeout,
|
|
420
|
-
parallel: false,
|
|
421
|
-
duration: item.duration,
|
|
422
|
-
poll_interval: item.poll_interval,
|
|
423
|
-
on_timeout: item.on_timeout,
|
|
424
|
-
notify: item.notify_channels,
|
|
425
|
-
approvers: item.approvers
|
|
426
|
-
}.compact]
|
|
427
|
-
end
|
|
428
|
-
end
|
|
429
|
-
end
|
|
430
|
-
|
|
431
|
-
# Returns the total number of steps
|
|
432
|
-
#
|
|
433
|
-
# @return [Integer]
|
|
434
|
-
def total_steps
|
|
435
|
-
step_configs.size
|
|
436
|
-
end
|
|
437
|
-
|
|
438
|
-
# Validates workflow configuration
|
|
439
|
-
#
|
|
440
|
-
# @return [Array<String>] Validation errors
|
|
441
|
-
def validate_configuration
|
|
442
|
-
errors = []
|
|
443
|
-
|
|
444
|
-
step_configs.each do |name, config|
|
|
445
|
-
if config.agent.nil? && !config.custom_block? && !config.routing?
|
|
446
|
-
errors << "Step :#{name} has no agent defined"
|
|
447
|
-
end
|
|
448
|
-
|
|
449
|
-
if config.routing?
|
|
450
|
-
builder = RouteBuilder.new
|
|
451
|
-
config.block.call(builder)
|
|
452
|
-
if builder.routes.empty? && builder.default.nil?
|
|
453
|
-
errors << "Step :#{name} has no routes defined"
|
|
454
|
-
end
|
|
455
|
-
end
|
|
456
|
-
end
|
|
457
|
-
|
|
458
|
-
errors
|
|
459
|
-
end
|
|
460
|
-
end
|
|
461
|
-
|
|
462
|
-
# Instance-level DSL methods
|
|
463
|
-
module InstanceMethods
|
|
464
|
-
# Returns the validated input
|
|
465
|
-
#
|
|
466
|
-
# @return [OpenStruct] Input with accessor methods
|
|
467
|
-
def input
|
|
468
|
-
@validated_input ||= begin
|
|
469
|
-
schema = self.class.input_schema
|
|
470
|
-
validated = schema ? schema.validate!(options) : options
|
|
471
|
-
OpenStruct.new(validated)
|
|
472
|
-
end
|
|
473
|
-
end
|
|
474
|
-
|
|
475
|
-
# Returns a step result by name
|
|
476
|
-
#
|
|
477
|
-
# @param name [Symbol] Step name
|
|
478
|
-
# @return [Result, nil]
|
|
479
|
-
def step_result(name)
|
|
480
|
-
@step_results[name]
|
|
481
|
-
end
|
|
482
|
-
|
|
483
|
-
# Returns all step results
|
|
484
|
-
#
|
|
485
|
-
# @return [Hash<Symbol, Result>]
|
|
486
|
-
attr_reader :step_results
|
|
487
|
-
|
|
488
|
-
# Provides dynamic access to step results
|
|
489
|
-
#
|
|
490
|
-
# Allows accessing step results as methods:
|
|
491
|
-
# validate.content # Returns the :validate step result's content
|
|
492
|
-
#
|
|
493
|
-
def method_missing(name, *args, &block)
|
|
494
|
-
if @step_results&.key?(name)
|
|
495
|
-
result = @step_results[name]
|
|
496
|
-
# Return a proxy that allows accessing content
|
|
497
|
-
StepResultProxy.new(result)
|
|
498
|
-
else
|
|
499
|
-
super
|
|
500
|
-
end
|
|
501
|
-
end
|
|
502
|
-
|
|
503
|
-
def respond_to_missing?(name, include_private = false)
|
|
504
|
-
@step_results&.key?(name) || super
|
|
505
|
-
end
|
|
506
|
-
|
|
507
|
-
protected
|
|
508
|
-
|
|
509
|
-
# Executes lifecycle hooks
|
|
510
|
-
#
|
|
511
|
-
# @param hook_name [Symbol] Hook type
|
|
512
|
-
# @param step_name [Symbol, nil] Current step name
|
|
513
|
-
# @param args [Array] Arguments to pass to hooks
|
|
514
|
-
def run_hooks(hook_name, step_name = nil, *args)
|
|
515
|
-
hooks = self.class.lifecycle_hooks[hook_name] || []
|
|
516
|
-
|
|
517
|
-
hooks.each do |hook|
|
|
518
|
-
# Skip if hook is for a specific step and this isn't it
|
|
519
|
-
next if hook[:step] && hook[:step] != step_name
|
|
520
|
-
|
|
521
|
-
if hook[:method]
|
|
522
|
-
send(hook[:method], *args)
|
|
523
|
-
elsif hook[:block]
|
|
524
|
-
instance_exec(*args, &hook[:block])
|
|
525
|
-
end
|
|
526
|
-
end
|
|
527
|
-
end
|
|
528
|
-
end
|
|
529
|
-
|
|
530
|
-
# Proxy for accessing step results
|
|
531
|
-
#
|
|
532
|
-
# Provides convenient access to step result content and methods.
|
|
533
|
-
#
|
|
534
|
-
# @api private
|
|
535
|
-
class StepResultProxy
|
|
536
|
-
def initialize(result)
|
|
537
|
-
@result = result
|
|
538
|
-
end
|
|
539
|
-
|
|
540
|
-
# Delegate content access
|
|
541
|
-
def content
|
|
542
|
-
@result&.content
|
|
543
|
-
end
|
|
544
|
-
|
|
545
|
-
# Allow hash-like access to content
|
|
546
|
-
def [](key)
|
|
547
|
-
content&.[](key)
|
|
548
|
-
end
|
|
549
|
-
|
|
550
|
-
# Allow method access to content hash keys
|
|
551
|
-
def method_missing(name, *args, &block)
|
|
552
|
-
if @result.respond_to?(name)
|
|
553
|
-
@result.send(name, *args, &block)
|
|
554
|
-
elsif content.is_a?(Hash) && content.key?(name)
|
|
555
|
-
content[name]
|
|
556
|
-
elsif content.is_a?(Hash) && content.key?(name.to_s)
|
|
557
|
-
content[name.to_s]
|
|
558
|
-
else
|
|
559
|
-
super
|
|
560
|
-
end
|
|
561
|
-
end
|
|
562
|
-
|
|
563
|
-
def respond_to_missing?(name, include_private = false)
|
|
564
|
-
@result.respond_to?(name) ||
|
|
565
|
-
(content.is_a?(Hash) && (content.key?(name) || content.key?(name.to_s))) ||
|
|
566
|
-
super
|
|
567
|
-
end
|
|
568
|
-
|
|
569
|
-
def to_h
|
|
570
|
-
content.is_a?(Hash) ? content : { value: content }
|
|
571
|
-
end
|
|
572
|
-
end
|
|
573
|
-
end
|
|
574
|
-
end
|
|
575
|
-
end
|
|
576
|
-
end
|