Class FlowBuilder
EasyAI.flow() pipeline: you lay out the named steps in the order they must
run, then build() returns a ready-to-run Flow.
Familiar analogy: writing a recipe card, or laying out the stations of an assembly line
in order. Each step(String, FlowStep) call adds the next station; nothing runs until you
build() the line and Flow.run(Object) it.
Flow flow = EasyAI.flow()
.step("understand", ctx -> EasyAI.extract(OrderRequest.class).from(ctx.inputText()))
.step("checkStock", ctx -> inventory.checkStock(ctx.get("understand", OrderRequest.class)))
.step("pay", ctx -> payment.charge(ctx.get("understand", OrderRequest.class)))
.step("summarize", ctx -> EasyAI.chat().build().send("Summarize:\n" + ctx.trail()))
.withEventListener(e -> log.info("{}", e))
.build();
Place in the chain
EasyAI.flow() → new FlowBuilder()
→ .step(name, fn) (repeated, order preserved) [.withEventListener(...)]
→ .build() → Flow → Flow.run(input)
- See Also:
-
Method Summary
Modifier and TypeMethodDescriptionbuild()Builds the ready-to-runFlowfrom the steps registered so far.Attaches a fallback alternative to the most recently added step: if that step's primary work throws, this alternative is tried instead (and furtherorElsecalls chain more alternatives, tried in order until one succeeds).Appends a named step to the pipeline.stepIf(String name, Predicate<FlowContext> condition, FlowStep<?> step) Appends a step that runs only if the given condition holds at that point in the flow; otherwise the step is skipped entirely (it stores no result, soctx.has(name)staysfalsefor later steps).withEventListener(EasyAIListener eventListener) Registers a transport-agnosticEasyAIListenerthat receives the flow's liveEasyAIEventstream (STARTED→STEP_STARTED/STEPper step →FINISHED/ERROR).
-
Method Details
-
step
Appends a named step to the pipeline.Steps run in the order you add them. The
nameis how later steps refer back to this step's result (ctx.get(name, Type.class)), how it appears inFlowContext.trail(), and how aFlowExceptionidentifies it if it fails — so pick a short, meaningful name ("checkStock","pay"). Names must be unique within a flow.Do whatever you need inside the step: call plain Java services, or reach for the LLM (
EasyAI.extract(...),EasyAI.chat()) at the edges where language is the point.- Parameters:
name- a short, unique, non-blank name for this stepstep- the work to run — a function of the sharedFlowContext- Returns:
- this builder, for chaining
- Throws:
IllegalArgumentException- ifnameis blank,stepisnull, or the name is already used
-
stepIf
Appends a step that runs only if the given condition holds at that point in the flow; otherwise the step is skipped entirely (it stores no result, soctx.has(name)staysfalsefor later steps).This makes a branch a first-class, named, skippable step — visible in
FlowContext.trail(), in the live event stream (a skipped step emits aSTEP/WARNING"skipped" row), and testable on its own. Use it when the branch is a whole sub-path worth naming; for a trivial in-step choice a plain Javaifinside a normalstep(String, FlowStep)is still perfectly fine.Familiar analogy: a station on the assembly line with a gate in front of it — the folder only rolls in if the gate condition is met, otherwise it slides straight past.
.step("checkStock", ctx -> inventory.checkStock(ctx.get("understand", OrderRequest.class))) .stepIf("reserve", ctx -> !ctx.get("checkStock", Boolean.class), // only when NOT in stock ctx -> inventory.getFromWarehouse(ctx.get("understand", OrderRequest.class)))- Parameters:
name- a short, unique, non-blank name for this stepcondition- the guard evaluated against the liveFlowContextwhen the flow reaches this step; the step runs only if it returnstruestep- the work to run when the condition holds- Returns:
- this builder, for chaining
- Throws:
IllegalArgumentException- ifnameis blank/duplicate, orcondition/stepis null
-
orElse
Attaches a fallback alternative to the most recently added step: if that step's primary work throws, this alternative is tried instead (and furtherorElsecalls chain more alternatives, tried in order until one succeeds). If every attempt throws, the flow aborts with aFlowExceptionnaming the step.This is the deterministic, declared form of "if a step fails, try another way" — you name the alternatives up front; nothing is discovered or searched. The alternative receives the same
FlowContextas the primary, and whichever attempt succeeds stores its result under the step's name.Familiar analogy: a backup machine at the same station — if the primary jams, the folder is handed to the backup; only if all of them jam does the line stop.
.step("reserve", ctx -> inventory.reserveFromMain(ctx.get("understand", OrderRequest.class))) .orElse(ctx -> inventory.reserveFromWarehouse(ctx.get("understand", OrderRequest.class), "WH-EU")) .orElse(ctx -> inventory.backorder(ctx.get("understand", OrderRequest.class)))- Parameters:
alternative- the fallback work to try if the current step's primary (and any earlier alternatives) throw- Returns:
- this builder, for chaining
- Throws:
IllegalStateException- if no step has been added yetIllegalArgumentException- ifalternativeis null
-
withEventListener
Registers a transport-agnosticEasyAIListenerthat receives the flow's liveEasyAIEventstream (STARTED→STEP_STARTED/STEPper step →FINISHED/ERROR).This is the same observability hook
agent()uses, so a flow run can feed a log, a metric, or the TabForge demo's Activity panel with no extra plumbing. Optional — omit it and the flow runs silently.- Parameters:
eventListener- the listener to receive events, ornullto disable emission- Returns:
- this builder, for chaining
- See Also:
-
build
Builds the ready-to-runFlowfrom the steps registered so far.- Returns:
- a new
Flow - Throws:
IllegalStateException- if no steps were added
-