@eigenpal/cli 0.5.10 → 0.5.12
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.
- package/CHANGELOG.md +24 -0
- package/dist/cli.js +7267 -118
- package/dist/skill/examples/hello-world/workflow.yaml +1 -1
- package/dist/skill/reference/dataset-format.md +63 -9
- package/dist/skill/reference/evaluators.md +8 -1
- package/dist/skill/reference/step-types.md +300 -18
- package/dist/skill/reference/workflow-yaml.md +8 -7
- package/dist/templates/blank/workflow.yaml +1 -2
- package/dist/templates/pdf-extraction/workflow.yaml +1 -1
- package/dist/templates/text-classification/workflow.yaml +1 -1
- package/package.json +1 -1
|
@@ -27,19 +27,28 @@ dataset/
|
|
|
27
27
|
│ │ ├── Contract_2026.pdf
|
|
28
28
|
│ │ └── Appendix.pdf
|
|
29
29
|
│ ├── expected/
|
|
30
|
-
│ │ ├── output.json OPTIONAL — raw ground truth JSON
|
|
30
|
+
│ │ ├── output.json OPTIONAL — raw ground truth JSON (success-expected)
|
|
31
31
|
│ │ └── generated_invoice/ OPTIONAL — expected file output
|
|
32
32
|
│ │ └── Invoice.docx
|
|
33
33
|
│ └── meta.json OPTIONAL — { rowOrder?, annotation?, overrides? }
|
|
34
34
|
│
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
35
|
+
├── invoice-bar/
|
|
36
|
+
│ └── input/
|
|
37
|
+
│ └── arguments.json { amount: 42 }
|
|
38
|
+
│
|
|
39
|
+
└── unsupported-format/
|
|
40
|
+
├── input/
|
|
41
|
+
│ └── arguments.json
|
|
42
|
+
└── expected/
|
|
43
|
+
└── error.json OPTIONAL — failure-expected, mutually exclusive with output.json
|
|
38
44
|
```
|
|
39
45
|
|
|
40
|
-
The `expected/` subfolder mirrors `input/` exactly
|
|
46
|
+
The `expected/` subfolder mirrors `input/` exactly. `output.json` is
|
|
41
47
|
the scalar/object data ground truth, and any expected file outputs go
|
|
42
48
|
in `expected/<docKey>/<file>` folders (one folder per output path).
|
|
49
|
+
`error.json` is the failure-expected counterpart (see "Failure-expected
|
|
50
|
+
examples" below) and cannot coexist with `output.json` for the same
|
|
51
|
+
example.
|
|
43
52
|
|
|
44
53
|
## Rules (every one is enforced server-side at import)
|
|
45
54
|
|
|
@@ -66,9 +75,29 @@ in `expected/<docKey>/<file>` folders (one folder per output path).
|
|
|
66
75
|
`{"contract": "..."}` AND `input/contract/file.pdf` exists, import
|
|
67
76
|
fails with `code: argument_name_collision`.
|
|
68
77
|
- **`expected/output.json`** is OPTIONAL. When present, it must be a
|
|
69
|
-
JSON object that mirrors your workflow's `output:` shape 1:1
|
|
78
|
+
JSON object that mirrors your workflow's `output:` shape 1:1, no
|
|
70
79
|
envelope, no wrapper. A scalar/array at the top level is rejected
|
|
71
|
-
with `code: invalid_expected_output`.
|
|
80
|
+
with `code: invalid_expected_output`. Mutually exclusive with
|
|
81
|
+
`expected/error.json` (see below).
|
|
82
|
+
- **`expected/error.json`** is OPTIONAL and marks the example as
|
|
83
|
+
failure-expected (the workflow is supposed to terminate via
|
|
84
|
+
`control.fail`). Must be a JSON object with at least one of
|
|
85
|
+
`{ code, messageContains, step }`; an empty object is rejected (it
|
|
86
|
+
would match any failure). When set, the scorer matches the example's
|
|
87
|
+
terminal `executions.error` envelope against the fields you specify
|
|
88
|
+
and ignores `expected/output.json` entirely. `code` is the HTTP-style
|
|
89
|
+
status code from `control.fail`'s `statusCode`; `messageContains` is a
|
|
90
|
+
case-sensitive substring of the failure message; `step` is the name
|
|
91
|
+
of the `control.fail` step expected to fire.
|
|
92
|
+
|
|
93
|
+
```json
|
|
94
|
+
// expected/error.json
|
|
95
|
+
{
|
|
96
|
+
"code": 422,
|
|
97
|
+
"messageContains": "unsupported document type",
|
|
98
|
+
"step": "reject-unsupported"
|
|
99
|
+
}
|
|
100
|
+
```
|
|
72
101
|
|
|
73
102
|
> **Note:** the importer is idempotent and accepts both bare objects and
|
|
74
103
|
> pre-wrapped `{ data: { ... } }` forms; bare is preferred.
|
|
@@ -145,9 +174,34 @@ The server materializes:
|
|
|
145
174
|
|
|
146
175
|
Each file is uploaded to S3 with a fresh `fileId`; the original
|
|
147
176
|
filename is preserved on the `files` row and shows up in the dashboard.
|
|
148
|
-
The `expectedOutput` envelope (`{ data?, expectedDocuments? }`) is
|
|
177
|
+
The `expectedOutput` envelope (`{ data?, expectedDocuments?, error? }`) is
|
|
149
178
|
internal storage shape — your dataset folder stays clean (raw
|
|
150
|
-
`output.json`
|
|
179
|
+
`output.json` or `error.json`, plus per-docKey folders).
|
|
180
|
+
|
|
181
|
+
## Failure-expected examples
|
|
182
|
+
|
|
183
|
+
When a workflow uses `control.fail` to reject bad inputs, you usually
|
|
184
|
+
want to test both paths: happy-path examples carry `expected/output.json`
|
|
185
|
+
and assert the workflow completed; rejection examples carry
|
|
186
|
+
`expected/error.json` and assert the workflow failed with the right
|
|
187
|
+
status code.
|
|
188
|
+
|
|
189
|
+
```
|
|
190
|
+
examples/
|
|
191
|
+
├── valid-invoice/
|
|
192
|
+
│ ├── input/...
|
|
193
|
+
│ └── expected/output.json # success-expected
|
|
194
|
+
└── unsupported-format/
|
|
195
|
+
├── input/...
|
|
196
|
+
└── expected/error.json # failure-expected
|
|
197
|
+
# { "code": 422, "step": "reject-unsupported" }
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
The two files are mutually exclusive for the same example (an example
|
|
201
|
+
is either a success-case or a failure-case, never both). The `exact-diff`
|
|
202
|
+
evaluator branches on the envelope shape and reports a clean
|
|
203
|
+
`expectedFailureButSucceeded` or `actualWasUntypedError` reason when
|
|
204
|
+
the actual run doesn't match the expectation.
|
|
151
205
|
|
|
152
206
|
## Validate before pushing
|
|
153
207
|
|
|
@@ -54,7 +54,7 @@ finance) who never see the YAML or the judge prompt. Write it for them:
|
|
|
54
54
|
|
|
55
55
|
## Evaluator types
|
|
56
56
|
|
|
57
|
-
### `exact-diff` — JSON deep-diff against `expected/output.json`
|
|
57
|
+
### `exact-diff` — JSON deep-diff against `expected/output.json` or `expected/error.json`
|
|
58
58
|
|
|
59
59
|
```yaml
|
|
60
60
|
- name: invoice-fields
|
|
@@ -70,6 +70,13 @@ finance) who never see the YAML or the judge prompt. Write it for them:
|
|
|
70
70
|
When set, only those paths are diffed. When absent, the whole object
|
|
71
71
|
is compared.
|
|
72
72
|
|
|
73
|
+
`exact-diff` branches on the example's expected shape: success-expected
|
|
74
|
+
examples (`expected/output.json`) are diffed against the workflow's actual
|
|
75
|
+
output, and failure-expected examples (`expected/error.json`, see
|
|
76
|
+
`reference/dataset-format.md`) are matched against the `control.fail`
|
|
77
|
+
envelope persisted to `executions.error`. The same evaluator handles both
|
|
78
|
+
paths; you do not need a separate evaluator for failure cases.
|
|
79
|
+
|
|
73
80
|
### `llm-judge` — LLM-as-judge scoring
|
|
74
81
|
|
|
75
82
|
```yaml
|
|
@@ -21,11 +21,11 @@ implement at runtime. Server-side validation errors point you here.
|
|
|
21
21
|
|
|
22
22
|
## Categories (high-level map)
|
|
23
23
|
|
|
24
|
-
- `ai.*` — model-backed processing (parse, extract). Cost + latency depend on the model.
|
|
24
|
+
- `ai.*` — model-backed processing (parse, extract, classify). Cost + latency depend on the model.
|
|
25
25
|
- `transform.*` — deterministic data transforms (set, remove, combine, split, merge,
|
|
26
26
|
script, template, pdf-embed, xlsx-to-json). WASM sandboxed where applicable.
|
|
27
27
|
- `action.*` — external side effects (HTTP, invoke another workflow, website reader).
|
|
28
|
-
- `control.*` — flow control (if, foreach, parallel, parallel_map, wait, approval, block).
|
|
28
|
+
- `control.*` — flow control (if, foreach, parallel, parallel_map, wait, approval, block, fail).
|
|
29
29
|
|
|
30
30
|
The full per-type catalog with field tables is auto-generated below from
|
|
31
31
|
`STEP_SCHEMAS`. The high-level map above tells you which family you want;
|
|
@@ -33,22 +33,24 @@ the catalog tells you what fields it takes.
|
|
|
33
33
|
|
|
34
34
|
## When to reach for what
|
|
35
35
|
|
|
36
|
-
| Use case
|
|
37
|
-
|
|
|
38
|
-
| Read a PDF / DOCX / image
|
|
39
|
-
| Pull a typed object from text
|
|
40
|
-
|
|
|
41
|
-
|
|
|
42
|
-
|
|
|
43
|
-
|
|
|
44
|
-
|
|
|
45
|
-
|
|
|
46
|
-
|
|
|
47
|
-
|
|
|
48
|
-
|
|
|
49
|
-
|
|
|
50
|
-
|
|
|
51
|
-
|
|
|
36
|
+
| Use case | Step |
|
|
37
|
+
| --------------------------------- | ------------------------------------------------- |
|
|
38
|
+
| Read a PDF / DOCX / image | `ai.parse` |
|
|
39
|
+
| Pull a typed object from text | `ai.extract` with `config.schema` |
|
|
40
|
+
| Pick one label from a fixed set | `ai.classify` with `config.labels` |
|
|
41
|
+
| Reject bad inputs with a 4xx code | `control.fail` (often after `ai.classify`) |
|
|
42
|
+
| Sum / filter / regex | `transform.script` (NOT Liquid) |
|
|
43
|
+
| Render a DOCX template | `transform.template` |
|
|
44
|
+
| Convert XLSX to JSON | `transform.xlsx-to-json` |
|
|
45
|
+
| Merge or combine objects | `transform.combine` / `transform.merge` |
|
|
46
|
+
| Conditional execution | `if:` on the step (Liquid) or `control.if` |
|
|
47
|
+
| Map over an input array | `forEach:` on the step or `control.foreach` |
|
|
48
|
+
| Concurrent map over an array | `control.parallel_map` |
|
|
49
|
+
| Independent parallel branches | `control.parallel` |
|
|
50
|
+
| Pause for human approval | `control.approval` |
|
|
51
|
+
| External HTTP call | `action.http` |
|
|
52
|
+
| Call another workflow | `action.invoke-workflow` |
|
|
53
|
+
| Fetch a webpage as markdown | `action.website-reader` |
|
|
52
54
|
|
|
53
55
|
## Liquid vs `transform.script`
|
|
54
56
|
|
|
@@ -101,6 +103,247 @@ $ eigenpal workflow step-type get ai.extract | jq '.outputSchema'
|
|
|
101
103
|
Reference user-supplied schema fields the same way:
|
|
102
104
|
`{{ steps.extract.output.extracted.invoiceNumber }}`.
|
|
103
105
|
|
|
106
|
+
## Control containers — nested step shape and scoping
|
|
107
|
+
|
|
108
|
+
`control.parallel`, `control.parallel_map`, `control.foreach`, `control.if`,
|
|
109
|
+
and `control.block` contain nested steps. The auto-generated catalog below
|
|
110
|
+
can't render that shape, so the YAML form lives here. Same for the scoping
|
|
111
|
+
rules — important because the runtime treats nested steps differently from
|
|
112
|
+
top-level ones, and getting the access path wrong silently returns
|
|
113
|
+
`undefined` (no error).
|
|
114
|
+
|
|
115
|
+
### `control.parallel` — independent branches
|
|
116
|
+
|
|
117
|
+
Runs each branch concurrently. Use when several pieces of work share input
|
|
118
|
+
but don't depend on each other.
|
|
119
|
+
|
|
120
|
+
```yaml
|
|
121
|
+
- name: enrich
|
|
122
|
+
type: control.parallel
|
|
123
|
+
branches:
|
|
124
|
+
- name: legal
|
|
125
|
+
steps:
|
|
126
|
+
- name: extract-clauses
|
|
127
|
+
type: ai.extract
|
|
128
|
+
with:
|
|
129
|
+
input: '{{ steps.parse.output.markdown }}'
|
|
130
|
+
schema: { clauses: { type: array, items: { type: string } } }
|
|
131
|
+
- name: financial
|
|
132
|
+
steps:
|
|
133
|
+
- name: extract-totals
|
|
134
|
+
type: ai.extract
|
|
135
|
+
with:
|
|
136
|
+
input: '{{ steps.parse.output.markdown }}'
|
|
137
|
+
schema: { total: { type: number } }
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
**Access the output:** branch step outputs are stored under the parent
|
|
141
|
+
parallel step keyed by branch name, then by inner step name. There is no
|
|
142
|
+
flat top-level entry for branch-internal steps.
|
|
143
|
+
|
|
144
|
+
```liquid
|
|
145
|
+
{{ steps.enrich.output.legal.extract-clauses.clauses }}
|
|
146
|
+
{{ steps.enrich.output.financial.extract-totals.total }}
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
**Scope inside a branch:** top-level steps that ran before the parallel,
|
|
150
|
+
plus earlier siblings in the same branch. **Other branches' steps are
|
|
151
|
+
NOT visible** — each branch runs in an isolated child scope.
|
|
152
|
+
|
|
153
|
+
```liquid
|
|
154
|
+
# Inside branch `legal`, second step:
|
|
155
|
+
{{ steps.parse.output.markdown }} # ancestor: visible
|
|
156
|
+
{{ steps.extract-clauses.output.clauses }} # sibling: visible
|
|
157
|
+
{{ steps.extract-totals.output.total }} # other branch: UNDEFINED
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
### `control.parallel_map` — fan-out over an array
|
|
161
|
+
|
|
162
|
+
Runs the inner step block concurrently for each item, up to `concurrency`.
|
|
163
|
+
|
|
164
|
+
```yaml
|
|
165
|
+
- name: process-pages
|
|
166
|
+
type: control.parallel_map
|
|
167
|
+
items: '{{ steps.split.output.pages }}'
|
|
168
|
+
as: page
|
|
169
|
+
indexAs: i # optional
|
|
170
|
+
concurrency: 5 # default 5, max 50
|
|
171
|
+
steps:
|
|
172
|
+
- name: extract-fields
|
|
173
|
+
type: ai.extract
|
|
174
|
+
with:
|
|
175
|
+
input: '{{ page.text }}'
|
|
176
|
+
schema: { fields: { type: object } }
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
**Access the output:** iteration results are returned as an array in the
|
|
180
|
+
original input order under the parent step's `items` field.
|
|
181
|
+
|
|
182
|
+
```liquid
|
|
183
|
+
{{ steps.process-pages.output.items[0].extract-fields.fields }}
|
|
184
|
+
{{ steps.process-pages.output.count }} # number of completed iterations
|
|
185
|
+
{{ steps.process-pages.output.totalIterations }}
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
`steps.extract-fields.output.fields` (the flat form) is **not** addressable
|
|
189
|
+
from outside the parallel_map.
|
|
190
|
+
|
|
191
|
+
**Multi-step iterations only key the LAST step's output into `items[i]`.**
|
|
192
|
+
If your iteration body has steps `[parse, extract]`, each `items[i]` is
|
|
193
|
+
`{ extract: <extract output> }` — `parse`'s output is gone. To preserve
|
|
194
|
+
intermediate fields, end the body with a `transform.script` that returns
|
|
195
|
+
the combined shape:
|
|
196
|
+
|
|
197
|
+
```yaml
|
|
198
|
+
steps:
|
|
199
|
+
- name: parse
|
|
200
|
+
type: ai.parse
|
|
201
|
+
with: { ... }
|
|
202
|
+
- name: extract
|
|
203
|
+
type: ai.extract
|
|
204
|
+
with: { input: '{{ steps.parse.output.markdown }}', ... }
|
|
205
|
+
- name: combine
|
|
206
|
+
type: transform.script
|
|
207
|
+
with:
|
|
208
|
+
inputs:
|
|
209
|
+
parsed: '{{ steps.parse.output }}'
|
|
210
|
+
extracted: '{{ steps.extract.output }}'
|
|
211
|
+
function: |
|
|
212
|
+
function script(parsed, extracted): { parsed: any; extracted: any } {
|
|
213
|
+
return { parsed, extracted };
|
|
214
|
+
}
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
Now `items[i].combine.parsed` / `items[i].combine.extracted` both survive.
|
|
218
|
+
|
|
219
|
+
### `control.foreach` — sequential loop over an array
|
|
220
|
+
|
|
221
|
+
Same shape as `parallel_map` minus `concurrency`; iterations run in order.
|
|
222
|
+
Use when each iteration depends on the previous, or when concurrency would
|
|
223
|
+
cause downstream rate limits.
|
|
224
|
+
|
|
225
|
+
```yaml
|
|
226
|
+
- name: per-row
|
|
227
|
+
type: control.foreach
|
|
228
|
+
items: '{{ steps.fetch.output.rows }}'
|
|
229
|
+
as: row
|
|
230
|
+
steps:
|
|
231
|
+
- name: persist
|
|
232
|
+
type: action.http-request
|
|
233
|
+
with:
|
|
234
|
+
method: POST
|
|
235
|
+
url: 'https://example.com/api/rows/{{ row.id }}'
|
|
236
|
+
body: '{{ row }}'
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
**Access the output:** same shape as parallel_map — an `items[]` array of
|
|
240
|
+
per-iteration results, in iteration order. Same "only the LAST inner step
|
|
241
|
+
is keyed into `items[i]`" rule applies (use a trailing `transform.script`
|
|
242
|
+
to preserve intermediate outputs).
|
|
243
|
+
|
|
244
|
+
> ⚠️ Foreach does NOT isolate scopes — the last iteration's inner step
|
|
245
|
+
> outputs technically persist on the top-level `steps` map and `steps.<inner>.output.x`
|
|
246
|
+
> resolves at runtime. That's an only-the-last-row footgun; the autocomplete
|
|
247
|
+
> hides these names and you should treat `steps.<foreach>.output.items[]`
|
|
248
|
+
> as the only correct access.
|
|
249
|
+
|
|
250
|
+
### `control.if` — conditional branch
|
|
251
|
+
|
|
252
|
+
```yaml
|
|
253
|
+
- name: route
|
|
254
|
+
type: control.if
|
|
255
|
+
condition: '{{ input.priority == "high" }}'
|
|
256
|
+
then:
|
|
257
|
+
- name: rush-extract
|
|
258
|
+
type: ai.extract
|
|
259
|
+
with: { ... }
|
|
260
|
+
else:
|
|
261
|
+
- name: normal-extract
|
|
262
|
+
type: ai.extract
|
|
263
|
+
with: { ... }
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
**Access the output:** the `if` step's output is
|
|
267
|
+
|
|
268
|
+
```ts
|
|
269
|
+
{
|
|
270
|
+
condition: boolean, // evaluated condition
|
|
271
|
+
branch: 'then' | 'else', // which branch ran
|
|
272
|
+
result: <last inner step's output> | null // null when chosen branch is empty
|
|
273
|
+
}
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
The canonical way to consume the result is through the if step itself:
|
|
277
|
+
|
|
278
|
+
```liquid
|
|
279
|
+
{{ steps.route.output.branch }} # "then" or "else"
|
|
280
|
+
{{ steps.route.output.result.foo }} # field from the branch's last step
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
`steps.<innerName>.output` references from outside the `if` also resolve
|
|
284
|
+
at runtime (unlike `parallel`/`parallel_map`, `if` does not isolate
|
|
285
|
+
scopes), but the template breaks the moment the OTHER branch runs. Use
|
|
286
|
+
`steps.<if>.output.result` so the access is branch-agnostic — or compute
|
|
287
|
+
the final shape in a trailing `transform.script` that handles both cases.
|
|
288
|
+
|
|
289
|
+
### `control.block` — call a reusable workflow block
|
|
290
|
+
|
|
291
|
+
```yaml
|
|
292
|
+
- name: invoice-flow
|
|
293
|
+
type: control.block
|
|
294
|
+
blockName: parse-invoice # other workflow's `name:`
|
|
295
|
+
inputs:
|
|
296
|
+
contract: '{{ input.contract }}'
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
**Access the output:** the block's declared output fields are exposed as
|
|
300
|
+
top-level keys under `steps.<this-step>.output`. Block-internal steps are
|
|
301
|
+
not visible — the block runs in an isolated scope.
|
|
302
|
+
|
|
303
|
+
## Classify-and-fail pattern
|
|
304
|
+
|
|
305
|
+
`ai.classify` + `control.fail` together let a workflow reject bad inputs
|
|
306
|
+
fast with a typed HTTP-style status code that callers (and evals) can
|
|
307
|
+
match against. The classifier picks one label from a closed set; the
|
|
308
|
+
fail step terminates the run when the label is one you don't want to
|
|
309
|
+
process.
|
|
310
|
+
|
|
311
|
+
```yaml
|
|
312
|
+
- name: parse
|
|
313
|
+
type: ai.parse
|
|
314
|
+
with:
|
|
315
|
+
input: '{{ input.document }}'
|
|
316
|
+
|
|
317
|
+
- name: classify
|
|
318
|
+
type: ai.classify
|
|
319
|
+
with:
|
|
320
|
+
input: '{{ steps.parse.output.text }}'
|
|
321
|
+
labels:
|
|
322
|
+
- name: invoice
|
|
323
|
+
description: A vendor invoice with line items and totals.
|
|
324
|
+
- name: contract
|
|
325
|
+
description: A multi-page contract or agreement.
|
|
326
|
+
- name: other
|
|
327
|
+
description: Anything that is not an invoice or contract.
|
|
328
|
+
|
|
329
|
+
- name: reject-unsupported
|
|
330
|
+
type: control.fail
|
|
331
|
+
condition: '{{ steps.classify.output.label == "other" }}'
|
|
332
|
+
statusCode: 422
|
|
333
|
+
message: 'Unsupported document: {{ steps.classify.output.reason }}'
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
> `control.fail` config is step-level (no `with:`), matching every other
|
|
337
|
+
> `control.*` step. `condition`, `statusCode`, and `message` sit directly
|
|
338
|
+
> on the step node.
|
|
339
|
+
|
|
340
|
+
The synchronous run endpoint surfaces the `statusCode` as the HTTP
|
|
341
|
+
response status; async runs persist `{ code, message, step }` to
|
|
342
|
+
`executions.error` so the eval scorer can match against
|
|
343
|
+
`expected/error.json` (see `reference/dataset-format.md`). When
|
|
344
|
+
`condition` is omitted, `control.fail` always fails when reached, so
|
|
345
|
+
compose with `control.if` for legacy gating.
|
|
346
|
+
|
|
104
347
|
<!-- GENERATED:STEP_CATALOG START -->
|
|
105
348
|
## Full catalog
|
|
106
349
|
|
|
@@ -173,6 +416,29 @@ Split a parsed document into named sections using an LLM. Consumes ai.parse outp
|
|
|
173
416
|
| --- | --- | --- | --- | --- |
|
|
174
417
|
| `splits` | array<object> | yes | | Sections found in the document, in page order. Absent sections are omitted. |
|
|
175
418
|
|
|
419
|
+
#### `ai.classify` — Classify
|
|
420
|
+
|
|
421
|
+
Classify a document or text into one of a fixed label set using an LLM. Output exposes the picked label (constrained to the configured names), a coarse confidence, and a short justification. Pair with control.fail to reject documents that match an undesired label.
|
|
422
|
+
|
|
423
|
+
**Config** (in `step.with`):
|
|
424
|
+
|
|
425
|
+
| Field | Type | Required | Default | Description |
|
|
426
|
+
| --- | --- | --- | --- | --- |
|
|
427
|
+
| `input` | string | yes | | Template expression for the text to classify. Typically the output of ai.parse, e.g. "{{ steps.parse.output.text }}". |
|
|
428
|
+
| `labels` | array<object> | yes | | Allowed labels. The LLM is constrained to pick exactly one of these names. |
|
|
429
|
+
| `prompt` | string | no | | Custom classification instructions appended to the system prompt. Use to clarify edge cases or emphasize evidence the model should weigh. |
|
|
430
|
+
| `provider` | string | no | | Provider ID from eigenpal.config.yaml (e.g. "openai-gpt4o-mini"). Falls back to the tenant default LLM provider when omitted. |
|
|
431
|
+
| `model` | string | no | | Model override (advanced) |
|
|
432
|
+
| `maxInputTokens` | integer | no | | Max input tokens. Truncates input text when exceeded. Omit for no limit. |
|
|
433
|
+
|
|
434
|
+
**Output:** `object`
|
|
435
|
+
|
|
436
|
+
| Field | Type | Required | Default | Description |
|
|
437
|
+
| --- | --- | --- | --- | --- |
|
|
438
|
+
| `label` | string | yes | | The selected label name (one of the configured labels). Compare against literal strings to gate downstream steps. |
|
|
439
|
+
| `confidence` | `"low"` \| `"medium"` \| `"high"` | yes | | LLM confidence in the classification. Coarse enum — numeric scores cluster meaninglessly at 0.85-0.95. |
|
|
440
|
+
| `reason` | string | yes | | Short justification for the chosen label — useful for debugging. |
|
|
441
|
+
|
|
176
442
|
### Transform steps — deterministic data transforms
|
|
177
443
|
|
|
178
444
|
#### `transform.set` — Set Value
|
|
@@ -568,4 +834,20 @@ Execute a reusable block workflow inline with input/output mapping
|
|
|
568
834
|
**Output:** `record<string, unknown>`
|
|
569
835
|
|
|
570
836
|
> Output from block's declared output mapping
|
|
837
|
+
|
|
838
|
+
#### `control.fail` — Fail
|
|
839
|
+
|
|
840
|
+
Terminate the workflow with a typed status code + message. With an optional condition, only fails when the condition is truthy; otherwise always fails when reached. Pair with ai.classify or any prior step to fail fast on bad inputs.
|
|
841
|
+
|
|
842
|
+
**Config** (at step level):
|
|
843
|
+
|
|
844
|
+
| Field | Type | Required | Default | Description |
|
|
845
|
+
| --- | --- | --- | --- | --- |
|
|
846
|
+
| `condition` | string | no | | Optional LiquidJS expression. When set, the step only fails if this evaluates truthy; when omitted, it always fails when reached (compose with control.if for legacy gating). |
|
|
847
|
+
| `statusCode` | integer | no | `422` | HTTP-style status code returned to the caller (sync runs) and persisted on the execution. Default 422 (Unprocessable Entity). |
|
|
848
|
+
| `message` | string | yes | | Human-readable failure message. Supports template expressions, e.g. "Document classified as {{ steps.classify.output.label }}". |
|
|
849
|
+
|
|
850
|
+
**Output:** `unknown`
|
|
851
|
+
|
|
852
|
+
> control.fail never produces output — it terminates the workflow when triggered.
|
|
571
853
|
<!-- GENERATED:STEP_CATALOG END -->
|
|
@@ -16,7 +16,7 @@ eigenpal workflow step-type get <type> # JSON Schema for one type's config + o
|
|
|
16
16
|
name: extract-invoices # string, lowercase kebab/snake-case, unique within tenant
|
|
17
17
|
version: 1.0.0 # bare semver `MAJOR.MINOR.PATCH`, no leading `v`
|
|
18
18
|
description: Parse a PDF and extract invoice fields. # string, optional
|
|
19
|
-
triggerMethods: [api] # array, ≥1 entry
|
|
19
|
+
triggerMethods: [{ type: api }] # array, ≥1 entry with type: api | manual | email
|
|
20
20
|
inputs: [...] # array of input definitions (see below)
|
|
21
21
|
steps: [...] # array of steps, executed in topological order
|
|
22
22
|
output: {...} # object — the workflow's return shape
|
|
@@ -36,13 +36,13 @@ folder.
|
|
|
36
36
|
|
|
37
37
|
### `triggerMethods`
|
|
38
38
|
|
|
39
|
-
Required. Each entry adds an invocation surface:
|
|
39
|
+
Required. Each entry is an object that adds an invocation surface:
|
|
40
40
|
|
|
41
|
-
- `api` — the workflow can be called via REST or the CLI's `workflow execution run`
|
|
42
|
-
- `
|
|
43
|
-
- `
|
|
41
|
+
- `{ type: api }` — the workflow can be called via REST or the CLI's `workflow execution run`
|
|
42
|
+
- `{ type: manual }` — the workflow can be started manually
|
|
43
|
+
- `{ type: email, whitelist?: ... }` — inbound email
|
|
44
44
|
|
|
45
|
-
If `api` is missing, `eigenpal workflow execution run`
|
|
45
|
+
If `{ type: api }` is missing, `eigenpal workflow execution run` rejects with a
|
|
46
46
|
403 / "Workflow does not have API trigger enabled."
|
|
47
47
|
|
|
48
48
|
### `inputs`
|
|
@@ -140,7 +140,7 @@ See `reference/step-types.md` for the full catalog. Categories:
|
|
|
140
140
|
- `action.*` — side-effectful operations (HTTP, email, connectors)
|
|
141
141
|
- `ai.*` — model-backed processing (parse, extract, classify, judge)
|
|
142
142
|
- `transform.*` — deterministic data transforms (script, template, merge)
|
|
143
|
-
- `control.*` — flow control (if, for-each, parallel-map)
|
|
143
|
+
- `control.*` — flow control (if, for-each, parallel-map, fail)
|
|
144
144
|
|
|
145
145
|
When in doubt, use `eigenpal workflow step-type get <type>` — it returns
|
|
146
146
|
the exact JSON Schema for the step's `config` + `output` so you know
|
|
@@ -486,5 +486,6 @@ _Generated from `WorkflowDefinitionSchema` in `@eigenpal/types/src/workflow/work
|
|
|
486
486
|
| `description` | string | no | | |
|
|
487
487
|
| `required` | boolean | no | `true` | |
|
|
488
488
|
| `default` | unknown | no | | |
|
|
489
|
+
| `values` | array<string> | no | | |
|
|
489
490
|
| `items` | object | no | | |
|
|
490
491
|
<!-- GENERATED:WORKFLOW_REFERENCE END -->
|
|
@@ -4,9 +4,8 @@ description: Replace this with what your workflow does.
|
|
|
4
4
|
|
|
5
5
|
# `triggerMethods` controls how this workflow can be invoked:
|
|
6
6
|
# - api: workflow can be called via the REST API and CLI
|
|
7
|
-
# - cron: workflow can be scheduled (set `cron` field below)
|
|
8
7
|
triggerMethods:
|
|
9
|
-
- api
|
|
8
|
+
- type: api
|
|
10
9
|
|
|
11
10
|
# `inputs` declares the schema of arguments the workflow accepts. Each entry
|
|
12
11
|
# becomes a top-level key on the call's `triggerInput` object. Use `file: true`
|