roast-ai 0.1.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.
Files changed (57) hide show
  1. checksums.yaml +7 -0
  2. data/.claude/settings.json +12 -0
  3. data/.github/workflows/ci.yaml +29 -0
  4. data/.github/workflows/cla.yml +22 -0
  5. data/.gitignore +13 -0
  6. data/.rspec +1 -0
  7. data/.rubocop.yml +12 -0
  8. data/.ruby-version +1 -0
  9. data/CHANGELOG.md +0 -0
  10. data/CLAUDE.md +31 -0
  11. data/CODE_OF_CONDUCT.md +133 -0
  12. data/CONTRIBUTING.md +35 -0
  13. data/Gemfile +19 -0
  14. data/Gemfile.lock +194 -0
  15. data/LICENSE.md +21 -0
  16. data/README.md +27 -0
  17. data/Rakefile +24 -0
  18. data/bin/console +11 -0
  19. data/examples/grading/analyze_coverage/prompt.md +52 -0
  20. data/examples/grading/calculate_final_grade.rb +67 -0
  21. data/examples/grading/format_result.rb +48 -0
  22. data/examples/grading/generate_grades/prompt.md +105 -0
  23. data/examples/grading/generate_recommendations/output.txt +17 -0
  24. data/examples/grading/generate_recommendations/prompt.md +60 -0
  25. data/examples/grading/run_coverage.rb +47 -0
  26. data/examples/grading/verify_mocks_and_stubs/prompt.md +12 -0
  27. data/examples/grading/verify_test_helpers/prompt.md +53 -0
  28. data/examples/grading/workflow.md +8 -0
  29. data/examples/grading/workflow.rb.md +6 -0
  30. data/examples/grading/workflow.ts+tsx.md +6 -0
  31. data/examples/grading/workflow.yml +46 -0
  32. data/exe/roast +17 -0
  33. data/lib/roast/helpers/function_caching_interceptor.rb +27 -0
  34. data/lib/roast/helpers/logger.rb +104 -0
  35. data/lib/roast/helpers/minitest_coverage_runner.rb +244 -0
  36. data/lib/roast/helpers/path_resolver.rb +148 -0
  37. data/lib/roast/helpers/prompt_loader.rb +97 -0
  38. data/lib/roast/helpers.rb +12 -0
  39. data/lib/roast/tools/cmd.rb +72 -0
  40. data/lib/roast/tools/grep.rb +43 -0
  41. data/lib/roast/tools/read_file.rb +49 -0
  42. data/lib/roast/tools/search_file.rb +51 -0
  43. data/lib/roast/tools/write_file.rb +60 -0
  44. data/lib/roast/tools.rb +50 -0
  45. data/lib/roast/version.rb +5 -0
  46. data/lib/roast/workflow/base_step.rb +94 -0
  47. data/lib/roast/workflow/base_workflow.rb +79 -0
  48. data/lib/roast/workflow/configuration.rb +117 -0
  49. data/lib/roast/workflow/configuration_parser.rb +92 -0
  50. data/lib/roast/workflow/validator.rb +37 -0
  51. data/lib/roast/workflow/workflow_executor.rb +119 -0
  52. data/lib/roast/workflow.rb +13 -0
  53. data/lib/roast.rb +40 -0
  54. data/roast.gemspec +44 -0
  55. data/schema/workflow.json +92 -0
  56. data/shipit.rubygems.yml +0 -0
  57. metadata +171 -0
