mirror of
https://github.com/langgenius/dify.git
synced 2026-09-24 23:22:26 +08:00
chore(api): upgrade graphon to 0.4.0 (#36124)
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> Co-authored-by: QuantumGhost <obelisk.reg+git@gmail.com>
This commit is contained in:
co-authored by
autofix-ci[bot]
QuantumGhost
parent
127fbf2c9a
commit
b96f372f45
@@ -10,7 +10,7 @@ from graphon.entities import WorkflowStartReason
|
||||
from graphon.entities.pause_reason import PauseReasonType
|
||||
from graphon.enums import WorkflowExecutionStatus, WorkflowNodeExecutionMetadataKey, WorkflowNodeExecutionStatus
|
||||
from graphon.model_runtime.entities.llm_entities import LLMResult, LLMUsage
|
||||
from graphon.nodes.human_input.entities import FormInput, UserAction
|
||||
from graphon.nodes.human_input.entities import FormInputConfig, UserActionConfig
|
||||
|
||||
|
||||
class AnnotationReplyAccount(BaseModel):
|
||||
@@ -284,8 +284,8 @@ class HumanInputRequiredResponse(StreamResponse):
|
||||
node_id: str
|
||||
node_title: str
|
||||
form_content: str
|
||||
inputs: Sequence[FormInput] = Field(default_factory=list)
|
||||
actions: Sequence[UserAction] = Field(default_factory=list)
|
||||
inputs: Sequence[FormInputConfig] = Field(default_factory=list)
|
||||
actions: Sequence[UserActionConfig] = Field(default_factory=list)
|
||||
display_in_ui: bool = False
|
||||
form_token: str | None = None
|
||||
resolved_default_values: Mapping[str, Any] = Field(default_factory=dict)
|
||||
@@ -307,8 +307,8 @@ class HumanInputRequiredPauseReasonPayload(BaseModel):
|
||||
node_id: str
|
||||
node_title: str
|
||||
form_content: str
|
||||
inputs: Sequence[FormInput] = Field(default_factory=list)
|
||||
actions: Sequence[UserAction] = Field(default_factory=list)
|
||||
inputs: Sequence[FormInputConfig] = Field(default_factory=list)
|
||||
actions: Sequence[UserActionConfig] = Field(default_factory=list)
|
||||
display_in_ui: bool = False
|
||||
form_token: str | None = None
|
||||
resolved_default_values: Mapping[str, Any] = Field(default_factory=dict)
|
||||
|
||||
@@ -5,7 +5,7 @@ from typing import Any, TypeAlias
|
||||
|
||||
from pydantic import BaseModel, ConfigDict, Field
|
||||
|
||||
from graphon.nodes.human_input.entities import FormInput, UserAction
|
||||
from graphon.nodes.human_input.entities import FormInputConfig, UserActionConfig
|
||||
from models.execution_extra_content import ExecutionContentType
|
||||
|
||||
|
||||
@@ -16,8 +16,8 @@ class HumanInputFormDefinition(BaseModel):
|
||||
node_id: str
|
||||
node_title: str
|
||||
form_content: str
|
||||
inputs: Sequence[FormInput] = Field(default_factory=list)
|
||||
actions: Sequence[UserAction] = Field(default_factory=list)
|
||||
inputs: Sequence[FormInputConfig] = Field(default_factory=list)
|
||||
actions: Sequence[UserActionConfig] = Field(default_factory=list)
|
||||
display_in_ui: bool = False
|
||||
form_token: str | None = None
|
||||
resolved_default_values: Mapping[str, Any] = Field(default_factory=dict)
|
||||
|
||||
@@ -47,7 +47,7 @@ from graphon.graph.graph import NodeFactory
|
||||
from graphon.model_runtime.memory import PromptMessageMemory
|
||||
from graphon.model_runtime.model_providers.base.large_language_model import LargeLanguageModel
|
||||
from graphon.nodes.base.node import Node
|
||||
from graphon.nodes.code.code_node import WorkflowCodeExecutor
|
||||
from graphon.nodes.code.code_node import CodeExecutorProtocol
|
||||
from graphon.nodes.code.entities import CodeLanguage
|
||||
from graphon.nodes.code.limits import CodeNodeLimits
|
||||
from graphon.nodes.document_extractor import UnstructuredApiConfig
|
||||
@@ -289,7 +289,7 @@ class DifyNodeFactory(NodeFactory):
|
||||
self.graph_init_params = graph_init_params
|
||||
self.graph_runtime_state = graph_runtime_state
|
||||
self._dify_context = self._resolve_dify_context(graph_init_params.run_context)
|
||||
self._code_executor: WorkflowCodeExecutor = DefaultWorkflowCodeExecutor()
|
||||
self._code_executor: CodeExecutorProtocol = DefaultWorkflowCodeExecutor()
|
||||
self._code_limits = CodeNodeLimits(
|
||||
max_string_length=dify_config.CODE_MAX_STRING_LENGTH,
|
||||
max_number=dify_config.CODE_MAX_NUMBER,
|
||||
@@ -397,6 +397,7 @@ class DifyNodeFactory(NodeFactory):
|
||||
},
|
||||
BuiltinNodeTypes.HUMAN_INPUT: lambda: {
|
||||
"runtime": self._human_input_runtime,
|
||||
"file_reference_factory": self._file_reference_factory,
|
||||
"form_repository": self._human_input_runtime.build_form_repository(),
|
||||
},
|
||||
BuiltinNodeTypes.LLM: lambda: self._build_llm_compatible_node_init_kwargs(
|
||||
@@ -434,7 +435,7 @@ class DifyNodeFactory(NodeFactory):
|
||||
include_jinja2_template_renderer=False,
|
||||
),
|
||||
BuiltinNodeTypes.TOOL: lambda: {
|
||||
"tool_file_manager_factory": self._bound_tool_file_manager_factory(),
|
||||
"tool_file_manager": self._bound_tool_file_manager_factory(),
|
||||
"runtime": self._tool_runtime,
|
||||
},
|
||||
BuiltinNodeTypes.AGENT: lambda: {
|
||||
|
||||
@@ -49,7 +49,7 @@ from graphon.model_runtime.entities.model_entities import AIModelEntity
|
||||
from graphon.model_runtime.model_providers.base.large_language_model import LargeLanguageModel
|
||||
from graphon.nodes.human_input.entities import HumanInputNodeData
|
||||
from graphon.nodes.llm.runtime_protocols import (
|
||||
PreparedLLMProtocol,
|
||||
LLMProtocol,
|
||||
PromptMessageSerializerProtocol,
|
||||
RetrieverAttachmentLoaderProtocol,
|
||||
)
|
||||
@@ -140,7 +140,7 @@ class DifyFileReferenceFactory(FileReferenceFactoryProtocol):
|
||||
)
|
||||
|
||||
|
||||
class DifyPreparedLLM(PreparedLLMProtocol):
|
||||
class DifyPreparedLLM(LLMProtocol):
|
||||
"""Workflow-layer adapter that hides the full `ModelInstance` API from `graphon` nodes."""
|
||||
|
||||
def __init__(self, model_instance: ModelInstance) -> None:
|
||||
|
||||
@@ -8,6 +8,7 @@ OpenAPI output early.
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import logging
|
||||
import subprocess
|
||||
import sys
|
||||
@@ -28,6 +29,125 @@ CONSOLE_SWAGGER_FILENAME = "console-swagger.json"
|
||||
STALE_COMBINED_MARKDOWN_FILENAME = "api-reference.md"
|
||||
|
||||
|
||||
def _definition_ref_name(schema: object) -> str | None:
|
||||
if not isinstance(schema, dict):
|
||||
return None
|
||||
|
||||
ref = schema.get("$ref")
|
||||
if not isinstance(ref, str) or not ref.startswith("#/definitions/"):
|
||||
return None
|
||||
|
||||
return ref.removeprefix("#/definitions/")
|
||||
|
||||
|
||||
def _markdown_anchor(name: str) -> str:
|
||||
return name.lower()
|
||||
|
||||
|
||||
def _schema_markdown_type(schema: object) -> str:
|
||||
if not isinstance(schema, dict):
|
||||
return ""
|
||||
|
||||
ref_name = _definition_ref_name(schema)
|
||||
if ref_name is not None:
|
||||
return f"[{ref_name}](#{_markdown_anchor(ref_name)})"
|
||||
|
||||
for union_key in ("oneOf", "anyOf"):
|
||||
variants = schema.get(union_key)
|
||||
if not isinstance(variants, list):
|
||||
continue
|
||||
|
||||
variant_types = [
|
||||
variant_type
|
||||
for variant in variants
|
||||
if not (isinstance(variant, dict) and variant.get("type") == "null")
|
||||
for variant_type in [_schema_markdown_type(variant)]
|
||||
if variant_type
|
||||
]
|
||||
if len(variant_types) == 1:
|
||||
return variant_types[0]
|
||||
if variant_types:
|
||||
return "<br>".join(variant_types)
|
||||
|
||||
schema_type = schema.get("type")
|
||||
if schema_type == "array":
|
||||
item_type = _schema_markdown_type(schema.get("items"))
|
||||
return f"[ {item_type or 'object'} ]"
|
||||
if isinstance(schema_type, str):
|
||||
return schema_type
|
||||
|
||||
return ""
|
||||
|
||||
|
||||
def _replace_schema_table_type(markdown: str, definition_name: str, row_name: str, type_markdown: str) -> str:
|
||||
if not type_markdown:
|
||||
return markdown
|
||||
|
||||
lines = markdown.splitlines()
|
||||
section_header = f"#### {definition_name}"
|
||||
in_section = False
|
||||
|
||||
for index, line in enumerate(lines):
|
||||
if line == section_header:
|
||||
in_section = True
|
||||
continue
|
||||
if in_section and line.startswith("#### "):
|
||||
break
|
||||
if not in_section or not line.startswith(f"| {row_name} |"):
|
||||
continue
|
||||
|
||||
cells = line.split("|")
|
||||
if len(cells) < 5:
|
||||
continue
|
||||
cells[2] = f" {type_markdown} "
|
||||
lines[index] = "|".join(cells)
|
||||
break
|
||||
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
def _patch_union_schema_markdown(markdown: str, spec_path: Path) -> str:
|
||||
"""Fill Swagger Markdown table cells that `swagger-markdown` leaves blank for union schemas."""
|
||||
|
||||
spec = json.loads(spec_path.read_text(encoding="utf-8"))
|
||||
definitions = spec.get("definitions")
|
||||
if not isinstance(definitions, dict):
|
||||
return markdown
|
||||
|
||||
for definition_name, schema in definitions.items():
|
||||
if not isinstance(definition_name, str) or not isinstance(schema, dict):
|
||||
continue
|
||||
one_of = schema.get("oneOf")
|
||||
if not isinstance(one_of, list):
|
||||
continue
|
||||
|
||||
markdown = _replace_schema_table_type(
|
||||
markdown,
|
||||
definition_name,
|
||||
definition_name,
|
||||
_schema_markdown_type(schema),
|
||||
)
|
||||
|
||||
for variant in one_of:
|
||||
variant_name = _definition_ref_name(variant)
|
||||
variant_schema = definitions.get(variant_name) if variant_name is not None else None
|
||||
if not isinstance(variant_name, str) or not isinstance(variant_schema, dict):
|
||||
continue
|
||||
properties = variant_schema.get("properties")
|
||||
if not isinstance(properties, dict):
|
||||
continue
|
||||
for property_name, property_schema in properties.items():
|
||||
if isinstance(property_name, str):
|
||||
markdown = _replace_schema_table_type(
|
||||
markdown,
|
||||
variant_name,
|
||||
property_name,
|
||||
_schema_markdown_type(property_schema),
|
||||
)
|
||||
|
||||
return markdown
|
||||
|
||||
|
||||
def _convert_spec_to_markdown(spec_path: Path, markdown_path: Path) -> None:
|
||||
markdown_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
with tempfile.TemporaryDirectory(prefix=f"{markdown_path.stem}-", dir=markdown_path.parent) as temp_dir:
|
||||
@@ -57,7 +177,10 @@ def _convert_spec_to_markdown(spec_path: Path, markdown_path: Path) -> None:
|
||||
converter_output = "\n".join(item for item in (result.stdout, result.stderr) if item).strip()
|
||||
raise RuntimeError(f"swagger-markdown did not write {markdown_path}: {converter_output}")
|
||||
|
||||
converted_markdown = temp_markdown_path.read_text(encoding="utf-8")
|
||||
converted_markdown = _patch_union_schema_markdown(
|
||||
temp_markdown_path.read_text(encoding="utf-8"),
|
||||
spec_path,
|
||||
)
|
||||
if not converted_markdown.strip():
|
||||
raise RuntimeError(f"swagger-markdown wrote an empty document for {markdown_path}")
|
||||
|
||||
|
||||
@@ -11805,6 +11805,27 @@ Request payload for bulk downloading documents as a zip archive.
|
||||
| ---- | ---- | ----------- | -------- |
|
||||
| file_ids | [ string ] | | Yes |
|
||||
|
||||
#### FileInputConfig
|
||||
|
||||
| Name | Type | Description | Required |
|
||||
| ---- | ---- | ----------- | -------- |
|
||||
| allowed_file_extensions | [ string ] | | No |
|
||||
| allowed_file_types | [ [FileType](#filetype) ] | | No |
|
||||
| allowed_file_upload_methods | [ [FileTransferMethod](#filetransfermethod) ] | | No |
|
||||
| output_variable_name | string | | Yes |
|
||||
| type | string | | No |
|
||||
|
||||
#### FileListInputConfig
|
||||
|
||||
| Name | Type | Description | Required |
|
||||
| ---- | ---- | ----------- | -------- |
|
||||
| allowed_file_extensions | [ string ] | | No |
|
||||
| allowed_file_types | [ [FileType](#filetype) ] | | No |
|
||||
| allowed_file_upload_methods | [ [FileTransferMethod](#filetransfermethod) ] | | No |
|
||||
| number_limits | integer | | No |
|
||||
| output_variable_name | string | | Yes |
|
||||
| type | string | | No |
|
||||
|
||||
#### FileResponse
|
||||
|
||||
| Name | Type | Description | Required |
|
||||
@@ -11824,6 +11845,18 @@ Request payload for bulk downloading documents as a zip archive.
|
||||
| tenant_id | string | | No |
|
||||
| user_id | string | | No |
|
||||
|
||||
#### FileTransferMethod
|
||||
|
||||
| Name | Type | Description | Required |
|
||||
| ---- | ---- | ----------- | -------- |
|
||||
| FileTransferMethod | string | | |
|
||||
|
||||
#### FileType
|
||||
|
||||
| Name | Type | Description | Required |
|
||||
| ---- | ---- | ----------- | -------- |
|
||||
| FileType | string | | |
|
||||
|
||||
#### ForgotPasswordCheckPayload
|
||||
|
||||
| Name | Type | Description | Required |
|
||||
@@ -11869,33 +11902,11 @@ Request payload for bulk downloading documents as a zip archive.
|
||||
| email | string | | Yes |
|
||||
| language | string | | No |
|
||||
|
||||
#### FormInput
|
||||
|
||||
Form input definition.
|
||||
#### FormInputConfig
|
||||
|
||||
| Name | Type | Description | Required |
|
||||
| ---- | ---- | ----------- | -------- |
|
||||
| default | [FormInputDefault](#forminputdefault) | | No |
|
||||
| output_variable_name | string | | Yes |
|
||||
| type | [FormInputType](#forminputtype) | | Yes |
|
||||
|
||||
#### FormInputDefault
|
||||
|
||||
Default configuration for form inputs.
|
||||
|
||||
| Name | Type | Description | Required |
|
||||
| ---- | ---- | ----------- | -------- |
|
||||
| selector | [ string ] | | No |
|
||||
| type | [PlaceholderType](#placeholdertype) | | Yes |
|
||||
| value | string | | No |
|
||||
|
||||
#### FormInputType
|
||||
|
||||
Form input types.
|
||||
|
||||
| Name | Type | Description | Required |
|
||||
| ---- | ---- | ----------- | -------- |
|
||||
| FormInputType | string | Form input types. | |
|
||||
| FormInputConfig | [ParagraphInputConfig](#paragraphinputconfig)<br>[SelectInputConfig](#selectinputconfig)<br>[FileInputConfig](#fileinputconfig)<br>[FileListInputConfig](#filelistinputconfig) | | |
|
||||
|
||||
#### GenerateSummaryPayload
|
||||
|
||||
@@ -12018,13 +12029,13 @@ Form input types.
|
||||
|
||||
| Name | Type | Description | Required |
|
||||
| ---- | ---- | ----------- | -------- |
|
||||
| actions | [ [UserAction](#useraction) ] | | No |
|
||||
| actions | [ [UserActionConfig](#useractionconfig) ] | | No |
|
||||
| display_in_ui | boolean | | No |
|
||||
| expiration_time | integer | | Yes |
|
||||
| form_content | string | | Yes |
|
||||
| form_id | string | | Yes |
|
||||
| form_token | string | | No |
|
||||
| inputs | [ [FormInput](#forminput) ] | | No |
|
||||
| inputs | [ [FormInputConfig](#forminputconfig) ] | | No |
|
||||
| node_id | string | | Yes |
|
||||
| node_title | string | | Yes |
|
||||
| resolved_default_values | object | | No |
|
||||
@@ -12662,6 +12673,16 @@ Enum class for model type.
|
||||
| page | integer | | Yes |
|
||||
| total | integer | | Yes |
|
||||
|
||||
#### ParagraphInputConfig
|
||||
|
||||
Form input definition.
|
||||
|
||||
| Name | Type | Description | Required |
|
||||
| ---- | ---- | ----------- | -------- |
|
||||
| default | [StringSource](#stringsource) | | No |
|
||||
| output_variable_name | string | | Yes |
|
||||
| type | string | | No |
|
||||
|
||||
#### Parser
|
||||
|
||||
| Name | Type | Description | Required |
|
||||
@@ -12991,14 +13012,6 @@ Enum class for model type.
|
||||
| unit | string | | No |
|
||||
| variable | string | | No |
|
||||
|
||||
#### PlaceholderType
|
||||
|
||||
Default value types for form inputs.
|
||||
|
||||
| Name | Type | Description | Required |
|
||||
| ---- | ---- | ----------- | -------- |
|
||||
| PlaceholderType | string | Default value types for form inputs. | |
|
||||
|
||||
#### PluginAutoUpgradeSettingsPayload
|
||||
|
||||
| Name | Type | Description | Required |
|
||||
@@ -13279,6 +13292,14 @@ Default value types for form inputs.
|
||||
| max_tokens | integer | | Yes |
|
||||
| separator | string | | No |
|
||||
|
||||
#### SelectInputConfig
|
||||
|
||||
| Name | Type | Description | Required |
|
||||
| ---- | ---- | ----------- | -------- |
|
||||
| option_source | [StringListSource](#stringlistsource) | | Yes |
|
||||
| output_variable_name | string | | Yes |
|
||||
| type | string | | No |
|
||||
|
||||
#### SimpleAccount
|
||||
|
||||
| Name | Type | Description | Required |
|
||||
@@ -13361,6 +13382,24 @@ Default value types for form inputs.
|
||||
| ---- | ---- | ----------- | -------- |
|
||||
| StrategySetting | string | | |
|
||||
|
||||
#### StringListSource
|
||||
|
||||
| Name | Type | Description | Required |
|
||||
| ---- | ---- | ----------- | -------- |
|
||||
| selector | [ string ] | | No |
|
||||
| type | [ValueSourceType](#valuesourcetype) | | Yes |
|
||||
| value | [ string ] | | No |
|
||||
|
||||
#### StringSource
|
||||
|
||||
Default configuration for form inputs.
|
||||
|
||||
| Name | Type | Description | Required |
|
||||
| ---- | ---- | ----------- | -------- |
|
||||
| selector | [ string ] | | No |
|
||||
| type | [ValueSourceType](#valuesourcetype) | | Yes |
|
||||
| value | string | | No |
|
||||
|
||||
#### SubscriptionQuery
|
||||
|
||||
| Name | Type | Description | Required |
|
||||
@@ -13792,7 +13831,7 @@ Tag type
|
||||
| video_file_size_limit | integer | | Yes |
|
||||
| workflow_file_upload_limit | integer | | Yes |
|
||||
|
||||
#### UserAction
|
||||
#### UserActionConfig
|
||||
|
||||
User action configuration.
|
||||
|
||||
@@ -13802,6 +13841,15 @@ User action configuration.
|
||||
| id | string | | Yes |
|
||||
| title | string | | Yes |
|
||||
|
||||
#### ValueSourceType
|
||||
|
||||
ValueSourceType records whether the value comes from a static setting
|
||||
in form definiton, or a variable while the workflow is running.
|
||||
|
||||
| Name | Type | Description | Required |
|
||||
| ---- | ---- | ----------- | -------- |
|
||||
| ValueSourceType | string | ValueSourceType records whether the value comes from a static setting in form definiton, or a variable while the workflow is running. | |
|
||||
|
||||
#### WebhookTriggerResponse
|
||||
|
||||
| Name | Type | Description | Required |
|
||||
|
||||
+1
-1
@@ -43,7 +43,7 @@ dependencies = [
|
||||
"resend>=2.27.0,<3.0.0",
|
||||
# Emerging: newer and fast-moving, use compatible pins
|
||||
"fastopenapi[flask]~=0.7.0",
|
||||
"graphon~=0.3.1",
|
||||
"graphon~=0.4.0",
|
||||
"httpx-sse~=0.4.0",
|
||||
"json-repair~=0.59.4",
|
||||
]
|
||||
|
||||
@@ -29,7 +29,11 @@ from core.workflow.node_factory import (
|
||||
get_node_type_classes_mapping,
|
||||
is_start_node_type,
|
||||
)
|
||||
from core.workflow.node_runtime import DifyHumanInputNodeRuntime, apply_dify_debug_email_recipient
|
||||
from core.workflow.node_runtime import (
|
||||
DifyFileReferenceFactory,
|
||||
DifyHumanInputNodeRuntime,
|
||||
apply_dify_debug_email_recipient,
|
||||
)
|
||||
from core.workflow.system_variables import build_bootstrap_variables, build_system_variables, default_system_variables
|
||||
from core.workflow.variable_pool_initializer import add_node_inputs_to_pool, add_variables_to_pool
|
||||
from core.workflow.workflow_entry import WorkflowEntry
|
||||
@@ -1259,6 +1263,7 @@ class WorkflowService:
|
||||
data=node_data,
|
||||
graph_init_params=graph_init_params,
|
||||
graph_runtime_state=graph_runtime_state,
|
||||
file_reference_factory=DifyFileReferenceFactory(graph_init_params.run_context),
|
||||
runtime=DifyHumanInputNodeRuntime(run_context),
|
||||
)
|
||||
return node
|
||||
|
||||
@@ -60,14 +60,14 @@ def init_tool_node(config: dict):
|
||||
|
||||
graph = Graph.init(graph_config=graph_config, node_factory=node_factory, root_node_id="start")
|
||||
|
||||
tool_file_manager_factory = MagicMock(spec=ToolFileManagerProtocol)
|
||||
tool_file_manager = MagicMock(spec=ToolFileManagerProtocol)
|
||||
|
||||
node = ToolNode(
|
||||
node_id=str(uuid.uuid4()),
|
||||
data=ToolNodeData.model_validate(config["data"]),
|
||||
graph_init_params=init_params,
|
||||
graph_runtime_state=graph_runtime_state,
|
||||
tool_file_manager_factory=tool_file_manager_factory,
|
||||
tool_file_manager=tool_file_manager,
|
||||
runtime=DifyToolNodeRuntime(init_params.run_context),
|
||||
)
|
||||
return node
|
||||
|
||||
+4
-4
@@ -17,7 +17,7 @@ from core.workflow.human_input_adapter import (
|
||||
MemberRecipient,
|
||||
WebAppDeliveryMethod,
|
||||
)
|
||||
from graphon.nodes.human_input.entities import FormDefinition, HumanInputNodeData, UserAction
|
||||
from graphon.nodes.human_input.entities import FormDefinition, HumanInputNodeData, UserActionConfig
|
||||
from models.account import (
|
||||
Account,
|
||||
AccountStatus,
|
||||
@@ -69,7 +69,7 @@ def _build_form_params(delivery_methods: list[DeliveryChannelConfig]) -> FormCre
|
||||
title="Human Approval",
|
||||
delivery_methods=delivery_methods,
|
||||
form_content="<p>Approve?</p>",
|
||||
user_actions=[UserAction(id="approve", title="Approve")],
|
||||
user_actions=[UserActionConfig(id="approve", title="Approve")],
|
||||
)
|
||||
return FormCreateParams(
|
||||
workflow_execution_id=str(uuid4()),
|
||||
@@ -185,7 +185,7 @@ class TestHumanInputFormRepositoryImplWithContainers:
|
||||
title="Human Approval",
|
||||
form_content="<p>Approve?</p>",
|
||||
inputs=[],
|
||||
user_actions=[UserAction(id="approve", title="Approve")],
|
||||
user_actions=[UserActionConfig(id="approve", title="Approve")],
|
||||
),
|
||||
rendered_content="<p>Approve?</p>",
|
||||
delivery_methods=[],
|
||||
@@ -220,7 +220,7 @@ class TestHumanInputFormRepositoryImplWithContainers:
|
||||
title="Human Approval",
|
||||
form_content="<p>Approve?</p>",
|
||||
inputs=[],
|
||||
user_actions=[UserAction(id="approve", title="Approve")],
|
||||
user_actions=[UserActionConfig(id="approve", title="Approve")],
|
||||
delivery_methods=[WebAppDeliveryMethod()],
|
||||
),
|
||||
rendered_content="<p>Approve?</p>",
|
||||
|
||||
+4
-3
@@ -13,7 +13,7 @@ from core.app.workflow.layers import PersistenceWorkflowInfo, WorkflowPersistenc
|
||||
from core.repositories.human_input_repository import HumanInputFormEntity, HumanInputFormRepository
|
||||
from core.repositories.sqlalchemy_workflow_execution_repository import SQLAlchemyWorkflowExecutionRepository
|
||||
from core.repositories.sqlalchemy_workflow_node_execution_repository import SQLAlchemyWorkflowNodeExecutionRepository
|
||||
from core.workflow.node_runtime import DifyHumanInputNodeRuntime
|
||||
from core.workflow.node_runtime import DifyFileReferenceFactory, DifyHumanInputNodeRuntime
|
||||
from core.workflow.system_variables import build_system_variables
|
||||
from graphon.enums import WorkflowType
|
||||
from graphon.graph import Graph
|
||||
@@ -21,7 +21,7 @@ from graphon.graph_engine import GraphEngine
|
||||
from graphon.graph_engine.command_channels import InMemoryChannel
|
||||
from graphon.nodes.end.end_node import EndNode
|
||||
from graphon.nodes.end.entities import EndNodeData
|
||||
from graphon.nodes.human_input.entities import HumanInputNodeData, UserAction
|
||||
from graphon.nodes.human_input.entities import HumanInputNodeData, UserActionConfig
|
||||
from graphon.nodes.human_input.enums import HumanInputFormStatus
|
||||
from graphon.nodes.human_input.human_input_node import HumanInputNode
|
||||
from graphon.nodes.start.entities import StartNodeData
|
||||
@@ -112,7 +112,7 @@ def _build_graph(
|
||||
form_content="Awaiting human input",
|
||||
inputs=[],
|
||||
user_actions=[
|
||||
UserAction(id="continue", title="Continue"),
|
||||
UserActionConfig(id="continue", title="Continue"),
|
||||
],
|
||||
)
|
||||
human_node = HumanInputNode(
|
||||
@@ -121,6 +121,7 @@ def _build_graph(
|
||||
graph_init_params=params,
|
||||
graph_runtime_state=runtime_state,
|
||||
form_repository=form_repository,
|
||||
file_reference_factory=DifyFileReferenceFactory(params.run_context),
|
||||
runtime=DifyHumanInputNodeRuntime(params.run_context),
|
||||
)
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ from datetime import timedelta
|
||||
from decimal import Decimal
|
||||
from uuid import uuid4
|
||||
|
||||
from graphon.nodes.human_input.entities import FormDefinition, UserAction
|
||||
from graphon.nodes.human_input.entities import FormDefinition, UserActionConfig
|
||||
from libs.datetime_utils import naive_utc_now
|
||||
from models.account import Account, Tenant, TenantAccountJoin
|
||||
from models.enums import ConversationFromSource, InvokeFrom
|
||||
@@ -116,7 +116,7 @@ def create_human_input_message_fixture(db_session) -> HumanInputMessageFixture:
|
||||
form_definition = FormDefinition(
|
||||
form_content="content",
|
||||
inputs=[],
|
||||
user_actions=[UserAction(id=action_id, title=action_text)],
|
||||
user_actions=[UserActionConfig(id=action_id, title=action_text)],
|
||||
rendered_content="Rendered block",
|
||||
expiration_time=naive_utc_now() + timedelta(days=1),
|
||||
node_title=node_title,
|
||||
|
||||
+5
-5
@@ -17,7 +17,7 @@ from extensions.ext_storage import storage
|
||||
from graphon.entities import WorkflowExecution
|
||||
from graphon.entities.pause_reason import HumanInputRequired, PauseReasonType
|
||||
from graphon.enums import WorkflowExecutionStatus
|
||||
from graphon.nodes.human_input.entities import FormDefinition, FormInput, UserAction
|
||||
from graphon.nodes.human_input.entities import FormDefinition, ParagraphInputConfig, UserActionConfig
|
||||
from graphon.nodes.human_input.enums import FormInputType, HumanInputFormStatus
|
||||
from libs.datetime_utils import naive_utc_now
|
||||
from models.enums import CreatorUserRole, WorkflowRunTriggeredFrom
|
||||
@@ -642,8 +642,8 @@ class TestBuildHumanInputRequiredReason:
|
||||
expiration_time = naive_utc_now()
|
||||
form_definition = FormDefinition(
|
||||
form_content="content",
|
||||
inputs=[FormInput(type=FormInputType.TEXT_INPUT, output_variable_name="name")],
|
||||
user_actions=[UserAction(id="approve", title="Approve")],
|
||||
inputs=[ParagraphInputConfig(type=FormInputType.PARAGRAPH, output_variable_name="name")],
|
||||
user_actions=[UserActionConfig(id="approve", title="Approve")],
|
||||
rendered_content="rendered",
|
||||
expiration_time=expiration_time,
|
||||
default_values={"name": "Alice"},
|
||||
@@ -754,8 +754,8 @@ class TestBuildHumanInputRequiredReason:
|
||||
expiration_time = naive_utc_now()
|
||||
form_definition = FormDefinition(
|
||||
form_content="content",
|
||||
inputs=[FormInput(type=FormInputType.TEXT_INPUT, output_variable_name="name")],
|
||||
user_actions=[UserAction(id="approve", title="Approve")],
|
||||
inputs=[ParagraphInputConfig(type=FormInputType.PARAGRAPH, output_variable_name="name")],
|
||||
user_actions=[UserActionConfig(id="approve", title="Approve")],
|
||||
rendered_content="rendered",
|
||||
expiration_time=expiration_time,
|
||||
default_values={"name": "Alice"},
|
||||
|
||||
+3
-3
@@ -15,7 +15,7 @@ import pytest
|
||||
from sqlalchemy import Engine, delete, select
|
||||
from sqlalchemy.orm import Session, sessionmaker
|
||||
|
||||
from graphon.nodes.human_input.entities import FormDefinition, UserAction
|
||||
from graphon.nodes.human_input.entities import FormDefinition, UserActionConfig
|
||||
from graphon.nodes.human_input.enums import HumanInputFormStatus
|
||||
from libs.datetime_utils import naive_utc_now
|
||||
from models.account import Account, Tenant, TenantAccountJoin, TenantAccountRole
|
||||
@@ -179,7 +179,7 @@ def _create_submitted_form(
|
||||
form_definition = FormDefinition(
|
||||
form_content="content",
|
||||
inputs=[],
|
||||
user_actions=[UserAction(id=action_id, title=action_title)],
|
||||
user_actions=[UserActionConfig(id=action_id, title=action_title)],
|
||||
rendered_content="rendered",
|
||||
expiration_time=expiration_time,
|
||||
node_title=node_title,
|
||||
@@ -212,7 +212,7 @@ def _create_waiting_form(
|
||||
form_definition = FormDefinition(
|
||||
form_content="content",
|
||||
inputs=[],
|
||||
user_actions=[UserAction(id="approve", title="Approve")],
|
||||
user_actions=[UserActionConfig(id="approve", title="Approve")],
|
||||
rendered_content="rendered",
|
||||
expiration_time=expiration_time,
|
||||
default_values=default_values or {"name": "John"},
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
"""Unit tests for the Markdown API docs generator."""
|
||||
|
||||
import importlib.util
|
||||
import json
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
@@ -101,3 +102,195 @@ def test_generate_markdown_docs_only_removes_generated_specs_from_separate_swagg
|
||||
|
||||
assert existing_file.read_text(encoding="utf-8") == "keep me"
|
||||
assert not list(swagger_dir.glob("*.json"))
|
||||
|
||||
|
||||
def test_patch_union_schema_markdown_fills_converter_blank_schema_types(tmp_path):
|
||||
module = _load_generate_swagger_markdown_docs_module()
|
||||
spec_path = tmp_path / "console-swagger.json"
|
||||
spec_path.write_text(
|
||||
json.dumps(
|
||||
{
|
||||
"definitions": {
|
||||
"FormInputConfig": {
|
||||
"oneOf": [
|
||||
{"$ref": "#/definitions/ParagraphInputConfig"},
|
||||
{"$ref": "#/definitions/SelectInputConfig"},
|
||||
{"$ref": "#/definitions/FileInputConfig"},
|
||||
],
|
||||
},
|
||||
"ParagraphInputConfig": {
|
||||
"properties": {
|
||||
"default": {
|
||||
"anyOf": [
|
||||
{"$ref": "#/definitions/StringSource"},
|
||||
{"type": "null"},
|
||||
],
|
||||
},
|
||||
"output_variable_name": {"type": "string"},
|
||||
},
|
||||
},
|
||||
"SelectInputConfig": {
|
||||
"properties": {
|
||||
"option_source": {"$ref": "#/definitions/StringListSource"},
|
||||
},
|
||||
},
|
||||
"FileInputConfig": {
|
||||
"properties": {
|
||||
"allowed_file_types": {
|
||||
"type": "array",
|
||||
"items": {"$ref": "#/definitions/FileType"},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
),
|
||||
encoding="utf-8",
|
||||
)
|
||||
markdown = """#### FormInputConfig
|
||||
|
||||
| Name | Type | Description | Required |
|
||||
| ---- | ---- | ----------- | -------- |
|
||||
| FormInputConfig | | | |
|
||||
|
||||
#### ParagraphInputConfig
|
||||
|
||||
| Name | Type | Description | Required |
|
||||
| ---- | ---- | ----------- | -------- |
|
||||
| default | | | No |
|
||||
| output_variable_name | string | | Yes |
|
||||
|
||||
#### SelectInputConfig
|
||||
|
||||
| Name | Type | Description | Required |
|
||||
| ---- | ---- | ----------- | -------- |
|
||||
| option_source | | | Yes |
|
||||
|
||||
#### FileInputConfig
|
||||
|
||||
| Name | Type | Description | Required |
|
||||
| ---- | ---- | ----------- | -------- |
|
||||
| allowed_file_types | | | No |
|
||||
"""
|
||||
|
||||
patched = module._patch_union_schema_markdown(markdown, spec_path)
|
||||
|
||||
assert (
|
||||
"| FormInputConfig | "
|
||||
"[ParagraphInputConfig](#paragraphinputconfig)<br>"
|
||||
"[SelectInputConfig](#selectinputconfig)<br>"
|
||||
"[FileInputConfig](#fileinputconfig) | | |"
|
||||
) in patched
|
||||
assert "| default | [StringSource](#stringsource) | | No |" in patched
|
||||
assert "| output_variable_name | string | | Yes |" in patched
|
||||
assert "| option_source | [StringListSource](#stringlistsource) | | Yes |" in patched
|
||||
assert "| allowed_file_types | [ [FileType](#filetype) ] | | No |" in patched
|
||||
|
||||
|
||||
def test_patch_union_schema_markdown_ignores_specs_without_definitions(tmp_path):
|
||||
module = _load_generate_swagger_markdown_docs_module()
|
||||
spec_path = tmp_path / "console-swagger.json"
|
||||
spec_path.write_text("{}", encoding="utf-8")
|
||||
|
||||
assert module._patch_union_schema_markdown("unchanged", spec_path) == "unchanged"
|
||||
|
||||
|
||||
def test_patch_union_schema_markdown_ignores_unrenderable_shapes(tmp_path):
|
||||
module = _load_generate_swagger_markdown_docs_module()
|
||||
spec_path = tmp_path / "console-swagger.json"
|
||||
spec_path.write_text(
|
||||
json.dumps(
|
||||
{
|
||||
"definitions": {
|
||||
"NotAMapping": [],
|
||||
"BrokenUnion": {
|
||||
"oneOf": [
|
||||
{},
|
||||
{"$ref": "#/definitions/Missing"},
|
||||
{"$ref": "#/definitions/NoPropertyMapping"},
|
||||
],
|
||||
},
|
||||
"NoPropertyMapping": {"properties": []},
|
||||
}
|
||||
}
|
||||
),
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
assert module._definition_ref_name(None) is None
|
||||
assert module._schema_markdown_type(None) == ""
|
||||
assert module._schema_markdown_type({"anyOf": [{"type": "null"}]}) == ""
|
||||
assert module._replace_schema_table_type("unchanged", "Definition", "field", "") == "unchanged"
|
||||
assert (
|
||||
module._replace_schema_table_type(
|
||||
"#### Definition\n#### Next\n| field | | | No |",
|
||||
"Definition",
|
||||
"field",
|
||||
"string",
|
||||
)
|
||||
== "#### Definition\n#### Next\n| field | | | No |"
|
||||
)
|
||||
assert (
|
||||
module._replace_schema_table_type("#### Definition\n| field |", "Definition", "field", "string")
|
||||
== "#### Definition\n| field |"
|
||||
)
|
||||
|
||||
assert module._patch_union_schema_markdown("#### BrokenUnion\n", spec_path) == "#### BrokenUnion"
|
||||
|
||||
|
||||
def test_convert_spec_to_markdown_patches_generated_union_tables(tmp_path, monkeypatch):
|
||||
module = _load_generate_swagger_markdown_docs_module()
|
||||
spec_path = tmp_path / "console-swagger.json"
|
||||
output_path = tmp_path / "console-swagger.md"
|
||||
spec_path.write_text(
|
||||
json.dumps(
|
||||
{
|
||||
"definitions": {
|
||||
"FormInputConfig": {
|
||||
"oneOf": [
|
||||
{"$ref": "#/definitions/ParagraphInputConfig"},
|
||||
],
|
||||
},
|
||||
"ParagraphInputConfig": {
|
||||
"properties": {
|
||||
"default": {
|
||||
"anyOf": [
|
||||
{"$ref": "#/definitions/StringSource"},
|
||||
{"type": "null"},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
),
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
def run_converter(args, **kwargs):
|
||||
assert kwargs["check"] is False
|
||||
markdown_path = Path(args[args.index("-o") + 1])
|
||||
markdown_path.write_text(
|
||||
"""#### FormInputConfig
|
||||
|
||||
| Name | Type | Description | Required |
|
||||
| ---- | ---- | ----------- | -------- |
|
||||
| FormInputConfig | | | |
|
||||
|
||||
#### ParagraphInputConfig
|
||||
|
||||
| Name | Type | Description | Required |
|
||||
| ---- | ---- | ----------- | -------- |
|
||||
| default | | | No |
|
||||
""",
|
||||
encoding="utf-8",
|
||||
)
|
||||
return module.subprocess.CompletedProcess(args=args, returncode=0, stdout="", stderr="")
|
||||
|
||||
monkeypatch.setattr(module.subprocess, "run", run_converter)
|
||||
|
||||
module._convert_spec_to_markdown(spec_path, output_path)
|
||||
|
||||
converted = output_path.read_text(encoding="utf-8")
|
||||
assert "| FormInputConfig | [ParagraphInputConfig](#paragraphinputconfig) | | |" in converted
|
||||
assert "| default | [StringSource](#stringsource) | | No |" in converted
|
||||
|
||||
@@ -12,7 +12,7 @@ from controllers.console.app import workflow_run as workflow_run_module
|
||||
from controllers.web.error import NotFoundError
|
||||
from graphon.entities.pause_reason import HumanInputRequired
|
||||
from graphon.enums import WorkflowExecutionStatus
|
||||
from graphon.nodes.human_input.entities import FormInput, UserAction
|
||||
from graphon.nodes.human_input.entities import ParagraphInputConfig, UserActionConfig
|
||||
from graphon.nodes.human_input.enums import FormInputType
|
||||
from libs import login as login_lib
|
||||
from models.account import Account, AccountStatus, TenantAccountRole
|
||||
@@ -63,8 +63,8 @@ def test_pause_details_returns_backstage_input_url(app: Flask, monkeypatch: pyte
|
||||
reason = HumanInputRequired(
|
||||
form_id="form-1",
|
||||
form_content="content",
|
||||
inputs=[FormInput(type=FormInputType.TEXT_INPUT, output_variable_name="name")],
|
||||
actions=[UserAction(id="approve", title="Approve")],
|
||||
inputs=[ParagraphInputConfig(type=FormInputType.PARAGRAPH, output_variable_name="name")],
|
||||
actions=[UserActionConfig(id="approve", title="Approve")],
|
||||
node_id="node-1",
|
||||
node_title="Ask Name",
|
||||
)
|
||||
|
||||
@@ -32,7 +32,7 @@ from core.workflow.system_variables import build_system_variables
|
||||
from graphon.entities import WorkflowStartReason
|
||||
from graphon.entities.pause_reason import HumanInputRequired, PauseReasonType
|
||||
from graphon.enums import WorkflowExecutionStatus, WorkflowNodeExecutionStatus
|
||||
from graphon.nodes.human_input.entities import FormInput, UserAction
|
||||
from graphon.nodes.human_input.entities import ParagraphInputConfig, UserActionConfig
|
||||
from graphon.nodes.human_input.enums import FormInputType
|
||||
from graphon.runtime import GraphRuntimeState, VariablePool
|
||||
from models.account import Account
|
||||
@@ -450,7 +450,7 @@ class TestHitlServiceApi:
|
||||
node_title="Approval",
|
||||
form_content="Need approval",
|
||||
inputs=[],
|
||||
actions=[UserAction(id="approve", title="Approve")],
|
||||
actions=[UserActionConfig(id="approve", title="Approve")],
|
||||
display_in_ui=True,
|
||||
form_token="token-1",
|
||||
resolved_default_values={},
|
||||
@@ -591,9 +591,9 @@ class TestHitlServiceApi:
|
||||
form_id="form-1",
|
||||
form_content="Rendered",
|
||||
inputs=[
|
||||
FormInput(type=FormInputType.TEXT_INPUT, output_variable_name="field", default=None),
|
||||
ParagraphInputConfig(type=FormInputType.PARAGRAPH, output_variable_name="field", default=None),
|
||||
],
|
||||
actions=[UserAction(id="approve", title="Approve")],
|
||||
actions=[UserActionConfig(id="approve", title="Approve")],
|
||||
display_in_ui=True,
|
||||
node_id="node-id",
|
||||
node_title="Human Step",
|
||||
|
||||
+2
-2
@@ -51,7 +51,7 @@ from core.base.tts.app_generator_tts_publisher import AudioTrunk
|
||||
from core.workflow.system_variables import build_system_variables
|
||||
from graphon.entities.pause_reason import PauseReasonType
|
||||
from graphon.enums import BuiltinNodeTypes
|
||||
from graphon.nodes.human_input.entities import UserAction
|
||||
from graphon.nodes.human_input.entities import UserActionConfig
|
||||
from graphon.runtime import GraphRuntimeState, VariablePool
|
||||
from libs.datetime_utils import naive_utc_now
|
||||
from models.enums import MessageStatus
|
||||
@@ -150,7 +150,7 @@ class TestAdvancedChatGenerateTaskPipeline:
|
||||
node_title="Approval",
|
||||
form_content="Need approval",
|
||||
inputs=[],
|
||||
actions=[UserAction(id="approve", title="Approve")],
|
||||
actions=[UserActionConfig(id="approve", title="Approve")],
|
||||
display_in_ui=True,
|
||||
form_token="token-1",
|
||||
resolved_default_values={},
|
||||
|
||||
@@ -14,7 +14,7 @@ from core.workflow.system_variables import build_system_variables
|
||||
from graphon.entities import WorkflowStartReason
|
||||
from graphon.entities.pause_reason import HumanInputRequired
|
||||
from graphon.graph_events import GraphRunPausedEvent
|
||||
from graphon.nodes.human_input.entities import FormInput, UserAction
|
||||
from graphon.nodes.human_input.entities import ParagraphInputConfig, UserActionConfig
|
||||
from graphon.nodes.human_input.enums import FormInputType
|
||||
from models.account import Account
|
||||
from models.human_input import RecipientType
|
||||
@@ -157,9 +157,9 @@ def test_queue_workflow_paused_event_to_stream_responses(monkeypatch: pytest.Mon
|
||||
form_id="form-1",
|
||||
form_content="Rendered",
|
||||
inputs=[
|
||||
FormInput(type=FormInputType.TEXT_INPUT, output_variable_name="field", default=None),
|
||||
ParagraphInputConfig(type=FormInputType.PARAGRAPH, output_variable_name="field", default=None),
|
||||
],
|
||||
actions=[UserAction(id="approve", title="Approve")],
|
||||
actions=[UserActionConfig(id="approve", title="Approve")],
|
||||
node_id="node-id",
|
||||
node_title="Human Step",
|
||||
)
|
||||
|
||||
@@ -4,7 +4,7 @@ from core.entities.execution_extra_content import (
|
||||
HumanInputFormDefinition,
|
||||
HumanInputFormSubmissionData,
|
||||
)
|
||||
from graphon.nodes.human_input.entities import FormInput, UserAction
|
||||
from graphon.nodes.human_input.entities import ParagraphInputConfig, UserActionConfig
|
||||
from graphon.nodes.human_input.enums import FormInputType
|
||||
from models.execution_extra_content import ExecutionContentType
|
||||
|
||||
@@ -16,8 +16,8 @@ def test_human_input_content_defaults_and_domain_alias() -> None:
|
||||
node_id="node-1",
|
||||
node_title="Human Input",
|
||||
form_content="Please confirm",
|
||||
inputs=[FormInput(type=FormInputType.TEXT_INPUT, output_variable_name="answer")],
|
||||
actions=[UserAction(id="confirm", title="Confirm")],
|
||||
inputs=[ParagraphInputConfig(type=FormInputType.PARAGRAPH, output_variable_name="answer")],
|
||||
actions=[UserActionConfig(id="confirm", title="Confirm")],
|
||||
resolved_default_values={"answer": "yes"},
|
||||
expiration_time=1_700_000_000,
|
||||
)
|
||||
|
||||
@@ -23,7 +23,7 @@ from core.workflow.human_input_adapter import (
|
||||
)
|
||||
from graphon.nodes.human_input.entities import (
|
||||
FormDefinition,
|
||||
UserAction,
|
||||
UserActionConfig,
|
||||
)
|
||||
from graphon.nodes.human_input.enums import HumanInputFormKind, HumanInputFormStatus
|
||||
from libs.datetime_utils import naive_utc_now
|
||||
@@ -272,7 +272,7 @@ def _make_form_definition() -> str:
|
||||
return FormDefinition(
|
||||
form_content="hello",
|
||||
inputs=[],
|
||||
user_actions=[UserAction(id="submit", title="Submit")],
|
||||
user_actions=[UserActionConfig(id="submit", title="Submit")],
|
||||
rendered_content="<p>hello</p>",
|
||||
expiration_time=naive_utc_now(),
|
||||
).model_dump_json()
|
||||
|
||||
@@ -29,7 +29,7 @@ from core.workflow.human_input_adapter import (
|
||||
MemberRecipient,
|
||||
WebAppDeliveryMethod,
|
||||
)
|
||||
from graphon.nodes.human_input.entities import HumanInputNodeData, UserAction
|
||||
from graphon.nodes.human_input.entities import HumanInputNodeData, UserActionConfig
|
||||
from graphon.nodes.human_input.enums import HumanInputFormKind, HumanInputFormStatus
|
||||
from libs.datetime_utils import naive_utc_now
|
||||
from models.human_input import HumanInputFormRecipient, RecipientType
|
||||
@@ -467,7 +467,7 @@ def test_create_form_adds_console_and_backstage_recipients(monkeypatch: pytest.M
|
||||
delivery_methods=[],
|
||||
form_content="hello",
|
||||
inputs=[],
|
||||
user_actions=[UserAction(id="submit", title="Submit")],
|
||||
user_actions=[UserActionConfig(id="submit", title="Submit")],
|
||||
)
|
||||
params = FormCreateParams(
|
||||
workflow_execution_id=None,
|
||||
|
||||
@@ -81,11 +81,11 @@ class MockNodeMixin:
|
||||
if isinstance(self, TemplateTransformNode):
|
||||
kwargs.setdefault("jinja2_template_renderer", _TestJinja2Renderer())
|
||||
|
||||
# Provide default tool_file_manager_factory for ToolNode subclasses
|
||||
# Provide default ToolNode dependencies for ToolNode subclasses.
|
||||
from graphon.nodes.tool import ToolNode as _ToolNode # local import to avoid cycles
|
||||
|
||||
if isinstance(self, _ToolNode):
|
||||
kwargs.setdefault("tool_file_manager_factory", MagicMock(spec=ToolFileManagerProtocol))
|
||||
kwargs.setdefault("tool_file_manager", MagicMock(spec=ToolFileManagerProtocol))
|
||||
kwargs.setdefault("runtime", DifyToolNodeRuntime(graph_init_params.run_context))
|
||||
|
||||
if isinstance(self, AgentNode):
|
||||
|
||||
+5
-3
@@ -9,7 +9,7 @@ from core.repositories.human_input_repository import (
|
||||
HumanInputFormEntity,
|
||||
HumanInputFormRepository,
|
||||
)
|
||||
from core.workflow.node_runtime import DifyHumanInputNodeRuntime
|
||||
from core.workflow.node_runtime import DifyFileReferenceFactory, DifyHumanInputNodeRuntime
|
||||
from core.workflow.system_variables import build_system_variables
|
||||
from graphon.entities import WorkflowStartReason
|
||||
from graphon.graph import Graph
|
||||
@@ -24,7 +24,7 @@ from graphon.graph_events import (
|
||||
from graphon.nodes.base.entities import OutputVariableEntity
|
||||
from graphon.nodes.end.end_node import EndNode
|
||||
from graphon.nodes.end.entities import EndNodeData
|
||||
from graphon.nodes.human_input.entities import HumanInputNodeData, UserAction
|
||||
from graphon.nodes.human_input.entities import HumanInputNodeData, UserActionConfig
|
||||
from graphon.nodes.human_input.enums import HumanInputFormStatus
|
||||
from graphon.nodes.human_input.human_input_node import HumanInputNode
|
||||
from graphon.nodes.start.entities import StartNodeData
|
||||
@@ -149,7 +149,7 @@ def _build_graph(runtime_state: GraphRuntimeState, repo: HumanInputFormRepositor
|
||||
title="Human Input",
|
||||
form_content="Human input required",
|
||||
inputs=[],
|
||||
user_actions=[UserAction(id="approve", title="Approve")],
|
||||
user_actions=[UserActionConfig(id="approve", title="Approve")],
|
||||
)
|
||||
|
||||
human_a_config = {"id": "human_a", "data": human_data.model_dump()}
|
||||
@@ -159,6 +159,7 @@ def _build_graph(runtime_state: GraphRuntimeState, repo: HumanInputFormRepositor
|
||||
graph_init_params=graph_init_params,
|
||||
graph_runtime_state=runtime_state,
|
||||
form_repository=repo,
|
||||
file_reference_factory=DifyFileReferenceFactory(graph_init_params.run_context),
|
||||
runtime=DifyHumanInputNodeRuntime(graph_init_params.run_context),
|
||||
)
|
||||
|
||||
@@ -169,6 +170,7 @@ def _build_graph(runtime_state: GraphRuntimeState, repo: HumanInputFormRepositor
|
||||
graph_init_params=graph_init_params,
|
||||
graph_runtime_state=runtime_state,
|
||||
form_repository=repo,
|
||||
file_reference_factory=DifyFileReferenceFactory(graph_init_params.run_context),
|
||||
runtime=DifyHumanInputNodeRuntime(graph_init_params.run_context),
|
||||
)
|
||||
|
||||
|
||||
@@ -30,23 +30,23 @@ from core.workflow.human_input_adapter import (
|
||||
WebAppDeliveryMethod,
|
||||
_WebAppDeliveryConfig,
|
||||
)
|
||||
from core.workflow.node_runtime import DifyHumanInputNodeRuntime
|
||||
from core.workflow.node_runtime import DifyFileReferenceFactory, DifyHumanInputNodeRuntime
|
||||
from core.workflow.system_variables import build_system_variables
|
||||
from graphon.entities import GraphInitParams
|
||||
from graphon.node_events import PauseRequestedEvent
|
||||
from graphon.node_events.node import StreamCompletedEvent
|
||||
from graphon.nodes.human_input.entities import (
|
||||
FormInput,
|
||||
FormInputDefault,
|
||||
HumanInputNodeData,
|
||||
UserAction,
|
||||
ParagraphInputConfig,
|
||||
StringSource,
|
||||
UserActionConfig,
|
||||
)
|
||||
from graphon.nodes.human_input.enums import (
|
||||
ButtonStyle,
|
||||
FormInputType,
|
||||
HumanInputFormStatus,
|
||||
PlaceholderType,
|
||||
TimeoutUnit,
|
||||
ValueSourceType,
|
||||
)
|
||||
from graphon.nodes.human_input.human_input_node import HumanInputNode
|
||||
from graphon.runtime import GraphRuntimeState, VariablePool
|
||||
@@ -152,6 +152,7 @@ def _build_human_input_node(
|
||||
data=typed_node_data,
|
||||
graph_init_params=graph_init_params,
|
||||
graph_runtime_state=graph_runtime_state,
|
||||
file_reference_factory=DifyFileReferenceFactory(graph_init_params.run_context),
|
||||
runtime=runtime,
|
||||
)
|
||||
|
||||
@@ -190,32 +191,36 @@ class TestDeliveryMethod:
|
||||
assert len(delivery_method.config.recipients.items) == 2
|
||||
|
||||
|
||||
class TestFormInput:
|
||||
"""Test FormInput entity."""
|
||||
class TestParagraphInputConfig:
|
||||
"""Test ParagraphInputConfig entity."""
|
||||
|
||||
def test_text_input_with_constant_default(self):
|
||||
"""Test text input with constant default value."""
|
||||
default = FormInputDefault(type=PlaceholderType.CONSTANT, value="Enter your response here...")
|
||||
def test_paragraph_input_with_constant_default(self):
|
||||
"""Test paragraph input with constant default value."""
|
||||
default = StringSource(type=ValueSourceType.CONSTANT, value="Enter your response here...")
|
||||
|
||||
form_input = FormInput(type=FormInputType.TEXT_INPUT, output_variable_name="user_input", default=default)
|
||||
form_input = ParagraphInputConfig(
|
||||
type=FormInputType.PARAGRAPH, output_variable_name="user_input", default=default
|
||||
)
|
||||
|
||||
assert form_input.type == FormInputType.TEXT_INPUT
|
||||
assert form_input.type == FormInputType.PARAGRAPH
|
||||
assert form_input.output_variable_name == "user_input"
|
||||
assert form_input.default.type == PlaceholderType.CONSTANT
|
||||
assert form_input.default.type == ValueSourceType.CONSTANT
|
||||
assert form_input.default.value == "Enter your response here..."
|
||||
|
||||
def test_text_input_with_variable_default(self):
|
||||
"""Test text input with variable default value."""
|
||||
default = FormInputDefault(type=PlaceholderType.VARIABLE, selector=["node_123", "output_var"])
|
||||
def test_paragraph_input_with_variable_default(self):
|
||||
"""Test paragraph input with variable default value."""
|
||||
default = StringSource(type=ValueSourceType.VARIABLE, selector=["node_123", "output_var"])
|
||||
|
||||
form_input = FormInput(type=FormInputType.TEXT_INPUT, output_variable_name="user_input", default=default)
|
||||
form_input = ParagraphInputConfig(
|
||||
type=FormInputType.PARAGRAPH, output_variable_name="user_input", default=default
|
||||
)
|
||||
|
||||
assert form_input.default.type == PlaceholderType.VARIABLE
|
||||
assert form_input.default.type == ValueSourceType.VARIABLE
|
||||
assert form_input.default.selector == ["node_123", "output_var"]
|
||||
|
||||
def test_form_input_without_default(self):
|
||||
"""Test form input without default value."""
|
||||
form_input = FormInput(type=FormInputType.PARAGRAPH, output_variable_name="description")
|
||||
form_input = ParagraphInputConfig(type=FormInputType.PARAGRAPH, output_variable_name="description")
|
||||
|
||||
assert form_input.type == FormInputType.PARAGRAPH
|
||||
assert form_input.output_variable_name == "description"
|
||||
@@ -223,11 +228,11 @@ class TestFormInput:
|
||||
|
||||
|
||||
class TestUserAction:
|
||||
"""Test UserAction entity."""
|
||||
"""Test UserActionConfig entity."""
|
||||
|
||||
def test_user_action_creation(self):
|
||||
"""Test user action creation."""
|
||||
action = UserAction(id="approve", title="Approve", button_style=ButtonStyle.PRIMARY)
|
||||
action = UserActionConfig(id="approve", title="Approve", button_style=ButtonStyle.PRIMARY)
|
||||
|
||||
assert action.id == "approve"
|
||||
assert action.title == "Approve"
|
||||
@@ -235,13 +240,13 @@ class TestUserAction:
|
||||
|
||||
def test_user_action_default_button_style(self):
|
||||
"""Test user action with default button style."""
|
||||
action = UserAction(id="cancel", title="Cancel")
|
||||
action = UserActionConfig(id="cancel", title="Cancel")
|
||||
|
||||
assert action.button_style == ButtonStyle.DEFAULT
|
||||
|
||||
def test_user_action_length_boundaries(self):
|
||||
"""Test user action id and title length boundaries."""
|
||||
action = UserAction(id="a" * 20, title="b" * 100)
|
||||
action = UserActionConfig(id="a" * 20, title="b" * 100)
|
||||
|
||||
assert action.id == "a" * 20
|
||||
assert action.title == "b" * 100
|
||||
@@ -259,7 +264,7 @@ class TestUserAction:
|
||||
data[field_name] = value
|
||||
|
||||
with pytest.raises(ValidationError) as exc_info:
|
||||
UserAction.model_validate(data)
|
||||
UserActionConfig.model_validate(data)
|
||||
|
||||
errors = exc_info.value.errors()
|
||||
assert any(error["loc"] == (field_name,) and error["type"] == "string_too_long" for error in errors)
|
||||
@@ -273,14 +278,14 @@ class TestHumanInputNodeData:
|
||||
delivery_methods = [WebAppDeliveryMethod(enabled=True, config=_WebAppDeliveryConfig())]
|
||||
|
||||
inputs = [
|
||||
FormInput(
|
||||
type=FormInputType.TEXT_INPUT,
|
||||
ParagraphInputConfig(
|
||||
type=FormInputType.PARAGRAPH,
|
||||
output_variable_name="content",
|
||||
default=FormInputDefault(type=PlaceholderType.CONSTANT, value="Enter content..."),
|
||||
default=StringSource(type=ValueSourceType.CONSTANT, value="Enter content..."),
|
||||
)
|
||||
]
|
||||
|
||||
user_actions = [UserAction(id="submit", title="Submit", button_style=ButtonStyle.PRIMARY)]
|
||||
user_actions = [UserActionConfig(id="submit", title="Submit", button_style=ButtonStyle.PRIMARY)]
|
||||
|
||||
node_data = HumanInputNodeData(
|
||||
title="Human Input Test",
|
||||
@@ -338,8 +343,8 @@ class TestHumanInputNodeData:
|
||||
def test_duplicate_input_output_variable_name_raises_validation_error(self):
|
||||
"""Duplicate form input output_variable_name should raise validation error."""
|
||||
duplicate_inputs = [
|
||||
FormInput(type=FormInputType.TEXT_INPUT, output_variable_name="content"),
|
||||
FormInput(type=FormInputType.TEXT_INPUT, output_variable_name="content"),
|
||||
ParagraphInputConfig(type=FormInputType.PARAGRAPH, output_variable_name="content"),
|
||||
ParagraphInputConfig(type=FormInputType.PARAGRAPH, output_variable_name="content"),
|
||||
]
|
||||
|
||||
with pytest.raises(ValidationError, match="duplicated output_variable_name 'content'"):
|
||||
@@ -348,8 +353,8 @@ class TestHumanInputNodeData:
|
||||
def test_duplicate_user_action_ids_raise_validation_error(self):
|
||||
"""Duplicate user action ids should raise validation error."""
|
||||
duplicate_actions = [
|
||||
UserAction(id="submit", title="Submit"),
|
||||
UserAction(id="submit", title="Submit Again"),
|
||||
UserActionConfig(id="submit", title="Submit"),
|
||||
UserActionConfig(id="submit", title="Submit Again"),
|
||||
]
|
||||
|
||||
with pytest.raises(ValidationError, match="duplicated user action id 'submit'"):
|
||||
@@ -458,18 +463,18 @@ class TestHumanInputNodeVariableResolution:
|
||||
title="Human Input",
|
||||
form_content="Provide your name",
|
||||
inputs=[
|
||||
FormInput(
|
||||
type=FormInputType.TEXT_INPUT,
|
||||
ParagraphInputConfig(
|
||||
type=FormInputType.PARAGRAPH,
|
||||
output_variable_name="user_name",
|
||||
default=FormInputDefault(type=PlaceholderType.VARIABLE, selector=["start", "name"]),
|
||||
default=StringSource(type=ValueSourceType.VARIABLE, selector=["start", "name"]),
|
||||
),
|
||||
FormInput(
|
||||
type=FormInputType.TEXT_INPUT,
|
||||
ParagraphInputConfig(
|
||||
type=FormInputType.PARAGRAPH,
|
||||
output_variable_name="user_email",
|
||||
default=FormInputDefault(type=PlaceholderType.CONSTANT, value="foo@example.com"),
|
||||
default=StringSource(type=ValueSourceType.CONSTANT, value="foo@example.com"),
|
||||
),
|
||||
],
|
||||
user_actions=[UserAction(id="submit", title="Submit")],
|
||||
user_actions=[UserActionConfig(id="submit", title="Submit")],
|
||||
)
|
||||
config = {"id": "human", "data": node_data.model_dump()}
|
||||
|
||||
@@ -534,7 +539,7 @@ class TestHumanInputNodeVariableResolution:
|
||||
title="Human Input",
|
||||
form_content="Provide your name",
|
||||
inputs=[],
|
||||
user_actions=[UserAction(id="submit", title="Submit")],
|
||||
user_actions=[UserActionConfig(id="submit", title="Submit")],
|
||||
)
|
||||
config = {"id": "human", "data": node_data.model_dump()}
|
||||
|
||||
@@ -661,7 +666,7 @@ class TestHumanInputNodeVariableResolution:
|
||||
title="Human Input",
|
||||
form_content="Provide your name",
|
||||
inputs=[],
|
||||
user_actions=[UserAction(id="submit", title="Submit")],
|
||||
user_actions=[UserActionConfig(id="submit", title="Submit")],
|
||||
delivery_methods=[
|
||||
EmailDeliveryMethod(
|
||||
enabled=True,
|
||||
@@ -721,7 +726,7 @@ class TestValidation:
|
||||
def test_invalid_form_input_type(self):
|
||||
"""Test validation with invalid form input type."""
|
||||
with pytest.raises(ValidationError):
|
||||
FormInput(
|
||||
ParagraphInputConfig(
|
||||
type="invalid-type", # Invalid type
|
||||
output_variable_name="test",
|
||||
)
|
||||
@@ -729,7 +734,7 @@ class TestValidation:
|
||||
def test_invalid_button_style(self):
|
||||
"""Test validation with invalid button style."""
|
||||
with pytest.raises(ValidationError):
|
||||
UserAction(
|
||||
UserActionConfig(
|
||||
id="test",
|
||||
title="Test",
|
||||
button_style="invalid-style", # Invalid style
|
||||
@@ -778,12 +783,12 @@ class TestHumanInputNodeRenderedContent:
|
||||
title="Human Input",
|
||||
form_content="Name: {{#$output.name#}}",
|
||||
inputs=[
|
||||
FormInput(
|
||||
type=FormInputType.TEXT_INPUT,
|
||||
ParagraphInputConfig(
|
||||
type=FormInputType.PARAGRAPH,
|
||||
output_variable_name="name",
|
||||
)
|
||||
],
|
||||
user_actions=[UserAction(id="approve", title="Approve")],
|
||||
user_actions=[UserActionConfig(id="approve", title="Approve")],
|
||||
)
|
||||
config = {"id": "human", "data": node_data.model_dump()}
|
||||
|
||||
@@ -810,4 +815,4 @@ class TestHumanInputNodeRenderedContent:
|
||||
last_event = events[-1]
|
||||
assert isinstance(last_event, StreamCompletedEvent)
|
||||
node_run_result = last_event.node_run_result
|
||||
assert node_run_result.outputs["__rendered_content"] == "Name: Alice"
|
||||
assert node_run_result.outputs["__rendered_content"].to_object() == "Name: Alice"
|
||||
|
||||
+4
-3
@@ -2,7 +2,7 @@ import datetime
|
||||
from types import SimpleNamespace
|
||||
|
||||
from core.app.entities.app_invoke_entities import DIFY_RUN_CONTEXT_KEY, InvokeFrom, UserFrom
|
||||
from core.workflow.node_runtime import DifyHumanInputNodeRuntime
|
||||
from core.workflow.node_runtime import DifyFileReferenceFactory, DifyHumanInputNodeRuntime
|
||||
from core.workflow.system_variables import default_system_variables
|
||||
from graphon.entities import GraphInitParams
|
||||
from graphon.enums import BuiltinNodeTypes
|
||||
@@ -44,6 +44,7 @@ def _create_human_input_node(
|
||||
graph_init_params=graph_init_params,
|
||||
graph_runtime_state=graph_runtime_state,
|
||||
form_repository=repo,
|
||||
file_reference_factory=DifyFileReferenceFactory(graph_init_params.run_context),
|
||||
runtime=DifyHumanInputNodeRuntime(graph_init_params.run_context),
|
||||
)
|
||||
|
||||
@@ -81,7 +82,7 @@ def _build_node(form_content: str = "Please enter your name:\n\n{{#$output.name#
|
||||
"form_content": form_content,
|
||||
"inputs": [
|
||||
{
|
||||
"type": "text_input",
|
||||
"type": "paragraph",
|
||||
"output_variable_name": "name",
|
||||
"default": {"type": "constant", "value": ""},
|
||||
}
|
||||
@@ -148,7 +149,7 @@ def _build_timeout_node() -> HumanInputNode:
|
||||
"form_content": "Please enter your name:\n\n{{#$output.name#}}",
|
||||
"inputs": [
|
||||
{
|
||||
"type": "text_input",
|
||||
"type": "paragraph",
|
||||
"output_variable_name": "name",
|
||||
"default": {"type": "constant", "value": ""},
|
||||
}
|
||||
|
||||
@@ -111,8 +111,8 @@ def tool_node(monkeypatch) -> ToolNode:
|
||||
|
||||
config = graph_config["nodes"][0]
|
||||
|
||||
# Provide a stub ToolFileManager to satisfy the updated ToolNode constructor
|
||||
tool_file_manager_factory = MagicMock(spec=ToolFileManagerProtocol)
|
||||
# Provide a stub ToolFileManager to satisfy the ToolNode constructor.
|
||||
tool_file_manager = MagicMock(spec=ToolFileManagerProtocol)
|
||||
runtime = _StubToolRuntime()
|
||||
|
||||
node = ToolNode(
|
||||
@@ -120,7 +120,7 @@ def tool_node(monkeypatch) -> ToolNode:
|
||||
data=ToolNodeData.model_validate(config["data"]),
|
||||
graph_init_params=init_params,
|
||||
graph_runtime_state=graph_runtime_state,
|
||||
tool_file_manager_factory=tool_file_manager_factory,
|
||||
tool_file_manager=tool_file_manager,
|
||||
runtime=runtime,
|
||||
)
|
||||
return node
|
||||
@@ -215,7 +215,7 @@ def test_image_link_messages_use_tool_file_id_metadata(tool_node: ToolNode):
|
||||
size=123,
|
||||
storage_key="file-key",
|
||||
)
|
||||
tool_node._tool_file_manager_factory.get_file_generator_by_tool_file_id.return_value = (
|
||||
tool_node._tool_file_manager.get_file_generator_by_tool_file_id.return_value = (
|
||||
None,
|
||||
SimpleNamespace(mime_type="application/pdf"),
|
||||
)
|
||||
@@ -228,7 +228,7 @@ def test_image_link_messages_use_tool_file_id_metadata(tool_node: ToolNode):
|
||||
|
||||
events, _ = _run_transform(tool_node, message)
|
||||
|
||||
tool_node._tool_file_manager_factory.get_file_generator_by_tool_file_id.assert_called_once_with("file-id")
|
||||
tool_node._tool_file_manager.get_file_generator_by_tool_file_id.assert_called_once_with("file-id")
|
||||
completed_events = [event for event in events if isinstance(event, StreamCompletedEvent)]
|
||||
assert len(completed_events) == 1
|
||||
files_segment = completed_events[0].node_run_result.outputs["files"]
|
||||
|
||||
@@ -452,7 +452,7 @@ class TestDifyNodeFactoryCreateNode:
|
||||
factory._jinja2_template_renderer = sentinel.jinja2_template_renderer
|
||||
factory._template_transform_max_output_length = 2048
|
||||
factory._http_request_http_client = sentinel.http_client
|
||||
factory._bound_tool_file_manager_factory = sentinel.tool_file_manager_factory
|
||||
factory._bound_tool_file_manager_factory = MagicMock(return_value=sentinel.tool_file_manager)
|
||||
factory._file_reference_factory = sentinel.file_reference_factory
|
||||
factory._prompt_message_serializer = sentinel.prompt_message_serializer
|
||||
factory._retriever_attachment_loader = sentinel.retriever_attachment_loader
|
||||
@@ -540,6 +540,7 @@ class TestDifyNodeFactoryCreateNode:
|
||||
(BuiltinNodeTypes.TEMPLATE_TRANSFORM, "TemplateTransformNode"),
|
||||
(BuiltinNodeTypes.HTTP_REQUEST, "HttpRequestNode"),
|
||||
(BuiltinNodeTypes.HUMAN_INPUT, "HumanInputNode"),
|
||||
(BuiltinNodeTypes.TOOL, "ToolNode"),
|
||||
(KNOWLEDGE_INDEX_NODE_TYPE, "KnowledgeIndexNode"),
|
||||
(BuiltinNodeTypes.DATASOURCE, "DatasourceNode"),
|
||||
(BuiltinNodeTypes.KNOWLEDGE_RETRIEVAL, "KnowledgeRetrievalNode"),
|
||||
@@ -580,13 +581,19 @@ class TestDifyNodeFactoryCreateNode:
|
||||
elif constructor_name == "HttpRequestNode":
|
||||
assert kwargs["http_request_config"] is sentinel.http_request_config
|
||||
assert kwargs["http_client"] is sentinel.http_client
|
||||
assert kwargs["tool_file_manager_factory"] is sentinel.tool_file_manager_factory
|
||||
assert kwargs["tool_file_manager_factory"] is factory._bound_tool_file_manager_factory
|
||||
assert kwargs["file_manager"] is sentinel.file_manager
|
||||
assert kwargs["file_reference_factory"] is sentinel.file_reference_factory
|
||||
factory._bound_tool_file_manager_factory.assert_not_called()
|
||||
elif constructor_name == "HumanInputNode":
|
||||
assert kwargs["form_repository"] is form_repository
|
||||
assert kwargs["file_reference_factory"] is sentinel.file_reference_factory
|
||||
assert kwargs["runtime"] is factory._human_input_runtime
|
||||
factory._human_input_runtime.build_form_repository.assert_called_once_with()
|
||||
elif constructor_name == "ToolNode":
|
||||
assert kwargs["tool_file_manager"] is sentinel.tool_file_manager
|
||||
assert kwargs["runtime"] is sentinel.tool_runtime
|
||||
factory._bound_tool_file_manager_factory.assert_called_once_with()
|
||||
elif constructor_name == "DocumentExtractorNode":
|
||||
assert kwargs["unstructured_api_config"] is sentinel.unstructured_api_config
|
||||
assert kwargs["http_client"] is sentinel.http_client
|
||||
|
||||
@@ -4,7 +4,7 @@ from dataclasses import dataclass, field
|
||||
from datetime import datetime, timedelta
|
||||
from typing import Any
|
||||
|
||||
from graphon.nodes.human_input.entities import FormInput
|
||||
from graphon.nodes.human_input.entities import ParagraphInputConfig
|
||||
from graphon.nodes.human_input.enums import TimeoutUnit
|
||||
from libs.datetime_utils import naive_utc_now
|
||||
|
||||
@@ -45,7 +45,7 @@ class HumanInputForm:
|
||||
tenant_id: str
|
||||
app_id: str | None
|
||||
form_content: str
|
||||
inputs: list[FormInput]
|
||||
inputs: list[ParagraphInputConfig]
|
||||
user_actions: list[dict[str, Any]]
|
||||
timeout: int
|
||||
timeout_unit: TimeoutUnit
|
||||
|
||||
@@ -7,8 +7,8 @@ from datetime import timedelta
|
||||
import pytest
|
||||
|
||||
from graphon.nodes.human_input.entities import (
|
||||
FormInput,
|
||||
UserAction,
|
||||
ParagraphInputConfig,
|
||||
UserActionConfig,
|
||||
)
|
||||
from graphon.nodes.human_input.enums import (
|
||||
FormInputType,
|
||||
@@ -50,8 +50,8 @@ class TestFormService:
|
||||
"tenant_id": "tenant-abc",
|
||||
"app_id": "app-def",
|
||||
"form_content": "# Test Form\n\nInput: {{#$output.input#}}",
|
||||
"inputs": [FormInput(type=FormInputType.TEXT_INPUT, output_variable_name="input", default=None)],
|
||||
"user_actions": [UserAction(id="submit", title="Submit")],
|
||||
"inputs": [ParagraphInputConfig(type=FormInputType.PARAGRAPH, output_variable_name="input", default=None)],
|
||||
"user_actions": [UserActionConfig(id="submit", title="Submit")],
|
||||
"timeout": 1,
|
||||
"timeout_unit": TimeoutUnit.HOUR,
|
||||
"form_token": "token-xyz",
|
||||
@@ -304,8 +304,10 @@ class TestFormValidation:
|
||||
"tenant_id": "tenant-abc",
|
||||
"app_id": "app-def",
|
||||
"form_content": "Test form",
|
||||
"inputs": [FormInput(type=FormInputType.TEXT_INPUT, output_variable_name="required_input", default=None)],
|
||||
"user_actions": [UserAction(id="submit", title="Submit")],
|
||||
"inputs": [
|
||||
ParagraphInputConfig(type=FormInputType.PARAGRAPH, output_variable_name="required_input", default=None)
|
||||
],
|
||||
"user_actions": [UserActionConfig(id="submit", title="Submit")],
|
||||
"timeout": 1,
|
||||
"timeout_unit": TimeoutUnit.HOUR,
|
||||
}
|
||||
|
||||
@@ -7,8 +7,8 @@ from datetime import datetime, timedelta
|
||||
import pytest
|
||||
|
||||
from graphon.nodes.human_input.entities import (
|
||||
FormInput,
|
||||
UserAction,
|
||||
ParagraphInputConfig,
|
||||
UserActionConfig,
|
||||
)
|
||||
from graphon.nodes.human_input.enums import (
|
||||
FormInputType,
|
||||
@@ -32,8 +32,8 @@ class TestHumanInputForm:
|
||||
"tenant_id": "tenant-abc",
|
||||
"app_id": "app-def",
|
||||
"form_content": "# Test Form\n\nInput: {{#$output.input#}}",
|
||||
"inputs": [FormInput(type=FormInputType.TEXT_INPUT, output_variable_name="input", default=None)],
|
||||
"user_actions": [UserAction(id="submit", title="Submit")],
|
||||
"inputs": [ParagraphInputConfig(type=FormInputType.PARAGRAPH, output_variable_name="input", default=None)],
|
||||
"user_actions": [UserActionConfig(id="submit", title="Submit")],
|
||||
"timeout": 2,
|
||||
"timeout_unit": TimeoutUnit.HOUR,
|
||||
"form_token": "token-xyz",
|
||||
@@ -132,7 +132,7 @@ class TestHumanInputForm:
|
||||
assert "site" not in response
|
||||
assert response["form_content"] == "# Test Form\n\nInput: {{#$output.input#}}"
|
||||
assert len(response["inputs"]) == 1
|
||||
assert response["inputs"][0]["type"] == "text-input"
|
||||
assert response["inputs"][0]["type"] == "paragraph"
|
||||
assert response["inputs"][0]["output_variable_name"] == "input"
|
||||
|
||||
def test_form_to_response_dict_with_site_info(self, sample_form_data):
|
||||
|
||||
@@ -3,7 +3,7 @@ from __future__ import annotations
|
||||
from datetime import UTC, datetime
|
||||
from types import SimpleNamespace
|
||||
|
||||
from graphon.nodes.human_input.entities import FormDefinition, FormInput, UserAction
|
||||
from graphon.nodes.human_input.entities import FormDefinition, ParagraphInputConfig, UserActionConfig
|
||||
from graphon.nodes.human_input.enums import FormInputType
|
||||
from models.human_input import RecipientType
|
||||
from repositories.sqlalchemy_api_workflow_run_repository import _build_human_input_required_reason
|
||||
@@ -13,8 +13,8 @@ def _build_form_model() -> SimpleNamespace:
|
||||
expiration_time = datetime(2024, 1, 1, tzinfo=UTC)
|
||||
definition = FormDefinition(
|
||||
form_content="content",
|
||||
inputs=[FormInput(type=FormInputType.TEXT_INPUT, output_variable_name="name")],
|
||||
user_actions=[UserAction(id="approve", title="Approve")],
|
||||
inputs=[ParagraphInputConfig(type=FormInputType.PARAGRAPH, output_variable_name="name")],
|
||||
user_actions=[UserActionConfig(id="approve", title="Approve")],
|
||||
rendered_content="rendered",
|
||||
expiration_time=expiration_time,
|
||||
default_values={"name": "Alice"},
|
||||
|
||||
@@ -12,8 +12,8 @@ from core.repositories.human_input_repository import (
|
||||
)
|
||||
from graphon.nodes.human_input.entities import (
|
||||
FormDefinition,
|
||||
FormInput,
|
||||
UserAction,
|
||||
ParagraphInputConfig,
|
||||
UserActionConfig,
|
||||
)
|
||||
from graphon.nodes.human_input.enums import FormInputType, HumanInputFormKind, HumanInputFormStatus
|
||||
from libs.datetime_utils import naive_utc_now
|
||||
@@ -51,7 +51,7 @@ def sample_form_record():
|
||||
definition=FormDefinition(
|
||||
form_content="hello",
|
||||
inputs=[],
|
||||
user_actions=[UserAction(id="submit", title="Submit")],
|
||||
user_actions=[UserActionConfig(id="submit", title="Submit")],
|
||||
rendered_content="<p>hello</p>",
|
||||
expiration_time=naive_utc_now() + timedelta(hours=1),
|
||||
),
|
||||
@@ -280,7 +280,7 @@ def test_submit_form_by_token_missing_inputs(sample_form_record, mock_session_fa
|
||||
|
||||
definition_with_input = FormDefinition(
|
||||
form_content="hello",
|
||||
inputs=[FormInput(type=FormInputType.TEXT_INPUT, output_variable_name="content")],
|
||||
inputs=[ParagraphInputConfig(type=FormInputType.PARAGRAPH, output_variable_name="content")],
|
||||
user_actions=sample_form_record.definition.user_actions,
|
||||
rendered_content="<p>hello</p>",
|
||||
expiration_time=sample_form_record.expiration_time,
|
||||
|
||||
@@ -2833,6 +2833,7 @@ class TestWorkflowServiceFreeNodeExecution:
|
||||
return_value=sentinel.adapted_node_data,
|
||||
) as mock_adapt_node_data,
|
||||
patch("services.workflow_service.build_dify_run_context") as mock_build_dify_run_context,
|
||||
patch("services.workflow_service.DifyFileReferenceFactory") as mock_file_reference_factory_cls,
|
||||
patch("services.workflow_service.DifyHumanInputNodeRuntime") as mock_runtime_cls,
|
||||
patch("services.workflow_service.HumanInputNode") as mock_node_cls,
|
||||
):
|
||||
@@ -2851,10 +2852,14 @@ class TestWorkflowServiceFreeNodeExecution:
|
||||
mock_runtime_cls.assert_called_once_with(mock_build_dify_run_context.return_value)
|
||||
mock_adapt_node_data.assert_called_once_with(node_config["data"])
|
||||
mock_node_cls.validate_node_data.assert_called_once_with(sentinel.adapted_node_data)
|
||||
mock_file_reference_factory_cls.assert_called_once_with(
|
||||
mock_graph_init_context_cls.return_value.to_graph_init_params.return_value.run_context
|
||||
)
|
||||
mock_node_cls.assert_called_once_with(
|
||||
node_id="n-1",
|
||||
data=sentinel.node_data,
|
||||
graph_init_params=mock_graph_init_context_cls.return_value.to_graph_init_params.return_value,
|
||||
graph_runtime_state=ANY,
|
||||
file_reference_factory=mock_file_reference_factory_cls.return_value,
|
||||
runtime=mock_runtime_cls.return_value,
|
||||
)
|
||||
|
||||
Generated
+4
-4
@@ -1628,7 +1628,7 @@ requires-dist = [
|
||||
{ name = "gmpy2", specifier = ">=2.3.0" },
|
||||
{ name = "google-api-python-client", specifier = ">=2.196.0" },
|
||||
{ name = "google-cloud-aiplatform", specifier = ">=1.151.0,<2.0.0" },
|
||||
{ name = "graphon", specifier = "~=0.3.1" },
|
||||
{ name = "graphon", specifier = "~=0.4.0" },
|
||||
{ name = "gunicorn", specifier = ">=26.0.0" },
|
||||
{ name = "httpx", extras = ["socks"], specifier = ">=0.28.1,<1.0.0" },
|
||||
{ name = "httpx-sse", specifier = "~=0.4.0" },
|
||||
@@ -2984,7 +2984,7 @@ httpx = [
|
||||
|
||||
[[package]]
|
||||
name = "graphon"
|
||||
version = "0.3.1"
|
||||
version = "0.4.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "charset-normalizer" },
|
||||
@@ -3005,9 +3005,9 @@ dependencies = [
|
||||
{ name = "unstructured", extra = ["docx", "epub", "md", "ppt", "pptx"] },
|
||||
{ name = "webvtt-py" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/5a/ef/43217842e84160acca64a95858f1689389a50e04a53fc94f2aa836b4eaf7/graphon-0.3.1.tar.gz", hash = "sha256:49971baed1eb16c8e1983f755e659902e4f117a68dc62fad19e91472950b937d", size = 242210, upload-time = "2026-05-07T06:58:21.879Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/76/24/eb1e7983404dcac84816b76ea450e1bb97023e55e00c699d609340bc361e/graphon-0.4.0.tar.gz", hash = "sha256:afb0c7a58f89e09cfa585296429b4d08cd0df80b9ac54d550f88e7d76ec48ee0", size = 261812, upload-time = "2026-05-13T11:48:39.198Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/62/37/bef16ed3d6da7446b36769fa388f4dc79f95337ffa16d6dfc3177152507e/graphon-0.3.1-py3-none-any.whl", hash = "sha256:e6422c7e3f1ce7d2185979c17e08201816ca25d46d400ebdd035c95d501c04fe", size = 349368, upload-time = "2026-05-07T06:58:20.217Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b7/de/bad6b3fd1e4b4defc16e6ea106e55c44725a159f1d191a99877bce1c9931/graphon-0.4.0-py3-none-any.whl", hash = "sha256:b33f95886da823d5b1b53d663a4f5f8fa383c37740f3bd19297b8d140fcb804c", size = 372711, upload-time = "2026-05-13T11:48:37.712Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
Reference in New Issue
Block a user