smith-agents 0.4.0 → 0.4.2

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.
@@ -5,7 +5,7 @@ module Smith
5
5
  class Transition
6
6
  attr_reader :name, :from, :to, :agent_name, :agent_opts, :success_transition, :failure_transition,
7
7
  :router_config, :workflow_class, :optimization_config, :orchestrator_config,
8
- :deterministic_block, :deterministic_kind
8
+ :fanout_config, :retry_config, :deterministic_block, :deterministic_kind
9
9
 
10
10
  def initialize(name, from:, to:, &)
11
11
  @name = name
@@ -15,7 +15,7 @@ module Smith
15
15
  end
16
16
 
17
17
  def execute(agent_name, **opts)
18
- raise WorkflowError, "transition cannot declare both execute and compute/run" if @deterministic_block
18
+ validate_execute_conflicts!
19
19
 
20
20
  @agent_name = agent_name
21
21
  @agent_opts = opts
@@ -30,7 +30,7 @@ module Smith
30
30
  end
31
31
 
32
32
  def route(agent_name, routes:, confidence_threshold:, fallback:)
33
- raise WorkflowError, "transition cannot declare both route and compute/run" if @deterministic_block
33
+ validate_route_conflicts!
34
34
 
35
35
  @agent_name = agent_name
36
36
  @router_config = { routes: routes, confidence_threshold: confidence_threshold, fallback: fallback }
@@ -39,9 +39,16 @@ module Smith
39
39
  def workflow(klass)
40
40
  raise WorkflowError, "workflow binding must be a Class" unless klass.is_a?(Class)
41
41
  raise WorkflowError, "workflow binding must be a Smith::Workflow subclass" unless klass < Workflow
42
- raise WorkflowError, "transition cannot declare both workflow and execute" if @agent_name && !@router_config
43
- raise WorkflowError, "transition cannot declare both workflow and route" if @router_config
44
- raise WorkflowError, "transition cannot declare both workflow and compute/run" if @deterministic_block
42
+
43
+ validate_conflicts!(
44
+ "workflow",
45
+ [
46
+ ["execute", @agent_name && !@router_config],
47
+ ["route", @router_config],
48
+ ["compute/run", @deterministic_block],
49
+ ["fan_out", @fanout_config]
50
+ ]
51
+ )
45
52
 
46
53
  @workflow_class = klass
47
54
  end
@@ -77,6 +84,25 @@ module Smith
77
84
  @orchestrator_config = opts
78
85
  end
79
86
 
87
+ def fan_out(branches:)
88
+ validate_fanout_conflicts!
89
+
90
+ @fanout_config = { branches: normalize_fanout_branches!(branches) }
91
+ end
92
+ alias fanout fan_out
93
+
94
+ def retry_on(*error_classes, attempts:, backoff: 0, max_delay: nil, jitter: 0)
95
+ validate_retry_controls!(error_classes, attempts:, backoff:, max_delay:, jitter:)
96
+
97
+ @retry_config = {
98
+ error_classes: error_classes.freeze,
99
+ attempts: attempts,
100
+ backoff: Float(backoff),
101
+ max_delay: max_delay.nil? ? nil : Float(max_delay),
102
+ jitter: Float(jitter)
103
+ }.freeze
104
+ end
105
+
80
106
  %i[compute run].each do |method_name|
81
107
  define_method(method_name) do |&block|
82
108
  validate_deterministic_conflicts!
@@ -95,6 +121,10 @@ module Smith
95
121
  !@orchestrator_config.nil?
96
122
  end
97
123
 
124
+ def fanout?
125
+ !@fanout_config.nil?
126
+ end
127
+
98
128
  def optimized?
99
129
  !@optimization_config.nil?
100
130
  end
@@ -113,28 +143,148 @@ module Smith
113
143
 
114
144
  private
115
145
 
146
+ def validate_execute_conflicts!
147
+ validate_conflicts!(
148
+ "execute",
149
+ [
150
+ ["compute/run", @deterministic_block],
151
+ ["fan_out", @fanout_config]
152
+ ]
153
+ )
154
+ end
155
+
156
+ def validate_route_conflicts!
157
+ validate_conflicts!(
158
+ "route",
159
+ [
160
+ ["compute/run", @deterministic_block],
161
+ ["fan_out", @fanout_config]
162
+ ]
163
+ )
164
+ end
165
+
116
166
  def validate_deterministic_conflicts!
117
- raise WorkflowError, "transition cannot declare both compute/run and execute" if @agent_name && !@router_config
118
- raise WorkflowError, "transition cannot declare both compute/run and route" if @router_config
119
- raise WorkflowError, "transition cannot declare both compute/run and workflow" if @workflow_class
120
- raise WorkflowError, "transition cannot declare both compute/run and optimize" if @optimization_config
121
- raise WorkflowError, "transition cannot declare both compute/run and orchestrate" if @orchestrator_config
167
+ validate_conflicts!(
168
+ "compute/run",
169
+ [
170
+ ["execute", @agent_name && !@router_config],
171
+ ["route", @router_config],
172
+ ["workflow", @workflow_class],
173
+ ["optimize", @optimization_config],
174
+ ["orchestrate", @orchestrator_config],
175
+ ["fan_out", @fanout_config]
176
+ ]
177
+ )
122
178
  raise WorkflowError, "transition cannot declare both compute and run" if @deterministic_block