@@ -0,0 +1,119 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Roast
4
+ module Workflow
5
+ # Handles the execution of workflow steps, including orchestration and threading
6
+ class WorkflowExecutor
7
+ DEFAULT_MODEL = "anthropic:claude-3-7-sonnet"
8
+
9
+ attr_reader :workflow, :config_hash, :context_path
10
+
11
+ def initialize(workflow, config_hash, context_path)
12
+ @workflow = workflow
13
+ @config_hash = config_hash
14
+ @context_path = context_path
15
+ end
16
+
17
+ def execute_steps(steps)
18
+ steps.each do |step|
19
+ case step
20
+ when Hash
21
+ execute_hash_step(step)
22
+ when Array
23
+ execute_parallel_steps(step)
24
+ when String
25
+ execute_string_step(step)
26
+ else
27
+ raise "Unknown step type: #{step.inspect}"
28
+ end
29
+ end
30
+ end
31
+
32
+ def execute_step(name)
33
+ $stderr.puts "Executing: #{name}"
34
+ return strip_and_execute(name) if name.starts_with?("%") || name.starts_with?("$(")
35
+
36
+ return glob(name) if name.include?("*")
37
+
38
+ step_object = find_and_load_step(name)
39
+ result = step_object.call
40
+
41
+ workflow.output[name] = result
42
+ result
43
+ end
44
+
45
+ private
46
+
47
+ def execute_hash_step(step)
48
+ # execute a command and store the output in a variable
49
+ name, command = step.to_a.flatten
50
+ if command.is_a?(Hash)
51
+ execute_steps([command])
52
+ else
53
+ workflow.output[name] = execute_step(command)
54
+ end
55
+ end
56
+
57
+ def execute_parallel_steps(steps)
58
+ # run steps in parallel, don't proceed until all are done
59
+ steps.map do |sub_step|
60
+ Thread.new { execute_steps([sub_step]) }
61
+ end.each(&:join)
62
+ end
63
+
64
+ def execute_string_step(step)
65
+ execute_step(step)
66
+ end
67
+
68
+ def find_and_load_step(step_name)
69
+ # First check for a ruby file with the step name
70
+ rb_file_path = File.join(context_path, "#{step_name}.rb")
71
+ if File.file?(rb_file_path)
72
+ return load_ruby_step(rb_file_path, step_name)
73
+ end
74
+
75
+ # Check in shared directory for ruby file
76
+ shared_rb_path = File.expand_path(File.join(context_path, "..", "shared", "#{step_name}.rb"))
77
+ if File.file?(shared_rb_path)
78
+ return load_ruby_step(shared_rb_path, step_name, File.dirname(shared_rb_path))
79
+ end
80
+
81
+ # Continue with existing directory check logic
82
+ step_path = File.join(context_path, step_name)
83
+ step_path = File.expand_path(File.join(context_path, "..", "shared", step_name)) unless File.directory?(step_path)
84
+ raise "Step directory or file not found: #{step_path}" unless File.directory?(step_path)
85
+
86
+ setup_step(Roast::Workflow::BaseStep, step_name, step_path)
87
+ end
88
+
89
+ def glob(name)
90
+ Dir.glob(name).join("\n")
91
+ end
92
+
93
+ def load_ruby_step(file_path, step_name, context_path = File.dirname(file_path))
94
+ $stderr.puts "Requiring step file: #{file_path}"
95
+ require file_path
96
+ step_class = step_name.classify.constantize
97
+ setup_step(step_class, step_name, context_path)
98
+ end
99
+
100
+ def setup_step(step_class, step_name, context_path)
101
+ step_class.new(workflow, name: step_name, context_path: context_path).tap do |step|
102
+ step_config = config_hash[step_name]
103
+ if step_config.present?
104
+ step.model = step_config["model"] || DEFAULT_MODEL
105
+ step.print_response = step_config["print_response"] if step_config["print_response"].present?
106
+ step.loop = step_config["loop"] if step_config["loop"].present?
107
+ step.json = step_config["json"] if step_config["json"].present?
108
+ step.params = step_config["params"] if step_config["params"].present?
109
+ end
110
+ end
111
+ end
112
+
113
+ def strip_and_execute(step)
114
+ command = step.gsub("%", "")
115
+ %x(#{command})
116
+ end
117
+ end
118
+ end
119
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "roast/workflow/base_step"
4
+ require "roast/workflow/base_workflow"
5
+ require "roast/workflow/configuration"
6
+ require "roast/workflow/workflow_executor"
7
+ require "roast/workflow/configuration_parser"
8
+ require "roast/workflow/validator"
9
+
10
+ module Roast
11
+ module Workflow
12
+ end
13
+ end
data/lib/roast.rb ADDED
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "raix"
4
+ require "thor"
5
+ require "roast/version"
6
+ require "roast/tools"
7
+ require "roast/helpers"
8
+ require "roast/workflow"
9
+
10
+ module Roast
11
+ ROOT = File.expand_path("../..", __FILE__)
12
+
13
+ class CLI < Thor
14
+ desc "execute [WORKFLOW_CONFIGURATION_FILE] [FILES...]", "Run a configured workflow"
15
+ option :concise, type: :boolean, aliases: "-c", desc: "Optional flag for use in output templates"
16
+ option :output, type: :string, aliases: "-o", desc: "Save results to a file"
17
+ option :verbose, type: :boolean, aliases: "-v", desc: "Show output from all steps as they are executed"
18
+ option :target, type: :string, aliases: "-t", desc: "Override target files. Can be file path, glob pattern, or $(shell command)"
19
+ option :subject, type: :string, aliases: "-s", desc: "Subject file to analyze"
20
+ def execute(*paths)
21
+ raise Thor::Error, "Workflow configuration file is required" if paths.empty?
22
+
23
+ workflow_path, *files = paths
24
+ expanded_workflow_path = File.expand_path(workflow_path)
25
+ raise Thor::Error, "Expected a Roast workflow configuration file, got directory: #{expanded_workflow_path}" if File.directory?(expanded_workflow_path)
26
+
27
+ if options[:subject] && !File.exist?(options[:subject])
28
+ raise Thor::Error, "Subject file does not exist: #{options[:subject]}"
29
+ end
30
+
31
+ Roast::Workflow::ConfigurationParser.new(expanded_workflow_path, files, options.transform_keys(&:to_sym)).begin!
32
+ end
33
+
34
+ class << self
35
+ def exit_on_failure?
36
+ true
37
+ end
38
+ end
39
+ end
40
+ end
data/roast.gemspec ADDED
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ lib = File.expand_path("../lib", __FILE__)
4
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
+ require "roast/version"
6
+
7
+ Gem::Specification.new do |spec|
8
+ spec.name = "roast-ai"
9
+ spec.version = Roast::VERSION
10
+ spec.authors = ["Shopify"]
11
+ spec.email = ["opensource@shopify.com"]
12
+
13
+ spec.summary = "A framework for executing structured AI workflows in Ruby"
14
+ spec.description = "Roast is a Ruby library for running structured AI workflows along with many building blocks for creating and executing them"
15
+ spec.homepage = "https://github.com/Shopify/roast"
16
+ spec.license = "MIT"
17
+
18
+ # Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
19
+ # to allow pushing to a single host or delete this section to allow pushing to any host.
20
+ if spec.respond_to?(:metadata)
21
+ spec.metadata["allowed_push_host"] = "https://rubygems.org"
22
+ spec.metadata["homepage_uri"] = spec.homepage
23
+ spec.metadata["source_code_uri"] = "https://github.com/Shopify/roast"
24
+ spec.metadata["changelog_uri"] = "https://github.com/Shopify/roast/blob/main/CHANGELOG.md"
25
+ else
26
+ raise "RubyGems 2.0 or newer is required to protect against " \
27
+ "public gem pushes."
28
+ end
29
+
30
+ # Specify which files should be added to the gem when it is released.
31
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
32
+ spec.files = Dir.chdir(File.expand_path("..", __FILE__)) do
33
+ %x(git ls-files -z).split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
34
+ end
35
+ spec.bindir = "exe"
36
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
37
+ spec.require_paths = ["lib"]
38
+
39
+ spec.add_dependency("activesupport", "~> 8.0")
40
+ spec.add_dependency("faraday-retry")
41
+ spec.add_dependency("json-schema")
42
+ spec.add_dependency("raix", "0.8.3")
43
+ spec.add_dependency("thor", "~> 1.3")
44
+ end
@@ -0,0 +1,92 @@
1
+ {
2
+ "type": "object",
3
+ "required": ["name", "tools", "steps"],
4
+ "properties": {
5
+ "name": {
6
+ "type": "string"
7
+ },
8
+ "tools": {
9
+ "type": "array",
10
+ "items": {
11
+ "type": "string"
12
+ }
13
+ },
14
+ "inputs": {
15
+ "type": "array",
16
+ "items": {
17
+ "oneOf": [
18
+ {
19
+ "type": "string"
20
+ },
21
+ {
22
+ "type": "object",
23
+ "additionalProperties": {
24
+ "type": "string"
25
+ },
26
+ "minProperties": 1,
27
+ "maxProperties": 1
28
+ }
29
+ ]
30
+ }
31
+ },
32
+ "steps": {
33
+ "type": "array",
34
+ "items": {
35
+ "oneOf": [
36
+ {
37
+ "type": "string"
38
+ },
39
+ {
40
+ "type": "array",
41
+ "items": {
42
+ "oneOf": [
43
+ {
44
+ "type": "string"
45
+ },
46
+ {
47
+ "type": "object",
48
+ "properties": {
49
+ "steps": {
50
+ "type": "array",
51
+ "items": {
52
+ "type": "string"
53
+ }
54
+ }
55
+ },
56
+ "required": ["steps"]
57
+ }
58
+ ]
59
+ }
60
+ },
61
+ {
62
+ "type": "object",
63
+ "properties": {
64
+ "proceed?": {
65
+ "type": "object",
66
+ "properties": {
67
+ "true": {
68
+ "$ref": "#/properties/steps"
69
+ },
70
+ "false": {
71
+ "$ref": "#/properties/steps"
72
+ }
73
+ },
74
+ "required": ["true", "false"]
75
+ }
76
+ },
77
+ "required": ["proceed?"]
78
+ }
79
+ ]
80
+ }
81
+ },
82
+ "proceed?": {
83
+ "type": "object",
84
+ "properties": {
85
+ "label": {
86
+ "type": "string"
87
+ }
88
+ },
89
+ "required": ["label"]
90
+ }
91
+ }
92
+ }
File without changes
metadata ADDED
@@ -0,0 +1,171 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: roast-ai
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Shopify
8
+ bindir: exe
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: activesupport
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - "~>"
17
+ - !ruby/object:Gem::Version
18
+ version: '8.0'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - "~>"
24
+ - !ruby/object:Gem::Version
25
+ version: '8.0'
26
+ - !ruby/object:Gem::Dependency
27
+ name: faraday-retry
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: '0'
40
+ - !ruby/object:Gem::Dependency
41
+ name: json-schema
42
+ requirement: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: '0'
47
+ type: :runtime
48
+ prerelease: false
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ - !ruby/object:Gem::Dependency
55
+ name: raix
56
+ requirement: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - '='
59
+ - !ruby/object:Gem::Version
60
+ version: 0.8.3
61
+ type: :runtime
62
+ prerelease: false
63
+ version_requirements: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - '='
66
+ - !ruby/object:Gem::Version
67
+ version: 0.8.3
68
+ - !ruby/object:Gem::Dependency
69
+ name: thor
70
+ requirement: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - "~>"
73
+ - !ruby/object:Gem::Version
74
+ version: '1.3'
75
+ type: :runtime
76
+ prerelease: false
77
+ version_requirements: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - "~>"
80
+ - !ruby/object:Gem::Version
81
+ version: '1.3'
82
+ description: Roast is a Ruby library for running structured AI workflows along with
83
+ many building blocks for creating and executing them
84
+ email:
85
+ - opensource@shopify.com
86
+ executables:
87
+ - roast
88
+ extensions: []
89
+ extra_rdoc_files: []
90
+ files:
91
+ - ".claude/settings.json"
92
+ - ".github/workflows/ci.yaml"
93
+ - ".github/workflows/cla.yml"
94
+ - ".gitignore"
95
+ - ".rspec"
96
+ - ".rubocop.yml"
97
+ - ".ruby-version"
98
+ - CHANGELOG.md
99
+ - CLAUDE.md
100
+ - CODE_OF_CONDUCT.md
101
+ - CONTRIBUTING.md
102
+ - Gemfile
103
+ - Gemfile.lock
104
+ - LICENSE.md
105
+ - README.md
106
+ - Rakefile
107
+ - bin/console
108
+ - examples/grading/analyze_coverage/prompt.md
109
+ - examples/grading/calculate_final_grade.rb
110
+ - examples/grading/format_result.rb
111
+ - examples/grading/generate_grades/prompt.md
112
+ - examples/grading/generate_recommendations/output.txt
113
+ - examples/grading/generate_recommendations/prompt.md
114
+ - examples/grading/run_coverage.rb
115
+ - examples/grading/verify_mocks_and_stubs/prompt.md
116
+ - examples/grading/verify_test_helpers/prompt.md
117
+ - examples/grading/workflow.md
118
+ - examples/grading/workflow.rb.md
119
+ - examples/grading/workflow.ts+tsx.md
120
+ - examples/grading/workflow.yml
121
+ - exe/roast
122
+ - lib/roast.rb
123
+ - lib/roast/helpers.rb
124
+ - lib/roast/helpers/function_caching_interceptor.rb
125
+ - lib/roast/helpers/logger.rb
126
+ - lib/roast/helpers/minitest_coverage_runner.rb
127
+ - lib/roast/helpers/path_resolver.rb
128
+ - lib/roast/helpers/prompt_loader.rb
129
+ - lib/roast/tools.rb
130
+ - lib/roast/tools/cmd.rb
131
+ - lib/roast/tools/grep.rb
132
+ - lib/roast/tools/read_file.rb
133
+ - lib/roast/tools/search_file.rb
134
+ - lib/roast/tools/write_file.rb
135
+ - lib/roast/version.rb
136
+ - lib/roast/workflow.rb
137
+ - lib/roast/workflow/base_step.rb
138
+ - lib/roast/workflow/base_workflow.rb
139
+ - lib/roast/workflow/configuration.rb
140
+ - lib/roast/workflow/configuration_parser.rb
141
+ - lib/roast/workflow/validator.rb
142
+ - lib/roast/workflow/workflow_executor.rb
143
+ - roast.gemspec
144
+ - schema/workflow.json
145
+ - shipit.rubygems.yml
146
+ homepage: https://github.com/Shopify/roast
147
+ licenses:
148
+ - MIT
149
+ metadata:
150
+ allowed_push_host: https://rubygems.org
151
+ homepage_uri: https://github.com/Shopify/roast
152
+ source_code_uri: https://github.com/Shopify/roast
153
+ changelog_uri: https://github.com/Shopify/roast/blob/main/CHANGELOG.md
154
+ rdoc_options: []
155
+ require_paths:
156
+ - lib
157
+ required_ruby_version: !ruby/object:Gem::Requirement
158
+ requirements:
159
+ - - ">="
160
+ - !ruby/object:Gem::Version
161
+ version: '0'
162
+ required_rubygems_version: !ruby/object:Gem::Requirement
163
+ requirements:
164
+ - - ">="
165
+ - !ruby/object:Gem::Version
166
+ version: '0'
167
+ requirements: []
168
+ rubygems_version: 3.6.8
169
+ specification_version: 4
170
+ summary: A framework for executing structured AI workflows in Ruby
171
+ test_files: []