roast-ai 0.4.2 → 0.4.4
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 +5 -0
- data/Gemfile.lock +11 -12
- data/README.md +228 -29
- data/examples/coding_agent_with_model.yml +20 -0
- data/examples/coding_agent_with_retries.yml +30 -0
- data/lib/roast/helpers/metadata_access.rb +39 -0
- data/lib/roast/helpers/timeout_handler.rb +1 -1
- data/lib/roast/tools/coding_agent.rb +98 -26
- data/lib/roast/tools/grep.rb +4 -0
- data/lib/roast/version.rb +1 -1
- data/lib/roast/workflow/agent_step.rb +57 -4
- data/lib/roast/workflow/base_workflow.rb +4 -2
- data/lib/roast/workflow/each_step.rb +5 -3
- data/lib/roast/workflow/metadata_manager.rb +47 -0
- data/lib/roast/workflow/output_handler.rb +1 -0
- data/lib/roast/workflow/replay_handler.rb +8 -0
- data/lib/roast/workflow/shell_script_step.rb +115 -0
- data/lib/roast/workflow/state_manager.rb +8 -0
- data/lib/roast/workflow/step_executor_coordinator.rb +43 -8
- data/lib/roast/workflow/step_executor_with_reporting.rb +2 -2
- data/lib/roast/workflow/step_loader.rb +55 -9
- data/lib/roast/workflow/workflow_executor.rb +3 -4
- data/lib/roast/workflow/workflow_initializer.rb +24 -13
- data/lib/roast/workflow/workflow_runner.rb +2 -2
- data/lib/roast.rb +1 -0
- data/roast.gemspec +1 -2
- metadata +9 -19
- data/lib/roast/workflow/step_orchestrator.rb +0 -48
@@ -60,10 +60,15 @@ module Roast
|
|
60
60
|
return step
|
61
61
|
end
|
62
62
|
|
63
|
-
# Look for Ruby file in various locations
|
64
|
-
|
65
|
-
if
|
66
|
-
|
63
|
+
# Look for Ruby or shell script file in various locations
|
64
|
+
step_file_info = find_step_file(name.to_s, per_step_path)
|
65
|
+
if step_file_info
|
66
|
+
case step_file_info[:type]
|
67
|
+
when :ruby
|
68
|
+
return load_ruby_step(step_file_info[:path], name.to_s, is_last_step:)
|
69
|
+
when :shell
|
70
|
+
return load_shell_script_step(step_file_info[:path], name.to_s, step_key, is_last_step:)
|
71
|
+
end
|
67
72
|
end
|
68
73
|
|
69
74
|
# Look for step directory
|
@@ -87,28 +92,40 @@ module Roast
|
|
87
92
|
File.expand_path(path, context_path)
|
88
93
|
end
|
89
94
|
|
90
|
-
# Find a Ruby step file in various locations
|
95
|
+
# Find a Ruby or shell script step file in various locations
|
91
96
|
def find_step_file(step_name, per_step_path = nil)
|
92
97
|
# Check in per-step path first
|
93
98
|
if per_step_path
|
94
99
|
resolved_per_step_path = resolve_path(per_step_path)
|
95
100
|
custom_rb_path = File.join(resolved_per_step_path, "#{step_name}.rb")
|
96
|
-
return custom_rb_path if File.file?(custom_rb_path)
|
101
|
+
return { path: custom_rb_path, type: :ruby } if File.file?(custom_rb_path)
|
102
|
+
|
103
|
+
custom_sh_path = File.join(resolved_per_step_path, "#{step_name}.sh")
|
104
|
+
return { path: custom_sh_path, type: :shell } if File.file?(custom_sh_path)
|
97
105
|
end
|
98
106
|
|
99
107
|
# Check in phase-specific directory first
|
100
108
|
if phase != :steps
|
101
109
|
phase_rb_path = File.join(context_path, phase.to_s, "#{step_name}.rb")
|
102
|
-
return phase_rb_path if File.file?(phase_rb_path)
|
110
|
+
return { path: phase_rb_path, type: :ruby } if File.file?(phase_rb_path)
|
111
|
+
|
112
|
+
phase_sh_path = File.join(context_path, phase.to_s, "#{step_name}.sh")
|
113
|
+
return { path: phase_sh_path, type: :shell } if File.file?(phase_sh_path)
|
103
114
|
end
|
104
115
|
|
105
116
|
# Check in context path
|
106
117
|
rb_file_path = File.join(context_path, "#{step_name}.rb")
|
107
|
-
return rb_file_path if File.file?(rb_file_path)
|
118
|
+
return { path: rb_file_path, type: :ruby } if File.file?(rb_file_path)
|
119
|
+
|
120
|
+
sh_file_path = File.join(context_path, "#{step_name}.sh")
|
121
|
+
return { path: sh_file_path, type: :shell } if File.file?(sh_file_path)
|
108
122
|
|
109
123
|
# Check in shared directory
|
110
124
|
shared_rb_path = File.expand_path(File.join(context_path, "..", "shared", "#{step_name}.rb"))
|
111
|
-
return shared_rb_path if File.file?(shared_rb_path)
|
125
|
+
return { path: shared_rb_path, type: :ruby } if File.file?(shared_rb_path)
|
126
|
+
|
127
|
+
shared_sh_path = File.expand_path(File.join(context_path, "..", "shared", "#{step_name}.sh"))
|
128
|
+
return { path: shared_sh_path, type: :shell } if File.file?(shared_sh_path)
|
112
129
|
|
113
130
|
nil
|
114
131
|
end
|
@@ -161,6 +178,23 @@ module Roast
|
|
161
178
|
step
|
162
179
|
end
|
163
180
|
|
181
|
+
# Load a shell script step from a file
|
182
|
+
def load_shell_script_step(file_path, step_name, step_key, is_last_step: nil)
|
183
|
+
$stderr.puts "Loading shell script step: #{file_path}"
|
184
|
+
|
185
|
+
step_name_obj = Roast::ValueObjects::StepName.new(step_name)
|
186
|
+
|
187
|
+
step = ShellScriptStep.new(
|
188
|
+
workflow,
|
189
|
+
script_path: file_path,
|
190
|
+
name: step_name_obj,
|
191
|
+
context_path: File.dirname(file_path),
|
192
|
+
)
|
193
|
+
|
194
|
+
configure_step(step, step_key || step_name, is_last_step:)
|
195
|
+
step
|
196
|
+
end
|
197
|
+
|
164
198
|
# Create and configure a step instance
|
165
199
|
def create_step_instance(step_class, step_name, context_path, options = {})
|
166
200
|
is_last_step = options[:is_last_step]
|
@@ -203,6 +237,18 @@ module Roast
|
|
203
237
|
if step_config.key?("available_tools")
|
204
238
|
step.available_tools = step_config["available_tools"]
|
205
239
|
end
|
240
|
+
|
241
|
+
# Apply any other configuration attributes that the step supports
|
242
|
+
step_config.each do |key, value|
|
243
|
+
# Skip keys we've already handled above
|
244
|
+
next if ["print_response", "json", "params", "coerce_to", "available_tools"].include?(key)
|
245
|
+
|
246
|
+
# Apply configuration if the step has a setter for this attribute
|
247
|
+
setter_method = "#{key}="
|
248
|
+
if step.respond_to?(setter_method)
|
249
|
+
step.public_send(setter_method, value)
|
250
|
+
end
|
251
|
+
end
|
206
252
|
end
|
207
253
|
end
|
208
254
|
end
|
@@ -42,7 +42,6 @@ module Roast
|
|
42
42
|
# @param state_manager [StateManager] Optional custom state manager
|
43
43
|
# @param iteration_executor [IterationExecutor] Optional custom iteration executor
|
44
44
|
# @param conditional_executor [ConditionalExecutor] Optional custom conditional executor
|
45
|
-
# @param step_orchestrator [StepOrchestrator] Optional custom step orchestrator
|
46
45
|
# @param step_executor_coordinator [StepExecutorCoordinator] Optional custom step executor coordinator
|
47
46
|
# @param phase [Symbol] The execution phase - determines where to load steps from
|
48
47
|
# Valid values:
|
@@ -52,7 +51,7 @@ module Roast
|
|
52
51
|
def initialize(workflow, config_hash, context_path,
|
53
52
|
error_handler: nil, step_loader: nil, command_executor: nil,
|
54
53
|
interpolator: nil, state_manager: nil, iteration_executor: nil,
|
55
|
-
conditional_executor: nil,
|
54
|
+
conditional_executor: nil, step_executor_coordinator: nil,
|
56
55
|
phase: :steps)
|
57
56
|
# Create context object to reduce data clump
|
58
57
|
@context = WorkflowContext.new(
|
@@ -69,7 +68,6 @@ module Roast
|
|
69
68
|
@state_manager = state_manager || StateManager.new(workflow, logger: @error_handler, storage_type: workflow.storage_type)
|
70
69
|
@iteration_executor = iteration_executor || IterationExecutor.new(workflow, context_path, @state_manager, config_hash)
|
71
70
|
@conditional_executor = conditional_executor || ConditionalExecutor.new(workflow, context_path, @state_manager, self)
|
72
|
-
@step_orchestrator = step_orchestrator || StepOrchestrator.new(workflow, @step_loader, @state_manager, @error_handler, self)
|
73
71
|
|
74
72
|
# Initialize coordinator with dependencies
|
75
73
|
base_coordinator = step_executor_coordinator || StepExecutorCoordinator.new(
|
@@ -80,7 +78,8 @@ module Roast
|
|
80
78
|
command_executor: @command_executor,
|
81
79
|
iteration_executor: @iteration_executor,
|
82
80
|
conditional_executor: @conditional_executor,
|
83
|
-
|
81
|
+
step_loader: @step_loader,
|
82
|
+
state_manager: @state_manager,
|
84
83
|
error_handler: @error_handler,
|
85
84
|
},
|
86
85
|
)
|
@@ -75,20 +75,28 @@ module Roast
|
|
75
75
|
puts ::CLI::UI.fmt("{{cyan: uri_base: \"https://openrouter.ai/api/v1\",}}")
|
76
76
|
puts ::CLI::UI.fmt("{{cyan: )}}")
|
77
77
|
else
|
78
|
-
puts ::CLI::UI.fmt("{{cyan:require \"faraday\"}}")
|
79
|
-
puts ::CLI::UI.fmt("{{cyan:require \"faraday/retry\"}}")
|
80
78
|
puts
|
81
|
-
puts ::CLI::UI.fmt("{{cyan:
|
79
|
+
puts ::CLI::UI.fmt("{{cyan:faraday_retry = false}}")
|
80
|
+
puts ::CLI::UI.fmt("{{cyan:begin}}")
|
81
|
+
puts ::CLI::UI.fmt("{{cyan: require \"faraday/retry\"}}")
|
82
|
+
puts ::CLI::UI.fmt("{{cyan: faraday_retry = true}}")
|
83
|
+
puts ::CLI::UI.fmt("{{cyan:rescue LoadError}}")
|
84
|
+
puts ::CLI::UI.fmt("{{cyan: # Do nothing}}")
|
85
|
+
puts ::CLI::UI.fmt("{{cyan:end}}")
|
86
|
+
puts
|
87
|
+
puts ::CLI::UI.fmt("{{cyan:Raix.configure do |config|}}")
|
82
88
|
puts ::CLI::UI.fmt("{{cyan: config.openai_client = OpenAI::Client.new(}}")
|
83
89
|
puts ::CLI::UI.fmt("{{cyan: access_token: ENV.fetch(\"OPENAI_API_KEY\"),}}")
|
84
90
|
puts ::CLI::UI.fmt("{{cyan: uri_base: \"https://api.openai.com/v1\",}}")
|
85
91
|
puts ::CLI::UI.fmt("{{cyan: ) do |f|}}")
|
86
|
-
puts ::CLI::UI.fmt("{{cyan:
|
87
|
-
puts ::CLI::UI.fmt("{{cyan:
|
88
|
-
puts ::CLI::UI.fmt("{{cyan:
|
89
|
-
puts ::CLI::UI.fmt("{{cyan:
|
90
|
-
puts ::CLI::UI.fmt("{{cyan:
|
91
|
-
puts ::CLI::UI.fmt("{{cyan:
|
92
|
+
puts ::CLI::UI.fmt("{{cyan: if faraday_retry}}")
|
93
|
+
puts ::CLI::UI.fmt("{{cyan: f.request(:retry, {}}")
|
94
|
+
puts ::CLI::UI.fmt("{{cyan: max: 2,}}")
|
95
|
+
puts ::CLI::UI.fmt("{{cyan: interval: 0.05,}}")
|
96
|
+
puts ::CLI::UI.fmt("{{cyan: interval_randomness: 0.5,}}")
|
97
|
+
puts ::CLI::UI.fmt("{{cyan: backoff_factor: 2,}}")
|
98
|
+
puts ::CLI::UI.fmt("{{cyan: })}}")
|
99
|
+
puts ::CLI::UI.fmt("{{cyan: end}}")
|
92
100
|
puts ::CLI::UI.fmt("{{cyan: end}}")
|
93
101
|
end
|
94
102
|
puts ::CLI::UI.fmt("{{cyan:end}}")
|
@@ -103,15 +111,18 @@ module Roast
|
|
103
111
|
def include_tools
|
104
112
|
return unless @configuration.tools.present? || @configuration.mcp_tools.present?
|
105
113
|
|
106
|
-
|
107
|
-
BaseWorkflow.include(
|
114
|
+
# Only include modules if they haven't been included already to avoid method redefinition warnings
|
115
|
+
BaseWorkflow.include(Raix::FunctionDispatch) unless BaseWorkflow.included_modules.include?(Raix::FunctionDispatch)
|
116
|
+
BaseWorkflow.include(Roast::Helpers::FunctionCachingInterceptor) unless BaseWorkflow.included_modules.include?(Roast::Helpers::FunctionCachingInterceptor)
|
108
117
|
|
109
118
|
if @configuration.tools.present?
|
110
|
-
|
119
|
+
@configuration.tools.map(&:constantize).each do |tool|
|
120
|
+
BaseWorkflow.include(tool) unless BaseWorkflow.included_modules.include?(tool)
|
121
|
+
end
|
111
122
|
end
|
112
123
|
|
113
124
|
if @configuration.mcp_tools.present?
|
114
|
-
BaseWorkflow.include(Raix::MCP)
|
125
|
+
BaseWorkflow.include(Raix::MCP) unless BaseWorkflow.included_modules.include?(Raix::MCP)
|
115
126
|
|
116
127
|
# Create an interpolator for MCP tool configuration
|
117
128
|
# We use Object.new as the context because this interpolation happens during
|
@@ -91,11 +91,11 @@ module Roast
|
|
91
91
|
executor = WorkflowExecutor.new(workflow, @configuration.config_hash, @configuration.context_path)
|
92
92
|
executor.execute_steps(steps)
|
93
93
|
|
94
|
-
$stderr.puts "🔥🔥🔥 ROAST COMPLETE! 🔥🔥🔥"
|
95
|
-
|
96
94
|
# Save outputs
|
97
95
|
@output_handler.save_final_output(workflow)
|
98
96
|
@output_handler.write_results(workflow)
|
97
|
+
|
98
|
+
$stderr.puts "🔥🔥🔥 ROAST COMPLETE! 🔥🔥🔥"
|
99
99
|
end
|
100
100
|
|
101
101
|
private
|
data/lib/roast.rb
CHANGED
data/roast.gemspec
CHANGED
@@ -40,10 +40,9 @@ Gem::Specification.new do |spec|
|
|
40
40
|
spec.add_dependency("cli-kit", "~> 5.0")
|
41
41
|
spec.add_dependency("cli-ui", "2.3.0")
|
42
42
|
spec.add_dependency("diff-lcs", "~> 1.5")
|
43
|
-
spec.add_dependency("faraday-retry")
|
44
43
|
spec.add_dependency("json-schema")
|
45
44
|
spec.add_dependency("open_router", "~> 0.3")
|
46
|
-
spec.add_dependency("raix", "~> 1.0")
|
45
|
+
spec.add_dependency("raix", "~> 1.0.2")
|
47
46
|
spec.add_dependency("ruby-graphviz", "~> 1.2")
|
48
47
|
spec.add_dependency("sqlite3", "~> 2.6")
|
49
48
|
spec.add_dependency("thor", "~> 1.3")
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: roast-ai
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.4.
|
4
|
+
version: 0.4.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Shopify
|
@@ -65,20 +65,6 @@ dependencies:
|
|
65
65
|
- - "~>"
|
66
66
|
- !ruby/object:Gem::Version
|
67
67
|
version: '1.5'
|
68
|
-
- !ruby/object:Gem::Dependency
|
69
|
-
name: faraday-retry
|
70
|
-
requirement: !ruby/object:Gem::Requirement
|
71
|
-
requirements:
|
72
|
-
- - ">="
|
73
|
-
- !ruby/object:Gem::Version
|
74
|
-
version: '0'
|
75
|
-
type: :runtime
|
76
|
-
prerelease: false
|
77
|
-
version_requirements: !ruby/object:Gem::Requirement
|
78
|
-
requirements:
|
79
|
-
- - ">="
|
80
|
-
- !ruby/object:Gem::Version
|
81
|
-
version: '0'
|
82
68
|
- !ruby/object:Gem::Dependency
|
83
69
|
name: json-schema
|
84
70
|
requirement: !ruby/object:Gem::Requirement
|
@@ -113,14 +99,14 @@ dependencies:
|
|
113
99
|
requirements:
|
114
100
|
- - "~>"
|
115
101
|
- !ruby/object:Gem::Version
|
116
|
-
version:
|
102
|
+
version: 1.0.2
|
117
103
|
type: :runtime
|
118
104
|
prerelease: false
|
119
105
|
version_requirements: !ruby/object:Gem::Requirement
|
120
106
|
requirements:
|
121
107
|
- - "~>"
|
122
108
|
- !ruby/object:Gem::Version
|
123
|
-
version:
|
109
|
+
version: 1.0.2
|
124
110
|
- !ruby/object:Gem::Dependency
|
125
111
|
name: ruby-graphviz
|
126
112
|
requirement: !ruby/object:Gem::Requirement
|
@@ -273,6 +259,8 @@ files:
|
|
273
259
|
- examples/cmd/explorer_workflow.png
|
274
260
|
- examples/cmd/explorer_workflow.yml
|
275
261
|
- examples/cmd/smart_tool_selection/prompt.md
|
262
|
+
- examples/coding_agent_with_model.yml
|
263
|
+
- examples/coding_agent_with_retries.yml
|
276
264
|
- examples/conditional/README.md
|
277
265
|
- examples/conditional/check_condition/prompt.md
|
278
266
|
- examples/conditional/simple_workflow.png
|
@@ -456,6 +444,7 @@ files:
|
|
456
444
|
- lib/roast/factories/api_provider_factory.rb
|
457
445
|
- lib/roast/helpers/function_caching_interceptor.rb
|
458
446
|
- lib/roast/helpers/logger.rb
|
447
|
+
- lib/roast/helpers/metadata_access.rb
|
459
448
|
- lib/roast/helpers/minitest_coverage_runner.rb
|
460
449
|
- lib/roast/helpers/path_resolver.rb
|
461
450
|
- lib/roast/helpers/prompt_loader.rb
|
@@ -517,6 +506,7 @@ files:
|
|
517
506
|
- lib/roast/workflow/interpolator.rb
|
518
507
|
- lib/roast/workflow/iteration_executor.rb
|
519
508
|
- lib/roast/workflow/llm_boolean_coercer.rb
|
509
|
+
- lib/roast/workflow/metadata_manager.rb
|
520
510
|
- lib/roast/workflow/output_handler.rb
|
521
511
|
- lib/roast/workflow/output_manager.rb
|
522
512
|
- lib/roast/workflow/parallel_executor.rb
|
@@ -525,6 +515,7 @@ files:
|
|
525
515
|
- lib/roast/workflow/replay_handler.rb
|
526
516
|
- lib/roast/workflow/resource_resolver.rb
|
527
517
|
- lib/roast/workflow/session_manager.rb
|
518
|
+
- lib/roast/workflow/shell_script_step.rb
|
528
519
|
- lib/roast/workflow/sqlite_state_repository.rb
|
529
520
|
- lib/roast/workflow/state_manager.rb
|
530
521
|
- lib/roast/workflow/state_repository.rb
|
@@ -542,7 +533,6 @@ files:
|
|
542
533
|
- lib/roast/workflow/step_finder.rb
|
543
534
|
- lib/roast/workflow/step_loader.rb
|
544
535
|
- lib/roast/workflow/step_name_extractor.rb
|
545
|
-
- lib/roast/workflow/step_orchestrator.rb
|
546
536
|
- lib/roast/workflow/step_runner.rb
|
547
537
|
- lib/roast/workflow/step_type_resolver.rb
|
548
538
|
- lib/roast/workflow/validation_command.rb
|
@@ -585,7 +575,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
585
575
|
- !ruby/object:Gem::Version
|
586
576
|
version: '0'
|
587
577
|
requirements: []
|
588
|
-
rubygems_version: 3.
|
578
|
+
rubygems_version: 3.7.1
|
589
579
|
specification_version: 4
|
590
580
|
summary: A framework for executing structured AI workflows in Ruby
|
591
581
|
test_files: []
|
@@ -1,48 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Roast
|
4
|
-
module Workflow
|
5
|
-
# Handles the orchestration of step execution, managing the flow and control
|
6
|
-
# of individual steps without knowing how to execute them
|
7
|
-
#
|
8
|
-
# This class is specifically for executing CUSTOM steps defined in the workflow's
|
9
|
-
# step directory (e.g., steps/*.rb files). It loads and executes Ruby step files
|
10
|
-
# that define a `call` method.
|
11
|
-
#
|
12
|
-
# The primary method execute_step is used by StepExecutorCoordinator for
|
13
|
-
# executing custom Ruby steps.
|
14
|
-
#
|
15
|
-
# TODO: Consider renaming this class to CustomStepOrchestrator to clarify its purpose
|
16
|
-
class StepOrchestrator
|
17
|
-
def initialize(workflow, step_loader, state_manager, error_handler, workflow_executor)
|
18
|
-
@workflow = workflow
|
19
|
-
@step_loader = step_loader
|
20
|
-
@state_manager = state_manager
|
21
|
-
@error_handler = error_handler
|
22
|
-
@workflow_executor = workflow_executor
|
23
|
-
end
|
24
|
-
|
25
|
-
def execute_step(name, exit_on_error: true, step_key: nil, **options)
|
26
|
-
resource_type = @workflow.respond_to?(:resource) ? @workflow.resource&.type : nil
|
27
|
-
|
28
|
-
@error_handler.with_error_handling(name, resource_type: resource_type) do
|
29
|
-
$stderr.puts "Executing: #{name} (Resource type: #{resource_type || "unknown"})"
|
30
|
-
|
31
|
-
# Use step_key for loading if provided, otherwise use name
|
32
|
-
load_key = step_key || name
|
33
|
-
is_last_step = options[:is_last_step]
|
34
|
-
step_object = @step_loader.load(name, step_key: load_key, is_last_step:, **options)
|
35
|
-
step_result = step_object.call
|
36
|
-
|
37
|
-
# Store result in workflow output
|
38
|
-
@workflow.output[name] = step_result
|
39
|
-
|
40
|
-
# Save state after each step
|
41
|
-
@state_manager.save_state(name, step_result)
|
42
|
-
|
43
|
-
step_result
|
44
|
-
end
|
45
|
-
end
|
46
|
-
end
|
47
|
-
end
|
48
|
-
end
|