mirror of
https://github.com/langgenius/dify.git
synced 2026-01-13 21:57:48 +08:00
feat(api): adjust form submission run api
Some checks are pending
Build and Push API & Web / build (api, DIFY_API_IMAGE_NAME, linux/amd64, build-api-amd64) (push) Waiting to run
Build and Push API & Web / build (api, DIFY_API_IMAGE_NAME, linux/arm64, build-api-arm64) (push) Waiting to run
Build and Push API & Web / build (web, DIFY_WEB_IMAGE_NAME, linux/amd64, build-web-amd64) (push) Waiting to run
Build and Push API & Web / build (web, DIFY_WEB_IMAGE_NAME, linux/arm64, build-web-arm64) (push) Waiting to run
Build and Push API & Web / create-manifest (api, DIFY_API_IMAGE_NAME, merge-api-images) (push) Blocked by required conditions
Build and Push API & Web / create-manifest (web, DIFY_WEB_IMAGE_NAME, merge-web-images) (push) Blocked by required conditions
Some checks are pending
Build and Push API & Web / build (api, DIFY_API_IMAGE_NAME, linux/amd64, build-api-amd64) (push) Waiting to run
Build and Push API & Web / build (api, DIFY_API_IMAGE_NAME, linux/arm64, build-api-arm64) (push) Waiting to run
Build and Push API & Web / build (web, DIFY_WEB_IMAGE_NAME, linux/amd64, build-web-amd64) (push) Waiting to run
Build and Push API & Web / build (web, DIFY_WEB_IMAGE_NAME, linux/arm64, build-web-arm64) (push) Waiting to run
Build and Push API & Web / create-manifest (api, DIFY_API_IMAGE_NAME, merge-api-images) (push) Blocked by required conditions
Build and Push API & Web / create-manifest (web, DIFY_WEB_IMAGE_NAME, merge-web-images) (push) Blocked by required conditions
Separate `inputs` and `form_inputs` fields.
This commit is contained in:
parent
99937aba2e
commit
9c287ee0ae
@ -528,13 +528,29 @@ class WorkflowDraftRunLoopNodeApi(Resource):
|
||||
raise InternalServerError()
|
||||
|
||||
|
||||
class HumanInputSubmitPayload(BaseModel):
|
||||
inputs: dict[str, Any]
|
||||
action: str
|
||||
class HumanInputFormPreviewPayload(BaseModel):
|
||||
inputs: dict[str, Any] = Field(
|
||||
default_factory=dict,
|
||||
description="Values used to fill missing upstream variables referenced in form_content",
|
||||
)
|
||||
|
||||
|
||||
class HumanInputFormSubmitPayload(BaseModel):
|
||||
form_inputs: dict[str, Any] = Field(..., description="Values the user provides for the form's own fields")
|
||||
inputs: dict[str, Any] = Field(
|
||||
...,
|
||||
description="Values used to fill missing upstream variables referenced in form_content",
|
||||
)
|
||||
action: str = Field(..., description="Selected action ID")
|
||||
|
||||
|
||||
class HumanInputDeliveryTestPayload(BaseModel):
|
||||
delivery_method_id: str
|
||||
delivery_method_id: str = Field(..., description="Delivery method ID")
|
||||
|
||||
|
||||
reg(HumanInputFormPreviewPayload)
|
||||
reg(HumanInputFormSubmitPayload)
|
||||
reg(HumanInputDeliveryTestPayload)
|
||||
|
||||
|
||||
@console_ns.route("/apps/<uuid:app_id>/advanced-chat/workflows/draft/human-input/nodes/<string:node_id>/form/preview")
|
||||
@ -542,6 +558,7 @@ class AdvancedChatDraftHumanInputFormPreviewApi(Resource):
|
||||
@console_ns.doc("get_advanced_chat_draft_human_input_form")
|
||||
@console_ns.doc(description="Get human input form preview for advanced chat workflow")
|
||||
@console_ns.doc(params={"app_id": "Application ID", "node_id": "Node ID"})
|
||||
@console_ns.expect(console_ns.models[HumanInputFormPreviewPayload.__name__])
|
||||
@setup_required
|
||||
@login_required
|
||||
@account_initialization_required
|
||||
@ -552,19 +569,15 @@ class AdvancedChatDraftHumanInputFormPreviewApi(Resource):
|
||||
Preview human input form content and placeholders
|
||||
"""
|
||||
current_user, _ = current_account_with_tenant()
|
||||
payload = request.get_json(silent=True) or {}
|
||||
manual_inputs = payload.get("inputs") if isinstance(payload, dict) else {}
|
||||
if manual_inputs is None:
|
||||
manual_inputs = {}
|
||||
if not isinstance(manual_inputs, dict):
|
||||
raise ValueError("inputs must be an object")
|
||||
args = HumanInputFormPreviewPayload.model_validate(console_ns.payload or {})
|
||||
inputs = args.inputs
|
||||
|
||||
workflow_service = WorkflowService()
|
||||
preview = workflow_service.get_human_input_form_preview(
|
||||
app_model=app_model,
|
||||
account=current_user,
|
||||
node_id=node_id,
|
||||
manual_inputs=manual_inputs,
|
||||
inputs=inputs,
|
||||
)
|
||||
return jsonable_encoder(preview)
|
||||
|
||||
@ -574,15 +587,7 @@ class AdvancedChatDraftHumanInputFormRunApi(Resource):
|
||||
@console_ns.doc("submit_advanced_chat_draft_human_input_form")
|
||||
@console_ns.doc(description="Submit human input form preview for advanced chat workflow")
|
||||
@console_ns.doc(params={"app_id": "Application ID", "node_id": "Node ID"})
|
||||
@console_ns.expect(
|
||||
console_ns.model(
|
||||
"AdvancedChatHumanInputFormSubmitRequest",
|
||||
{
|
||||
"inputs": fields.Raw(required=True, description="Form input values"),
|
||||
"action": fields.String(required=True, description="Selected action ID"),
|
||||
},
|
||||
)
|
||||
)
|
||||
@console_ns.expect(console_ns.models[HumanInputFormSubmitPayload.__name__])
|
||||
@setup_required
|
||||
@login_required
|
||||
@account_initialization_required
|
||||
@ -593,13 +598,14 @@ class AdvancedChatDraftHumanInputFormRunApi(Resource):
|
||||
Submit human input form preview
|
||||
"""
|
||||
current_user, _ = current_account_with_tenant()
|
||||
args = HumanInputSubmitPayload.model_validate(console_ns.payload or {})
|
||||
args = HumanInputFormSubmitPayload.model_validate(console_ns.payload or {})
|
||||
workflow_service = WorkflowService()
|
||||
result = workflow_service.submit_human_input_form_preview(
|
||||
app_model=app_model,
|
||||
account=current_user,
|
||||
node_id=node_id,
|
||||
form_inputs=args.inputs,
|
||||
form_inputs=args.form_inputs,
|
||||
inputs=args.inputs,
|
||||
action=args.action,
|
||||
)
|
||||
return jsonable_encoder(result)
|
||||
@ -610,6 +616,7 @@ class WorkflowDraftHumanInputFormPreviewApi(Resource):
|
||||
@console_ns.doc("get_workflow_draft_human_input_form")
|
||||
@console_ns.doc(description="Get human input form preview for workflow")
|
||||
@console_ns.doc(params={"app_id": "Application ID", "node_id": "Node ID"})
|
||||
@console_ns.expect(console_ns.models[HumanInputFormPreviewPayload.__name__])
|
||||
@setup_required
|
||||
@login_required
|
||||
@account_initialization_required
|
||||
@ -620,19 +627,15 @@ class WorkflowDraftHumanInputFormPreviewApi(Resource):
|
||||
Preview human input form content and placeholders
|
||||
"""
|
||||
current_user, _ = current_account_with_tenant()
|
||||
payload = request.get_json(silent=True) or {}
|
||||
manual_inputs = payload.get("inputs") if isinstance(payload, dict) else {}
|
||||
if manual_inputs is None:
|
||||
manual_inputs = {}
|
||||
if not isinstance(manual_inputs, dict):
|
||||
raise ValueError("inputs must be an object")
|
||||
args = HumanInputFormPreviewPayload.model_validate(console_ns.payload or {})
|
||||
inputs = args.inputs
|
||||
|
||||
workflow_service = WorkflowService()
|
||||
preview = workflow_service.get_human_input_form_preview(
|
||||
app_model=app_model,
|
||||
account=current_user,
|
||||
node_id=node_id,
|
||||
manual_inputs=manual_inputs,
|
||||
inputs=inputs,
|
||||
)
|
||||
return jsonable_encoder(preview)
|
||||
|
||||
@ -642,15 +645,7 @@ class WorkflowDraftHumanInputFormRunApi(Resource):
|
||||
@console_ns.doc("submit_workflow_draft_human_input_form")
|
||||
@console_ns.doc(description="Submit human input form preview for workflow")
|
||||
@console_ns.doc(params={"app_id": "Application ID", "node_id": "Node ID"})
|
||||
@console_ns.expect(
|
||||
console_ns.model(
|
||||
"WorkflowHumanInputFormSubmitRequest",
|
||||
{
|
||||
"inputs": fields.Raw(required=True, description="Form input values"),
|
||||
"action": fields.String(required=True, description="Selected action ID"),
|
||||
},
|
||||
)
|
||||
)
|
||||
@console_ns.expect(console_ns.models[HumanInputFormSubmitPayload.__name__])
|
||||
@setup_required
|
||||
@login_required
|
||||
@account_initialization_required
|
||||
@ -662,12 +657,13 @@ class WorkflowDraftHumanInputFormRunApi(Resource):
|
||||
"""
|
||||
current_user, _ = current_account_with_tenant()
|
||||
workflow_service = WorkflowService()
|
||||
args = HumanInputSubmitPayload.model_validate(console_ns.payload or {})
|
||||
args = HumanInputFormSubmitPayload.model_validate(console_ns.payload or {})
|
||||
result = workflow_service.submit_human_input_form_preview(
|
||||
app_model=app_model,
|
||||
account=current_user,
|
||||
node_id=node_id,
|
||||
form_inputs=args.inputs,
|
||||
form_inputs=args.form_inputs,
|
||||
inputs=args.inputs,
|
||||
action=args.action,
|
||||
)
|
||||
return jsonable_encoder(result)
|
||||
@ -678,14 +674,7 @@ class AdvancedChatDraftHumanInputDeliveryTestApi(Resource):
|
||||
@console_ns.doc("test_advanced_chat_draft_human_input_delivery")
|
||||
@console_ns.doc(description="Test human input delivery for advanced chat workflow")
|
||||
@console_ns.doc(params={"app_id": "Application ID", "node_id": "Node ID"})
|
||||
@console_ns.expect(
|
||||
console_ns.model(
|
||||
"AdvancedChatHumanInputDeliveryTestRequest",
|
||||
{
|
||||
"delivery_method_id": fields.String(required=True, description="Delivery method ID"),
|
||||
},
|
||||
)
|
||||
)
|
||||
@console_ns.expect(console_ns.models[HumanInputDeliveryTestPayload.__name__])
|
||||
@setup_required
|
||||
@login_required
|
||||
@account_initialization_required
|
||||
@ -715,14 +704,7 @@ class WorkflowDraftHumanInputDeliveryTestApi(Resource):
|
||||
@console_ns.doc("test_workflow_draft_human_input_delivery")
|
||||
@console_ns.doc(description="Test human input delivery for workflow")
|
||||
@console_ns.doc(params={"app_id": "Application ID", "node_id": "Node ID"})
|
||||
@console_ns.expect(
|
||||
console_ns.model(
|
||||
"WorkflowHumanInputDeliveryTestRequest",
|
||||
{
|
||||
"delivery_method_id": fields.String(required=True, description="Delivery method ID"),
|
||||
},
|
||||
)
|
||||
)
|
||||
@console_ns.expect(console_ns.models[HumanInputDeliveryTestPayload.__name__])
|
||||
@setup_required
|
||||
@login_required
|
||||
@account_initialization_required
|
||||
|
||||
@ -766,8 +766,17 @@ class WorkflowService:
|
||||
app_model: App,
|
||||
account: Account,
|
||||
node_id: str,
|
||||
manual_inputs: Mapping[str, Any] | None = None,
|
||||
inputs: Mapping[str, Any] | None = None,
|
||||
) -> Mapping[str, Any]:
|
||||
"""
|
||||
Build a human input form preview for a draft workflow.
|
||||
|
||||
Args:
|
||||
app_model: Target application model.
|
||||
account: Current account.
|
||||
node_id: Human input node ID.
|
||||
inputs: Values used to fill missing upstream variables referenced in form_content.
|
||||
"""
|
||||
draft_workflow = self.get_draft_workflow(app_model=app_model)
|
||||
if not draft_workflow:
|
||||
raise ValueError("Workflow not initialized")
|
||||
@ -777,11 +786,12 @@ class WorkflowService:
|
||||
if node_type is not NodeType.HUMAN_INPUT:
|
||||
raise ValueError("Node type must be human-input.")
|
||||
|
||||
# inputs: values used to fill missing upstream variables referenced in form_content.
|
||||
variable_pool = self._build_human_input_variable_pool(
|
||||
app_model=app_model,
|
||||
workflow=draft_workflow,
|
||||
node_config=node_config,
|
||||
manual_inputs=manual_inputs or {},
|
||||
manual_inputs=inputs or {},
|
||||
)
|
||||
node = self._build_human_input_node(
|
||||
workflow=draft_workflow,
|
||||
@ -812,8 +822,20 @@ class WorkflowService:
|
||||
account: Account,
|
||||
node_id: str,
|
||||
form_inputs: Mapping[str, Any],
|
||||
inputs: Mapping[str, Any] | None = None,
|
||||
action: str,
|
||||
) -> Mapping[str, Any]:
|
||||
"""
|
||||
Submit a human input form preview for a draft workflow.
|
||||
|
||||
Args:
|
||||
app_model: Target application model.
|
||||
account: Current account.
|
||||
node_id: Human input node ID.
|
||||
form_inputs: Values the user provides for the form's own fields.
|
||||
inputs: Values used to fill missing upstream variables referenced in form_content.
|
||||
action: Selected action ID.
|
||||
"""
|
||||
draft_workflow = self.get_draft_workflow(app_model=app_model)
|
||||
if not draft_workflow:
|
||||
raise ValueError("Workflow not initialized")
|
||||
@ -823,11 +845,13 @@ class WorkflowService:
|
||||
if node_type is not NodeType.HUMAN_INPUT:
|
||||
raise ValueError("Node type must be human-input.")
|
||||
|
||||
# inputs: values used to fill missing upstream variables referenced in form_content.
|
||||
# form_inputs: values the user provides for the form's own fields.
|
||||
variable_pool = self._build_human_input_variable_pool(
|
||||
app_model=app_model,
|
||||
workflow=draft_workflow,
|
||||
node_config=node_config,
|
||||
manual_inputs={},
|
||||
manual_inputs=inputs or {},
|
||||
)
|
||||
node = self._build_human_input_node(
|
||||
workflow=draft_workflow,
|
||||
|
||||
@ -6,6 +6,7 @@ from unittest.mock import MagicMock
|
||||
|
||||
import pytest
|
||||
from flask import Flask
|
||||
from pydantic import ValidationError
|
||||
|
||||
from controllers.console import wraps as console_wraps
|
||||
from controllers.console.app import workflow as workflow_module
|
||||
@ -58,13 +59,13 @@ class PreviewCase:
|
||||
"case",
|
||||
[
|
||||
PreviewCase(
|
||||
resource_cls=workflow_module.AdvancedChatDraftHumanInputFormApi,
|
||||
path="/console/api/apps/app-123/advanced-chat/workflows/draft/human-input/nodes/node-42/form",
|
||||
resource_cls=workflow_module.AdvancedChatDraftHumanInputFormPreviewApi,
|
||||
path="/console/api/apps/app-123/advanced-chat/workflows/draft/human-input/nodes/node-42/form/preview",
|
||||
mode=AppMode.ADVANCED_CHAT,
|
||||
),
|
||||
PreviewCase(
|
||||
resource_cls=workflow_module.WorkflowDraftHumanInputFormApi,
|
||||
path="/console/api/apps/app-123/workflows/draft/human-input/nodes/node-42/form",
|
||||
resource_cls=workflow_module.WorkflowDraftHumanInputFormPreviewApi,
|
||||
path="/console/api/apps/app-123/workflows/draft/human-input/nodes/node-42/form/preview",
|
||||
mode=AppMode.WORKFLOW,
|
||||
),
|
||||
],
|
||||
@ -86,15 +87,15 @@ def test_human_input_preview_delegates_to_service(
|
||||
service_instance.get_human_input_form_preview.return_value = preview_payload
|
||||
monkeypatch.setattr(workflow_module, "WorkflowService", MagicMock(return_value=service_instance))
|
||||
|
||||
with app.test_request_context(case.path, method="GET", json={"inputs": {"topic": "tech"}}):
|
||||
response = case.resource_cls().get(app_id=app_model.id, node_id="node-42")
|
||||
with app.test_request_context(case.path, method="POST", json={"inputs": {"topic": "tech"}}):
|
||||
response = case.resource_cls().post(app_id=app_model.id, node_id="node-42")
|
||||
|
||||
assert response == preview_payload
|
||||
service_instance.get_human_input_form_preview.assert_called_once_with(
|
||||
app_model=app_model,
|
||||
account=account,
|
||||
node_id="node-42",
|
||||
manual_inputs={"topic": "tech"},
|
||||
inputs={"topic": "tech"},
|
||||
)
|
||||
|
||||
|
||||
@ -109,13 +110,13 @@ class SubmitCase:
|
||||
"case",
|
||||
[
|
||||
SubmitCase(
|
||||
resource_cls=workflow_module.AdvancedChatDraftHumanInputFormApi,
|
||||
path="/console/api/apps/app-123/advanced-chat/workflows/draft/human-input/nodes/node-99/form",
|
||||
resource_cls=workflow_module.AdvancedChatDraftHumanInputFormRunApi,
|
||||
path="/console/api/apps/app-123/advanced-chat/workflows/draft/human-input/nodes/node-99/form/run",
|
||||
mode=AppMode.ADVANCED_CHAT,
|
||||
),
|
||||
SubmitCase(
|
||||
resource_cls=workflow_module.WorkflowDraftHumanInputFormApi,
|
||||
path="/console/api/apps/app-123/workflows/draft/human-input/nodes/node-99/form",
|
||||
resource_cls=workflow_module.WorkflowDraftHumanInputFormRunApi,
|
||||
path="/console/api/apps/app-123/workflows/draft/human-input/nodes/node-99/form/run",
|
||||
mode=AppMode.WORKFLOW,
|
||||
),
|
||||
],
|
||||
@ -133,7 +134,7 @@ def test_human_input_submit_forwards_payload(app: Flask, monkeypatch: pytest.Mon
|
||||
with app.test_request_context(
|
||||
case.path,
|
||||
method="POST",
|
||||
json={"inputs": {"answer": "42"}, "action": "approve"},
|
||||
json={"form_inputs": {"answer": "42"}, "inputs": {"#node-1.result#": "LLM output"}, "action": "approve"},
|
||||
):
|
||||
response = case.resource_cls().post(app_id=app_model.id, node_id="node-99")
|
||||
|
||||
@ -143,6 +144,7 @@ def test_human_input_submit_forwards_payload(app: Flask, monkeypatch: pytest.Mon
|
||||
account=account,
|
||||
node_id="node-99",
|
||||
form_inputs={"answer": "42"},
|
||||
inputs={"#node-1.result#": "LLM output"},
|
||||
action="approve",
|
||||
)
|
||||
|
||||
@ -219,9 +221,9 @@ def test_human_input_preview_rejects_non_mapping(app: Flask, monkeypatch: pytest
|
||||
_patch_console_guards(monkeypatch, account, app_model)
|
||||
|
||||
with app.test_request_context(
|
||||
"/console/api/apps/app-123/advanced-chat/workflows/draft/human-input/nodes/node-1/form",
|
||||
method="GET",
|
||||
"/console/api/apps/app-123/advanced-chat/workflows/draft/human-input/nodes/node-1/form/preview",
|
||||
method="POST",
|
||||
json={"inputs": ["not-a-dict"]},
|
||||
):
|
||||
with pytest.raises(ValueError):
|
||||
workflow_module.AdvancedChatDraftHumanInputFormApi().get(app_id=app_model.id, node_id="node-1")
|
||||
with pytest.raises(ValidationError):
|
||||
workflow_module.AdvancedChatDraftHumanInputFormPreviewApi().post(app_id=app_model.id, node_id="node-1")
|
||||
|
||||
@ -225,9 +225,17 @@ class TestWorkflowService:
|
||||
account=account,
|
||||
node_id="node-1",
|
||||
form_inputs={"name": "Ada", "extra": "ignored"},
|
||||
inputs={"#node-0.result#": "LLM output"},
|
||||
action="approve",
|
||||
)
|
||||
|
||||
service._build_human_input_variable_pool.assert_called_once_with(
|
||||
app_model=app_model,
|
||||
workflow=workflow,
|
||||
node_config={"id": "node-1", "data": {"type": NodeType.HUMAN_INPUT.value}},
|
||||
manual_inputs={"#node-0.result#": "LLM output"},
|
||||
)
|
||||
|
||||
node._render_form_content_with_outputs.assert_called_once()
|
||||
called_args = node._render_form_content_with_outputs.call_args.args
|
||||
assert called_args[0] == "<p>preview</p>"
|
||||
@ -271,6 +279,7 @@ class TestWorkflowService:
|
||||
account=account,
|
||||
node_id="node-1",
|
||||
form_inputs={},
|
||||
inputs={},
|
||||
action="approve",
|
||||
)
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user