mirror of
https://github.com/langgenius/dify.git
synced 2026-06-16 22:11:09 +08:00
fix: support Agent v2 plugin tool runtime params (#37526)
This commit is contained in:
parent
1427b0b098
commit
813a1677b2
@ -398,6 +398,8 @@ class ToolManager:
|
||||
user_id: str | None = None,
|
||||
invoke_from: InvokeFrom = InvokeFrom.DEBUGGER,
|
||||
variable_pool: "VariablePool | None" = None,
|
||||
allow_file_parameters: bool = False,
|
||||
use_default_for_missing_form_parameters: bool = False,
|
||||
) -> Tool:
|
||||
"""
|
||||
get the agent tool runtime
|
||||
@ -415,7 +417,12 @@ class ToolManager:
|
||||
runtime_parameters: dict[str, Any] = {}
|
||||
parameters = tool_entity.get_merged_runtime_parameters()
|
||||
runtime_parameters = cls._convert_tool_parameters_type(
|
||||
parameters, variable_pool, agent_tool.tool_parameters, typ="agent"
|
||||
parameters,
|
||||
variable_pool,
|
||||
agent_tool.tool_parameters,
|
||||
typ="agent",
|
||||
allow_file_parameters=allow_file_parameters,
|
||||
use_default_for_missing_form_parameters=use_default_for_missing_form_parameters,
|
||||
)
|
||||
# decrypt runtime parameters
|
||||
encryption_manager = ToolParameterConfigurationManager(
|
||||
@ -1063,6 +1070,8 @@ class ToolManager:
|
||||
variable_pool: "VariablePool | None",
|
||||
tool_configurations: Mapping[str, Any],
|
||||
typ: Literal["agent", "workflow", "tool"] = "workflow",
|
||||
allow_file_parameters: bool = False,
|
||||
use_default_for_missing_form_parameters: bool = False,
|
||||
) -> dict[str, Any]:
|
||||
"""
|
||||
Convert tool parameters type
|
||||
@ -1081,6 +1090,7 @@ class ToolManager:
|
||||
}
|
||||
and parameter.required
|
||||
and typ == "agent"
|
||||
and not allow_file_parameters
|
||||
):
|
||||
raise ValueError(f"file type parameter {parameter.name} not supported in agent")
|
||||
# save tool parameter to tool entity memory
|
||||
@ -1117,7 +1127,19 @@ class ToolManager:
|
||||
runtime_parameters[parameter.name] = parameter_value
|
||||
|
||||
else:
|
||||
value = parameter.init_frontend_parameter(tool_configurations.get(parameter.name))
|
||||
parameter_value = tool_configurations.get(parameter.name)
|
||||
if use_default_for_missing_form_parameters and parameter_value is None:
|
||||
if parameter.default is not None:
|
||||
parameter_value = parameter.default
|
||||
elif (
|
||||
parameter.required
|
||||
and parameter.type == ToolParameter.ToolParameterType.SELECT
|
||||
and parameter.options
|
||||
):
|
||||
parameter_value = parameter.options[0].value
|
||||
else:
|
||||
continue
|
||||
value = parameter.init_frontend_parameter(parameter_value)
|
||||
runtime_parameters[parameter.name] = value
|
||||
return runtime_parameters
|
||||
|
||||
|
||||
@ -42,6 +42,8 @@ class AgentToolRuntimeProvider(Protocol):
|
||||
user_id: str | None = None,
|
||||
invoke_from: InvokeFrom = InvokeFrom.DEBUGGER,
|
||||
variable_pool: Any | None = None,
|
||||
allow_file_parameters: bool = False,
|
||||
use_default_for_missing_form_parameters: bool = False,
|
||||
) -> Tool: ...
|
||||
|
||||
|
||||
@ -176,6 +178,8 @@ class WorkflowAgentPluginToolsBuilder:
|
||||
user_id=user_id,
|
||||
invoke_from=invoke_from,
|
||||
variable_pool=None,
|
||||
allow_file_parameters=True,
|
||||
use_default_for_missing_form_parameters=True,
|
||||
)
|
||||
except ToolProviderNotFoundError as exc:
|
||||
raise WorkflowAgentPluginToolsBuildError(
|
||||
|
||||
@ -17,6 +17,7 @@ from core.tools.entities.tool_entities import (
|
||||
ToolInvokeMessage,
|
||||
ToolParameter,
|
||||
)
|
||||
from core.tools.tool_manager import ToolManager
|
||||
from core.workflow.nodes.agent_v2.plugin_tools_builder import (
|
||||
WorkflowAgentPluginToolsBuilder,
|
||||
WorkflowAgentPluginToolsBuildError,
|
||||
@ -32,6 +33,8 @@ class FakeRuntimeProvider:
|
||||
self.tool = tool
|
||||
self.last_agent_tool: AgentToolEntity | None = None
|
||||
self.last_invoke_from: InvokeFrom | None = None
|
||||
self.last_allow_file_parameters: bool | None = None
|
||||
self.last_use_default_for_missing_form_parameters: bool | None = None
|
||||
|
||||
def get_agent_tool_runtime(
|
||||
self,
|
||||
@ -41,11 +44,25 @@ class FakeRuntimeProvider:
|
||||
user_id: str | None = None,
|
||||
invoke_from: InvokeFrom = InvokeFrom.DEBUGGER,
|
||||
variable_pool: Any | None = None,
|
||||
allow_file_parameters: bool = False,
|
||||
use_default_for_missing_form_parameters: bool = False,
|
||||
) -> Tool:
|
||||
self.last_agent_tool = agent_tool
|
||||
self.last_invoke_from = invoke_from
|
||||
self.last_allow_file_parameters = allow_file_parameters
|
||||
self.last_use_default_for_missing_form_parameters = use_default_for_missing_form_parameters
|
||||
if isinstance(self.tool, Exception):
|
||||
raise self.tool
|
||||
if self.tool.runtime is not None:
|
||||
runtime_parameters = ToolManager._convert_tool_parameters_type(
|
||||
self.tool.get_merged_runtime_parameters(),
|
||||
variable_pool,
|
||||
agent_tool.tool_parameters,
|
||||
typ="agent",
|
||||
allow_file_parameters=allow_file_parameters,
|
||||
use_default_for_missing_form_parameters=use_default_for_missing_form_parameters,
|
||||
)
|
||||
self.tool.runtime.runtime_parameters.update(runtime_parameters)
|
||||
return self.tool
|
||||
|
||||
|
||||
@ -103,6 +120,67 @@ def _tool(*, runtime_parameters: dict[str, Any] | None = None) -> FakeTool:
|
||||
return FakeTool(entity=entity, runtime=runtime)
|
||||
|
||||
|
||||
def _file_tool() -> FakeTool:
|
||||
parameters = [
|
||||
ToolParameter(
|
||||
name="audio_file",
|
||||
label=I18nObject(en_US="Audio File"),
|
||||
type=ToolParameter.ToolParameterType.FILE,
|
||||
form=ToolParameter.ToolParameterForm.LLM,
|
||||
required=True,
|
||||
llm_description="The audio file to be converted.",
|
||||
)
|
||||
]
|
||||
entity = ToolEntity(
|
||||
identity=ToolIdentity(
|
||||
author="langgenius",
|
||||
name="asr",
|
||||
label=I18nObject(en_US="Speech To Text"),
|
||||
provider="audio",
|
||||
),
|
||||
description=ToolDescription(human=I18nObject(en_US="Speech To Text"), llm="Convert audio file to text."),
|
||||
parameters=parameters,
|
||||
)
|
||||
runtime = ToolRuntime(tenant_id="tenant-1", user_id="user-1", credentials={}, runtime_parameters={})
|
||||
return FakeTool(entity=entity, runtime=runtime)
|
||||
|
||||
|
||||
def _tts_tool() -> FakeTool:
|
||||
parameters = [
|
||||
ToolParameter(
|
||||
name="text",
|
||||
label=I18nObject(en_US="Text"),
|
||||
type=ToolParameter.ToolParameterType.STRING,
|
||||
form=ToolParameter.ToolParameterForm.LLM,
|
||||
required=True,
|
||||
llm_description="The text to be converted.",
|
||||
),
|
||||
ToolParameter(
|
||||
name="model",
|
||||
label=I18nObject(en_US="Model"),
|
||||
type=ToolParameter.ToolParameterType.SELECT,
|
||||
form=ToolParameter.ToolParameterForm.FORM,
|
||||
required=True,
|
||||
options=[
|
||||
{"value": "provider-a#model-a", "label": {"en_US": "model-a(provider-a)"}},
|
||||
{"value": "provider-b#model-b", "label": {"en_US": "model-b(provider-b)"}},
|
||||
],
|
||||
),
|
||||
]
|
||||
entity = ToolEntity(
|
||||
identity=ToolIdentity(
|
||||
author="langgenius",
|
||||
name="tts",
|
||||
label=I18nObject(en_US="Text To Speech"),
|
||||
provider="audio",
|
||||
),
|
||||
description=ToolDescription(human=I18nObject(en_US="Text To Speech"), llm="Convert text to audio file."),
|
||||
parameters=parameters,
|
||||
)
|
||||
runtime = ToolRuntime(tenant_id="tenant-1", user_id="user-1", credentials={}, runtime_parameters={})
|
||||
return FakeTool(entity=entity, runtime=runtime)
|
||||
|
||||
|
||||
def _build(
|
||||
builder: WorkflowAgentPluginToolsBuilder,
|
||||
tools: AgentSoulToolsConfig,
|
||||
@ -157,6 +235,62 @@ def test_builds_dify_plugin_tools_layer_from_existing_tool_runtime():
|
||||
assert runtime_provider.last_agent_tool.provider_type.value == "plugin"
|
||||
|
||||
|
||||
def test_builds_dify_plugin_tool_with_file_llm_parameter():
|
||||
runtime_provider = FakeRuntimeProvider(_file_tool())
|
||||
builder = WorkflowAgentPluginToolsBuilder(tool_runtime_provider=runtime_provider)
|
||||
tools = AgentSoulToolsConfig.model_validate(
|
||||
{
|
||||
"dify_tools": [
|
||||
{
|
||||
"provider_id": "audio",
|
||||
"provider_type": "builtin",
|
||||
"tool_name": "asr",
|
||||
"credential_type": "unauthorized",
|
||||
}
|
||||
]
|
||||
}
|
||||
)
|
||||
|
||||
result = _build(builder, tools)
|
||||
|
||||
assert result is not None
|
||||
prepared = result.tools[0]
|
||||
assert prepared.tool_name == "asr"
|
||||
assert prepared.runtime_parameters == {}
|
||||
assert prepared.parameters[0].name == "audio_file"
|
||||
assert prepared.parameters[0].type == "file"
|
||||
# The public Agent backend DTO carries non-scalar tool inputs in
|
||||
# ``parameters``; legacy JSON schema generation omits file fields.
|
||||
assert prepared.parameters_json_schema == {"type": "object", "properties": {}, "required": []}
|
||||
assert runtime_provider.last_allow_file_parameters is True
|
||||
assert runtime_provider.last_use_default_for_missing_form_parameters is True
|
||||
|
||||
|
||||
def test_builds_dify_plugin_tool_with_missing_required_select_default():
|
||||
runtime_provider = FakeRuntimeProvider(_tts_tool())
|
||||
builder = WorkflowAgentPluginToolsBuilder(tool_runtime_provider=runtime_provider)
|
||||
tools = AgentSoulToolsConfig.model_validate(
|
||||
{
|
||||
"dify_tools": [
|
||||
{
|
||||
"provider_id": "audio",
|
||||
"provider_type": "builtin",
|
||||
"tool_name": "tts",
|
||||
"credential_type": "unauthorized",
|
||||
}
|
||||
]
|
||||
}
|
||||
)
|
||||
|
||||
result = _build(builder, tools)
|
||||
|
||||
assert result is not None
|
||||
prepared = result.tools[0]
|
||||
assert prepared.tool_name == "tts"
|
||||
assert prepared.runtime_parameters == {"model": "provider-a#model-a"}
|
||||
assert runtime_provider.last_use_default_for_missing_form_parameters is True
|
||||
|
||||
|
||||
def test_rejects_duplicate_exposed_tool_names():
|
||||
builder = WorkflowAgentPluginToolsBuilder(tool_runtime_provider=FakeRuntimeProvider(_tool()))
|
||||
tools = AgentSoulToolsConfig.model_validate(
|
||||
|
||||
Loading…
Reference in New Issue
Block a user