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,244 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module RubyLLM
|
|
4
|
-
module Agents
|
|
5
|
-
class Workflow
|
|
6
|
-
module DSL
|
|
7
|
-
# Defines and validates input schema for a workflow
|
|
8
|
-
#
|
|
9
|
-
# Provides a DSL for declaring required and optional input parameters
|
|
10
|
-
# with type validation and default values.
|
|
11
|
-
#
|
|
12
|
-
# @example Defining input schema
|
|
13
|
-
# class MyWorkflow < RubyLLM::Agents::Workflow
|
|
14
|
-
# input do
|
|
15
|
-
# required :order_id, String
|
|
16
|
-
# required :user_id, Integer
|
|
17
|
-
# optional :priority, String, default: "normal"
|
|
18
|
-
# optional :expedited, Boolean, default: false
|
|
19
|
-
# end
|
|
20
|
-
# end
|
|
21
|
-
#
|
|
22
|
-
# @api private
|
|
23
|
-
class InputSchema
|
|
24
|
-
# Error raised when input validation fails
|
|
25
|
-
class ValidationError < StandardError
|
|
26
|
-
attr_reader :errors
|
|
27
|
-
|
|
28
|
-
def initialize(message, errors: [])
|
|
29
|
-
super(message)
|
|
30
|
-
@errors = errors
|
|
31
|
-
end
|
|
32
|
-
end
|
|
33
|
-
|
|
34
|
-
# Represents a single field in the schema
|
|
35
|
-
class Field
|
|
36
|
-
attr_reader :name, :type, :required, :default, :options
|
|
37
|
-
|
|
38
|
-
def initialize(name, type, required:, default: nil, **options)
|
|
39
|
-
@name = name
|
|
40
|
-
@type = type
|
|
41
|
-
@required = required
|
|
42
|
-
@default = default
|
|
43
|
-
@options = options
|
|
44
|
-
end
|
|
45
|
-
|
|
46
|
-
def required?
|
|
47
|
-
@required
|
|
48
|
-
end
|
|
49
|
-
|
|
50
|
-
def optional?
|
|
51
|
-
!@required
|
|
52
|
-
end
|
|
53
|
-
|
|
54
|
-
def has_default?
|
|
55
|
-
!@default.nil? || @options.key?(:default)
|
|
56
|
-
end
|
|
57
|
-
|
|
58
|
-
def validate(value)
|
|
59
|
-
errors = []
|
|
60
|
-
|
|
61
|
-
# Check required
|
|
62
|
-
if required? && value.nil?
|
|
63
|
-
errors << "#{name} is required"
|
|
64
|
-
return errors
|
|
65
|
-
end
|
|
66
|
-
|
|
67
|
-
# Skip validation for nil optional values
|
|
68
|
-
return errors if value.nil? && optional?
|
|
69
|
-
|
|
70
|
-
# Type validation
|
|
71
|
-
unless valid_type?(value)
|
|
72
|
-
errors << "#{name} must be a #{type_description}"
|
|
73
|
-
end
|
|
74
|
-
|
|
75
|
-
# Enum validation
|
|
76
|
-
if options[:in] && !options[:in].include?(value)
|
|
77
|
-
errors << "#{name} must be one of: #{options[:in].join(', ')}"
|
|
78
|
-
end
|
|
79
|
-
|
|
80
|
-
# Custom validation
|
|
81
|
-
if options[:validate] && !options[:validate].call(value)
|
|
82
|
-
errors << "#{name} failed custom validation"
|
|
83
|
-
end
|
|
84
|
-
|
|
85
|
-
errors
|
|
86
|
-
end
|
|
87
|
-
|
|
88
|
-
def to_h
|
|
89
|
-
{
|
|
90
|
-
name: name,
|
|
91
|
-
type: type_description,
|
|
92
|
-
required: required?,
|
|
93
|
-
default: default,
|
|
94
|
-
options: options.except(:validate)
|
|
95
|
-
}.compact
|
|
96
|
-
end
|
|
97
|
-
|
|
98
|
-
private
|
|
99
|
-
|
|
100
|
-
def valid_type?(value)
|
|
101
|
-
return true if type.nil?
|
|
102
|
-
|
|
103
|
-
case type
|
|
104
|
-
when :boolean, "Boolean"
|
|
105
|
-
value == true || value == false
|
|
106
|
-
else
|
|
107
|
-
value.is_a?(type)
|
|
108
|
-
end
|
|
109
|
-
end
|
|
110
|
-
|
|
111
|
-
def type_description
|
|
112
|
-
case type
|
|
113
|
-
when :boolean, "Boolean"
|
|
114
|
-
"Boolean"
|
|
115
|
-
when Class
|
|
116
|
-
type.name
|
|
117
|
-
else
|
|
118
|
-
type.to_s
|
|
119
|
-
end
|
|
120
|
-
end
|
|
121
|
-
end
|
|
122
|
-
|
|
123
|
-
def initialize
|
|
124
|
-
@fields = {}
|
|
125
|
-
end
|
|
126
|
-
|
|
127
|
-
# Defines a required field
|
|
128
|
-
#
|
|
129
|
-
# @param name [Symbol] Field name
|
|
130
|
-
# @param type [Class, Symbol] Expected type
|
|
131
|
-
# @param options [Hash] Additional options
|
|
132
|
-
# @return [void]
|
|
133
|
-
def required(name, type = nil, **options)
|
|
134
|
-
@fields[name] = Field.new(name, type, required: true, **options)
|
|
135
|
-
end
|
|
136
|
-
|
|
137
|
-
# Defines an optional field
|
|
138
|
-
#
|
|
139
|
-
# @param name [Symbol] Field name
|
|
140
|
-
# @param type [Class, Symbol] Expected type
|
|
141
|
-
# @param default [Object] Default value
|
|
142
|
-
# @param options [Hash] Additional options
|
|
143
|
-
# @return [void]
|
|
144
|
-
def optional(name, type = nil, default: nil, **options)
|
|
145
|
-
@fields[name] = Field.new(name, type, required: false, default: default, **options)
|
|
146
|
-
end
|
|
147
|
-
|
|
148
|
-
# Returns all fields
|
|
149
|
-
#
|
|
150
|
-
# @return [Hash<Symbol, Field>]
|
|
151
|
-
attr_reader :fields
|
|
152
|
-
|
|
153
|
-
# Returns required field names
|
|
154
|
-
#
|
|
155
|
-
# @return [Array<Symbol>]
|
|
156
|
-
def required_fields
|
|
157
|
-
@fields.select { |_, f| f.required? }.keys
|
|
158
|
-
end
|
|
159
|
-
|
|
160
|
-
# Returns optional field names
|
|
161
|
-
#
|
|
162
|
-
# @return [Array<Symbol>]
|
|
163
|
-
def optional_fields
|
|
164
|
-
@fields.select { |_, f| f.optional? }.keys
|
|
165
|
-
end
|
|
166
|
-
|
|
167
|
-
# Validates input against the schema
|
|
168
|
-
#
|
|
169
|
-
# @param input [Hash] Input data to validate
|
|
170
|
-
# @return [Hash] Validated and normalized input
|
|
171
|
-
# @raise [ValidationError] If validation fails
|
|
172
|
-
def validate!(input)
|
|
173
|
-
errors = []
|
|
174
|
-
normalized = {}
|
|
175
|
-
|
|
176
|
-
@fields.each do |name, field|
|
|
177
|
-
value = input.key?(name) ? input[name] : field.default
|
|
178
|
-
field_errors = field.validate(value)
|
|
179
|
-
errors.concat(field_errors)
|
|
180
|
-
normalized[name] = value unless value.nil? && field.optional?
|
|
181
|
-
end
|
|
182
|
-
|
|
183
|
-
# Include any extra fields not in schema
|
|
184
|
-
input.each do |key, value|
|
|
185
|
-
normalized[key] = value unless @fields.key?(key)
|
|
186
|
-
end
|
|
187
|
-
|
|
188
|
-
if errors.any?
|
|
189
|
-
raise ValidationError.new(
|
|
190
|
-
"Input validation failed: #{errors.join(', ')}",
|
|
191
|
-
errors: errors
|
|
192
|
-
)
|
|
193
|
-
end
|
|
194
|
-
|
|
195
|
-
normalized
|
|
196
|
-
end
|
|
197
|
-
|
|
198
|
-
# Applies defaults to input without validation
|
|
199
|
-
#
|
|
200
|
-
# @param input [Hash] Input data
|
|
201
|
-
# @return [Hash] Input with defaults applied
|
|
202
|
-
def apply_defaults(input)
|
|
203
|
-
result = input.dup
|
|
204
|
-
@fields.each do |name, field|
|
|
205
|
-
result[name] = field.default if !result.key?(name) && field.has_default?
|
|
206
|
-
end
|
|
207
|
-
result
|
|
208
|
-
end
|
|
209
|
-
|
|
210
|
-
# Converts to hash for serialization
|
|
211
|
-
#
|
|
212
|
-
# @return [Hash]
|
|
213
|
-
def to_h
|
|
214
|
-
{
|
|
215
|
-
fields: @fields.transform_values(&:to_h)
|
|
216
|
-
}
|
|
217
|
-
end
|
|
218
|
-
|
|
219
|
-
# Returns whether the schema is empty
|
|
220
|
-
#
|
|
221
|
-
# @return [Boolean]
|
|
222
|
-
def empty?
|
|
223
|
-
@fields.empty?
|
|
224
|
-
end
|
|
225
|
-
end
|
|
226
|
-
|
|
227
|
-
# Output schema for workflow results
|
|
228
|
-
#
|
|
229
|
-
# Similar to InputSchema but for validating workflow output.
|
|
230
|
-
class OutputSchema < InputSchema
|
|
231
|
-
# Validates output against the schema
|
|
232
|
-
#
|
|
233
|
-
# @param output [Hash] Output data to validate
|
|
234
|
-
# @return [Hash] Validated output
|
|
235
|
-
# @raise [ValidationError] If validation fails
|
|
236
|
-
def validate!(output)
|
|
237
|
-
output_hash = output.is_a?(Hash) ? output : { result: output }
|
|
238
|
-
super(output_hash)
|
|
239
|
-
end
|
|
240
|
-
end
|
|
241
|
-
end
|
|
242
|
-
end
|
|
243
|
-
end
|
|
244
|
-
end
|
|
@@ -1,289 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module RubyLLM
|
|
4
|
-
module Agents
|
|
5
|
-
class Workflow
|
|
6
|
-
module DSL
|
|
7
|
-
# Executes iteration steps with sequential or parallel processing
|
|
8
|
-
#
|
|
9
|
-
# Handles `each:` option on steps to process collections with support for:
|
|
10
|
-
# - Sequential iteration
|
|
11
|
-
# - Parallel iteration with configurable concurrency
|
|
12
|
-
# - Fail-fast behavior
|
|
13
|
-
# - Continue-on-error behavior
|
|
14
|
-
#
|
|
15
|
-
# @api private
|
|
16
|
-
class IterationExecutor
|
|
17
|
-
attr_reader :workflow, :config, :previous_result
|
|
18
|
-
|
|
19
|
-
# @param workflow [Workflow] The workflow instance
|
|
20
|
-
# @param config [StepConfig] The step configuration
|
|
21
|
-
# @param previous_result [Result, nil] Previous step result
|
|
22
|
-
def initialize(workflow, config, previous_result)
|
|
23
|
-
@workflow = workflow
|
|
24
|
-
@config = config
|
|
25
|
-
@previous_result = previous_result
|
|
26
|
-
end
|
|
27
|
-
|
|
28
|
-
# Executes the iteration
|
|
29
|
-
#
|
|
30
|
-
# @yield [chunk] Streaming callback
|
|
31
|
-
# @return [IterationResult] Aggregated results for all items
|
|
32
|
-
def execute(&block)
|
|
33
|
-
items = resolve_items
|
|
34
|
-
return Workflow::IterationResult.empty(config.name) if items.empty?
|
|
35
|
-
|
|
36
|
-
if config.iteration_concurrency && config.iteration_concurrency > 1
|
|
37
|
-
execute_parallel(items, &block)
|
|
38
|
-
else
|
|
39
|
-
execute_sequential(items, &block)
|
|
40
|
-
end
|
|
41
|
-
end
|
|
42
|
-
|
|
43
|
-
private
|
|
44
|
-
|
|
45
|
-
def resolve_items
|
|
46
|
-
source = config.each_source
|
|
47
|
-
items = workflow.instance_exec(&source)
|
|
48
|
-
Array(items)
|
|
49
|
-
rescue StandardError => e
|
|
50
|
-
raise IterationSourceError, "Failed to resolve iteration source: #{e.message}"
|
|
51
|
-
end
|
|
52
|
-
|
|
53
|
-
def execute_sequential(items, &block)
|
|
54
|
-
item_results = []
|
|
55
|
-
errors = {}
|
|
56
|
-
|
|
57
|
-
items.each_with_index do |item, index|
|
|
58
|
-
begin
|
|
59
|
-
result = execute_for_item(item, index, &block)
|
|
60
|
-
item_results << result
|
|
61
|
-
|
|
62
|
-
# Check for fail-fast on error
|
|
63
|
-
if config.iteration_fail_fast? && result.respond_to?(:error?) && result.error?
|
|
64
|
-
break
|
|
65
|
-
end
|
|
66
|
-
rescue StandardError => e
|
|
67
|
-
if config.iteration_fail_fast?
|
|
68
|
-
errors[index] = e
|
|
69
|
-
break
|
|
70
|
-
elsif config.continue_on_error?
|
|
71
|
-
errors[index] = e
|
|
72
|
-
# Continue to next item
|
|
73
|
-
else
|
|
74
|
-
raise
|
|
75
|
-
end
|
|
76
|
-
end
|
|
77
|
-
end
|
|
78
|
-
|
|
79
|
-
Workflow::IterationResult.new(
|
|
80
|
-
step_name: config.name,
|
|
81
|
-
item_results: item_results,
|
|
82
|
-
errors: errors
|
|
83
|
-
)
|
|
84
|
-
end
|
|
85
|
-
|
|
86
|
-
def execute_parallel(items, &block)
|
|
87
|
-
results_mutex = Mutex.new
|
|
88
|
-
item_results = Array.new(items.size)
|
|
89
|
-
errors = {}
|
|
90
|
-
aborted = false
|
|
91
|
-
|
|
92
|
-
pool = create_executor_pool(config.iteration_concurrency)
|
|
93
|
-
|
|
94
|
-
items.each_with_index do |item, index|
|
|
95
|
-
pool.post do
|
|
96
|
-
next if aborted
|
|
97
|
-
|
|
98
|
-
begin
|
|
99
|
-
result = execute_for_item(item, index, &block)
|
|
100
|
-
|
|
101
|
-
results_mutex.synchronize do
|
|
102
|
-
item_results[index] = result
|
|
103
|
-
|
|
104
|
-
# Check for fail-fast
|
|
105
|
-
if config.iteration_fail_fast? && result.respond_to?(:error?) && result.error?
|
|
106
|
-
aborted = true
|
|
107
|
-
pool.abort! if pool.respond_to?(:abort!)
|
|
108
|
-
end
|
|
109
|
-
end
|
|
110
|
-
rescue StandardError => e
|
|
111
|
-
results_mutex.synchronize do
|
|
112
|
-
errors[index] = e
|
|
113
|
-
|
|
114
|
-
if config.iteration_fail_fast?
|
|
115
|
-
aborted = true
|
|
116
|
-
pool.abort! if pool.respond_to?(:abort!)
|
|
117
|
-
end
|
|
118
|
-
end
|
|
119
|
-
|
|
120
|
-
raise unless config.continue_on_error? || config.iteration_fail_fast?
|
|
121
|
-
end
|
|
122
|
-
end
|
|
123
|
-
end
|
|
124
|
-
|
|
125
|
-
pool.wait_for_completion
|
|
126
|
-
pool.shutdown
|
|
127
|
-
|
|
128
|
-
# Remove nil entries from results (unfilled due to abort)
|
|
129
|
-
item_results.compact!
|
|
130
|
-
|
|
131
|
-
Workflow::IterationResult.new(
|
|
132
|
-
step_name: config.name,
|
|
133
|
-
item_results: item_results,
|
|
134
|
-
errors: errors
|
|
135
|
-
)
|
|
136
|
-
end
|
|
137
|
-
|
|
138
|
-
def execute_for_item(item, index, &block)
|
|
139
|
-
if config.custom_block?
|
|
140
|
-
execute_block_for_item(item, index, &block)
|
|
141
|
-
elsif config.workflow?
|
|
142
|
-
execute_workflow_for_item(item, index, &block)
|
|
143
|
-
else
|
|
144
|
-
execute_agent_for_item(item, index, &block)
|
|
145
|
-
end
|
|
146
|
-
end
|
|
147
|
-
|
|
148
|
-
def execute_block_for_item(item, index, &block)
|
|
149
|
-
context = IterationContext.new(workflow, config, previous_result, item, index)
|
|
150
|
-
result = context.instance_exec(item, &config.block)
|
|
151
|
-
|
|
152
|
-
# If block returns a Result, use it; otherwise wrap it
|
|
153
|
-
if result.is_a?(Workflow::Result) || result.is_a?(RubyLLM::Agents::Result)
|
|
154
|
-
result
|
|
155
|
-
else
|
|
156
|
-
SimpleResult.new(content: result, success: true)
|
|
157
|
-
end
|
|
158
|
-
end
|
|
159
|
-
|
|
160
|
-
def execute_agent_for_item(item, index, &block)
|
|
161
|
-
# Build input for this item
|
|
162
|
-
step_input = build_item_input(item, index)
|
|
163
|
-
workflow.send(:execute_agent, config.agent, step_input, step_name: config.name, &block)
|
|
164
|
-
end
|
|
165
|
-
|
|
166
|
-
def execute_workflow_for_item(item, index, &block)
|
|
167
|
-
step_input = build_item_input(item, index)
|
|
168
|
-
|
|
169
|
-
# Build execution metadata
|
|
170
|
-
parent_metadata = {
|
|
171
|
-
parent_execution_id: workflow.execution_id,
|
|
172
|
-
root_execution_id: workflow.send(:root_execution_id),
|
|
173
|
-
workflow_id: workflow.workflow_id,
|
|
174
|
-
workflow_type: workflow.class.name,
|
|
175
|
-
workflow_step: config.name.to_s,
|
|
176
|
-
iteration_index: index,
|
|
177
|
-
recursion_depth: (workflow.instance_variable_get(:@recursion_depth) || 0) + (config.agent == workflow.class ? 1 : 0)
|
|
178
|
-
}.compact
|
|
179
|
-
|
|
180
|
-
merged_input = step_input.merge(
|
|
181
|
-
execution_metadata: parent_metadata.merge(step_input[:execution_metadata] || {})
|
|
182
|
-
)
|
|
183
|
-
|
|
184
|
-
result = config.agent.call(**merged_input, &block)
|
|
185
|
-
|
|
186
|
-
# Track accumulated cost
|
|
187
|
-
if result.respond_to?(:total_cost) && result.total_cost
|
|
188
|
-
workflow.instance_variable_set(
|
|
189
|
-
:@accumulated_cost,
|
|
190
|
-
(workflow.instance_variable_get(:@accumulated_cost) || 0.0) + result.total_cost
|
|
191
|
-
)
|
|
192
|
-
workflow.send(:check_cost_threshold!)
|
|
193
|
-
end
|
|
194
|
-
|
|
195
|
-
Workflow::SubWorkflowResult.new(
|
|
196
|
-
content: result.content,
|
|
197
|
-
sub_workflow_result: result,
|
|
198
|
-
workflow_type: config.agent.name,
|
|
199
|
-
step_name: config.name
|
|
200
|
-
)
|
|
201
|
-
end
|
|
202
|
-
|
|
203
|
-
def build_item_input(item, index)
|
|
204
|
-
# If there's an input mapper, use it with item context
|
|
205
|
-
if config.input_mapper
|
|
206
|
-
# Create a temporary context that has access to item and index
|
|
207
|
-
context = IterationInputContext.new(workflow, item, index)
|
|
208
|
-
context.instance_exec(&config.input_mapper)
|
|
209
|
-
else
|
|
210
|
-
# Default: wrap item in a hash
|
|
211
|
-
item.is_a?(Hash) ? item : { item: item, index: index }
|
|
212
|
-
end
|
|
213
|
-
end
|
|
214
|
-
|
|
215
|
-
def create_executor_pool(size)
|
|
216
|
-
config_obj = RubyLLM::Agents.configuration
|
|
217
|
-
|
|
218
|
-
if config_obj.respond_to?(:async_context?) && config_obj.async_context?
|
|
219
|
-
AsyncExecutor.new(max_concurrent: size)
|
|
220
|
-
else
|
|
221
|
-
ThreadPool.new(size: size)
|
|
222
|
-
end
|
|
223
|
-
end
|
|
224
|
-
end
|
|
225
|
-
|
|
226
|
-
# Context for executing iteration block steps
|
|
227
|
-
#
|
|
228
|
-
# Extends BlockContext with item and index access.
|
|
229
|
-
#
|
|
230
|
-
# @api private
|
|
231
|
-
class IterationContext < BlockContext
|
|
232
|
-
attr_reader :item, :index
|
|
233
|
-
|
|
234
|
-
def initialize(workflow, config, previous_result, item, index)
|
|
235
|
-
super(workflow, config, previous_result)
|
|
236
|
-
@item = item
|
|
237
|
-
@index = index
|
|
238
|
-
end
|
|
239
|
-
|
|
240
|
-
# Access the current item being processed
|
|
241
|
-
def current_item
|
|
242
|
-
@item
|
|
243
|
-
end
|
|
244
|
-
|
|
245
|
-
# Access the current iteration index
|
|
246
|
-
def current_index
|
|
247
|
-
@index
|
|
248
|
-
end
|
|
249
|
-
end
|
|
250
|
-
|
|
251
|
-
# Context for building iteration input
|
|
252
|
-
#
|
|
253
|
-
# Provides access to item and index for input mappers.
|
|
254
|
-
#
|
|
255
|
-
# @api private
|
|
256
|
-
class IterationInputContext
|
|
257
|
-
def initialize(workflow, item, index)
|
|
258
|
-
@workflow = workflow
|
|
259
|
-
@item = item
|
|
260
|
-
@index = index
|
|
261
|
-
end
|
|
262
|
-
|
|
263
|
-
attr_reader :item, :index
|
|
264
|
-
|
|
265
|
-
# Access workflow input
|
|
266
|
-
def input
|
|
267
|
-
@workflow.input
|
|
268
|
-
end
|
|
269
|
-
|
|
270
|
-
# Delegate to workflow for step results access
|
|
271
|
-
def method_missing(name, *args, &block)
|
|
272
|
-
if @workflow.respond_to?(name, true)
|
|
273
|
-
@workflow.send(name, *args, &block)
|
|
274
|
-
else
|
|
275
|
-
super
|
|
276
|
-
end
|
|
277
|
-
end
|
|
278
|
-
|
|
279
|
-
def respond_to_missing?(name, include_private = false)
|
|
280
|
-
@workflow.respond_to?(name, include_private) || super
|
|
281
|
-
end
|
|
282
|
-
end
|
|
283
|
-
|
|
284
|
-
# Error raised when iteration source resolution fails
|
|
285
|
-
class IterationSourceError < StandardError; end
|
|
286
|
-
end
|
|
287
|
-
end
|
|
288
|
-
end
|
|
289
|
-
end
|
|
@@ -1,107 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module RubyLLM
|
|
4
|
-
module Agents
|
|
5
|
-
class Workflow
|
|
6
|
-
module DSL
|
|
7
|
-
# Represents a group of steps that execute in parallel
|
|
8
|
-
#
|
|
9
|
-
# Parallel groups allow multiple steps to run concurrently and
|
|
10
|
-
# their results to be available to subsequent steps.
|
|
11
|
-
#
|
|
12
|
-
# @example Basic parallel group
|
|
13
|
-
# parallel do
|
|
14
|
-
# step :sentiment, SentimentAgent
|
|
15
|
-
# step :keywords, KeywordAgent
|
|
16
|
-
# step :entities, EntityAgent
|
|
17
|
-
# end
|
|
18
|
-
#
|
|
19
|
-
# @example Named parallel group
|
|
20
|
-
# parallel :analysis do
|
|
21
|
-
# step :sentiment, SentimentAgent
|
|
22
|
-
# step :keywords, KeywordAgent
|
|
23
|
-
# end
|
|
24
|
-
#
|
|
25
|
-
# step :combine, CombinerAgent,
|
|
26
|
-
# input: -> { { analysis: analysis } }
|
|
27
|
-
#
|
|
28
|
-
# @api private
|
|
29
|
-
class ParallelGroup
|
|
30
|
-
attr_reader :name, :step_names, :options
|
|
31
|
-
|
|
32
|
-
# @param name [Symbol, nil] Optional name for the group
|
|
33
|
-
# @param step_names [Array<Symbol>] Names of steps in the group
|
|
34
|
-
# @param options [Hash] Group options
|
|
35
|
-
def initialize(name: nil, step_names: [], options: {})
|
|
36
|
-
@name = name
|
|
37
|
-
@step_names = step_names
|
|
38
|
-
@options = options
|
|
39
|
-
end
|
|
40
|
-
|
|
41
|
-
# Adds a step to the group
|
|
42
|
-
#
|
|
43
|
-
# @param step_name [Symbol]
|
|
44
|
-
# @return [void]
|
|
45
|
-
def add_step(step_name)
|
|
46
|
-
@step_names << step_name
|
|
47
|
-
end
|
|
48
|
-
|
|
49
|
-
# Returns the number of steps in the group
|
|
50
|
-
#
|
|
51
|
-
# @return [Integer]
|
|
52
|
-
def size
|
|
53
|
-
@step_names.size
|
|
54
|
-
end
|
|
55
|
-
|
|
56
|
-
# Returns whether the group is empty
|
|
57
|
-
#
|
|
58
|
-
# @return [Boolean]
|
|
59
|
-
def empty?
|
|
60
|
-
@step_names.empty?
|
|
61
|
-
end
|
|
62
|
-
|
|
63
|
-
# Returns the fail-fast setting for this group
|
|
64
|
-
#
|
|
65
|
-
# @return [Boolean]
|
|
66
|
-
def fail_fast?
|
|
67
|
-
options[:fail_fast] == true
|
|
68
|
-
end
|
|
69
|
-
|
|
70
|
-
# Returns the concurrency limit for this group
|
|
71
|
-
#
|
|
72
|
-
# @return [Integer, nil]
|
|
73
|
-
def concurrency
|
|
74
|
-
options[:concurrency]
|
|
75
|
-
end
|
|
76
|
-
|
|
77
|
-
# Returns the timeout for the entire group
|
|
78
|
-
#
|
|
79
|
-
# @return [Integer, nil]
|
|
80
|
-
def timeout
|
|
81
|
-
options[:timeout]
|
|
82
|
-
end
|
|
83
|
-
|
|
84
|
-
# Converts to hash for serialization
|
|
85
|
-
#
|
|
86
|
-
# @return [Hash]
|
|
87
|
-
def to_h
|
|
88
|
-
{
|
|
89
|
-
name: name,
|
|
90
|
-
step_names: step_names,
|
|
91
|
-
fail_fast: fail_fast?,
|
|
92
|
-
concurrency: concurrency,
|
|
93
|
-
timeout: timeout
|
|
94
|
-
}.compact
|
|
95
|
-
end
|
|
96
|
-
|
|
97
|
-
# String representation
|
|
98
|
-
#
|
|
99
|
-
# @return [String]
|
|
100
|
-
def inspect
|
|
101
|
-
"#<ParallelGroup name=#{name.inspect} steps=#{step_names.inspect}>"
|
|
102
|
-
end
|
|
103
|
-
end
|
|
104
|
-
end
|
|
105
|
-
end
|
|
106
|
-
end
|
|
107
|
-
end
|