authoring exmaples
This commit is contained in:
+5
-1
@@ -20,6 +20,8 @@ This repository has three main packages plus examples and tests.
|
||||
- `wf_authoring`: public authoring facade.
|
||||
- `wf_authoring.WorkflowBuilder`: graph construction.
|
||||
- `wf_authoring.node`: typed Python function to `NodeSpec`.
|
||||
- [`docs/wf_authoring_control_flow.md`](wf_authoring_control_flow.md): when to
|
||||
use `branch`, `handle`, `match`, `when`, and `choose`.
|
||||
- `wf_mcp`: public MCP facade.
|
||||
- `wf-mcp`: CLI script from `pyproject.toml`.
|
||||
- `wf_mcp.broker.WfMcpService.get_catalog()`: backend MCP catalog snapshots.
|
||||
@@ -28,9 +30,11 @@ This repository has three main packages plus examples and tests.
|
||||
|
||||
## Examples
|
||||
|
||||
`examples/demo_workflow.py` contains the declared demo workflow and demo node
|
||||
- `examples/demo_workflow.py` contains the declared demo workflow and demo node
|
||||
registry used by `main.py` and workflow tests. It is intentionally outside
|
||||
`wf_core` so the kernel package does not carry fixture/demo code.
|
||||
- `examples/authoring_control_flow.py` demonstrates `WorkflowBuilder.branch`,
|
||||
`handle`, `match`, `when`, `choose`, and `use_ref` with executable examples.
|
||||
|
||||
## Tests
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ Each public control-flow method should name one decision mechanism.
|
||||
| `match` | one graph value | compare that value against equality cases |
|
||||
| `when` | one boolean condition | route through `true` / `false` |
|
||||
| `choose` | ordered boolean conditions | first true condition wins |
|
||||
| future `handle` | several source/outcome pairs | send shared outcomes to one target |
|
||||
| `handle` | several source/outcome pairs | send shared outcomes to one target |
|
||||
|
||||
Future fluent builders or operator sugar must call these methods rather than
|
||||
constructing edges/conditions independently.
|
||||
@@ -174,13 +174,36 @@ Recommended behavior during compatibility:
|
||||
The public docs should prefer only:
|
||||
|
||||
- `branch`
|
||||
- `handle`
|
||||
- `match`
|
||||
- `when`
|
||||
- `choose`
|
||||
|
||||
## Shared Outcome Handlers
|
||||
|
||||
`handle()` is the reverse-shaped companion to `branch()`: it connects several
|
||||
source/outcome pairs to one shared target.
|
||||
|
||||
```python
|
||||
g.handle(
|
||||
(lookup_user, "error"),
|
||||
(charge_card, "error"),
|
||||
to=fail,
|
||||
)
|
||||
```
|
||||
|
||||
Meaning:
|
||||
|
||||
```text
|
||||
lookup_user.error -> fail
|
||||
charge_card.error -> fail
|
||||
```
|
||||
|
||||
It does not create a join, wait for multiple branches, or inspect state. It is
|
||||
just outcome-edge sugar for the common "several things fail the same way" case.
|
||||
|
||||
## Not In This Pass
|
||||
|
||||
- reverse-branch/shared handlers (`handle`)
|
||||
- fluent/cursor builder APIs
|
||||
- operator overloading
|
||||
- graph-as-node/subgraph support
|
||||
|
||||
@@ -0,0 +1,213 @@
|
||||
# `wf_authoring` Control Flow
|
||||
|
||||
Use this document when choosing how to wire branches with
|
||||
`WorkflowBuilder`.
|
||||
|
||||
The authoring API intentionally separates different control-flow ideas instead
|
||||
of putting them all behind one overloaded method.
|
||||
|
||||
| Method | Use when | Creates condition nodes? |
|
||||
| --- | --- | --- |
|
||||
| `branch` | an existing step already returned an outcome label | no |
|
||||
| `handle` | several source/outcome pairs should go to one target | no |
|
||||
| `match` | one state/input/context value should equal one of several values | yes |
|
||||
| `when` | one boolean expression chooses between two targets | yes |
|
||||
| `choose` | ordered boolean expressions choose the first matching target | yes |
|
||||
|
||||
`route()` still exists only as deprecated compatibility sugar. New code should
|
||||
use `match()` or `when()` directly.
|
||||
|
||||
## `branch`: Route Node Outcomes
|
||||
|
||||
Use `branch()` when a node already decides its own outcome.
|
||||
|
||||
```python
|
||||
router = g.use(classify_message)
|
||||
send = g.use(send_email)
|
||||
skip = g.use(skip_email)
|
||||
fail = g.use(runtime_error)
|
||||
|
||||
branches = g.branch(
|
||||
router,
|
||||
{
|
||||
"send": send,
|
||||
"skip": skip,
|
||||
"error": fail,
|
||||
},
|
||||
)
|
||||
```
|
||||
|
||||
This only adds edges:
|
||||
|
||||
```text
|
||||
classify_message.send -> send_email
|
||||
classify_message.skip -> skip_email
|
||||
classify_message.error -> runtime_error
|
||||
```
|
||||
|
||||
The return value is a `BranchResult`. It exposes the resolved source and lets
|
||||
tests or later code retrieve targets by outcome:
|
||||
|
||||
```python
|
||||
assert branches.source is router
|
||||
assert branches["send"] is send
|
||||
```
|
||||
|
||||
## `handle`: Shared Outcome Target
|
||||
|
||||
Use `handle()` when several steps should route the same kind of outcome to one
|
||||
target.
|
||||
|
||||
```python
|
||||
fail = g.use(runtime_error)
|
||||
|
||||
errors = g.handle(
|
||||
(lookup_user, "error"),
|
||||
(charge_card, "error"),
|
||||
(send_receipt, "error"),
|
||||
to=fail,
|
||||
)
|
||||
```
|
||||
|
||||
This is not a join and it does not wait for multiple branches. It only writes
|
||||
edges:
|
||||
|
||||
```text
|
||||
lookup_user.error -> fail
|
||||
charge_card.error -> fail
|
||||
send_receipt.error -> fail
|
||||
```
|
||||
|
||||
The return value is a `HandleResult` with the shared target and the resolved
|
||||
source/outcome pairs.
|
||||
|
||||
## `match`: Equality Dispatch
|
||||
|
||||
Use `match()` when one graph value chooses a target by equality.
|
||||
|
||||
```python
|
||||
decision = g.match(
|
||||
state("status"),
|
||||
{
|
||||
"approved": approve,
|
||||
"rejected": reject,
|
||||
"pending": wait,
|
||||
},
|
||||
default=fail,
|
||||
)
|
||||
```
|
||||
|
||||
This lowers to an ordered chain of generated condition nodes:
|
||||
|
||||
```text
|
||||
if state.status == "approved": approve
|
||||
elif state.status == "rejected": reject
|
||||
elif state.status == "pending": wait
|
||||
else: fail
|
||||
```
|
||||
|
||||
Condition ids are source-derived by default, such as `state_status`,
|
||||
`state_status_2`, and so on. Pass `id="status_choice"` when stable generated
|
||||
ids matter.
|
||||
|
||||
The return value is a `DecisionResult`:
|
||||
|
||||
```python
|
||||
g.set_entry_point(decision.entry)
|
||||
assert decision["approved"] is approve
|
||||
assert decision["default"] is fail
|
||||
```
|
||||
|
||||
## `when`: Boolean Dispatch
|
||||
|
||||
Use `when()` when one boolean expression chooses between two targets.
|
||||
|
||||
```python
|
||||
decision = g.when(
|
||||
state("retry_count").lt(3),
|
||||
then=retry,
|
||||
otherwise=fail,
|
||||
)
|
||||
```
|
||||
|
||||
This lowers to one condition node with `true` and `false` edges.
|
||||
|
||||
The return value is also a `DecisionResult`:
|
||||
|
||||
```python
|
||||
assert decision[True] is retry
|
||||
assert decision[False] is fail
|
||||
```
|
||||
|
||||
## `choose`: Ordered Predicate Chain
|
||||
|
||||
Use `choose()` when the graph should try several boolean expressions in order
|
||||
and route to the first true target.
|
||||
|
||||
```python
|
||||
decision = g.choose(
|
||||
(state("score").ge(90), gold),
|
||||
(state("score").ge(70), silver),
|
||||
(state("score").ge(50), bronze),
|
||||
default=fail,
|
||||
id="score_tier",
|
||||
)
|
||||
```
|
||||
|
||||
This lowers to:
|
||||
|
||||
```text
|
||||
if state.score >= 90: gold
|
||||
elif state.score >= 70: silver
|
||||
elif state.score >= 50: bronze
|
||||
else: fail
|
||||
```
|
||||
|
||||
`choose()` is still one explicit call. Fluent or operator-heavy syntax can be
|
||||
built on top later, but should delegate to this API rather than rebuilding edge
|
||||
logic itself.
|
||||
|
||||
## Defaults
|
||||
|
||||
`match()`, `when()`, and `choose()` default their fallback path to the standard
|
||||
`runtime_error` node. This makes missing cases fail loudly instead of silently
|
||||
ending or continuing with unclear state.
|
||||
|
||||
Pass an explicit `default=` or `otherwise=` when the fallback is valid business
|
||||
logic.
|
||||
|
||||
## `NodeSpec` Targets
|
||||
|
||||
`connect()`, `branch()`, `handle()`, `match()`, `when()`, and `choose()` accept
|
||||
either existing step refs or `NodeSpec` objects as targets. Passing a `NodeSpec`
|
||||
creates a fresh `use()` step with auto-mapping and an auto id.
|
||||
|
||||
Use existing step refs when the same node use should be shared. Pass a
|
||||
`NodeSpec` when you want a new use at that point in the graph.
|
||||
|
||||
## Deprecated `route`
|
||||
|
||||
`route()` is a compatibility shim:
|
||||
|
||||
- `route(state("x"), {"a": step})` forwards to `match(...)`.
|
||||
- `route(state("x").exists(), {True: step})` forwards to `when(...)`.
|
||||
|
||||
It emits a `DeprecationWarning` and should not appear in new examples.
|
||||
|
||||
## Drafts
|
||||
|
||||
Workflow drafts currently expose only explicit outcome routes:
|
||||
|
||||
```json
|
||||
{
|
||||
"routes": {
|
||||
"classify": {
|
||||
"send": "send_email",
|
||||
"skip": "__end__"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Draft JSON does not yet have `match`, `when`, or `choose` sugar. Add that only
|
||||
after the Python authoring surface stays stable enough to be mirrored.
|
||||
@@ -0,0 +1,357 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
from wf_authoring import NodeReturn, NodeSpec, WorkflowBuilder, node, outcome, state
|
||||
from wf_core import END
|
||||
|
||||
|
||||
class TextInput(BaseModel):
|
||||
"""Common workflow input used by the authoring examples."""
|
||||
|
||||
text: str
|
||||
|
||||
|
||||
class ExampleState(BaseModel):
|
||||
"""Small shared state shape so examples can focus on control flow."""
|
||||
|
||||
message: str = ""
|
||||
status: str = ""
|
||||
length: int = 0
|
||||
|
||||
|
||||
class MessageOutput(BaseModel):
|
||||
"""Common workflow output payload."""
|
||||
|
||||
message: str
|
||||
|
||||
|
||||
class StatusOutput(BaseModel):
|
||||
"""Intermediate node output that records a status in workflow state."""
|
||||
|
||||
status: str
|
||||
|
||||
|
||||
class MetricsOutput(BaseModel):
|
||||
"""Intermediate node output used by condition examples."""
|
||||
|
||||
message: str
|
||||
length: int
|
||||
|
||||
|
||||
@node(outcomes=("send", "skip", "error"))
|
||||
def classify_message(input: TextInput) -> NodeReturn[MessageOutput]:
|
||||
"""Choose an outcome directly from node logic."""
|
||||
if input.text == "bad":
|
||||
return outcome("error", MessageOutput(message="classification failed"))
|
||||
if "send" in input.text:
|
||||
return outcome("send", MessageOutput(message=input.text))
|
||||
return outcome("skip", MessageOutput(message=input.text))
|
||||
|
||||
|
||||
@node(outcomes=("ok", "error"))
|
||||
def lookup_message(input: TextInput) -> NodeReturn[MessageOutput]:
|
||||
"""Pretend to fetch data and expose a business error outcome."""
|
||||
if input.text == "bad":
|
||||
return outcome("error", MessageOutput(message="lookup failed"))
|
||||
return outcome("ok", MessageOutput(message=input.text))
|
||||
|
||||
|
||||
@node(outcomes=("ok", "error"))
|
||||
def deliver_message(input: MessageOutput) -> NodeReturn[MessageOutput]:
|
||||
"""Pretend delivery can also fail with the same error outcome."""
|
||||
if input.message == "undeliverable":
|
||||
return outcome("error", MessageOutput(message="delivery failed"))
|
||||
return outcome("ok", MessageOutput(message=f"delivered: {input.message}"))
|
||||
|
||||
|
||||
@node
|
||||
def mark_sent(input: MessageOutput) -> MessageOutput:
|
||||
"""Normalize the branch payload for the send path."""
|
||||
return MessageOutput(message=f"sent: {input.message}")
|
||||
|
||||
|
||||
@node
|
||||
def mark_skipped(input: MessageOutput) -> MessageOutput:
|
||||
"""Normalize the branch payload for the skip path."""
|
||||
return MessageOutput(message=f"skipped: {input.message}")
|
||||
|
||||
|
||||
@node
|
||||
def fail_safely(input: MessageOutput) -> MessageOutput:
|
||||
"""Collapse several error outcomes into one workflow-facing payload."""
|
||||
return MessageOutput(message="failed safely")
|
||||
|
||||
|
||||
@node
|
||||
def classify_status(input: TextInput) -> StatusOutput:
|
||||
"""Write a status value for `match()` to inspect."""
|
||||
if input.text == "approve":
|
||||
return StatusOutput(status="approved")
|
||||
if input.text == "reject":
|
||||
return StatusOutput(status="rejected")
|
||||
return StatusOutput(status="pending")
|
||||
|
||||
|
||||
@node
|
||||
def approved(input: StatusOutput) -> MessageOutput:
|
||||
"""Target for the approved status."""
|
||||
return MessageOutput(message="approved")
|
||||
|
||||
|
||||
@node
|
||||
def rejected(input: StatusOutput) -> MessageOutput:
|
||||
"""Target for the rejected status."""
|
||||
return MessageOutput(message="rejected")
|
||||
|
||||
|
||||
@node
|
||||
def pending(input: StatusOutput) -> MessageOutput:
|
||||
"""Target for the pending/default status."""
|
||||
return MessageOutput(message="pending")
|
||||
|
||||
|
||||
@node
|
||||
def measure_text(input: TextInput) -> MetricsOutput:
|
||||
"""Record derived state for `when()` and `choose()` examples."""
|
||||
return MetricsOutput(message=input.text, length=len(input.text))
|
||||
|
||||
|
||||
@node
|
||||
def enthusiastic(input: MetricsOutput) -> MessageOutput:
|
||||
"""Target used when text is long enough to be considered excited."""
|
||||
return MessageOutput(message="enthusiastic")
|
||||
|
||||
|
||||
@node
|
||||
def calm(input: MetricsOutput) -> MessageOutput:
|
||||
"""Target used when text is not long enough for the positive branch."""
|
||||
return MessageOutput(message="calm")
|
||||
|
||||
|
||||
@node
|
||||
def long_message(input: MetricsOutput) -> MessageOutput:
|
||||
"""Target for the first true `choose()` clause."""
|
||||
return MessageOutput(message="long")
|
||||
|
||||
|
||||
@node
|
||||
def medium_message(input: MetricsOutput) -> MessageOutput:
|
||||
"""Target for a later `choose()` clause."""
|
||||
return MessageOutput(message="medium")
|
||||
|
||||
|
||||
@node
|
||||
def short_message(input: MetricsOutput) -> MessageOutput:
|
||||
"""Default target for the ordered predicate chain."""
|
||||
return MessageOutput(message="short")
|
||||
|
||||
|
||||
def _graph(name: str) -> WorkflowBuilder:
|
||||
"""Create the shared example graph shell."""
|
||||
return WorkflowBuilder(
|
||||
name=name,
|
||||
input_schema=TextInput,
|
||||
state_schema=ExampleState,
|
||||
output_schema=MessageOutput,
|
||||
)
|
||||
|
||||
|
||||
def _message_use(
|
||||
graph: WorkflowBuilder,
|
||||
spec: NodeSpec[Any, MessageOutput],
|
||||
*,
|
||||
id: str,
|
||||
):
|
||||
"""Use a message node with explicit state mappings for readability."""
|
||||
return graph.use(
|
||||
spec,
|
||||
id=id,
|
||||
in_map={"state.message": "message"},
|
||||
out_map={"message": "state.message"},
|
||||
)
|
||||
|
||||
|
||||
def _status_use(
|
||||
graph: WorkflowBuilder,
|
||||
spec: NodeSpec[Any, MessageOutput],
|
||||
*,
|
||||
id: str,
|
||||
):
|
||||
"""Use a status target with explicit state mappings for readability."""
|
||||
return graph.use(
|
||||
spec,
|
||||
id=id,
|
||||
in_map={"state.status": "status"},
|
||||
out_map={"message": "state.message"},
|
||||
)
|
||||
|
||||
|
||||
def _metrics_use(
|
||||
graph: WorkflowBuilder,
|
||||
spec: NodeSpec[Any, MessageOutput],
|
||||
*,
|
||||
id: str,
|
||||
):
|
||||
"""Use a metrics target with explicit state mappings for readability."""
|
||||
return graph.use(
|
||||
spec,
|
||||
id=id,
|
||||
in_map={
|
||||
"state.message": "message",
|
||||
"state.length": "length",
|
||||
},
|
||||
out_map={"message": "state.message"},
|
||||
)
|
||||
|
||||
|
||||
def build_branch_workflow() -> WorkflowBuilder:
|
||||
"""Build a workflow that demonstrates outcome routing with `branch()`."""
|
||||
graph = _graph("branch_example")
|
||||
router = graph.use(
|
||||
classify_message,
|
||||
id="classify",
|
||||
in_map={"input.text": "text"},
|
||||
out_map={"message": "state.message"},
|
||||
)
|
||||
graph.branch(
|
||||
router,
|
||||
{
|
||||
"send": _message_use(graph, mark_sent, id="sent"),
|
||||
"skip": _message_use(graph, mark_skipped, id="skipped"),
|
||||
"error": _message_use(graph, fail_safely, id="failed"),
|
||||
},
|
||||
)
|
||||
graph.connect("sent", "ok", END)
|
||||
graph.connect("skipped", "ok", END)
|
||||
graph.connect("failed", "ok", END)
|
||||
graph.set_entry_point(router)
|
||||
return graph
|
||||
|
||||
|
||||
def build_handle_workflow() -> WorkflowBuilder:
|
||||
"""Build a workflow that demonstrates shared error handling."""
|
||||
graph = _graph("handle_example")
|
||||
lookup = graph.use(
|
||||
lookup_message,
|
||||
id="lookup",
|
||||
in_map={"input.text": "text"},
|
||||
out_map={"message": "state.message"},
|
||||
)
|
||||
deliver = _message_use(graph, deliver_message, id="deliver")
|
||||
failed = _message_use(graph, fail_safely, id="failed")
|
||||
graph.connect(lookup, "ok", deliver)
|
||||
graph.connect(deliver, "ok", END)
|
||||
graph.handle((lookup, "error"), (deliver, "error"), to=failed)
|
||||
graph.connect(failed, "ok", END)
|
||||
graph.set_entry_point(lookup)
|
||||
return graph
|
||||
|
||||
|
||||
def build_match_workflow() -> WorkflowBuilder:
|
||||
"""Build a workflow that demonstrates equality dispatch with `match()`."""
|
||||
graph = _graph("match_example")
|
||||
classifier = graph.use(
|
||||
classify_status,
|
||||
id="classify_status",
|
||||
in_map={"input.text": "text"},
|
||||
out_map={"status": "state.status"},
|
||||
)
|
||||
decision = graph.match(
|
||||
state("status"),
|
||||
{
|
||||
"approved": _status_use(graph, approved, id="approved"),
|
||||
"rejected": _status_use(graph, rejected, id="rejected"),
|
||||
},
|
||||
default=_status_use(graph, pending, id="pending"),
|
||||
id="status",
|
||||
)
|
||||
graph.connect(classifier, "ok", decision.entry)
|
||||
graph.connect("approved", "ok", END)
|
||||
graph.connect("rejected", "ok", END)
|
||||
graph.connect("pending", "ok", END)
|
||||
graph.set_entry_point(classifier)
|
||||
return graph
|
||||
|
||||
|
||||
def build_when_workflow() -> WorkflowBuilder:
|
||||
"""Build a workflow that demonstrates one boolean condition with `when()`."""
|
||||
graph = _graph("when_example")
|
||||
measure = graph.use(
|
||||
measure_text,
|
||||
id="measure",
|
||||
in_map={"input.text": "text"},
|
||||
out_map={
|
||||
"message": "state.message",
|
||||
"length": "state.length",
|
||||
},
|
||||
)
|
||||
decision = graph.when(
|
||||
state("length").ge(6),
|
||||
then=_metrics_use(graph, enthusiastic, id="enthusiastic"),
|
||||
otherwise=_metrics_use(graph, calm, id="calm"),
|
||||
id="long_enough",
|
||||
)
|
||||
graph.connect(measure, "ok", decision.entry)
|
||||
graph.connect("enthusiastic", "ok", END)
|
||||
graph.connect("calm", "ok", END)
|
||||
graph.set_entry_point(measure)
|
||||
return graph
|
||||
|
||||
|
||||
def build_choose_workflow() -> WorkflowBuilder:
|
||||
"""Build a workflow that demonstrates ordered predicates with `choose()`."""
|
||||
graph = _graph("choose_example")
|
||||
measure = graph.use(
|
||||
measure_text,
|
||||
id="measure",
|
||||
in_map={"input.text": "text"},
|
||||
out_map={
|
||||
"message": "state.message",
|
||||
"length": "state.length",
|
||||
},
|
||||
)
|
||||
decision = graph.choose(
|
||||
(state("length").ge(20), _metrics_use(graph, long_message, id="long")),
|
||||
(state("length").ge(8), _metrics_use(graph, medium_message, id="medium")),
|
||||
default=_metrics_use(graph, short_message, id="short"),
|
||||
id="message_size",
|
||||
)
|
||||
graph.connect(measure, "ok", decision.entry)
|
||||
graph.connect("long", "ok", END)
|
||||
graph.connect("medium", "ok", END)
|
||||
graph.connect("short", "ok", END)
|
||||
graph.set_entry_point(measure)
|
||||
return graph
|
||||
|
||||
|
||||
def build_use_ref_workflow() -> WorkflowBuilder:
|
||||
"""Compile a graph that references an externally resolved capability."""
|
||||
graph = _graph("use_ref_example")
|
||||
echo = graph.use_ref(
|
||||
"demo.echo",
|
||||
id="echo",
|
||||
in_map={"input.text": "message"},
|
||||
out_map={"echoed": "state.message"},
|
||||
)
|
||||
graph.connect(echo, "ok", END)
|
||||
graph.set_entry_point(echo)
|
||||
return graph
|
||||
|
||||
|
||||
def main() -> None:
|
||||
"""Run a few examples directly from the command line."""
|
||||
for build, payload in (
|
||||
(build_branch_workflow, {"text": "send this"}),
|
||||
(build_match_workflow, {"text": "approve"}),
|
||||
(build_choose_workflow, {"text": "this is a very long message"}),
|
||||
):
|
||||
graph = build()
|
||||
run = graph.execute(payload)
|
||||
print(graph.name, run.status.value, run.output)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,67 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from wf_core import END, NodeUse, RunStatus
|
||||
|
||||
from examples.authoring_control_flow import (
|
||||
build_branch_workflow,
|
||||
build_choose_workflow,
|
||||
build_handle_workflow,
|
||||
build_match_workflow,
|
||||
build_use_ref_workflow,
|
||||
build_when_workflow,
|
||||
)
|
||||
|
||||
|
||||
def test_branch_example_routes_node_outcomes() -> None:
|
||||
workflow = build_branch_workflow()
|
||||
|
||||
run = workflow.execute({"text": "send this"})
|
||||
|
||||
assert run.status == RunStatus.COMPLETED
|
||||
assert run.output["message"] == "sent: send this"
|
||||
|
||||
|
||||
def test_handle_example_routes_shared_error_outcomes() -> None:
|
||||
workflow = build_handle_workflow()
|
||||
|
||||
run = workflow.execute({"text": "bad"})
|
||||
|
||||
assert run.status == RunStatus.COMPLETED
|
||||
assert run.output["message"] == "failed safely"
|
||||
|
||||
|
||||
def test_match_example_dispatches_by_state_value() -> None:
|
||||
workflow = build_match_workflow()
|
||||
|
||||
run = workflow.execute({"text": "approve"})
|
||||
|
||||
assert run.status == RunStatus.COMPLETED
|
||||
assert run.output["message"] == "approved"
|
||||
|
||||
|
||||
def test_when_example_dispatches_one_boolean_condition() -> None:
|
||||
workflow = build_when_workflow()
|
||||
|
||||
run = workflow.execute({"text": "hello!"})
|
||||
|
||||
assert run.status == RunStatus.COMPLETED
|
||||
assert run.output["message"] == "enthusiastic"
|
||||
|
||||
|
||||
def test_choose_example_dispatches_first_true_condition() -> None:
|
||||
workflow = build_choose_workflow()
|
||||
|
||||
run = workflow.execute({"text": "this is a very long message"})
|
||||
|
||||
assert run.status == RunStatus.COMPLETED
|
||||
assert run.output["message"] == "long"
|
||||
|
||||
|
||||
def test_use_ref_example_compiles_external_capability_reference() -> None:
|
||||
workflow = build_use_ref_workflow().compile()
|
||||
|
||||
assert workflow.start == "echo"
|
||||
assert workflow.node_defs == []
|
||||
assert isinstance(workflow.nodes[0], NodeUse)
|
||||
assert workflow.nodes[0].node == "demo.echo"
|
||||
assert workflow.edges[0].to == END
|
||||
Reference in New Issue
Block a user