123
179
  end
124
180
 
125
181
  def validate_optimize_conflicts!
126
- raise WorkflowError, "transition cannot declare both optimize and execute" if @agent_name && !@router_config
127
- raise WorkflowError, "transition cannot declare both optimize and route" if @router_config
128
- raise WorkflowError, "transition cannot declare both optimize and workflow" if @workflow_class
129
- raise WorkflowError, "transition cannot declare both optimize and compute/run" if @deterministic_block
182
+ validate_conflicts!(
183
+ "optimize",
184
+ [
185
+ ["execute", @agent_name && !@router_config],
186
+ ["route", @router_config],
187
+ ["workflow", @workflow_class],
188
+ ["compute/run", @deterministic_block],
189
+ ["fan_out", @fanout_config]
190
+ ]
191
+ )
130
192
  end
131
193
 
132
194
  def validate_orchestrate_conflicts!
133
- raise WorkflowError, "transition cannot declare both orchestrate and execute" if @agent_name && !@router_config
134
- raise WorkflowError, "transition cannot declare both orchestrate and route" if @router_config
135
- raise WorkflowError, "transition cannot declare both orchestrate and workflow" if @workflow_class
136
- raise WorkflowError, "transition cannot declare both orchestrate and optimize" if @optimization_config
137
- raise WorkflowError, "transition cannot declare both orchestrate and compute/run" if @deterministic_block
195
+ validate_conflicts!(
196
+ "orchestrate",
197
+ [
198
+ ["execute", @agent_name && !@router_config],
199
+ ["route", @router_config],
200
+ ["workflow", @workflow_class],
201
+ ["optimize", @optimization_config],
202
+ ["compute/run", @deterministic_block],
203
+ ["fan_out", @fanout_config]
204
+ ]
205
+ )
206
+ end
207
+
208
+ def validate_fanout_conflicts!
209
+ validate_conflicts!(
210
+ "fan_out",
211
+ [
212
+ ["execute", @agent_name && !@router_config],
213
+ ["route", @router_config],
214
+ ["workflow", @workflow_class],
215
+ ["optimize", @optimization_config],
216
+ ["orchestrate", @orchestrator_config],
217
+ ["compute/run", @deterministic_block]
218
+ ]
219
+ )
220
+ end
221
+
222
+ def validate_conflicts!(primitive, conflicts)
223
+ conflicts.each do |other, present|
224
+ raise WorkflowError, "transition cannot declare both #{primitive} and #{other}" if present
225
+ end
226
+ end
227
+
228
+ def normalize_fanout_branches!(branches)
229
+ raise WorkflowError, "fan_out branches must be a Hash" unless branches.is_a?(Hash)
230
+ raise WorkflowError, "fan_out requires at least one branch" if branches.empty?
231
+
232
+ normalized = branches.each_with_object({}) do |(branch_key, agent_name), map|
233
+ key = normalize_fanout_branch_key!(branch_key)
234
+ agent = normalize_fanout_agent_name!(agent_name, key)
235
+ raise WorkflowError, "fan_out branch #{key.inspect} is duplicated" if map.key?(key)
236
+
237
+ map[key] = agent
238
+ end
239
+
240
+ validate_distinct_fanout_agents!(normalized)
241
+ normalized.freeze
242
+ end
243
+
244
+ def normalize_fanout_branch_key!(branch_key)
245
+ key = branch_key.to_s.strip
246
+ raise WorkflowError, "fan_out branch keys must not be blank" if key.empty?
247
+
248
+ key.to_sym
249
+ end
250
+
251
+ def normalize_fanout_agent_name!(agent_name, branch_key)
252
+ value = agent_name.to_s.strip
253
+ raise WorkflowError, "fan_out branch #{branch_key.inspect} must declare an agent" if value.empty?
254
+
255
+ value.to_sym
256
+ end
257
+
258
+ def validate_distinct_fanout_agents!(branches)
259
+ duplicates = branches.values.tally.select { |_agent, count| count > 1 }.keys
260
+ return if duplicates.empty?
261
+
262
+ raise WorkflowError, "fan_out branch agents must be distinct: #{duplicates.map(&:inspect).join(", ")}"
263
+ end
264
+
265
+ def validate_retry_controls!(error_classes, attempts:, backoff:, max_delay:, jitter:)
266
+ unless attempts.is_a?(Integer) && attempts.positive?
267
+ raise WorkflowError, "retry_on attempts must be a positive integer"
268
+ end
269
+
270
+ error_classes.each do |error_class|
271
+ next if error_class.is_a?(Class) && error_class <= StandardError
272
+
273
+ raise WorkflowError, "retry_on error classes must inherit from StandardError"
274
+ end
275
+
276
+ validate_non_negative_numeric!(:backoff, backoff)
277
+ validate_non_negative_numeric!(:jitter, jitter)
278
+ validate_non_negative_numeric!(:max_delay, max_delay) unless max_delay.nil?
279
+ end
280
+
281
+ def validate_non_negative_numeric!(name, value)
282
+ numeric = Float(value)
283
+ return if numeric >= 0.0
284
+
285
+ raise WorkflowError, "retry_on #{name} must be non-negative"
286
+ rescue TypeError, ArgumentError
287
+ raise WorkflowError, "retry_on #{name} must be numeric"
138
288
  end
