代码提交
This commit is contained in:
1 parent
4ac7bc21cd
commit
17cf849361
110 files changed
+17007
-518
No files matched your search
@@ -0,0 +1,77 @@
|
||||
"""``human_input`` node: suspends the run and validates the resumed payload."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
from deerflow.workflows.errors import WorkflowError
|
||||
from deerflow.workflows.expressions import render_template
|
||||
from deerflow.workflows.runtime.context import RunContext, WorkflowPaused
|
||||
from deerflow.workflows.schemas import NodeResult, WorkflowNode
|
||||
|
||||
DEFAULT_ACTIONS = ("submit",)
|
||||
|
||||
|
||||
def _form_schema(node: WorkflowNode) -> dict[str, Any]:
|
||||
schema = node.config.get("formSchema") or node.config.get("form_schema") or {}
|
||||
return schema if isinstance(schema, dict) else {}
|
||||
|
||||
|
||||
def _actions(node: WorkflowNode) -> list[str]:
|
||||
raw = node.config.get("actions")
|
||||
actions = [str(a) for a in raw] if isinstance(raw, list) and raw else list(DEFAULT_ACTIONS)
|
||||
return actions
|
||||
|
||||
|
||||
class HumanInputNodeExecutor:
|
||||
"""First pass raises :class:`WorkflowPaused`; the resume pass consumes the
|
||||
payload the API stored on the context and validates it against the form."""
|
||||
|
||||
async def execute(self, node: WorkflowNode, ctx: RunContext) -> NodeResult:
|
||||
payload = ctx.resume_payload
|
||||
if not payload or str(payload.get("nodeId") or "") != node.id:
|
||||
raise WorkflowPaused(
|
||||
node.id,
|
||||
{
|
||||
"nodeId": node.id,
|
||||
"name": node.name,
|
||||
"prompt": render_template(str(node.config.get("prompt") or ""), ctx.state()),
|
||||
"formSchema": _form_schema(node),
|
||||
"actions": _actions(node),
|
||||
},
|
||||
)
|
||||
|
||||
action = str(payload.get("action") or "submit")
|
||||
allowed = _actions(node)
|
||||
if action not in allowed:
|
||||
raise WorkflowError(
|
||||
"WORKFLOW_HUMAN_INPUT_INVALID",
|
||||
f"不支持的操作:{action}",
|
||||
node_id=node.id,
|
||||
details={"allowed": allowed},
|
||||
)
|
||||
values = payload.get("values")
|
||||
values = values if isinstance(values, dict) else {}
|
||||
schema = _form_schema(node)
|
||||
required = schema.get("required")
|
||||
if isinstance(required, list) and action != "cancel":
|
||||
missing = [str(key) for key in required if values.get(str(key)) in (None, "")]
|
||||
if missing:
|
||||
raise WorkflowError(
|
||||
"WORKFLOW_HUMAN_INPUT_INVALID",
|
||||
f"缺少必填项:{'、'.join(missing)}",
|
||||
node_id=node.id,
|
||||
details={"missing": missing},
|
||||
)
|
||||
if action == "cancel":
|
||||
raise WorkflowError(
|
||||
"WORKFLOW_CANCELLED",
|
||||
"用户在人工节点选择终止运行",
|
||||
node_id=node.id,
|
||||
)
|
||||
# One-shot: a later human node in the same run must pause again.
|
||||
ctx.resume_payload = None
|
||||
return NodeResult(data={"action": action, "values": values})
|
||||
|
||||
|
||||
__all__ = ["HumanInputNodeExecutor"]
|
||||
Reference in new issue
Block a user