From 7e5b7d2736a6647fc8268c8197d9c1428d8093e6 Mon Sep 17 00:00:00 2001 From: zyqzyq Date: Wed, 13 May 2026 15:52:41 +0800 Subject: [PATCH] tool plugin add param type dynamic-tree-select --- src/dify_plugin/entities/parameters.py | 7 +++ src/dify_plugin/entities/provider_config.py | 1 + src/dify_plugin/entities/tool.py | 2 + src/dify_plugin/interfaces/tool/tool.py | 3 ++ src/dify_plugin/protocol/dynamic_select.py | 2 +- tests/interfaces/tool/test_costruct_tool.py | 56 +++++++++++++++++++++ 6 files changed, 70 insertions(+), 1 deletion(-) diff --git a/src/dify_plugin/entities/parameters.py b/src/dify_plugin/entities/parameters.py index 5e1e7006..f6b25bf1 100644 --- a/src/dify_plugin/entities/parameters.py +++ b/src/dify_plugin/entities/parameters.py @@ -51,6 +51,10 @@ class ParameterOption(BaseModel): default=None, description="The icon of the option, can be a URL or a base64 encoded string", ) + children: list["ParameterOption"] | None = Field( + default=None, + description="The children options of the option, used for tree select", + ) @field_validator("value", mode="before") @classmethod @@ -75,3 +79,6 @@ class Type(StrEnum): ) class ParameterTemplate(BaseModel): enabled: bool = Field(..., description="Whether the parameter is jinja enabled") + + +ParameterOption.model_rebuild() diff --git a/src/dify_plugin/entities/provider_config.py b/src/dify_plugin/entities/provider_config.py index 867fcf22..e2b5a944 100644 --- a/src/dify_plugin/entities/provider_config.py +++ b/src/dify_plugin/entities/provider_config.py @@ -38,6 +38,7 @@ class CommonParameterType(Enum): OBJECT = "object" ARRAY = "array" DYNAMIC_SELECT = "dynamic-select" + DYNAMIC_TREE_SELECT = "dynamic-tree-select" @docs( diff --git a/src/dify_plugin/entities/tool.py b/src/dify_plugin/entities/tool.py index 5fda0019..df0bab4c 100644 --- a/src/dify_plugin/entities/tool.py +++ b/src/dify_plugin/entities/tool.py @@ -87,6 +87,7 @@ class ToolParameterType(StrEnum): OBJECT = CommonParameterType.OBJECT.value ARRAY = CommonParameterType.ARRAY.value DYNAMIC_SELECT = CommonParameterType.DYNAMIC_SELECT.value + DYNAMIC_TREE_SELECT = CommonParameterType.DYNAMIC_TREE_SELECT.value class ToolParameterForm(Enum): SCHEMA = "schema" # should be set while adding tool @@ -115,6 +116,7 @@ class ToolParameterForm(Enum): ) llm_description: str | None = None required: bool | None = False + multiple: bool | None = False default: int | float | str | None = None min: float | int | None = None max: float | int | None = None diff --git a/src/dify_plugin/interfaces/tool/tool.py b/src/dify_plugin/interfaces/tool/tool.py index 29b5e45c..d509f5cb 100644 --- a/src/dify_plugin/interfaces/tool/tool.py +++ b/src/dify_plugin/interfaces/tool/tool.py @@ -443,6 +443,9 @@ def _fetch_parameter_options(self, parameter: str) -> list[ParameterOption]: To be implemented by subclasses. Also, it's optional to implement, that's why it's not an abstract method. + + For `dynamic-tree-select` parameters, you can return options with nested + `children` to build a hierarchical selection tree. """ msg = ( "This plugin should implement `_fetch_parameter_options` method " diff --git a/src/dify_plugin/protocol/dynamic_select.py b/src/dify_plugin/protocol/dynamic_select.py index 613aa318..50e310a8 100644 --- a/src/dify_plugin/protocol/dynamic_select.py +++ b/src/dify_plugin/protocol/dynamic_select.py @@ -9,7 +9,7 @@ def fetch_parameter_options(self, parameter: str) -> list[ParameterOption]: Fetch the parameter options. Classes that implement this protocol should have at least one - parameter with type `dynamic-select`. + parameter with type `dynamic-select` or `dynamic-tree-select`. At some scenarios, we don't know the available options, it could not be defined in the plugin directly. diff --git a/tests/interfaces/tool/test_costruct_tool.py b/tests/interfaces/tool/test_costruct_tool.py index 28b2621e..0a0538b8 100644 --- a/tests/interfaces/tool/test_costruct_tool.py +++ b/tests/interfaces/tool/test_costruct_tool.py @@ -97,3 +97,59 @@ def _fetch_parameter_options(self, parameter: str) -> list[ParameterOption]: assert tool.fetch_parameter_options("test") == [ ParameterOption(value="test", label=I18nObject(en_us="test")) ] + + +def test_fetch_tree_parameter_options() -> None: + """ + Test that the Tool can fetch tree parameter options with nested children + """ + + class ToolImpl(Tool): + def _invoke( + self, tool_parameters: Mapping + ) -> Generator[ToolInvokeMessage, None, None]: + del tool_parameters + yield self.create_text_message("Hello, world!") + + def _fetch_parameter_options(self, parameter: str) -> list[ParameterOption]: + del parameter + return [ + ParameterOption( + value="root", + label=I18nObject(en_us="Root"), + children=[ + ParameterOption( + value="child1", label=I18nObject(en_us="Child 1") + ), + ParameterOption( + value="child2", + label=I18nObject(en_us="Child 2"), + children=[ + ParameterOption( + value="grandchild", + label=I18nObject(en_us="Grandchild"), + ) + ], + ), + ], + ) + ] + + session = Session( + session_id="test", + executor=ThreadPoolExecutor(max_workers=1), + reader=StdioRequestReader(), + writer=StdioResponseWriter(), + ) + + tool = ToolImpl( + runtime=ToolRuntime(credentials={}, user_id="test", session_id="test"), + session=session, + ) + options = tool.fetch_parameter_options("test") + assert len(options) == 1 + assert options[0].value == "root" + assert options[0].children is not None + assert len(options[0].children) == 2 + assert options[0].children[1].children is not None + assert options[0].children[1].children[0].value == "grandchild"