diff --git a/dify-agent/src/dify_agent/adapters/llm/model.py b/dify-agent/src/dify_agent/adapters/llm/model.py index 02b0f71b464..1f4c93a161e 100644 --- a/dify-agent/src/dify_agent/adapters/llm/model.py +++ b/dify-agent/src/dify_agent/adapters/llm/model.py @@ -343,7 +343,8 @@ def _map_model_request_to_prompt_messages(message: ModelRequest) -> list[PromptM for part in message.parts: if isinstance(part, SystemPromptPart): - prompt_messages.append(SystemPromptMessage(content=part.content)) + if part.content.strip(): + prompt_messages.append(SystemPromptMessage(content=part.content)) elif isinstance(part, UserPromptPart): prompt_messages.append(UserPromptMessage(content=_map_user_prompt_content(part.content))) elif isinstance(part, ToolReturnPart): diff --git a/dify-agent/src/dify_agent/layers/shell/layer.py b/dify-agent/src/dify_agent/layers/shell/layer.py index 2e39185aa64..163680451eb 100644 --- a/dify-agent/src/dify_agent/layers/shell/layer.py +++ b/dify-agent/src/dify_agent/layers/shell/layer.py @@ -301,6 +301,8 @@ class DifyShellLayer(PydanticAILayer[DifyShellLayerDeps, object, DifyShellLayerC self._clear_tracked_jobs() async def _tool_run(self, script: str, timeout: float = DEFAULT_TIMEOUT_SECONDS) -> ShellRunToolResult: + """Start a shell job in the current workspace and return its output and status.""" + try: env = self._build_shell_command_env(include_agent_stub_env=True) agent_stub_token = env.get(AGENT_STUB_AUTH_JWE_ENV_VAR) @@ -337,6 +339,8 @@ class DifyShellLayer(PydanticAILayer[DifyShellLayerDeps, object, DifyShellLayerC return _tool_unexpected_error("shell_run", exc) async def _tool_wait(self, job_id: str, timeout: float = DEFAULT_TIMEOUT_SECONDS) -> ShellRunToolResult: + """Wait for more output or completion from an existing shell job.""" + try: offset = self._tracked_offset(job_id) result = await self._require_resource().commands.wait(job_id, offset=offset, timeout=timeout) @@ -369,6 +373,8 @@ class DifyShellLayer(PydanticAILayer[DifyShellLayerDeps, object, DifyShellLayerC return _tool_unexpected_error("shell_wait", exc, job_id=job_id) async def _tool_input(self, job_id: str, text: str, timeout: float = DEFAULT_TIMEOUT_SECONDS) -> ShellRunToolResult: + """Send text to a running shell job and wait for its next output.""" + try: offset = self._tracked_offset(job_id) result = await self._require_resource().commands.input(job_id, text, offset=offset, timeout=timeout) @@ -405,6 +411,8 @@ class DifyShellLayer(PydanticAILayer[DifyShellLayerDeps, object, DifyShellLayerC job_id: str, grace_seconds: float = DEFAULT_TERMINATE_GRACE_SECONDS, ) -> ShellInterruptToolResult: + """Interrupt a running shell job and return its final status.""" + try: self._ensure_tracked_job(job_id) result = await self._require_resource().commands.interrupt(job_id, grace_seconds=grace_seconds) diff --git a/dify-agent/tests/local/dify_agent/adapters/llm/test_model.py b/dify-agent/tests/local/dify_agent/adapters/llm/test_model.py index 589e420bf80..eb6df382054 100644 --- a/dify-agent/tests/local/dify_agent/adapters/llm/test_model.py +++ b/dify-agent/tests/local/dify_agent/adapters/llm/test_model.py @@ -101,6 +101,7 @@ class DifyLLMAdapterModelTests(unittest.IsolatedAsyncioTestCase): messages = [ ModelRequest( parts=[ + SystemPromptPart(" "), SystemPromptPart("request system"), UserPromptPart("hello"), ToolReturnPart( diff --git a/dify-agent/tests/local/dify_agent/layers/shell/test_layer.py b/dify-agent/tests/local/dify_agent/layers/shell/test_layer.py index 0ffc47c9bd8..ae75ff1aea0 100644 --- a/dify-agent/tests/local/dify_agent/layers/shell/test_layer.py +++ b/dify-agent/tests/local/dify_agent/layers/shell/test_layer.py @@ -303,6 +303,15 @@ def test_shell_type_id_constant_matches_implementation_class() -> None: assert DIFY_SHELL_LAYER_TYPE_ID == DifyShellLayer.type_id +def test_shell_layer_tools_have_non_empty_descriptions() -> None: + layer = DifyShellLayer.from_config_with_settings(DifyShellLayerConfig()) + + descriptions = {tool.name: tool.description for tool in layer.tools} + + assert set(descriptions) == {"shell_run", "shell_wait", "shell_input", "shell_interrupt"} + assert all(description and description.strip() for description in descriptions.values()) + + def test_shell_prefix_prompt_describes_workspace_as_temp_space() -> None: prompt = shell_layer_module._SHELL_LAYER_PREFIX_PROMPT