mirror of
https://github.com/langgenius/dify.git
synced 2026-01-13 21:57:48 +08:00
feat(agent-sandbox): new tool resolver and bash execution implementation
Some checks are pending
Build and Push API & Web / build (api, DIFY_API_IMAGE_NAME, linux/amd64, build-api-amd64) (push) Waiting to run
Build and Push API & Web / build (api, DIFY_API_IMAGE_NAME, linux/arm64, build-api-arm64) (push) Waiting to run
Build and Push API & Web / build (web, DIFY_WEB_IMAGE_NAME, linux/amd64, build-web-amd64) (push) Waiting to run
Build and Push API & Web / build (web, DIFY_WEB_IMAGE_NAME, linux/arm64, build-web-arm64) (push) Waiting to run
Build and Push API & Web / create-manifest (api, DIFY_API_IMAGE_NAME, merge-api-images) (push) Blocked by required conditions
Build and Push API & Web / create-manifest (web, DIFY_WEB_IMAGE_NAME, merge-web-images) (push) Blocked by required conditions
Some checks are pending
Build and Push API & Web / build (api, DIFY_API_IMAGE_NAME, linux/amd64, build-api-amd64) (push) Waiting to run
Build and Push API & Web / build (api, DIFY_API_IMAGE_NAME, linux/arm64, build-api-arm64) (push) Waiting to run
Build and Push API & Web / build (web, DIFY_WEB_IMAGE_NAME, linux/amd64, build-web-amd64) (push) Waiting to run
Build and Push API & Web / build (web, DIFY_WEB_IMAGE_NAME, linux/arm64, build-web-arm64) (push) Waiting to run
Build and Push API & Web / create-manifest (api, DIFY_API_IMAGE_NAME, merge-api-images) (push) Blocked by required conditions
Build and Push API & Web / create-manifest (web, DIFY_WEB_IMAGE_NAME, merge-web-images) (push) Blocked by required conditions
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.
|
||||
TRIGGER = "trigger"
|
||||
|
||||
# AGENT indicates that this invocation is from an agent.
|
||||
AGENT = "agent"
|
||||
|
||||
# EXPLORE indicates that this invocation is from
|
||||
# the workflow (or chatflow) explore page.
|
||||
EXPLORE = "explore"
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import shlex
|
||||
from collections.abc import Generator
|
||||
from typing import Any
|
||||
|
||||
from core.sandbox.debug import sandbox_debug
|
||||
from core.tools.__base.tool import Tool
|
||||
from core.tools.__base.tool_runtime import ToolRuntime
|
||||
from core.tools.entities.common_entities import I18nObject
|
||||
@ -68,7 +68,9 @@ class SandboxBashTool(Tool):
|
||||
|
||||
connection_handle = self._sandbox.establish_connection()
|
||||
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)
|
||||
timeout = COMMAND_TIMEOUT_SECONDS if COMMAND_TIMEOUT_SECONDS > 0 else None
|
||||
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 core.model_runtime.utils.encoders import jsonable_encoder
|
||||
from core.sandbox.constants import DIFY_CLI_PATH_PATTERN
|
||||
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
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@ -77,11 +78,26 @@ class DifyCliToolConfig(BaseModel):
|
||||
def create_from_tool(cls, tool: Tool) -> DifyCliToolConfig:
|
||||
return cls(
|
||||
provider_type=cls.transform_provider_type(tool.tool_provider_type()),
|
||||
identity=tool.entity.identity.model_dump(),
|
||||
description=tool.entity.description.model_dump() if tool.entity.description else {},
|
||||
parameters=[param.model_dump() for param in tool.entity.parameters],
|
||||
identity=to_json(tool.entity.identity),
|
||||
description=to_json(tool.entity.description),
|
||||
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):
|
||||
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__ = [
|
||||
"DifyCliBinary",
|
||||
"DifyCliConfig",
|
||||
|
||||
@ -7,6 +7,7 @@ from types import TracebackType
|
||||
|
||||
from core.sandbox.bash_tool import SandboxBashTool
|
||||
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.manager import SandboxManager
|
||||
from core.session.cli_api import CliApiSessionManager
|
||||
@ -46,6 +47,7 @@ class SandboxSession:
|
||||
config = DifyCliConfig.create(session, self._tools)
|
||||
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")))
|
||||
|
||||
connection_handle = sandbox.establish_connection()
|
||||
|
||||
@ -4,6 +4,7 @@ import shlex
|
||||
from collections.abc import Mapping, Sequence
|
||||
from typing import Any
|
||||
|
||||
from core.sandbox.debug import sandbox_debug
|
||||
from core.sandbox.manager import SandboxManager
|
||||
from core.virtual_environment.__base.command_future import CommandCancelledError, CommandTimeoutError
|
||||
from core.virtual_environment.__base.virtual_environment import VirtualEnvironment
|
||||
@ -83,6 +84,9 @@ class CommandNode(Node[CommandNodeData]):
|
||||
|
||||
try:
|
||||
command = shlex.split(raw_command)
|
||||
|
||||
sandbox_debug("command_node", "command", command)
|
||||
|
||||
future = sandbox.run_command(connection_handle, command, cwd=working_directory)
|
||||
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.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.helper.code_executor import CodeExecutor, CodeLanguage
|
||||
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)
|
||||
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(
|
||||
self,
|
||||
model_instance: ModelInstance,
|
||||
@ -1592,7 +1621,7 @@ class LLMNode(Node[LLMNodeData]):
|
||||
if not workflow_execution_id:
|
||||
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
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user