roast-ai 0.4.10 → 0.5.1
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/.claude/commands/docs/write-comments.md +36 -0
- data/.github/CODEOWNERS +1 -1
- data/.github/workflows/ci.yaml +10 -6
- data/.gitignore +0 -1
- data/.rubocop.yml +7 -1
- data/.ruby-version +1 -1
- data/CLAUDE.md +2 -2
- data/CONTRIBUTING.md +2 -0
- data/Gemfile +19 -18
- data/Gemfile.lock +35 -58
- data/README.md +118 -1432
- data/README_LEGACY.md +1464 -0
- data/Rakefile +39 -4
- data/dev.yml +29 -0
- data/dsl/agent_sessions.rb +20 -0
- data/dsl/async_cogs.rb +49 -0
- data/dsl/async_cogs_complex.rb +67 -0
- data/dsl/call.rb +44 -0
- data/dsl/collect_from.rb +72 -0
- data/dsl/json_output.rb +28 -0
- data/dsl/map.rb +55 -0
- data/dsl/map_reduce.rb +37 -0
- data/dsl/map_with_index.rb +49 -0
- data/dsl/next_break.rb +45 -0
- data/dsl/next_break_parallel.rb +44 -0
- data/dsl/outputs.rb +39 -0
- data/dsl/outputs_bang.rb +36 -0
- data/dsl/parallel_map.rb +37 -0
- data/dsl/prompts/simple_prompt.md.erb +3 -0
- data/dsl/prototype.rb +5 -7
- data/dsl/repeat_loop_results.rb +53 -0
- data/dsl/ruby_cog.rb +72 -0
- data/dsl/simple_agent.rb +18 -0
- data/dsl/simple_chat.rb +15 -1
- data/dsl/simple_repeat.rb +29 -0
- data/dsl/skip.rb +36 -0
- data/dsl/step_communication.rb +2 -3
- data/dsl/targets_and_params.rb +57 -0
- data/dsl/temperature.rb +17 -0
- data/dsl/temporary_directory.rb +22 -0
- data/dsl/tutorial/01_your_first_workflow/README.md +179 -0
- data/dsl/tutorial/01_your_first_workflow/configured_chat.rb +33 -0
- data/dsl/tutorial/01_your_first_workflow/hello.rb +23 -0
- data/dsl/tutorial/02_chaining_cogs/README.md +310 -0
- data/dsl/tutorial/02_chaining_cogs/code_review.rb +104 -0
- data/dsl/tutorial/02_chaining_cogs/session_resumption.rb +92 -0
- data/dsl/tutorial/02_chaining_cogs/simple_chain.rb +84 -0
- data/dsl/tutorial/03_targets_and_params/README.md +230 -0
- data/dsl/tutorial/03_targets_and_params/multiple_targets.rb +65 -0
- data/dsl/tutorial/03_targets_and_params/single_target.rb +65 -0
- data/dsl/tutorial/04_configuration_options/README.md +209 -0
- data/dsl/tutorial/04_configuration_options/control_display_and_temperature.rb +104 -0
- data/dsl/tutorial/04_configuration_options/simple_config.rb +68 -0
- data/dsl/tutorial/05_control_flow/README.md +156 -0
- data/dsl/tutorial/05_control_flow/conditional_execution.rb +62 -0
- data/dsl/tutorial/05_control_flow/handling_failures.rb +77 -0
- data/dsl/tutorial/06_reusable_scopes/README.md +172 -0
- data/dsl/tutorial/06_reusable_scopes/accessing_scope_outputs.rb +126 -0
- data/dsl/tutorial/06_reusable_scopes/basic_scope.rb +63 -0
- data/dsl/tutorial/06_reusable_scopes/parameterized_scope.rb +78 -0
- data/dsl/tutorial/07_processing_collections/README.md +152 -0
- data/dsl/tutorial/07_processing_collections/basic_map.rb +70 -0
- data/dsl/tutorial/07_processing_collections/parallel_map.rb +74 -0
- data/dsl/tutorial/08_iterative_workflows/README.md +231 -0
- data/dsl/tutorial/08_iterative_workflows/basic_repeat.rb +57 -0
- data/dsl/tutorial/08_iterative_workflows/conditional_break.rb +57 -0
- data/dsl/tutorial/09_async_cogs/README.md +197 -0
- data/dsl/tutorial/09_async_cogs/basic_async.rb +38 -0
- data/dsl/tutorial/README.md +222 -0
- data/dsl/working_directory.rb +16 -0
- data/exe/roast +1 -1
- data/internal/documentation/architectural-notes.md +115 -0
- data/internal/documentation/doc-comments-external.md +686 -0
- data/internal/documentation/doc-comments-internal.md +342 -0
- data/internal/documentation/doc-comments.md +211 -0
- data/lib/roast/dsl/cog/config.rb +274 -3
- data/lib/roast/dsl/cog/input.rb +53 -10
- data/lib/roast/dsl/cog/output.rb +297 -8
- data/lib/roast/dsl/cog/registry.rb +35 -3
- data/lib/roast/dsl/cog/stack.rb +1 -1
- data/lib/roast/dsl/cog/store.rb +5 -5
- data/lib/roast/dsl/cog.rb +70 -14
- data/lib/roast/dsl/cog_input_context.rb +36 -1
- data/lib/roast/dsl/cog_input_manager.rb +116 -7
- data/lib/roast/dsl/cogs/agent/config.rb +465 -0
- data/lib/roast/dsl/cogs/agent/input.rb +81 -0
- data/lib/roast/dsl/cogs/agent/output.rb +59 -0
- data/lib/roast/dsl/cogs/agent/provider.rb +51 -0
- data/lib/roast/dsl/cogs/agent/providers/claude/claude_invocation.rb +185 -0
- data/lib/roast/dsl/cogs/agent/providers/claude/message.rb +73 -0
- data/lib/roast/dsl/cogs/agent/providers/claude/messages/assistant_message.rb +36 -0
- data/lib/roast/dsl/cogs/agent/providers/claude/messages/result_message.rb +61 -0
- data/lib/roast/dsl/cogs/agent/providers/claude/messages/system_message.rb +47 -0
- data/lib/roast/dsl/cogs/agent/providers/claude/messages/text_message.rb +36 -0
- data/lib/roast/dsl/cogs/agent/providers/claude/messages/tool_result_message.rb +47 -0
- data/lib/roast/dsl/cogs/agent/providers/claude/messages/tool_use_message.rb +46 -0
- data/lib/roast/dsl/cogs/agent/providers/claude/messages/unknown_message.rb +27 -0
- data/lib/roast/dsl/cogs/agent/providers/claude/messages/user_message.rb +37 -0
- data/lib/roast/dsl/cogs/agent/providers/claude/tool_result.rb +51 -0
- data/lib/roast/dsl/cogs/agent/providers/claude/tool_use.rb +48 -0
- data/lib/roast/dsl/cogs/agent/providers/claude.rb +31 -0
- data/lib/roast/dsl/cogs/agent/stats.rb +92 -0
- data/lib/roast/dsl/cogs/agent/usage.rb +62 -0
- data/lib/roast/dsl/cogs/agent.rb +75 -0
- data/lib/roast/dsl/cogs/chat/config.rb +453 -0
- data/lib/roast/dsl/cogs/chat/input.rb +92 -0
- data/lib/roast/dsl/cogs/chat/output.rb +64 -0
- data/lib/roast/dsl/cogs/chat/session.rb +68 -0
- data/lib/roast/dsl/cogs/chat.rb +59 -56
- data/lib/roast/dsl/cogs/cmd.rb +251 -61
- data/lib/roast/dsl/cogs/ruby.rb +171 -0
- data/lib/roast/dsl/command_runner.rb +191 -0
- data/lib/roast/dsl/config_manager.rb +58 -11
- data/lib/roast/dsl/control_flow.rb +41 -0
- data/lib/roast/dsl/execution_manager.rb +162 -32
- data/lib/roast/dsl/nil_assertions.rb +23 -0
- data/lib/roast/dsl/system_cog/params.rb +32 -0
- data/lib/roast/dsl/system_cog.rb +36 -0
- data/lib/roast/dsl/system_cogs/call.rb +163 -0
- data/lib/roast/dsl/system_cogs/map.rb +454 -0
- data/lib/roast/dsl/system_cogs/repeat.rb +242 -0
- data/lib/roast/dsl/workflow.rb +26 -16
- data/lib/roast/dsl/workflow_context.rb +20 -0
- data/lib/roast/dsl/workflow_params.rb +24 -0
- data/lib/roast/helpers/minitest_coverage_runner.rb +1 -1
- data/lib/roast/sorbet_runtime_stub.rb +154 -0
- data/lib/roast/tools/apply_diff.rb +1 -3
- data/lib/roast/tools/cmd.rb +4 -3
- data/lib/roast/tools/read_file.rb +1 -1
- data/lib/roast/tools/update_files.rb +1 -1
- data/lib/roast/tools/write_file.rb +1 -1
- data/lib/roast/version.rb +1 -1
- data/lib/roast/workflow/base_workflow.rb +4 -0
- data/lib/roast/workflow/step_loader.rb +14 -2
- data/lib/roast-ai.rb +4 -0
- data/lib/roast.rb +58 -21
- data/{roast.gemspec → roast-ai.gemspec} +9 -13
- data/sorbet/rbi/gems/async@2.34.0.rbi +1577 -0
- data/sorbet/rbi/gems/cli-kit@5.2.0.rbi +2063 -0
- data/sorbet/rbi/gems/{cli-ui@2.3.0.rbi → cli-ui@2.7.0-6bdefd1d06305e5d6ae312ac76f9c88f88658dda.rbi} +1418 -1013
- data/sorbet/rbi/gems/console@1.34.2.rbi +1193 -0
- data/sorbet/rbi/gems/fiber-annotation@0.2.0.rbi +50 -0
- data/sorbet/rbi/gems/fiber-local@1.1.0.rbi +35 -0
- data/sorbet/rbi/gems/fiber-storage@1.0.1.rbi +41 -0
- data/sorbet/rbi/gems/io-event@1.14.0.rbi +724 -0
- data/sorbet/rbi/gems/metrics@0.15.0.rbi +9 -0
- data/sorbet/rbi/gems/traces@0.18.2.rbi +9 -0
- data/sorbet/rbi/shims/lib/roast/dsl/cog_input_context.rbi +1185 -5
- data/sorbet/rbi/shims/lib/roast/dsl/config_context.rbi +311 -5
- data/sorbet/rbi/shims/lib/roast/dsl/execution_context.rbi +486 -5
- data/sorbet/tapioca/config.yml +6 -0
- data/sorbet/tapioca/require.rb +2 -0
- metadata +157 -30
- data/dsl/less_simple.rb +0 -112
- data/dsl/scoped_executors.rb +0 -28
- data/dsl/simple.rb +0 -8
- data/lib/roast/dsl/cogs/execute.rb +0 -46
- data/lib/roast/dsl/cogs/graph.rb +0 -53
- data/sorbet/rbi/gems/cgi@0.5.0.rbi +0 -2961
- data/sorbet/rbi/gems/claude_swarm@0.1.19.rbi +0 -568
- data/sorbet/rbi/gems/cli-kit@5.0.1.rbi +0 -1991
- data/sorbet/rbi/gems/dry-configurable@1.3.0.rbi +0 -672
- data/sorbet/rbi/gems/dry-core@1.1.0.rbi +0 -1894
- data/sorbet/rbi/gems/dry-inflector@1.2.0.rbi +0 -659
- data/sorbet/rbi/gems/dry-initializer@3.2.0.rbi +0 -781
- data/sorbet/rbi/gems/dry-logic@1.6.0.rbi +0 -1127
- data/sorbet/rbi/gems/dry-schema@1.14.1.rbi +0 -3727
- data/sorbet/rbi/gems/dry-types@1.8.3.rbi +0 -3969
- data/sorbet/rbi/gems/fast-mcp-annotations@1.5.3.rbi +0 -1588
- data/sorbet/rbi/gems/mime-types-data@3.2025.0617.rbi +0 -136
- data/sorbet/rbi/gems/mime-types@3.7.0.rbi +0 -1342
- data/sorbet/rbi/gems/rack@2.2.19.rbi +0 -5676
- data/sorbet/rbi/gems/yard-sorbet@0.9.0.rbi +0 -435
- data/sorbet/rbi/gems/yard@0.9.37.rbi +0 -18492
data/dsl/parallel_map.rb
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# typed: true
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
#: self as Roast::DSL::Workflow
|
|
5
|
+
|
|
6
|
+
config do
|
|
7
|
+
cmd { display! }
|
|
8
|
+
map(:words) do
|
|
9
|
+
# By default, maximum parallelism is 1 and map executions will run synchronously
|
|
10
|
+
# Calling `parallel` with a larger value will allow multiple map items to be run in parallel, up to the limit given
|
|
11
|
+
# Calling `parallel!` or `parallel(0)` will allow unlimited parallelism, processing all map items in parallel
|
|
12
|
+
parallel 3
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
execute(:capitalize_a_word) do
|
|
17
|
+
cmd(:to_upper) do |_, word, index|
|
|
18
|
+
sleep(0.1) if index == 3 # "three" will be slow --> finishing second last
|
|
19
|
+
sleep(0.2) if index == 1 # "one" will be slowest --> finishing last
|
|
20
|
+
["sh", "-c", "echo \"#{word}\" | tr '[:lower:]' '[:upper:]'"]
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
execute do
|
|
25
|
+
# Call a subroutine with `map`
|
|
26
|
+
map(:words, run: :capitalize_a_word) do |my|
|
|
27
|
+
my.items = ["one", "two", "three", "four", "five", "six"]
|
|
28
|
+
my.initial_index = 1 # for convenience, just because our items begin with "one"
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
cmd do |my|
|
|
32
|
+
my.command = "echo"
|
|
33
|
+
# Regardless of the order in which the items were processed by a parallel map,
|
|
34
|
+
# their results will always be provided to `collect` and `reduce` in the order in which they were given.
|
|
35
|
+
my.args << collect(map!(:words)) { cmd!(:to_upper).text }.join(", ")
|
|
36
|
+
end
|
|
37
|
+
end
|
data/dsl/prototype.rb
CHANGED
|
@@ -4,12 +4,8 @@
|
|
|
4
4
|
#: self as Roast::DSL::Workflow
|
|
5
5
|
|
|
6
6
|
config do
|
|
7
|
-
|
|
8
|
-
cmd(
|
|
9
|
-
cmd(:date) { print_stdout! }
|
|
10
|
-
chat { "" }
|
|
11
|
-
# TODO: add a way to apply config to a subset of named cog by pattern
|
|
12
|
-
# cmd(/^date.*/) { print_all! }
|
|
7
|
+
cmd(:echo) { display! }
|
|
8
|
+
cmd(/date/) { display! }
|
|
13
9
|
end
|
|
14
10
|
|
|
15
11
|
execute do
|
|
@@ -21,5 +17,7 @@ execute do
|
|
|
21
17
|
cmd(:echo) { |my| my.command = "echo 'Hello World!'" }
|
|
22
18
|
|
|
23
19
|
# Cogs can implement input coercion for simple return values
|
|
24
|
-
|
|
20
|
+
fmt = "\"+%a %b %d %T %Z %Y\""
|
|
21
|
+
cmd(:date_today) { "/bin/date #{fmt}" }
|
|
22
|
+
cmd(:date_yesterday) { RUBY_PLATFORM.include?("darwin") ? "/bin/date -jv -1d #{fmt}" : "/bin/date -d '-1 day' #{fmt}" }
|
|
25
23
|
end
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# typed: true
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
#: self as Roast::DSL::Workflow
|
|
5
|
+
|
|
6
|
+
config do
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
execute do
|
|
10
|
+
repeat(:loop, run: :loop_body) { 7 }
|
|
11
|
+
|
|
12
|
+
ruby do
|
|
13
|
+
# You can access the final output value of a `repeat` cog directly
|
|
14
|
+
result = repeat!(:loop)
|
|
15
|
+
puts "Ultimate Loop Result: #{result.value}"
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
ruby do
|
|
19
|
+
puts "---"
|
|
20
|
+
# You can also access the final result, or individual cog results, of any specific iteration
|
|
21
|
+
# In the same manner as you would use `from` to access the results of a single `call` invocation.
|
|
22
|
+
puts "First Iteration Result: #{from(repeat!(:loop).first)}"
|
|
23
|
+
puts "Final Iteration Result: #{from(repeat!(:loop).last)}"
|
|
24
|
+
puts "Second-to-last Iteration Result: #{from(repeat!(:loop).iteration(-2))}"
|
|
25
|
+
# NOTE: accessing a specific iteration will raise an IndexException if the requested index is out of bounds
|
|
26
|
+
# for the number of iterations that ran.
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
ruby do
|
|
30
|
+
puts "---"
|
|
31
|
+
# You can access the results of all iterations in the same manner as you would use `collect` or `reduce`
|
|
32
|
+
# to access the complete results of a `map` invocation.
|
|
33
|
+
puts "All :add cog outputs: #{collect(repeat!(:loop).results) { ruby!(:add).value }}"
|
|
34
|
+
puts "Sum of :add cog output: #{reduce(repeat!(:loop).results, 0) { |acc| acc + ruby!(:add).value }}"
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
execute(:loop_body) do
|
|
39
|
+
# on each loop iteration, the input value provided will be the output value of the previous iteration
|
|
40
|
+
# the initial value will be what was provided when `repeat` was called in the outer scope.
|
|
41
|
+
ruby(:add) do |_, num, idx|
|
|
42
|
+
new_num = num + idx
|
|
43
|
+
s = "iteration #{idx}: #{num} + #{idx} -> #{new_num}"
|
|
44
|
+
puts s
|
|
45
|
+
new_num
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
ruby { |_, _, idx| break! if idx >= 3 }
|
|
49
|
+
|
|
50
|
+
# The value provided to `outputs` will be the input value for the next iteration
|
|
51
|
+
# On the final iteration, it will also be the `repeat` cog's own output value
|
|
52
|
+
outputs { ruby!(:add).value }
|
|
53
|
+
end
|
data/dsl/ruby_cog.rb
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# typed: true
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
#: self as Roast::DSL::Workflow
|
|
5
|
+
|
|
6
|
+
class MyClass
|
|
7
|
+
class << self
|
|
8
|
+
def add_stuff(a, b)
|
|
9
|
+
a + b
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
config do
|
|
15
|
+
cmd { display! }
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
execute do
|
|
19
|
+
cmd(:roast) { "echo Roast" }
|
|
20
|
+
|
|
21
|
+
# The `ruby` cog just passes the return value from its input block directly to its output.
|
|
22
|
+
# Letting you write whatever ruby code you want to generate that output
|
|
23
|
+
ruby(:whatever) do
|
|
24
|
+
# Do whatever you want in this block
|
|
25
|
+
value = cmd!(:roast).text
|
|
26
|
+
puts "Hello, #{value.upcase}"
|
|
27
|
+
puts "Calling a method: #{MyClass.add_stuff(3, 4)}"
|
|
28
|
+
# The value you return will be exposed as the .value attribute on the cog's output
|
|
29
|
+
1..5
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
cmd(:numbers) do |my|
|
|
33
|
+
my.command = "echo"
|
|
34
|
+
value = ruby!(:whatever).value.map { |n| n.to_s * n }
|
|
35
|
+
my.args = value
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
ruby(:advanced_hash_output) do
|
|
39
|
+
{
|
|
40
|
+
some_number: 7,
|
|
41
|
+
some_string: "Hello, world!",
|
|
42
|
+
multiply: proc { |a, b| a * b },
|
|
43
|
+
}
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
ruby do
|
|
47
|
+
result = ruby!(:advanced_hash_output) #: untyped
|
|
48
|
+
|
|
49
|
+
# If the output's `value` is a Hash, you can access its items directly from the output object
|
|
50
|
+
puts "Some Number + 1: #{result[:some_number] + 1}"
|
|
51
|
+
# You can also access its items as getter methods on the output object
|
|
52
|
+
puts "Some String to Upper: #{result.some_string.upcase}"
|
|
53
|
+
# And, if one of those items is a proc, you can call it directly on the output object
|
|
54
|
+
puts "Multiply 4 * 3: #{result.multiply(4, 3)}"
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
ruby(:advanced_object_output) do |my|
|
|
58
|
+
my.value = <<~STRING
|
|
59
|
+
This is a long block of test
|
|
60
|
+
Consisting of many lines
|
|
61
|
+
Three, to be precise
|
|
62
|
+
STRING
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
ruby do
|
|
66
|
+
result = ruby!(:advanced_object_output) #: untyped
|
|
67
|
+
|
|
68
|
+
# You can also call methods on the output's `value` directly, regardless of its type
|
|
69
|
+
puts "The long string has #{result.lines.length} lines"
|
|
70
|
+
puts "And it has #{result.length} characters"
|
|
71
|
+
end
|
|
72
|
+
end
|
data/dsl/simple_agent.rb
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# typed: true
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
#: self as Roast::DSL::Workflow
|
|
5
|
+
|
|
6
|
+
config do
|
|
7
|
+
agent do
|
|
8
|
+
provider :claude
|
|
9
|
+
model "haiku"
|
|
10
|
+
initial_prompt "Always respond in haiku form"
|
|
11
|
+
show_prompt!
|
|
12
|
+
dump_raw_agent_messages_to "tmp/claude-messages.log"
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
execute do
|
|
17
|
+
agent { "What is the world's largest lake?" }
|
|
18
|
+
end
|
data/dsl/simple_chat.rb
CHANGED
|
@@ -4,9 +4,23 @@
|
|
|
4
4
|
#: self as Roast::DSL::Workflow
|
|
5
5
|
|
|
6
6
|
config do
|
|
7
|
-
|
|
7
|
+
chat(:lake) { model("gpt-4o") }
|
|
8
|
+
chat(:next) { model("gpt-4o-mini") }
|
|
8
9
|
end
|
|
9
10
|
|
|
10
11
|
execute do
|
|
12
|
+
# Ask a question
|
|
11
13
|
chat(:lake) { "What is the deepest lake?" }
|
|
14
|
+
|
|
15
|
+
# Continue the conversation (with a different model!)
|
|
16
|
+
chat(:next) do |my|
|
|
17
|
+
my.prompt = "What answer did you just give, and why?"
|
|
18
|
+
my.session = chat!(:lake).session
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Ask a question with a template prompt. You can pass variables to it as you would an ERB template
|
|
22
|
+
chat { template("dsl/prompts/simple_prompt.md.erb", { lake_answer: chat!(:lake).response }) }
|
|
23
|
+
|
|
24
|
+
# Shorthand to look up a template prompt
|
|
25
|
+
# chat { template("simple_prompt", { lake_answer: chat!(:lake).response }) }
|
|
12
26
|
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# typed: true
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
#: self as Roast::DSL::Workflow
|
|
5
|
+
|
|
6
|
+
config do
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
execute do
|
|
10
|
+
# You can use `repeat` to repeat an executor multiple times
|
|
11
|
+
# The executor will run forever until it breaks according to its internal logic
|
|
12
|
+
repeat(:loop, run: :loop_body) {}
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
execute(:loop_body) do
|
|
16
|
+
ruby(:one) do |_, _, idx|
|
|
17
|
+
s = "iteration #{idx}"
|
|
18
|
+
puts s
|
|
19
|
+
s
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Use `break!` to terminate the repeat loop
|
|
23
|
+
# Cogs that occur after `break!` is called will not run on the final iteration
|
|
24
|
+
ruby { |_, _, idx| break! if idx >= 3 }
|
|
25
|
+
|
|
26
|
+
# The `outputs` block will *always* run, including on the iteration in which the `break!` was issued
|
|
27
|
+
# NOTE: Be careful not to depend on the output of cogs that only run after the break point
|
|
28
|
+
outputs { |_, idx| "output of iteration #{idx}" }
|
|
29
|
+
end
|
data/dsl/skip.rb
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# typed: true
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
#: self as Roast::DSL::Workflow
|
|
5
|
+
|
|
6
|
+
config do
|
|
7
|
+
cmd { display! }
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
execute do
|
|
11
|
+
cmd(:seconds) { "date +'%S'" }
|
|
12
|
+
|
|
13
|
+
cmd(:even) do
|
|
14
|
+
# `cmd!` output accessor bang method will raise an exception if the named cog did not complete successfully
|
|
15
|
+
seconds = cmd!(:seconds).out.to_i
|
|
16
|
+
# Use the `skip!` method in a cog's input proc to conditionally skip this cog
|
|
17
|
+
# If `skip!` is called, the input proc will immediately terminate
|
|
18
|
+
skip! if seconds.odd?
|
|
19
|
+
"echo '#{seconds} is even'"
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
cmd(:odd) do
|
|
23
|
+
seconds = cmd!(:seconds).out.to_i
|
|
24
|
+
skip! if seconds.even?
|
|
25
|
+
"echo '#{seconds} is odd'"
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
cmd do |my|
|
|
29
|
+
my.command = "echo"
|
|
30
|
+
# `cmd` output accessor non-bang method will return the cog's output or
|
|
31
|
+
# nil if the named cog did not run yet or did not complete successfully
|
|
32
|
+
my.args << "'even' cog ran" unless cmd(:even).nil?
|
|
33
|
+
# `cmd?` output accessor question method will return true/false if the cog did / did not complete successfully yet
|
|
34
|
+
my.args << "'odd' cog ran" if cmd?(:odd)
|
|
35
|
+
end
|
|
36
|
+
end
|
data/dsl/step_communication.rb
CHANGED
|
@@ -14,9 +14,8 @@ execute do
|
|
|
14
14
|
cmd(:ls) { "ls -al" }
|
|
15
15
|
cmd(:echo) do |my|
|
|
16
16
|
my.command = "echo"
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
last_line = cmd(:ls).out.split("\n").last
|
|
17
|
+
first_line = cmd!(:ls).lines.second
|
|
18
|
+
last_line = cmd!(:ls).lines.last
|
|
20
19
|
my.args << first_line unless first_line.blank?
|
|
21
20
|
my.args << "\n---\n"
|
|
22
21
|
my.args << last_line if last_line != first_line && last_line.present?
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# typed: true
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
#: self as Roast::DSL::Workflow
|
|
5
|
+
|
|
6
|
+
config do
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
execute do
|
|
10
|
+
ruby do |_, params|
|
|
11
|
+
# Any files listed after the standard roast command-line args are collected as your workflows targets
|
|
12
|
+
# (shell globs are expanded as you would expect)
|
|
13
|
+
# e.g., `roast execute --executor=dsl dsl/targets_and_params.rb Gemfile Gemfile.lock`
|
|
14
|
+
# or, `roast execute --executor=dsl dsl/targets_and_params.rb Gemfile*`
|
|
15
|
+
puts "workflow targets: #{params.targets}"
|
|
16
|
+
|
|
17
|
+
# You can specify custom arguments for your workflow. Anything coming after `--` on the roast command
|
|
18
|
+
# line will be parsed as custom arguments.
|
|
19
|
+
# Simple word tokens are collected as `args`. Tokens in the form `key=value` are parsed into the `kwargs` hash.
|
|
20
|
+
# e.g., `roast execute --executor=dsl dsl/targets_and_params.rb Gemfile* -- foo=bar abc=pqr hello world`
|
|
21
|
+
puts "workflow args: #{params.args}" # [:hello, :world] (args are parsed as symbols)
|
|
22
|
+
|
|
23
|
+
# {abc: "pqr", foo: "bar"} (keys are parsed as symbols, values as strings)
|
|
24
|
+
# (Note: using explicit formatting for compatibility with Ruby versions < 3.4)
|
|
25
|
+
puts "workflow kwargs: {#{params.kwargs.map { |k, v| "#{k}: #{v.inspect}" }.join(", ")}}"
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# There are convenience methods to access the workflow params from any cog input context in any scope
|
|
29
|
+
ruby do
|
|
30
|
+
puts
|
|
31
|
+
# `target!` will raise an exception unless exactly one target is provided
|
|
32
|
+
# puts "Explicit target = #{target!}"
|
|
33
|
+
|
|
34
|
+
# `targets` will return the (possibly empty) array of targets
|
|
35
|
+
puts "All targets = #{targets}"
|
|
36
|
+
|
|
37
|
+
# `arg?` will return a boolean indicating whether a specific value / flag argument is present
|
|
38
|
+
puts "Argument 'foo' provided? #{arg?(:foo) ? "yes" : "no"}"
|
|
39
|
+
|
|
40
|
+
# `args` will return the (possibly empty) array of simple value / flag arguments
|
|
41
|
+
puts "All args = #{args}"
|
|
42
|
+
|
|
43
|
+
# `kwarg` will return the value associated with a specific keyword argument,
|
|
44
|
+
# or nil if that keyword argument was not provided
|
|
45
|
+
puts "Keyword argument 'name': '#{kwarg(:name)}'"
|
|
46
|
+
|
|
47
|
+
# `kwarg!` will return the value associated with a specific keyword argument,
|
|
48
|
+
# or raise an exception if that keyword argument was not provided
|
|
49
|
+
# puts "Keyword argument 'name': #{kwarg!(:name)}"
|
|
50
|
+
|
|
51
|
+
# `kwarg?` will return a boolean indicating whether a specific keyword argument was provided
|
|
52
|
+
puts "Keyword argument 'name' provided: #{kwarg?(:name) ? "yes" : "no"}"
|
|
53
|
+
|
|
54
|
+
# `kwargs` will return the (possibly empty) hash of all keyword arguments
|
|
55
|
+
# puts "All kwargs = #{kwargs}"
|
|
56
|
+
end
|
|
57
|
+
end
|
data/dsl/temperature.rb
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# typed: false
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
config do
|
|
5
|
+
chat(:low_temp) do
|
|
6
|
+
temperature(0.0)
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
chat(:high_temp) do
|
|
10
|
+
temperature(1.0)
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
execute do
|
|
15
|
+
chat(:low_temp) { "Write a haiku about the capital of France." }
|
|
16
|
+
chat(:high_temp) { "Write a haiku about the capital of France." }
|
|
17
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# typed: true
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
#: self as Roast::DSL::Workflow
|
|
5
|
+
|
|
6
|
+
config do
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
execute do
|
|
10
|
+
ruby do
|
|
11
|
+
# The workflow automatically gets a temporary directory created for it
|
|
12
|
+
# unique to a single execution of the workflow, but shared across all executor scopes.
|
|
13
|
+
# This working directory will be cleaned up automatically when the workflow completes or fails.
|
|
14
|
+
puts tmpdir
|
|
15
|
+
|
|
16
|
+
# The value of `tmpdir` will always be a directory that exists
|
|
17
|
+
raise StandardError, "temporary directory does not exist" unless tmpdir.exist?
|
|
18
|
+
|
|
19
|
+
# The value of `tmpdir` will always be an absolute path
|
|
20
|
+
raise StandardError, "temporary directory is not an absolute path" unless tmpdir.absolute?
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
# Chapter 1: Your First Workflow
|
|
2
|
+
|
|
3
|
+
In this chapter, you'll create and run your first Roast workflow. We'll start with the absolute simplest example, then
|
|
4
|
+
show you how to add basic configuration.
|
|
5
|
+
|
|
6
|
+
## What You'll Learn
|
|
7
|
+
|
|
8
|
+
- The basic structure of a Roast workflow file
|
|
9
|
+
- How to use the `chat` cog to interact with an LLM
|
|
10
|
+
- How to run workflows from the command line
|
|
11
|
+
- How to configure model settings
|
|
12
|
+
|
|
13
|
+
## Understanding Workflows
|
|
14
|
+
|
|
15
|
+
Before we dive into code, let's understand what a Roast workflow is:
|
|
16
|
+
|
|
17
|
+
**A workflow is a sequence of steps that accomplish a task.** Think of it like a recipe: you define each step in order,
|
|
18
|
+
specify what inputs each step needs, and describe how they connect together.
|
|
19
|
+
|
|
20
|
+
### Key Concepts
|
|
21
|
+
|
|
22
|
+
- **Steps** - Individual units of work (like "analyze this code" or "summarize this text")
|
|
23
|
+
- **Cogs** - The building blocks that implement step types (Roast comes with a comprehensive set of basic cogs for
|
|
24
|
+
interacting with LLMs, invoking coding agents, running shell commands, processing data, as wells as complex flow
|
|
25
|
+
control)
|
|
26
|
+
- **Declarative style** - You describe *what* you want done, the inputs each step needs, and how they connect together.
|
|
27
|
+
Roast takes care of the rest
|
|
28
|
+
|
|
29
|
+
For example, instead of writing code to open files, parse them, call APIs, handle errors, etc., you write:
|
|
30
|
+
|
|
31
|
+
```ruby
|
|
32
|
+
execute do
|
|
33
|
+
chat { "Analyze this code and suggest improvements: #{my_code}" }
|
|
34
|
+
end
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Roast handles the details. You focus on the workflow.
|
|
38
|
+
|
|
39
|
+
In this chapter, we'll start with the simplest workflow: a single step using the `chat` cog. In later chapters, you'll
|
|
40
|
+
learn how to invoke coding agents, run shell commands, and chain multiple steps together to build sophisticated
|
|
41
|
+
workflows.
|
|
42
|
+
|
|
43
|
+
## The Basics
|
|
44
|
+
|
|
45
|
+
Every Roast workflow is a Ruby file that defines what work to do. The simplest workflow has just one part:
|
|
46
|
+
|
|
47
|
+
1. **Execute block** - Contains the work to perform
|
|
48
|
+
|
|
49
|
+
### Minimal Syntax
|
|
50
|
+
|
|
51
|
+
```ruby
|
|
52
|
+
execute do
|
|
53
|
+
# Your workflow goes here
|
|
54
|
+
end
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
That's it! Everything happens inside the `execute do ... end` block.
|
|
58
|
+
|
|
59
|
+
## Your First Chat Cog
|
|
60
|
+
|
|
61
|
+
The `chat` cog sends a prompt to a cloud-based LLM and gets a response back. Here's a simplest example:
|
|
62
|
+
|
|
63
|
+
```ruby
|
|
64
|
+
execute do
|
|
65
|
+
chat { "Say hello!" }
|
|
66
|
+
end
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
When you run this, Roast will:
|
|
70
|
+
|
|
71
|
+
1. Send the prompt "Say hello!" to default LLM provider and model (OpenAI gpt-4o-mini)
|
|
72
|
+
2. Wait for the response
|
|
73
|
+
3. Display the response in your terminal, along with LLM usage statistics
|
|
74
|
+
|
|
75
|
+
### Longer Prompts
|
|
76
|
+
|
|
77
|
+
For real workflows, you'll want more detailed prompts. Use Ruby's heredoc syntax for multi-line prompts:
|
|
78
|
+
|
|
79
|
+
```ruby
|
|
80
|
+
execute do
|
|
81
|
+
chat do
|
|
82
|
+
<<~PROMPT
|
|
83
|
+
You are a helpful AI assistant.
|
|
84
|
+
|
|
85
|
+
Please introduce yourself and explain what Roast is in 2-3 sentences.
|
|
86
|
+
Keep your response friendly and concise.
|
|
87
|
+
PROMPT
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Or define your prompts in a template file using ERB syntax and include them in your workflow
|
|
93
|
+
|
|
94
|
+
```ruby
|
|
95
|
+
execute do
|
|
96
|
+
chat { template("path_to_prompt_file.md.erb", { context: }) }
|
|
97
|
+
end
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## Adding Configuration
|
|
101
|
+
|
|
102
|
+
You can configure which model to use, which provider, and other settings. Configuration goes in a `config do ... end`
|
|
103
|
+
block:
|
|
104
|
+
|
|
105
|
+
```ruby
|
|
106
|
+
config do
|
|
107
|
+
chat do
|
|
108
|
+
model "gpt-4o-mini" # Use OpenAI's fast model
|
|
109
|
+
provider :openai # Use OpenAI (can also be :anthropic)
|
|
110
|
+
show_prompt! # Display the prompt before sending
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
execute do
|
|
115
|
+
chat do
|
|
116
|
+
<<~PROMPT
|
|
117
|
+
Explain what an AI workflow is in simple terms.
|
|
118
|
+
PROMPT
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### Configuration Options
|
|
124
|
+
|
|
125
|
+
Common options you can set:
|
|
126
|
+
|
|
127
|
+
- `model "name"` - Which LLM model to use
|
|
128
|
+
- OpenAI: "gpt-4o", "gpt-4o-mini", "gpt-4-turbo"
|
|
129
|
+
- Anthropic: "claude-3-5-sonnet-20241022", "claude-3-5-haiku-20241022"
|
|
130
|
+
- `provider :name` - Which LLM provider
|
|
131
|
+
- `:openai` or `:anthropic`
|
|
132
|
+
- `show_prompt!` - Display the prompt being sent
|
|
133
|
+
- `show_response!` - Display the response (on by default)
|
|
134
|
+
- `show_stats!` - Display token usage statistics (on by default)
|
|
135
|
+
- `no_display!` - Turn off all output (useful when you just want to pass the output to a subsequent cog)
|
|
136
|
+
|
|
137
|
+
## Running the Workflows
|
|
138
|
+
|
|
139
|
+
To run any workflow in this chapter:
|
|
140
|
+
|
|
141
|
+
```bash
|
|
142
|
+
# From the project root
|
|
143
|
+
bin/roast execute --executor=dsl dsl/tutorial/01_your_first_workflow/hello.rb
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
Or the configured version:
|
|
147
|
+
|
|
148
|
+
```bash
|
|
149
|
+
bin/roast execute --executor=dsl dsl/tutorial/01_your_first_workflow/configured_chat.rb
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
You should see:
|
|
153
|
+
|
|
154
|
+
1. The prompt being sent (if you enabled `show_prompt!`)
|
|
155
|
+
2. The LLM's response
|
|
156
|
+
3. Statistics about token usage
|
|
157
|
+
|
|
158
|
+
## Try It Yourself
|
|
159
|
+
|
|
160
|
+
1. **Run both example workflows** in this chapter
|
|
161
|
+
2. **Modify the prompts** - Ask different questions
|
|
162
|
+
3. **Try different models** - Change "gpt-4o-mini" to "gpt-4o" in the config
|
|
163
|
+
4. **Experiment with display settings** - Try `no_show_stats!` and see what happens
|
|
164
|
+
|
|
165
|
+
## Key Takeaways
|
|
166
|
+
|
|
167
|
+
- The `execute do ... end` block contains your workflow logic
|
|
168
|
+
- The `chat` cog sends prompts to cloud-based LLMs
|
|
169
|
+
- Use `<<~PROMPT ... PROMPT` for multi-line prompts
|
|
170
|
+
- The `config do ... end` block (before execute) sets up cog behavior
|
|
171
|
+
- Run workflows with `bin/roast execute --executor=dsl path/to/workflow.rb`
|
|
172
|
+
|
|
173
|
+
## What's Next?
|
|
174
|
+
|
|
175
|
+
In the next chapter, you'll learn how to chain multiple cogs together, so the output of one becomes the input to
|
|
176
|
+
another. This is where Roast becomes powerful!
|
|
177
|
+
|
|
178
|
+
But first, make sure you can successfully run both workflows in this chapter. Experiment with different prompts and
|
|
179
|
+
configurations until you're comfortable.
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# typed: true
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
#: self as Roast::DSL::Workflow
|
|
5
|
+
|
|
6
|
+
# This workflow demonstrates how to configure chat cogs.
|
|
7
|
+
# Configuration is set in a 'config' block before the 'execute' block.
|
|
8
|
+
|
|
9
|
+
config do
|
|
10
|
+
# Configure all chat cogs in this workflow
|
|
11
|
+
chat do
|
|
12
|
+
model "gpt-4o-mini" # Use OpenAI's fast, cost-effective model
|
|
13
|
+
provider :openai # Use OpenAI (alternative: :anthropic)
|
|
14
|
+
show_prompt! # Display the prompt before sending it
|
|
15
|
+
show_response! # Display the response (this is the default)
|
|
16
|
+
show_stats! # Display token usage statistics (default)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
execute do
|
|
21
|
+
chat do
|
|
22
|
+
<<~PROMPT
|
|
23
|
+
You are a knowledgeable software development assistant.
|
|
24
|
+
|
|
25
|
+
Explain what a "workflow" is in the context of software automation,
|
|
26
|
+
using simple language that a beginner could understand.
|
|
27
|
+
|
|
28
|
+
Provide a brief example of when you might use a workflow.
|
|
29
|
+
|
|
30
|
+
Keep your response to 3-4 sentences.
|
|
31
|
+
PROMPT
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# typed: true
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# This type annotation helps Sorbet type-check your workflow.
|
|
5
|
+
# It's optional if you don't want to use Sorbet.
|
|
6
|
+
#: self as Roast::DSL::Workflow
|
|
7
|
+
|
|
8
|
+
# This is the simplest possible Roast workflow.
|
|
9
|
+
# It sends a single prompt to a chat LLM and displays the response.
|
|
10
|
+
|
|
11
|
+
config {}
|
|
12
|
+
|
|
13
|
+
execute do
|
|
14
|
+
chat do
|
|
15
|
+
<<~PROMPT
|
|
16
|
+
You are a friendly AI assistant helping someone learn Roast,
|
|
17
|
+
a Ruby-based workflow system for AI tasks.
|
|
18
|
+
|
|
19
|
+
Say hello and give them one encouraging tip about learning
|
|
20
|
+
new tools. Keep it brief and friendly!
|
|
21
|
+
PROMPT
|
|
22
|
+
end
|
|
23
|
+
end
|