smith-agents 0.4.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 +7 -0
- data/CHANGELOG.md +139 -0
- data/CODE_OF_CONDUCT.md +128 -0
- data/LICENSE +21 -0
- data/README.md +226 -0
- data/Rakefile +14 -0
- data/UPSTREAM_PROPOSAL.md +141 -0
- data/docs/CONFIGURATION.md +123 -0
- data/docs/PATTERNS.md +492 -0
- data/docs/PERSISTENCE.md +169 -0
- data/docs/TOOLS_AND_GUARDRAILS.md +140 -0
- data/docs/workflow_claim.md +58 -0
- data/exe/smith +7 -0
- data/lib/generators/smith/install/install_generator.rb +22 -0
- data/lib/generators/smith/install/templates/smith.rb.tt +44 -0
- data/lib/smith/agent/lifecycle.rb +264 -0
- data/lib/smith/agent/registry.rb +128 -0
- data/lib/smith/agent.rb +259 -0
- data/lib/smith/artifacts/file.rb +59 -0
- data/lib/smith/artifacts/memory.rb +75 -0
- data/lib/smith/artifacts/scoped_store.rb +29 -0
- data/lib/smith/artifacts.rb +5 -0
- data/lib/smith/budget/ledger.rb +42 -0
- data/lib/smith/budget.rb +5 -0
- data/lib/smith/cli.rb +82 -0
- data/lib/smith/context/observation_masking.rb +19 -0
- data/lib/smith/context/session.rb +42 -0
- data/lib/smith/context/state_injection.rb +24 -0
- data/lib/smith/context.rb +61 -0
- data/lib/smith/doctor/check.rb +12 -0
- data/lib/smith/doctor/checks/baseline.rb +84 -0
- data/lib/smith/doctor/checks/configuration.rb +56 -0
- data/lib/smith/doctor/checks/durability.rb +103 -0
- data/lib/smith/doctor/checks/live.rb +55 -0
- data/lib/smith/doctor/checks/models_registry.rb +66 -0
- data/lib/smith/doctor/checks/openai_api_mode.rb +51 -0
- data/lib/smith/doctor/checks/persistence.rb +99 -0
- data/lib/smith/doctor/checks/persistence_capabilities.rb +60 -0
- data/lib/smith/doctor/checks/persistence_registry.rb +82 -0
- data/lib/smith/doctor/checks/rails.rb +39 -0
- data/lib/smith/doctor/checks/serialization.rb +78 -0
- data/lib/smith/doctor/installer.rb +103 -0
- data/lib/smith/doctor/printer.rb +62 -0
- data/lib/smith/doctor/report.rb +39 -0
- data/lib/smith/doctor.rb +53 -0
- data/lib/smith/errors.rb +191 -0
- data/lib/smith/event.rb +11 -0
- data/lib/smith/events/.keep +0 -0
- data/lib/smith/events/bus.rb +60 -0
- data/lib/smith/events/step_completed.rb +11 -0
- data/lib/smith/events/subscription.rb +24 -0
- data/lib/smith/events.rb +5 -0
- data/lib/smith/guardrails/runner.rb +44 -0
- data/lib/smith/guardrails/url_verifier.rb +7 -0
- data/lib/smith/guardrails.rb +35 -0
- data/lib/smith/models/inference.rb +199 -0
- data/lib/smith/models/normalizer.rb +186 -0
- data/lib/smith/models/profile.rb +39 -0
- data/lib/smith/models.rb +132 -0
- data/lib/smith/persistence_adapters/active_record_store.rb +99 -0
- data/lib/smith/persistence_adapters/cache_store.rb +79 -0
- data/lib/smith/persistence_adapters/memory.rb +105 -0
- data/lib/smith/persistence_adapters/rails_cache.rb +20 -0
- data/lib/smith/persistence_adapters/redis_store.rb +136 -0
- data/lib/smith/persistence_adapters/retry.rb +42 -0
- data/lib/smith/persistence_adapters.rb +112 -0
- data/lib/smith/pricing.rb +65 -0
- data/lib/smith/providers/openai/responses.rb +315 -0
- data/lib/smith/providers/openai/routing.rb +67 -0
- data/lib/smith/providers/openai/tools_extensions.rb +106 -0
- data/lib/smith/railtie.rb +9 -0
- data/lib/smith/tasks/doctor.rake +38 -0
- data/lib/smith/tool/budget_enforcement.rb +33 -0
- data/lib/smith/tool/capability_builder.rb +18 -0
- data/lib/smith/tool/capture.rb +22 -0
- data/lib/smith/tool/compatibility.rb +72 -0
- data/lib/smith/tool/policy.rb +40 -0
- data/lib/smith/tool.rb +171 -0
- data/lib/smith/tools/think.rb +25 -0
- data/lib/smith/tools/url_fetcher.rb +16 -0
- data/lib/smith/tools/web_search.rb +17 -0
- data/lib/smith/tools.rb +5 -0
- data/lib/smith/trace/logger.rb +46 -0
- data/lib/smith/trace/memory.rb +53 -0
- data/lib/smith/trace/open_telemetry.rb +57 -0
- data/lib/smith/trace.rb +89 -0
- data/lib/smith/types.rb +16 -0
- data/lib/smith/version.rb +5 -0
- data/lib/smith/workflow/artifact_integration.rb +41 -0
- data/lib/smith/workflow/budget_integration.rb +105 -0
- data/lib/smith/workflow/claim.rb +118 -0
- data/lib/smith/workflow/data_volume_policy.rb +36 -0
- data/lib/smith/workflow/deadline_enforcement.rb +100 -0
- data/lib/smith/workflow/deterministic_execution.rb +53 -0
- data/lib/smith/workflow/deterministic_step.rb +57 -0
- data/lib/smith/workflow/dsl.rb +223 -0
- data/lib/smith/workflow/durability.rb +369 -0
- data/lib/smith/workflow/evaluator_optimizer.rb +220 -0
- data/lib/smith/workflow/event_integration.rb +24 -0
- data/lib/smith/workflow/execution.rb +127 -0
- data/lib/smith/workflow/execution_frame.rb +166 -0
- data/lib/smith/workflow/guardrail_integration.rb +40 -0
- data/lib/smith/workflow/nested_execution.rb +69 -0
- data/lib/smith/workflow/orchestrator_worker.rb +145 -0
- data/lib/smith/workflow/parallel.rb +50 -0
- data/lib/smith/workflow/parallel_execution.rb +75 -0
- data/lib/smith/workflow/persistence.rb +358 -0
- data/lib/smith/workflow/pipeline.rb +117 -0
- data/lib/smith/workflow/router.rb +53 -0
- data/lib/smith/workflow/transition.rb +208 -0
- data/lib/smith/workflow.rb +555 -0
- data/lib/smith.rb +254 -0
- data/script/profile_tool_results.rb +94 -0
- data/sig/smith.rbs +4 -0
- metadata +258 -0
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Smith
|
|
4
|
+
class Workflow
|
|
5
|
+
class Pipeline
|
|
6
|
+
attr_reader :name, :from, :to, :stages, :failure_transition
|
|
7
|
+
|
|
8
|
+
def initialize(name, from:, to:, &)
|
|
9
|
+
raise WorkflowError, "pipeline name is required" if name.nil?
|
|
10
|
+
raise WorkflowError, "pipeline :#{name} requires from:" if from.nil?
|
|
11
|
+
raise WorkflowError, "pipeline :#{name} requires to:" if to.nil?
|
|
12
|
+
|
|
13
|
+
@name = name
|
|
14
|
+
@from = from
|
|
15
|
+
@to = to
|
|
16
|
+
@stages = []
|
|
17
|
+
@failure_transition = nil
|
|
18
|
+
instance_eval(&)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def stage(stage_name, execute:)
|
|
22
|
+
raise WorkflowError, "pipeline :#{name} stage name is required" if stage_name.nil?
|
|
23
|
+
raise WorkflowError, "pipeline :#{name} stage :#{stage_name} requires execute:" if execute.nil?
|
|
24
|
+
|
|
25
|
+
@stages << { name: stage_name, agent: execute }
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def on_failure(transition_name)
|
|
29
|
+
@failure_transition = transition_name
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def compile!(workflow_class)
|
|
33
|
+
validate!
|
|
34
|
+
validate_no_collisions!(workflow_class)
|
|
35
|
+
generate_transitions(workflow_class)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
private
|
|
39
|
+
|
|
40
|
+
def validate!
|
|
41
|
+
raise WorkflowError, "pipeline :#{name} must declare at least one stage" if stages.empty?
|
|
42
|
+
|
|
43
|
+
seen = {}
|
|
44
|
+
stages.each do |stg|
|
|
45
|
+
raise WorkflowError, "pipeline :#{name} has duplicate stage :#{stg[:name]}" if seen[stg[:name]]
|
|
46
|
+
|
|
47
|
+
seen[stg[:name]] = true
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def validate_no_collisions!(workflow_class)
|
|
52
|
+
stages.each do |stg|
|
|
53
|
+
t_name = stage_transition_name(stg)
|
|
54
|
+
next unless workflow_class.find_transition(t_name)
|
|
55
|
+
|
|
56
|
+
raise WorkflowError, "pipeline :#{name} transition :#{t_name} collides with existing transition"
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def generate_transitions(workflow_class)
|
|
61
|
+
stages.each_with_index do |stg, idx|
|
|
62
|
+
declare_intermediate_state(workflow_class, idx)
|
|
63
|
+
register_stage_transition(workflow_class, stg, idx)
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def declare_intermediate_state(workflow_class, idx)
|
|
68
|
+
return if idx.zero?
|
|
69
|
+
|
|
70
|
+
workflow_class.state(stage_after_state(stages[idx - 1]))
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def register_stage_transition(workflow_class, stg, idx)
|
|
74
|
+
attrs = stage_attributes(stg, idx)
|
|
75
|
+
|
|
76
|
+
workflow_class.transition(attrs[:name], from: attrs[:from], to: attrs[:to]) do
|
|
77
|
+
execute attrs[:agent]
|
|
78
|
+
on_success attrs[:next] if attrs[:next]
|
|
79
|
+
on_failure attrs[:fail] if attrs[:fail]
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def stage_attributes(stg, idx)
|
|
84
|
+
{ name: stage_transition_name(stg), from: stage_from(idx), to: stage_to(stg, idx),
|
|
85
|
+
next: stage_next(idx), fail: failure_transition, agent: stg[:agent] }
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def stage_from(idx)
|
|
89
|
+
idx.zero? ? from : stage_after_state(stages[idx - 1])
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def stage_to(stg, idx)
|
|
93
|
+
idx == stages.length - 1 ? to : stage_after_state(stg)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def stage_next(idx)
|
|
97
|
+
idx < stages.length - 1 ? stage_transition_name(stages[idx + 1]) : nil
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def stage_transition_name(stg)
|
|
101
|
+
:"#{name}__#{stg[:name]}"
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def stage_after_state(stg)
|
|
105
|
+
:"#{name}__after_#{stg[:name]}"
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
module DSL
|
|
110
|
+
module ClassMethods
|
|
111
|
+
def pipeline(name, from:, to:, &)
|
|
112
|
+
Pipeline.new(name, from: from, to: to, &).compile!(self)
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Smith
|
|
4
|
+
class Workflow
|
|
5
|
+
class Router
|
|
6
|
+
def self.resolve(classifier_output, config, workflow_class:)
|
|
7
|
+
validate!(classifier_output, config)
|
|
8
|
+
transition_name = select_transition(classifier_output, config)
|
|
9
|
+
validate_transition_exists!(transition_name, workflow_class)
|
|
10
|
+
transition_name
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def self.validate!(output, config)
|
|
14
|
+
validate_structure!(output)
|
|
15
|
+
validate_confidence!(output[:confidence])
|
|
16
|
+
validate_route_key!(output[:route].to_sym, output[:confidence], config)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def self.validate_structure!(output)
|
|
20
|
+
raise WorkflowError, "router classifier output must be a Hash" unless output.is_a?(Hash)
|
|
21
|
+
raise WorkflowError, "router classifier output missing :route" unless output.key?(:route)
|
|
22
|
+
raise WorkflowError, "router classifier output missing :confidence" unless output.key?(:confidence)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def self.validate_confidence!(confidence)
|
|
26
|
+
return if confidence.is_a?(Numeric) && confidence >= 0.0 && confidence <= 1.0
|
|
27
|
+
|
|
28
|
+
raise WorkflowError, "router confidence must be a number in 0.0..1.0"
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def self.validate_route_key!(route_key, confidence, config)
|
|
32
|
+
return if confidence < config[:confidence_threshold]
|
|
33
|
+
return if config[:routes].key?(route_key)
|
|
34
|
+
|
|
35
|
+
raise WorkflowError, "router route :#{route_key} not found in declared routes"
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def self.select_transition(output, config)
|
|
39
|
+
if output[:confidence] >= config[:confidence_threshold]
|
|
40
|
+
config[:routes][output[:route].to_sym]
|
|
41
|
+
else
|
|
42
|
+
config[:fallback]
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def self.validate_transition_exists!(transition_name, workflow_class)
|
|
47
|
+
return if workflow_class.find_transition(transition_name)
|
|
48
|
+
|
|
49
|
+
raise WorkflowError, "router selected transition :#{transition_name} which is not declared on the workflow"
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Smith
|
|
4
|
+
class Workflow
|
|
5
|
+
class Transition
|
|
6
|
+
attr_reader :name, :from, :to, :agent_name, :agent_opts, :success_transition, :failure_transition,
|
|
7
|
+
:router_config, :workflow_class, :optimization_config, :orchestrator_config,
|
|
8
|
+
:deterministic_block, :deterministic_kind
|
|
9
|
+
|
|
10
|
+
def initialize(name, from:, to:, &)
|
|
11
|
+
@name = name
|
|
12
|
+
@from = from
|
|
13
|
+
@to = to
|
|
14
|
+
instance_eval(&) if block_given?
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def execute(agent_name, **opts)
|
|
18
|
+
raise WorkflowError, "transition cannot declare both execute and compute/run" if @deterministic_block
|
|
19
|
+
|
|
20
|
+
@agent_name = agent_name
|
|
21
|
+
@agent_opts = opts
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def on_success(transition_name)
|
|
25
|
+
@success_transition = transition_name
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def on_failure(transition_name)
|
|
29
|
+
@failure_transition = transition_name
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def route(agent_name, routes:, confidence_threshold:, fallback:)
|
|
33
|
+
raise WorkflowError, "transition cannot declare both route and compute/run" if @deterministic_block
|
|
34
|
+
|
|
35
|
+
@agent_name = agent_name
|
|
36
|
+
@router_config = { routes: routes, confidence_threshold: confidence_threshold, fallback: fallback }
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def workflow(klass)
|
|
40
|
+
raise WorkflowError, "workflow binding must be a Class" unless klass.is_a?(Class)
|
|
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
|
|
45
|
+
|
|
46
|
+
@workflow_class = klass
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def optimize(generator:, evaluator:, max_rounds:, evaluator_schema:,
|
|
50
|
+
improvement_threshold: nil,
|
|
51
|
+
evaluator_context: nil,
|
|
52
|
+
before_eval: nil,
|
|
53
|
+
on_exhaustion: :raise,
|
|
54
|
+
on_converged: :raise,
|
|
55
|
+
on_threshold: :raise)
|
|
56
|
+
validate_optimize_conflicts!
|
|
57
|
+
validate_optimize_controls!(generator, evaluator, max_rounds, evaluator_schema)
|
|
58
|
+
validate_optimize_exit_modes!(on_exhaustion: on_exhaustion, on_converged: on_converged,
|
|
59
|
+
on_threshold: on_threshold)
|
|
60
|
+
validate_optimize_evaluator_context!(evaluator_context)
|
|
61
|
+
validate_optimize_before_eval!(before_eval)
|
|
62
|
+
|
|
63
|
+
@optimization_config = {
|
|
64
|
+
generator: generator, evaluator: evaluator, max_rounds: max_rounds,
|
|
65
|
+
evaluator_schema: evaluator_schema, improvement_threshold: improvement_threshold,
|
|
66
|
+
evaluator_context: evaluator_context,
|
|
67
|
+
before_eval: before_eval,
|
|
68
|
+
on_exhaustion: on_exhaustion,
|
|
69
|
+
on_converged: on_converged,
|
|
70
|
+
on_threshold: on_threshold
|
|
71
|
+
}
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def orchestrate(**opts)
|
|
75
|
+
validate_orchestrate_conflicts!
|
|
76
|
+
validate_orchestrate_controls!(opts)
|
|
77
|
+
@orchestrator_config = opts
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
%i[compute run].each do |method_name|
|
|
81
|
+
define_method(method_name) do |&block|
|
|
82
|
+
validate_deterministic_conflicts!
|
|
83
|
+
raise WorkflowError, "#{method_name} requires a block" unless block
|
|
84
|
+
|
|
85
|
+
@deterministic_block = block
|
|
86
|
+
@deterministic_kind = method_name
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def deterministic?
|
|
91
|
+
!@deterministic_block.nil?
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def orchestrated?
|
|
95
|
+
!@orchestrator_config.nil?
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def optimized?
|
|
99
|
+
!@optimization_config.nil?
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def nested?
|
|
103
|
+
!@workflow_class.nil?
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def routed?
|
|
107
|
+
!@router_config.nil?
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def parallel?
|
|
111
|
+
agent_opts&.dig(:parallel) == true
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
private
|
|
115
|
+
|
|
116
|
+
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
|
|
122
|
+
raise WorkflowError, "transition cannot declare both compute and run" if @deterministic_block
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
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
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
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
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def validate_orchestrate_controls!(opts)
|
|
141
|
+
validate_orchestrate_required_fields!(opts)
|
|
142
|
+
validate_orchestrate_bounds!(opts)
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def validate_orchestrate_required_fields!(opts)
|
|
146
|
+
raise WorkflowError, "orchestrate requires an orchestrator" if opts[:orchestrator].nil?
|
|
147
|
+
raise WorkflowError, "orchestrate requires a worker" if opts[:worker].nil?
|
|
148
|
+
|
|
149
|
+
validate_schema_surface!(:task_schema, opts[:task_schema])
|
|
150
|
+
validate_schema_surface!(:worker_output_schema, opts[:worker_output_schema])
|
|
151
|
+
validate_schema_surface!(:final_output_schema, opts[:final_output_schema])
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def validate_schema_surface!(name, schema)
|
|
155
|
+
raise WorkflowError, "orchestrate requires a #{name}" if schema.nil?
|
|
156
|
+
return if schema.respond_to?(:required_keys)
|
|
157
|
+
|
|
158
|
+
raise WorkflowError, "orchestrate #{name} must respond to :required_keys"
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
def validate_orchestrate_bounds!(opts)
|
|
162
|
+
unless opts[:max_workers].is_a?(Integer) && opts[:max_workers].positive?
|
|
163
|
+
raise WorkflowError, "orchestrate max_workers must be a positive integer"
|
|
164
|
+
end
|
|
165
|
+
return if opts[:max_delegation_rounds].is_a?(Integer) && opts[:max_delegation_rounds].positive?
|
|
166
|
+
|
|
167
|
+
raise WorkflowError, "orchestrate max_delegation_rounds must be a positive integer"
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
def validate_optimize_controls!(generator, evaluator, max_rounds, evaluator_schema)
|
|
171
|
+
raise WorkflowError, "optimize requires a generator" if generator.nil?
|
|
172
|
+
raise WorkflowError, "optimize requires an evaluator" if evaluator.nil?
|
|
173
|
+
raise WorkflowError, "optimize requires an evaluator_schema" if evaluator_schema.nil?
|
|
174
|
+
|
|
175
|
+
return if max_rounds.is_a?(Integer) && max_rounds.positive?
|
|
176
|
+
|
|
177
|
+
raise WorkflowError, "optimize max_rounds must be a positive integer"
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
VALID_EXIT_MODES = [:raise, :return_last].freeze
|
|
181
|
+
private_constant :VALID_EXIT_MODES
|
|
182
|
+
|
|
183
|
+
def validate_optimize_exit_modes!(on_exhaustion:, on_converged:, on_threshold:)
|
|
184
|
+
{ on_exhaustion: on_exhaustion, on_converged: on_converged, on_threshold: on_threshold }.each do |name, value|
|
|
185
|
+
next if VALID_EXIT_MODES.include?(value)
|
|
186
|
+
next if value.respond_to?(:call)
|
|
187
|
+
|
|
188
|
+
raise WorkflowError,
|
|
189
|
+
"optimize #{name} must be :raise, :return_last, or a callable; got #{value.inspect}"
|
|
190
|
+
end
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
def validate_optimize_evaluator_context!(evaluator_context)
|
|
194
|
+
return if evaluator_context.nil? || evaluator_context == :inject_state
|
|
195
|
+
|
|
196
|
+
raise WorkflowError,
|
|
197
|
+
"optimize evaluator_context must be nil or :inject_state; got #{evaluator_context.inspect}"
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
def validate_optimize_before_eval!(before_eval)
|
|
201
|
+
return if before_eval.nil?
|
|
202
|
+
return if before_eval.respond_to?(:call)
|
|
203
|
+
|
|
204
|
+
raise WorkflowError, "optimize before_eval must respond to :call; got #{before_eval.inspect}"
|
|
205
|
+
end
|
|
206
|
+
end
|
|
207
|
+
end
|
|
208
|
+
end
|