add match!

This commit is contained in:
lda
2026-05-19 06:43:05 +07:00 Verified
parent 66db83dc5a
commit 6ad3d20975
6 changed files with 137 additions and 4 deletions
+29 -2
View File
@@ -209,6 +209,33 @@ Creates one boolean decision step. The condition uses the same JSON shape as
The draft adapter lowers this through `WorkflowBuilder.when()`. The draft step
id becomes the generated condition entry id, so other routes can target it.
### `match`
Matches one graph value against ordered equality cases.
```json
{
"match": {
"value": "state.status",
"cases": [
{
"equals": "ready",
"then": "run"
},
{
"equals": "waiting",
"then": "pause"
}
],
"default": "__end__"
}
}
```
Cases are a list rather than a JSON object so values such as `1`, `"1"`, and
`true` are not silently coerced into object keys. The draft adapter lowers this
through `WorkflowBuilder.match()`.
### `choose`
Creates an ordered first-true decision chain.
@@ -243,8 +270,8 @@ Creates an ordered first-true decision chain.
```
`choose` lowers through `WorkflowBuilder.choose()` and expands to generated
condition nodes. It replaces the deprecated `route()` concept for draft JSON;
there is intentionally no draft `route` step kind.
condition nodes. `match`, `when`, and `choose` replace the deprecated `route()`
concept for draft JSON; there is intentionally no draft `route` step kind.
## Draft Tools
+4
View File
@@ -11,6 +11,8 @@ from .models import (
DraftForeachStep,
DraftInterruptStep,
DraftJoinStep,
DraftMatchCase,
DraftMatchStep,
DraftWhenStep,
DraftUseStep,
WorkflowDraft,
@@ -23,6 +25,8 @@ __all__ = [
"DraftForeachStep",
"DraftInterruptStep",
"DraftJoinStep",
"DraftMatchCase",
"DraftMatchStep",
"DraftWhenStep",
"DraftUseStep",
"WorkflowDraft",
+10 -1
View File
@@ -1,16 +1,18 @@
from __future__ import annotations
from wf_authoring import WorkflowBuilder
from wf_authoring.dsl import PathExpr
from wf_core import JoinNode, Workflow
from .models import (
DraftChooseStep,
DraftForeachStep,
DraftInterruptStep,
DraftJoinStep,
DraftMatchStep,
DraftStep,
DraftUseStep,
DraftWhenStep,
DraftChooseStep,
WorkflowDraft,
)
@@ -79,4 +81,11 @@ def _add_step(builder: WorkflowBuilder, step_id: str, step: DraftStep):
id=step_id,
default=step.choose.default,
).entry
if isinstance(step, DraftMatchStep):
return builder.match(
PathExpr(step.match.value),
{case.equals: case.then for case in step.match.cases},
id=step_id,
default=step.match.default,
).entry
raise TypeError(f"unsupported draft step {type(step)!r}")
+25 -1
View File
@@ -7,7 +7,9 @@ from pydantic import BaseModel, Field, model_validator
from wf_core.models.conditions import Condition
JsonObject = dict[str, Any]
STEP_KIND_KEYS = frozenset({"use", "foreach", "interrupt", "join", "when", "choose"})
STEP_KIND_KEYS = frozenset(
{"use", "foreach", "interrupt", "join", "when", "choose", "match"}
)
class DraftUseStep(BaseModel):
@@ -91,6 +93,27 @@ class DraftChooseStep(BaseModel):
choose: DraftChoosePayload
class DraftMatchCase(BaseModel):
"""One ordered equality case in a draft match decision."""
equals: Any
then: str
class DraftMatchPayload(BaseModel):
"""Payload for matching one graph value against ordered equality cases."""
value: str
cases: list[DraftMatchCase] = Field(min_length=1)
default: str = "__end__"
class DraftMatchStep(BaseModel):
"""Draft step that delegates equality decisions to `WorkflowBuilder.match`."""
match: DraftMatchPayload
DraftStep = (
DraftUseStep
| DraftForeachStep
@@ -98,6 +121,7 @@ DraftStep = (
| DraftJoinStep
| DraftWhenStep
| DraftChooseStep
| DraftMatchStep
)
+44
View File
@@ -121,3 +121,47 @@ def test_adapter_lowers_choose_step_through_builder() -> None:
("pick_2", "true", "fallback"),
("pick_2", "false", "__end__"),
]
def test_adapter_lowers_match_step_through_builder() -> None:
draft = WorkflowDraft.model_validate(
{
"name": "match_example",
"input_schema": {},
"state_schema": {"fields": {}},
"output_schema": {},
"start": "match_status",
"steps": {
"match_status": {
"match": {
"value": "state.status",
"cases": [
{"equals": "ready", "then": "ready"},
{"equals": "waiting", "then": "waiting"},
],
"default": "__end__",
}
},
"ready": {"use": "demo.ready"},
"waiting": {"use": "demo.waiting"},
},
"routes": {
"ready": {"ok": "__end__"},
"waiting": {"ok": "__end__"},
},
}
)
workflow = build_workflow_from_draft(draft)
condition_ids = [
node.id for node in workflow.nodes if isinstance(node, ConditionNode)
]
assert condition_ids == ["match_status", "match_status_2"]
assert workflow.start == "match_status"
assert [(edge.from_, edge.outcome, edge.to) for edge in workflow.edges[:4]] == [
("match_status", "true", "ready"),
("match_status", "false", "match_status_2"),
("match_status_2", "true", "waiting"),
("match_status_2", "false", "__end__"),
]
+25
View File
@@ -7,6 +7,7 @@ from pydantic import ValidationError
from wf_artifacts.drafts import (
DraftChooseStep,
DraftMatchStep,
DraftUseStep,
DraftWhenStep,
WorkflowDraft,
@@ -87,6 +88,30 @@ def test_workflow_draft_accepts_choose_step() -> None:
assert isinstance(draft.steps["choose_next"], DraftChooseStep)
def test_workflow_draft_accepts_match_step() -> None:
draft = WorkflowDraft.model_validate(
{
**_keyed_echo_draft(),
"start": "match_status",
"steps": {
**_keyed_echo_draft()["steps"],
"match_status": {
"match": {
"value": "state.status",
"cases": [
{"equals": "ready", "then": "echo"},
{"equals": "done", "then": "__end__"},
],
"default": "__end__",
}
},
},
}
)
assert isinstance(draft.steps["match_status"], DraftMatchStep)
def _keyed_echo_draft() -> dict[str, Any]:
return {
"name": "echo",