139
289
 
140
290
  def validate_orchestrate_controls!(opts)
@@ -177,7 +327,7 @@ module Smith
177
327
  raise WorkflowError, "optimize max_rounds must be a positive integer"
178
328
  end
179
329
 
180
- VALID_EXIT_MODES = [:raise, :return_last].freeze
330
+ VALID_EXIT_MODES = %i[raise return_last].freeze
181
331
  private_constant :VALID_EXIT_MODES
182
332
 
183
333
  def validate_optimize_exit_modes!(on_exhaustion:, on_converged:, on_threshold:)
data/lib/smith.rb CHANGED
@@ -70,6 +70,7 @@ module Smith
70
70
  unless %i[off auto].include?(value)
71
71
  raise ArgumentError, "Smith.config.openai_api_mode must be :off or :auto, got #{value.inspect}"
72
72
  end
73
+
73
74
  value
74
75
  }
75
76
 
@@ -227,6 +228,8 @@ require_relative "smith/agent/registry"
227
228
 
228
229
  # Workflow (Transition, DSL, Persistence, and Execution must load before Workflow)
229
230
  require_relative "smith/workflow/transition"
231
+ require_relative "smith/workflow/graph"
232
+ require_relative "smith/workflow/graph_dsl"
230
233
  require_relative "smith/workflow/dsl"
231
234
  require_relative "smith/workflow/persistence"
232
235
  require_relative "smith/workflow/durability"
@@ -240,6 +243,8 @@ require_relative "smith/workflow/nested_execution"
240
243
  require_relative "smith/workflow/evaluator_optimizer"
241
244
  require_relative "smith/workflow/orchestrator_worker"
242
245
  require_relative "smith/workflow/parallel_execution"
246
+ require_relative "smith/workflow/fanout_execution"
247
+ require_relative "smith/workflow/retry_execution"
243
248
  require_relative "smith/workflow/deterministic_step"
244
249
  require_relative "smith/workflow/deterministic_execution"
245
250
  require_relative "smith/workflow/execution"
@@ -249,6 +254,7 @@ require_relative "smith/workflow/execution_frame"
249
254
  require_relative "smith/workflow/pipeline"
250
255
  require_relative "smith/workflow/router"
251
256
  require_relative "smith/workflow/parallel"
257
+ require_relative "smith/workflow/parallel/cancellation"
252
258
 
253
259
  # Conditional Rails integration
254
260
  require_relative "smith/railtie" if defined?(Rails::Railtie)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: smith-agents
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.0
4
+ version: 0.4.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Samuel Ralak
@@ -219,13 +219,29 @@ files:
219
219
  - lib/smith/workflow/event_integration.rb
220
220
  - lib/smith/workflow/execution.rb
221
221
  - lib/smith/workflow/execution_frame.rb
222
+ - lib/smith/workflow/fanout_execution.rb
223
+ - lib/smith/workflow/graph.rb
224
+ - lib/smith/workflow/graph/diagnostic.rb
225
+ - lib/smith/workflow/graph/metrics.rb
226
+ - lib/smith/workflow/graph/reachability.rb
227
+ - lib/smith/workflow/graph/reachability_diagnostics.rb
228
+ - lib/smith/workflow/graph/reference.rb
229
+ - lib/smith/workflow/graph/report.rb
230
+ - lib/smith/workflow/graph/state_diagnostics.rb
231
+ - lib/smith/workflow/graph/targets.rb
232
+ - lib/smith/workflow/graph/transition_diagnostics.rb
233
+ - lib/smith/workflow/graph/transition_snapshot.rb
234
+ - lib/smith/workflow/graph/validator.rb
235
+ - lib/smith/workflow/graph_dsl.rb
222
236
  - lib/smith/workflow/guardrail_integration.rb
223
237
  - lib/smith/workflow/nested_execution.rb
224
238
  - lib/smith/workflow/orchestrator_worker.rb
225
239
  - lib/smith/workflow/parallel.rb
240
+ - lib/smith/workflow/parallel/cancellation.rb
226
241
  - lib/smith/workflow/parallel_execution.rb
227
242
  - lib/smith/workflow/persistence.rb
228
243
  - lib/smith/workflow/pipeline.rb
244
+ - lib/smith/workflow/retry_execution.rb
229
245
  - lib/smith/workflow/router.rb
230
246
  - lib/smith/workflow/transition.rb
231
247
  - script/profile_tool_results.rb