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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +52 -0
- data/README.md +40 -2
- data/docs/PATTERNS.md +43 -4
- data/lib/smith/agent/lifecycle.rb +11 -7
- data/lib/smith/version.rb +1 -1
- data/lib/smith/workflow/budget_integration.rb +25 -5
- data/lib/smith/workflow/evaluator_optimizer.rb +13 -2
- data/lib/smith/workflow/execution.rb +6 -1
- data/lib/smith/workflow/fanout_execution.rb +119 -0
- data/lib/smith/workflow/graph/diagnostic.rb +33 -0
- data/lib/smith/workflow/graph/metrics.rb +33 -0
- data/lib/smith/workflow/graph/reachability.rb +59 -0
- data/lib/smith/workflow/graph/reachability_diagnostics.rb +47 -0
- data/lib/smith/workflow/graph/reference.rb +15 -0
- data/lib/smith/workflow/graph/report.rb +50 -0
- data/lib/smith/workflow/graph/state_diagnostics.rb +65 -0
- data/lib/smith/workflow/graph/targets.rb +38 -0
- data/lib/smith/workflow/graph/transition_diagnostics.rb +78 -0
- data/lib/smith/workflow/graph/transition_snapshot.rb +86 -0
- data/lib/smith/workflow/graph/validator.rb +50 -0
- data/lib/smith/workflow/graph.rb +36 -0
- data/lib/smith/workflow/graph_dsl.rb +18 -0
- data/lib/smith/workflow/guardrail_integration.rb +28 -10
- data/lib/smith/workflow/orchestrator_worker.rb +2 -1
- data/lib/smith/workflow/parallel/cancellation.rb +9 -0
- data/lib/smith/workflow/parallel.rb +6 -1
- data/lib/smith/workflow/parallel_execution.rb +3 -1
- data/lib/smith/workflow/retry_execution.rb +52 -0
- data/lib/smith/workflow/transition.rb +171 -21
- data/lib/smith.rb +6 -0
- metadata +17 -1
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
43
|
-
|
|
44
|
-
|
|
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
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
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
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
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
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
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 = [
|
|
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.
|
|
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
|