roast-ai 0.2.0 → 0.2.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/CHANGELOG.md +9 -0
- data/CLAUDE_NOTES.md +68 -0
- data/Gemfile.lock +1 -1
- data/README.md +79 -13
- data/docs/ITERATION_SYNTAX.md +31 -3
- data/examples/case_when/README.md +58 -0
- data/examples/case_when/detect_language/prompt.md +16 -0
- data/examples/case_when/workflow.yml +58 -0
- data/examples/direct_coerce_syntax/README.md +32 -0
- data/examples/direct_coerce_syntax/workflow.yml +36 -0
- data/examples/grading/workflow.yml +6 -4
- data/examples/json_handling/README.md +32 -0
- data/examples/json_handling/workflow.yml +52 -0
- data/examples/smart_coercion_defaults/README.md +65 -0
- data/examples/smart_coercion_defaults/workflow.yml +44 -0
- data/examples/step_configuration/README.md +87 -0
- data/examples/step_configuration/workflow.yml +60 -0
- data/lib/roast/version.rb +1 -1
- data/lib/roast/workflow/base_iteration_step.rb +22 -3
- data/lib/roast/workflow/base_step.rb +40 -3
- data/lib/roast/workflow/case_executor.rb +49 -0
- data/lib/roast/workflow/case_step.rb +82 -0
- data/lib/roast/workflow/conditional_step.rb +6 -43
- data/lib/roast/workflow/expression_evaluator.rb +78 -0
- data/lib/roast/workflow/iteration_executor.rb +18 -0
- data/lib/roast/workflow/prompt_step.rb +4 -1
- data/lib/roast/workflow/repeat_step.rb +2 -2
- data/lib/roast/workflow/step_executor_coordinator.rb +16 -0
- data/lib/roast/workflow/step_loader.rb +6 -5
- data/lib/roast/workflow/step_type_resolver.rb +16 -0
- data/schema/workflow.json +27 -0
- metadata +16 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: db58f659180113d7a2bb1bface8da2898be6717033107247156a464b4d14159d
|
4
|
+
data.tar.gz: f14c2caae89fab3353b66ae3eb4457c56975e65c464037a2d401cfca30840d81
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 88c3f83651c535077bdfe29408a19b874f66f19d8b7e743dd4933922f82fd7aee53ee215968e3d10c0207e92d4c53018a0b190475ba7ac4b70d15cda07a634ff
|
7
|
+
data.tar.gz: 5ebae3dcc5d7d775a3f19ab79ead81d7c11f53fc31b5b20f1db4781a950032e693a2d331fdc4509eb0661c04072f421defcd56d15b9d6ce74f7bfc12357ee1b0
|
data/CHANGELOG.md
CHANGED
@@ -5,6 +5,15 @@ All notable changes to this project will be documented in this file.
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
7
7
|
|
8
|
+
## [0.2.1]
|
9
|
+
|
10
|
+
### Added
|
11
|
+
- Smart coercion defaults for boolean expressions based on step type
|
12
|
+
- Ruby expressions (`{{expr}}`) default to regular boolean coercion
|
13
|
+
- Bash commands (`$(cmd)`) default to exit code interpretation
|
14
|
+
- Inline prompts and regular steps default to "smart" LLM-powered interpretation (looks for truthy or falsy language)
|
15
|
+
- Direct syntax for step configuration - `coerce_to` and other options are now specified directly on iteration steps
|
16
|
+
|
8
17
|
## [0.2.0] - 2025-05-26
|
9
18
|
|
10
19
|
### Added
|
data/CLAUDE_NOTES.md
ADDED
@@ -0,0 +1,68 @@
|
|
1
|
+
# Important Notes for Claude
|
2
|
+
|
3
|
+
## Roast Workflow Syntax
|
4
|
+
|
5
|
+
### IMPORTANT: No inline configuration for steps!
|
6
|
+
|
7
|
+
Roast workflows DO NOT support inline configuration syntax like this:
|
8
|
+
```yaml
|
9
|
+
# WRONG - This syntax does NOT exist:
|
10
|
+
steps:
|
11
|
+
- step_name:
|
12
|
+
prompt: "some prompt"
|
13
|
+
output: variable_name
|
14
|
+
```
|
15
|
+
|
16
|
+
The correct syntax is:
|
17
|
+
```yaml
|
18
|
+
# CORRECT - Steps are just names or hash assignments:
|
19
|
+
steps:
|
20
|
+
- step_name
|
21
|
+
- variable_name: step_name
|
22
|
+
- variable_name: $(command)
|
23
|
+
```
|
24
|
+
|
25
|
+
### Control flow structures
|
26
|
+
|
27
|
+
Only control flow structures (if/unless, case/when/else, each, repeat) support nested configuration:
|
28
|
+
|
29
|
+
```yaml
|
30
|
+
steps:
|
31
|
+
# Simple step - just the name
|
32
|
+
- detect_language
|
33
|
+
|
34
|
+
# Variable assignment
|
35
|
+
- my_var: detect_language
|
36
|
+
|
37
|
+
# Command execution with assignment
|
38
|
+
- env_type: $(echo $ENVIRONMENT)
|
39
|
+
|
40
|
+
# Control flow - these DO have nested structure
|
41
|
+
- if: "{{ condition }}"
|
42
|
+
then:
|
43
|
+
- step1
|
44
|
+
- step2
|
45
|
+
|
46
|
+
- case: "{{ expression }}"
|
47
|
+
when:
|
48
|
+
value1:
|
49
|
+
- step1
|
50
|
+
value2:
|
51
|
+
- step2
|
52
|
+
else:
|
53
|
+
- step3
|
54
|
+
```
|
55
|
+
|
56
|
+
### Step Configuration
|
57
|
+
|
58
|
+
Step configuration (prompts, outputs, etc.) is handled through:
|
59
|
+
1. File naming conventions (step_name/prompt.md, step_name/output.txt)
|
60
|
+
2. The workflow configuration file itself (tools, target, etc.)
|
61
|
+
|
62
|
+
NOT through inline step configuration in the steps array.
|
63
|
+
|
64
|
+
## Remember:
|
65
|
+
- Steps array contains step names, not step configurations
|
66
|
+
- Only control flow structures have nested configuration
|
67
|
+
- Variable assignment uses hash syntax: `var_name: step_name`
|
68
|
+
- Commands use $() syntax: `var_name: $(command)`
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -4,6 +4,18 @@
|
|
4
4
|
|
5
5
|
A convention-oriented framework for creating structured AI workflows, maintained by the Augmented Engineering team at Shopify.
|
6
6
|
|
7
|
+
## Installation
|
8
|
+
|
9
|
+
```bash
|
10
|
+
$ gem install roast-ai
|
11
|
+
```
|
12
|
+
|
13
|
+
Or add to your Gemfile:
|
14
|
+
|
15
|
+
```ruby
|
16
|
+
gem 'roast-ai'
|
17
|
+
```
|
18
|
+
|
7
19
|
## Why you should use Roast
|
8
20
|
|
9
21
|
Roast provides a structured, declarative approach to building AI workflows with:
|
@@ -64,6 +76,42 @@ steps:
|
|
64
76
|
- Summarize the changes made to {{File.basename(file)}}.
|
65
77
|
```
|
66
78
|
|
79
|
+
## Try it
|
80
|
+
|
81
|
+
If you don’t have one already, get an OpenAI key from [here](https://platform.openai.com/settings/organization/api-keys). You will need an account with a credit card, make sure that a basic completion works.
|
82
|
+
|
83
|
+
```bash
|
84
|
+
export OPENAI_API_KEY=sk-proj-....
|
85
|
+
|
86
|
+
curl -H "Content-Type: application/json" \
|
87
|
+
-H "Authorization: Bearer $API_TOKEN" \
|
88
|
+
-d '{"model":"gpt-4.1-mini","messages":[{"role":"user","content":"What is 1+1?"}]}' \
|
89
|
+
https://api.openai.com/v1/chat/completions
|
90
|
+
```
|
91
|
+
|
92
|
+
The [test grading workflow](examples/grading/workflow.md) in this repository is a senior software engineer and testing expert that evaluates the quality of a test based on guidelines.
|
93
|
+
|
94
|
+
Try the workflow.
|
95
|
+
|
96
|
+
```bash
|
97
|
+
./exe/roast execute examples/grading/workflow.yml test/roast/resources_test.rb
|
98
|
+
|
99
|
+
🔥🔥🔥 Everyone loves a good roast 🔥🔥🔥
|
100
|
+
...
|
101
|
+
```
|
102
|
+
|
103
|
+
This will output a test grade.
|
104
|
+
|
105
|
+
```
|
106
|
+
========== TEST GRADE REPORT ==========
|
107
|
+
Test file: test/roast/resources_test.rb
|
108
|
+
|
109
|
+
FINAL GRADE:
|
110
|
+
Score: 80/100
|
111
|
+
Letter Grade: B
|
112
|
+
```
|
113
|
+
Note that you may also need `shadowenv` and `rg`, on MacOS run `brew install shadowenv` and `brew install rg`.
|
114
|
+
|
67
115
|
## How to use Roast
|
68
116
|
|
69
117
|
1. Create a workflow YAML file defining your steps and tools
|
@@ -178,7 +226,37 @@ Roast supports several types of steps:
|
|
178
226
|
- Until conditions: `until: "{{condition}}"`
|
179
227
|
- Maximum iterations: `max_iterations: 10`
|
180
228
|
|
181
|
-
6. **
|
229
|
+
6. **Case/when/else steps**: Select different execution paths based on a value (similar to Ruby's case statement)
|
230
|
+
```yaml
|
231
|
+
steps:
|
232
|
+
- detect_language
|
233
|
+
|
234
|
+
- case: "{{ workflow.output.detect_language }}"
|
235
|
+
when:
|
236
|
+
ruby:
|
237
|
+
- lint_with_rubocop
|
238
|
+
- test_with_rspec
|
239
|
+
javascript:
|
240
|
+
- lint_with_eslint
|
241
|
+
- test_with_jest
|
242
|
+
python:
|
243
|
+
- lint_with_pylint
|
244
|
+
- test_with_pytest
|
245
|
+
else:
|
246
|
+
- analyze_generic
|
247
|
+
- generate_basic_report
|
248
|
+
```
|
249
|
+
|
250
|
+
Case expressions can be:
|
251
|
+
- Workflow outputs: `case: "{{ workflow.output.variable }}"`
|
252
|
+
- Ruby expressions: `case: "{{ count > 10 ? 'high' : 'low' }}"`
|
253
|
+
- Bash commands: `case: "$(echo $ENVIRONMENT)"`
|
254
|
+
- Direct values: `case: "production"`
|
255
|
+
|
256
|
+
The value is compared against each key in the `when` clause, and matching steps are executed.
|
257
|
+
If no match is found, the `else` steps are executed (if provided).
|
258
|
+
|
259
|
+
7. **Raw prompt step**: Simple text prompts for the model without tools
|
182
260
|
```yaml
|
183
261
|
steps:
|
184
262
|
- Summarize the changes made to the codebase.
|
@@ -668,18 +746,6 @@ your-project/
|
|
668
746
|
└── ...
|
669
747
|
```
|
670
748
|
|
671
|
-
## Installation
|
672
|
-
|
673
|
-
```bash
|
674
|
-
$ gem install roast-ai
|
675
|
-
```
|
676
|
-
|
677
|
-
Or add to your Gemfile:
|
678
|
-
|
679
|
-
```ruby
|
680
|
-
gem 'roast-ai'
|
681
|
-
```
|
682
|
-
|
683
749
|
## Development
|
684
750
|
|
685
751
|
After checking out the repo, run `bundle install` to install dependencies. Then, run `bundle exec rake` to run the tests and linter. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
data/docs/ITERATION_SYNTAX.md
CHANGED
@@ -90,10 +90,38 @@ For defining prompts directly in the workflow:
|
|
90
90
|
|
91
91
|
## Type Coercion
|
92
92
|
|
93
|
-
|
93
|
+
### Smart Defaults
|
94
94
|
|
95
|
-
|
96
|
-
|
95
|
+
Roast applies intelligent defaults for boolean coercion based on the type of expression:
|
96
|
+
|
97
|
+
- **Ruby expressions** (`{{expr}}`) → Regular boolean coercion (`!!value`)
|
98
|
+
- **Bash commands** (`$(cmd)`) → Exit code interpretation (0 = true, non-zero = false)
|
99
|
+
- **Inline prompts/step names** → LLM boolean interpretation (analyzes yes/no intent)
|
100
|
+
|
101
|
+
### Manual Coercion
|
102
|
+
|
103
|
+
You can override the smart defaults by specifying `coerce_to` directly in the step:
|
104
|
+
|
105
|
+
```yaml
|
106
|
+
# Override prompt to use regular boolean instead of LLM boolean
|
107
|
+
- repeat:
|
108
|
+
until: "check_condition"
|
109
|
+
coerce_to: boolean
|
110
|
+
steps:
|
111
|
+
- process_item
|
112
|
+
|
113
|
+
# Force a step result to be treated as iterable
|
114
|
+
- each: "get_items"
|
115
|
+
as: "item"
|
116
|
+
coerce_to: iterable
|
117
|
+
steps:
|
118
|
+
- process: "{{item}}"
|
119
|
+
```
|
120
|
+
|
121
|
+
Available coercion types:
|
122
|
+
- `boolean` - Standard Ruby truthiness (`!!` operator)
|
123
|
+
- `llm_boolean` - Natural language yes/no interpretation
|
124
|
+
- `iterable` - Convert to array (splits strings on newlines)
|
97
125
|
|
98
126
|
## Migrating Existing Workflows
|
99
127
|
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# Case/When/Else Example
|
2
|
+
|
3
|
+
This example demonstrates the use of `case/when/else` control flow in Roast workflows.
|
4
|
+
|
5
|
+
## Overview
|
6
|
+
|
7
|
+
The `case/when/else` construct allows you to execute different steps based on the value of an expression, similar to Ruby's case statement or switch statements in other languages.
|
8
|
+
|
9
|
+
## Syntax
|
10
|
+
|
11
|
+
```yaml
|
12
|
+
- case: <expression>
|
13
|
+
when:
|
14
|
+
<value1>:
|
15
|
+
- <steps>
|
16
|
+
<value2>:
|
17
|
+
- <steps>
|
18
|
+
else:
|
19
|
+
- <steps>
|
20
|
+
```
|
21
|
+
|
22
|
+
## Features Demonstrated
|
23
|
+
|
24
|
+
1. **Basic case/when/else**: Detect file language and execute language-specific analysis
|
25
|
+
2. **Bash command evaluation**: Use environment variables to determine deployment strategy
|
26
|
+
3. **Complex expressions**: Use Ruby expressions to categorize numeric values
|
27
|
+
|
28
|
+
## Expression Types
|
29
|
+
|
30
|
+
The `case` expression can be:
|
31
|
+
- A simple string value
|
32
|
+
- An interpolated workflow output: `{{ workflow.output.variable }}`
|
33
|
+
- A Ruby expression: `{{ workflow.output.count > 10 ? 'high' : 'low' }}`
|
34
|
+
- A bash command: `$(echo $ENVIRONMENT)`
|
35
|
+
- A reference to a previous step's output
|
36
|
+
|
37
|
+
## How It Works
|
38
|
+
|
39
|
+
1. The `case` expression is evaluated to produce a value
|
40
|
+
2. The value is compared against each key in the `when` clause
|
41
|
+
3. If a match is found, the steps under that key are executed
|
42
|
+
4. If no match is found and an `else` clause exists, those steps are executed
|
43
|
+
5. If no match is found and no `else` clause exists, execution continues
|
44
|
+
|
45
|
+
## Running the Example
|
46
|
+
|
47
|
+
```bash
|
48
|
+
roast execute examples/case_when/workflow.yml
|
49
|
+
```
|
50
|
+
|
51
|
+
This will process all Ruby, JavaScript, Python, and Go files in the current directory, detecting their language and running appropriate analysis steps.
|
52
|
+
|
53
|
+
## Use Cases
|
54
|
+
|
55
|
+
- **Multi-language projects**: Different linting/testing for different file types
|
56
|
+
- **Environment-specific workflows**: Different deployment steps for prod/staging/dev
|
57
|
+
- **Conditional processing**: Different handling based on file size, complexity, or other metrics
|
58
|
+
- **Error handling**: Different recovery strategies based on error types
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# Detect Programming Language
|
2
|
+
|
3
|
+
Based on the file extension and content, determine the primary programming language of this file:
|
4
|
+
- If it's a `.rb` file, return "ruby"
|
5
|
+
- If it's a `.js` file, return "javascript"
|
6
|
+
- If it's a `.py` file, return "python"
|
7
|
+
- If it's a `.go` file, return "go"
|
8
|
+
- Otherwise, return "unknown"
|
9
|
+
|
10
|
+
Return ONLY the language name in lowercase, nothing else.
|
11
|
+
|
12
|
+
File: {{ context.resource_uri }}
|
13
|
+
Content:
|
14
|
+
```
|
15
|
+
{{ context.resource }}
|
16
|
+
```
|
@@ -0,0 +1,58 @@
|
|
1
|
+
name: "Case/When/Else Example"
|
2
|
+
|
3
|
+
tools:
|
4
|
+
- Roast::Tools::Cmd
|
5
|
+
- Roast::Tools::ReadFile
|
6
|
+
- Roast::Tools::WriteFile
|
7
|
+
|
8
|
+
target: "**/*.{rb,js,py,go}"
|
9
|
+
|
10
|
+
steps:
|
11
|
+
- detect_language
|
12
|
+
|
13
|
+
- case: "{{ workflow.output.detect_language }}"
|
14
|
+
when:
|
15
|
+
ruby:
|
16
|
+
- analyze_ruby
|
17
|
+
- generate_ruby_report
|
18
|
+
javascript:
|
19
|
+
- analyze_javascript
|
20
|
+
- generate_js_report
|
21
|
+
python:
|
22
|
+
- analyze_python
|
23
|
+
- generate_python_report
|
24
|
+
go:
|
25
|
+
- analyze_go
|
26
|
+
- generate_go_report
|
27
|
+
else:
|
28
|
+
- analyze_generic
|
29
|
+
- generate_generic_report
|
30
|
+
|
31
|
+
# Another example using bash command for case expression
|
32
|
+
- get_environment: $(echo $ENVIRONMENT || echo "development")
|
33
|
+
|
34
|
+
- case: "{{ workflow.output.get_environment }}"
|
35
|
+
when:
|
36
|
+
production:
|
37
|
+
- production_checks
|
38
|
+
- deploy_production
|
39
|
+
staging:
|
40
|
+
- staging_checks
|
41
|
+
- deploy_staging
|
42
|
+
development:
|
43
|
+
- run_tests
|
44
|
+
- local_deploy
|
45
|
+
else:
|
46
|
+
- unknown_environment
|
47
|
+
|
48
|
+
# Example with numeric case values
|
49
|
+
- count_issues
|
50
|
+
|
51
|
+
- case: "{{ workflow.output.count_issues.to_i > 10 ? 'high' : workflow.output.count_issues.to_i > 5 ? 'medium' : 'low' }}"
|
52
|
+
when:
|
53
|
+
high:
|
54
|
+
- high_priority_alert
|
55
|
+
medium:
|
56
|
+
- medium_priority_notice
|
57
|
+
low:
|
58
|
+
- low_priority_info
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# Direct Coerce Syntax
|
2
|
+
|
3
|
+
This example demonstrates the simplified syntax for specifying `coerce_to` and other configuration options directly on iteration steps.
|
4
|
+
|
5
|
+
## Direct Syntax
|
6
|
+
|
7
|
+
Configuration options are specified directly on the step:
|
8
|
+
|
9
|
+
```yaml
|
10
|
+
- repeat:
|
11
|
+
until: "condition"
|
12
|
+
coerce_to: boolean
|
13
|
+
print_response: true
|
14
|
+
model: "claude-3-haiku"
|
15
|
+
steps: [...]
|
16
|
+
```
|
17
|
+
|
18
|
+
## Benefits
|
19
|
+
|
20
|
+
1. **Cleaner YAML** - No unnecessary nesting
|
21
|
+
2. **More intuitive** - Configuration options are at the same level as other step properties
|
22
|
+
3. **Consistent** - Matches how other step properties are specified
|
23
|
+
|
24
|
+
## Supported Options
|
25
|
+
|
26
|
+
All step configuration options can be specified directly:
|
27
|
+
- `coerce_to` - Type coercion (boolean, llm_boolean, iterable)
|
28
|
+
- `print_response` - Whether to print LLM responses
|
29
|
+
- `loop` - Auto-loop behavior
|
30
|
+
- `json` - JSON response mode
|
31
|
+
- `params` - Additional parameters
|
32
|
+
- `model` - Model override
|
@@ -0,0 +1,36 @@
|
|
1
|
+
name: Direct Coerce Syntax Demo
|
2
|
+
description: Demonstrates the simplified coerce_to syntax without config blocks
|
3
|
+
|
4
|
+
steps:
|
5
|
+
# Example 1: Direct coerce_to on repeat
|
6
|
+
- repeat:
|
7
|
+
until: "check_api_ready"
|
8
|
+
coerce_to: boolean # Direct syntax - no config block needed
|
9
|
+
max_iterations: 5
|
10
|
+
steps:
|
11
|
+
- check_api_ready:
|
12
|
+
prompt: "Check if the API endpoint returns a 200 status"
|
13
|
+
- wait: 2
|
14
|
+
|
15
|
+
# Example 2: Direct coerce_to on each
|
16
|
+
- get_data_sources:
|
17
|
+
prompt: "List available data sources, one per line"
|
18
|
+
|
19
|
+
- each: "get_data_sources"
|
20
|
+
as: "source"
|
21
|
+
coerce_to: iterable # Direct syntax
|
22
|
+
steps:
|
23
|
+
- validate_source: "Validating {{source}}..."
|
24
|
+
- process_source:
|
25
|
+
prompt: "Process data from {{source}}"
|
26
|
+
|
27
|
+
# Example 3: Multiple configuration options
|
28
|
+
- repeat:
|
29
|
+
until: "all_tests_pass"
|
30
|
+
coerce_to: llm_boolean # Override default
|
31
|
+
print_response: true # Other options work too
|
32
|
+
max_iterations: 10
|
33
|
+
steps:
|
34
|
+
- run_tests: "$(rake test)"
|
35
|
+
- all_tests_pass:
|
36
|
+
prompt: "Did all tests pass successfully?"
|
@@ -1,5 +1,7 @@
|
|
1
1
|
name: Test Grading
|
2
|
-
|
2
|
+
api_token: $(echo $OPENAI_API_KEY)
|
3
|
+
# model: anthropic:claude-opus-4
|
4
|
+
model: gpt-4.1-mini
|
3
5
|
|
4
6
|
tools:
|
5
7
|
- Roast::Tools::Grep
|
@@ -23,16 +25,16 @@ steps:
|
|
23
25
|
|
24
26
|
# set non-default attributes for steps below
|
25
27
|
analyze_coverage:
|
26
|
-
model: gpt-4.1-mini
|
28
|
+
# model: gpt-4.1-mini
|
27
29
|
auto_loop: false
|
28
30
|
json: true
|
29
31
|
|
30
32
|
generate_grades:
|
31
|
-
model: o3
|
33
|
+
# model: o3
|
32
34
|
json: true
|
33
35
|
|
34
36
|
generate_recommendations:
|
35
|
-
model: o3
|
37
|
+
# model: o3
|
36
38
|
auto_loop: false
|
37
39
|
json: true
|
38
40
|
params:
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# JSON Handling Example
|
2
|
+
|
3
|
+
This example demonstrates how Roast handles JSON responses from LLM steps.
|
4
|
+
|
5
|
+
## Key Features
|
6
|
+
|
7
|
+
1. **JSON Arrays**: When a step has `json: true` and returns an array, the result is `flatten.first` - the first element after flattening the array. This is useful when the LLM returns an array with a single object.
|
8
|
+
|
9
|
+
2. **JSON Objects**: Hash/object responses are maintained as proper Ruby hashes in the workflow output.
|
10
|
+
|
11
|
+
3. **Replay Support**: JSON data structures are properly serialized and deserialized when saving/loading workflow state for replay functionality.
|
12
|
+
|
13
|
+
## Running the Example
|
14
|
+
|
15
|
+
```bash
|
16
|
+
bin/roast examples/json_handling/workflow.yml
|
17
|
+
```
|
18
|
+
|
19
|
+
## How It Works
|
20
|
+
|
21
|
+
1. The `fetch_users` step generates a JSON array of user objects
|
22
|
+
2. The `fetch_metadata` step generates a JSON object with metadata
|
23
|
+
3. The `process_data` step can access the structured data using `{{output.fetch_users}}` and `{{output.fetch_metadata}}`
|
24
|
+
4. The structured data is preserved through the workflow, including during replay scenarios
|
25
|
+
|
26
|
+
## Implementation Details
|
27
|
+
|
28
|
+
When `json: true` is set on a step:
|
29
|
+
- Array responses return `flatten.first` - the first element after flattening
|
30
|
+
- Object responses are preserved as hashes
|
31
|
+
- Non-JSON array responses (when `json: false`) are joined with newlines
|
32
|
+
- The data maintains its structure when saved to and loaded from workflow state files
|
@@ -0,0 +1,52 @@
|
|
1
|
+
name: JSON Data Handling Example
|
2
|
+
api_provider: openai
|
3
|
+
model: gpt-4
|
4
|
+
|
5
|
+
tools:
|
6
|
+
- type: write_file
|
7
|
+
allowed_paths:
|
8
|
+
- examples/json_handling/
|
9
|
+
|
10
|
+
steps:
|
11
|
+
- name: fetch_users
|
12
|
+
prompt: |
|
13
|
+
Generate a JSON array of 3 user objects. Each user should have:
|
14
|
+
- id (number)
|
15
|
+
- name (string)
|
16
|
+
- email (string)
|
17
|
+
- active (boolean)
|
18
|
+
json: true
|
19
|
+
|
20
|
+
- name: fetch_metadata
|
21
|
+
prompt: |
|
22
|
+
Generate a JSON object with metadata about a dataset:
|
23
|
+
- total_records (number)
|
24
|
+
- last_updated (ISO date string)
|
25
|
+
- categories (array of strings)
|
26
|
+
- filters (object with status and sort fields)
|
27
|
+
json: true
|
28
|
+
|
29
|
+
- name: process_data
|
30
|
+
prompt: |
|
31
|
+
Based on the users data: {{output.fetch_users}}
|
32
|
+
And metadata: {{output.fetch_metadata}}
|
33
|
+
|
34
|
+
Create a summary report describing:
|
35
|
+
1. How many active users there are
|
36
|
+
2. The categories available
|
37
|
+
3. When the data was last updated
|
38
|
+
|
39
|
+
- name: save_report
|
40
|
+
tool: write_file
|
41
|
+
path: examples/json_handling/report.txt
|
42
|
+
content: |
|
43
|
+
# JSON Data Processing Report
|
44
|
+
|
45
|
+
## Users Data
|
46
|
+
{{output.fetch_users}}
|
47
|
+
|
48
|
+
## Metadata
|
49
|
+
{{output.fetch_metadata}}
|
50
|
+
|
51
|
+
## Summary
|
52
|
+
{{output.process_data}}
|
@@ -0,0 +1,65 @@
|
|
1
|
+
# Smart Coercion Defaults
|
2
|
+
|
3
|
+
This example demonstrates how Roast applies intelligent defaults for boolean coercion based on the type of expression being evaluated.
|
4
|
+
|
5
|
+
## Default Coercion Rules
|
6
|
+
|
7
|
+
When a step is used in a boolean context (like `if`, `unless`, or `until` conditions) and no explicit `coerce_to` is specified, Roast applies these smart defaults:
|
8
|
+
|
9
|
+
1. **Ruby Expressions** (`{{expression}}`) → Regular boolean coercion (`!!value`)
|
10
|
+
- `nil` and `false` are falsy
|
11
|
+
- Everything else is truthy (including 0, empty arrays, etc.)
|
12
|
+
|
13
|
+
2. **Bash Commands** (`$(command)`) → Exit code interpretation
|
14
|
+
- Exit code 0 = true (success)
|
15
|
+
- Non-zero exit code = false (failure)
|
16
|
+
|
17
|
+
3. **Prompt/Step Names** → LLM boolean interpretation
|
18
|
+
- Analyzes natural language responses for yes/no intent
|
19
|
+
- "Yes", "True", "Affirmative" → true
|
20
|
+
- "No", "False", "Negative" → false
|
21
|
+
|
22
|
+
4. **Non-string Values** → Regular boolean coercion
|
23
|
+
|
24
|
+
## Examples
|
25
|
+
|
26
|
+
### Ruby Expression (Regular Boolean)
|
27
|
+
```yaml
|
28
|
+
- repeat:
|
29
|
+
until: "{{counter >= 5}}" # Uses !! coercion
|
30
|
+
steps:
|
31
|
+
- increment: counter
|
32
|
+
```
|
33
|
+
|
34
|
+
### Bash Command (Exit Code)
|
35
|
+
```yaml
|
36
|
+
- repeat:
|
37
|
+
until: "$(test -f /tmp/done)" # True when file exists (exit 0)
|
38
|
+
steps:
|
39
|
+
- wait: 1
|
40
|
+
```
|
41
|
+
|
42
|
+
### Prompt Response (LLM Boolean)
|
43
|
+
```yaml
|
44
|
+
- if: "Should we continue?" # Interprets "Yes, let's continue" as true
|
45
|
+
then:
|
46
|
+
- proceed: "Continuing..."
|
47
|
+
```
|
48
|
+
|
49
|
+
## Overriding Defaults
|
50
|
+
|
51
|
+
You can always override the default coercion by specifying `coerce_to` directly in the step:
|
52
|
+
|
53
|
+
```yaml
|
54
|
+
- each: "get_items"
|
55
|
+
as: "item"
|
56
|
+
coerce_to: iterable # Override default to split into array
|
57
|
+
steps:
|
58
|
+
- process: "{{item}}"
|
59
|
+
```
|
60
|
+
|
61
|
+
## Supported Coercion Types
|
62
|
+
|
63
|
+
- `boolean` - Standard Ruby truthiness (!! operator)
|
64
|
+
- `llm_boolean` - Natural language yes/no interpretation
|
65
|
+
- `iterable` - Convert to array (splits strings on newlines)
|