mirror of
https://github.com/langgenius/dify.git
synced 2026-05-13 08:57:28 +08:00
feat(agent-sandbox): new tool resolver and bash execution implementation
This commit is contained in:
parent
c6ba51127f
commit
f28ded8455
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -36,6 +36,9 @@ class InvokeFrom(StrEnum):
|
|||||||
# this is used for plugin trigger and webhook trigger.
|
# this is used for plugin trigger and webhook trigger.
|
||||||
TRIGGER = "trigger"
|
TRIGGER = "trigger"
|
||||||
|
|
||||||
|
# AGENT indicates that this invocation is from an agent.
|
||||||
|
AGENT = "agent"
|
||||||
|
|
||||||
# EXPLORE indicates that this invocation is from
|
# EXPLORE indicates that this invocation is from
|
||||||
# the workflow (or chatflow) explore page.
|
# the workflow (or chatflow) explore page.
|
||||||
EXPLORE = "explore"
|
EXPLORE = "explore"
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import shlex
|
|
||||||
from collections.abc import Generator
|
from collections.abc import Generator
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
|
from core.sandbox.debug import sandbox_debug
|
||||||
from core.tools.__base.tool import Tool
|
from core.tools.__base.tool import Tool
|
||||||
from core.tools.__base.tool_runtime import ToolRuntime
|
from core.tools.__base.tool_runtime import ToolRuntime
|
||||||
from core.tools.entities.common_entities import I18nObject
|
from core.tools.entities.common_entities import I18nObject
|
||||||
@ -68,7 +68,9 @@ class SandboxBashTool(Tool):
|
|||||||
|
|
||||||
connection_handle = self._sandbox.establish_connection()
|
connection_handle = self._sandbox.establish_connection()
|
||||||
try:
|
try:
|
||||||
cmd_list = shlex.split(command)
|
cmd_list = ["bash", "-c", command]
|
||||||
|
|
||||||
|
sandbox_debug("bash_tool", "cmd_list", cmd_list)
|
||||||
future = self._sandbox.run_command(connection_handle, cmd_list)
|
future = self._sandbox.run_command(connection_handle, cmd_list)
|
||||||
timeout = COMMAND_TIMEOUT_SECONDS if COMMAND_TIMEOUT_SECONDS > 0 else None
|
timeout = COMMAND_TIMEOUT_SECONDS if COMMAND_TIMEOUT_SECONDS > 0 else None
|
||||||
result = future.result(timeout=timeout)
|
result = future.result(timeout=timeout)
|
||||||
|
|||||||
18
api/core/sandbox/debug.py
Normal file
18
api/core/sandbox/debug.py
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
"""Sandbox debug utilities. TODO: Remove this module when sandbox debugging is complete."""
|
||||||
|
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
from core.callback_handler.agent_tool_callback_handler import print_text
|
||||||
|
|
||||||
|
SANDBOX_DEBUG_ENABLED = True
|
||||||
|
|
||||||
|
|
||||||
|
def sandbox_debug(tag: str, message: str, data: Any = None) -> None:
|
||||||
|
if not SANDBOX_DEBUG_ENABLED:
|
||||||
|
return
|
||||||
|
|
||||||
|
print_text(f"\n[{tag}]\n", color="blue")
|
||||||
|
if data is not None:
|
||||||
|
print_text(f"{message}: {data}\n", color="blue")
|
||||||
|
else:
|
||||||
|
print_text(f"{message}\n", color="blue")
|
||||||
@ -5,9 +5,10 @@ from typing import TYPE_CHECKING, Any
|
|||||||
|
|
||||||
from pydantic import BaseModel, Field
|
from pydantic import BaseModel, Field
|
||||||
|
|
||||||
|
from core.model_runtime.utils.encoders import jsonable_encoder
|
||||||
from core.sandbox.constants import DIFY_CLI_PATH_PATTERN
|
from core.sandbox.constants import DIFY_CLI_PATH_PATTERN
|
||||||
from core.session.cli_api import CliApiSession
|
from core.session.cli_api import CliApiSession
|
||||||
from core.tools.entities.tool_entities import ToolProviderType
|
from core.tools.entities.tool_entities import ToolParameter, ToolProviderType
|
||||||
from core.virtual_environment.__base.entities import Arch, OperatingSystem
|
from core.virtual_environment.__base.entities import Arch, OperatingSystem
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
@ -77,11 +78,26 @@ class DifyCliToolConfig(BaseModel):
|
|||||||
def create_from_tool(cls, tool: Tool) -> DifyCliToolConfig:
|
def create_from_tool(cls, tool: Tool) -> DifyCliToolConfig:
|
||||||
return cls(
|
return cls(
|
||||||
provider_type=cls.transform_provider_type(tool.tool_provider_type()),
|
provider_type=cls.transform_provider_type(tool.tool_provider_type()),
|
||||||
identity=tool.entity.identity.model_dump(),
|
identity=to_json(tool.entity.identity),
|
||||||
description=tool.entity.description.model_dump() if tool.entity.description else {},
|
description=to_json(tool.entity.description),
|
||||||
parameters=[param.model_dump() for param in tool.entity.parameters],
|
parameters=[cls.transform_parameter(parameter) for parameter in tool.entity.parameters],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def transform_parameter(cls, parameter: ToolParameter) -> dict[str, Any]:
|
||||||
|
transformed_parameter = to_json(parameter)
|
||||||
|
transformed_parameter.pop("input_schema", None)
|
||||||
|
transformed_parameter.pop("form", None)
|
||||||
|
match parameter.type:
|
||||||
|
case (
|
||||||
|
ToolParameter.ToolParameterType.SYSTEM_FILES
|
||||||
|
| ToolParameter.ToolParameterType.FILE
|
||||||
|
| ToolParameter.ToolParameterType.FILES
|
||||||
|
):
|
||||||
|
return transformed_parameter
|
||||||
|
case _:
|
||||||
|
return transformed_parameter
|
||||||
|
|
||||||
|
|
||||||
class DifyCliConfig(BaseModel):
|
class DifyCliConfig(BaseModel):
|
||||||
env: DifyCliEnvConfig
|
env: DifyCliEnvConfig
|
||||||
@ -104,6 +120,10 @@ class DifyCliConfig(BaseModel):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def to_json(obj: Any) -> dict[str, Any]:
|
||||||
|
return jsonable_encoder(obj, exclude_unset=True, exclude_defaults=True, exclude_none=True)
|
||||||
|
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"DifyCliBinary",
|
"DifyCliBinary",
|
||||||
"DifyCliConfig",
|
"DifyCliConfig",
|
||||||
|
|||||||
@ -7,6 +7,7 @@ from types import TracebackType
|
|||||||
|
|
||||||
from core.sandbox.bash_tool import SandboxBashTool
|
from core.sandbox.bash_tool import SandboxBashTool
|
||||||
from core.sandbox.constants import DIFY_CLI_CONFIG_PATH, DIFY_CLI_PATH
|
from core.sandbox.constants import DIFY_CLI_CONFIG_PATH, DIFY_CLI_PATH
|
||||||
|
from core.sandbox.debug import sandbox_debug
|
||||||
from core.sandbox.dify_cli import DifyCliConfig
|
from core.sandbox.dify_cli import DifyCliConfig
|
||||||
from core.sandbox.manager import SandboxManager
|
from core.sandbox.manager import SandboxManager
|
||||||
from core.session.cli_api import CliApiSessionManager
|
from core.session.cli_api import CliApiSessionManager
|
||||||
@ -46,6 +47,7 @@ class SandboxSession:
|
|||||||
config = DifyCliConfig.create(session, self._tools)
|
config = DifyCliConfig.create(session, self._tools)
|
||||||
config_json = json.dumps(config.model_dump(mode="json"), ensure_ascii=False)
|
config_json = json.dumps(config.model_dump(mode="json"), ensure_ascii=False)
|
||||||
|
|
||||||
|
sandbox_debug("sandbox", "config_json", config_json)
|
||||||
sandbox.upload_file(DIFY_CLI_CONFIG_PATH, BytesIO(config_json.encode("utf-8")))
|
sandbox.upload_file(DIFY_CLI_CONFIG_PATH, BytesIO(config_json.encode("utf-8")))
|
||||||
|
|
||||||
connection_handle = sandbox.establish_connection()
|
connection_handle = sandbox.establish_connection()
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import shlex
|
|||||||
from collections.abc import Mapping, Sequence
|
from collections.abc import Mapping, Sequence
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
|
from core.sandbox.debug import sandbox_debug
|
||||||
from core.sandbox.manager import SandboxManager
|
from core.sandbox.manager import SandboxManager
|
||||||
from core.virtual_environment.__base.command_future import CommandCancelledError, CommandTimeoutError
|
from core.virtual_environment.__base.command_future import CommandCancelledError, CommandTimeoutError
|
||||||
from core.virtual_environment.__base.virtual_environment import VirtualEnvironment
|
from core.virtual_environment.__base.virtual_environment import VirtualEnvironment
|
||||||
@ -83,6 +84,9 @@ class CommandNode(Node[CommandNodeData]):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
command = shlex.split(raw_command)
|
command = shlex.split(raw_command)
|
||||||
|
|
||||||
|
sandbox_debug("command_node", "command", command)
|
||||||
|
|
||||||
future = sandbox.run_command(connection_handle, command, cwd=working_directory)
|
future = sandbox.run_command(connection_handle, command, cwd=working_directory)
|
||||||
result = future.result(timeout=timeout)
|
result = future.result(timeout=timeout)
|
||||||
|
|
||||||
|
|||||||
@ -13,7 +13,7 @@ from sqlalchemy import select
|
|||||||
|
|
||||||
from core.agent.entities import AgentEntity, AgentLog, AgentResult, AgentToolEntity, ExecutionContext
|
from core.agent.entities import AgentEntity, AgentLog, AgentResult, AgentToolEntity, ExecutionContext
|
||||||
from core.agent.patterns import StrategyFactory
|
from core.agent.patterns import StrategyFactory
|
||||||
from core.app.entities.app_invoke_entities import ModelConfigWithCredentialsEntity
|
from core.app.entities.app_invoke_entities import InvokeFrom, ModelConfigWithCredentialsEntity
|
||||||
from core.file import File, FileTransferMethod, FileType, file_manager
|
from core.file import File, FileTransferMethod, FileType, file_manager
|
||||||
from core.helper.code_executor import CodeExecutor, CodeLanguage
|
from core.helper.code_executor import CodeExecutor, CodeLanguage
|
||||||
from core.llm_generator.output_parser.errors import OutputParserError
|
from core.llm_generator.output_parser.errors import OutputParserError
|
||||||
@ -1581,6 +1581,35 @@ class LLMNode(Node[LLMNodeData]):
|
|||||||
result = yield from self._process_tool_outputs(outputs)
|
result = yield from self._process_tool_outputs(outputs)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
def _prepare_sandbox_tools(self) -> list[Tool]:
|
||||||
|
"""Prepare sandbox tools."""
|
||||||
|
tool_instances = []
|
||||||
|
|
||||||
|
for tool in self._node_data.tools or []:
|
||||||
|
try:
|
||||||
|
# Get tool runtime from ToolManager
|
||||||
|
tool_runtime = ToolManager.get_tool_runtime(
|
||||||
|
tenant_id=self.tenant_id,
|
||||||
|
tool_name=tool.tool_name,
|
||||||
|
provider_id=tool.provider_name,
|
||||||
|
provider_type=tool.type,
|
||||||
|
invoke_from=InvokeFrom.AGENT,
|
||||||
|
credential_id=tool.credential_id,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Apply custom description from extra field if available
|
||||||
|
if tool.extra.get("description") and tool_runtime.entity.description:
|
||||||
|
tool_runtime.entity.description.llm = (
|
||||||
|
tool.extra.get("description") or tool_runtime.entity.description.llm
|
||||||
|
)
|
||||||
|
|
||||||
|
tool_instances.append(tool_runtime)
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning("Failed to load tool %s: %s", tool, str(e))
|
||||||
|
continue
|
||||||
|
|
||||||
|
return tool_instances
|
||||||
|
|
||||||
def _invoke_llm_with_sandbox(
|
def _invoke_llm_with_sandbox(
|
||||||
self,
|
self,
|
||||||
model_instance: ModelInstance,
|
model_instance: ModelInstance,
|
||||||
@ -1592,7 +1621,7 @@ class LLMNode(Node[LLMNodeData]):
|
|||||||
if not workflow_execution_id:
|
if not workflow_execution_id:
|
||||||
raise LLMNodeError("workflow_execution_id is required for sandbox runtime mode")
|
raise LLMNodeError("workflow_execution_id is required for sandbox runtime mode")
|
||||||
|
|
||||||
configured_tools = self._prepare_tool_instances(variable_pool)
|
configured_tools = self._prepare_sandbox_tools()
|
||||||
|
|
||||||
result: LLMGenerationData | None = None
|
result: LLMGenerationData | None = None
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user