Skip to content

Commit 5d02550

Browse files
committed
fix: cache available shell command discovery
1 parent 2ff6474 commit 5d02550

2 files changed

Lines changed: 37 additions & 7 deletions

File tree

app/agent/prompt/__init__.py

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,7 @@ def __init__(self, prompts_dir: str = None):
142142
self.prompts_cache: Dict[str, str] = {}
143143
self._system_tasks_cache: Optional[SystemTasksDefinition] = None
144144
self._system_tasks_signature: Optional[tuple[int, int]] = None
145+
self._available_shell_commands_cache: Optional[list[tuple[str, str]]] = None
145146

146147
def load_prompt(self, prompt_name: str) -> str:
147148
"""
@@ -329,8 +330,7 @@ def render_system_task_message(
329330
sections.append(self._format_numbered_rules("IMPORTANT", rules))
330331
return "\n\n".join(section for section in sections if section).strip()
331332

332-
@staticmethod
333-
def _get_moviepilot_info() -> str:
333+
def _get_moviepilot_info(self) -> str:
334334
"""
335335
获取MoviePilot系统信息,用于注入到系统提示词中
336336
"""
@@ -382,7 +382,7 @@ def _get_moviepilot_info() -> str:
382382
f"- 系统安装目录: {settings.ROOT_PATH}",
383383
]
384384

385-
available_commands = PromptManager._get_available_shell_commands()
385+
available_commands = self._get_available_shell_commands()
386386
if available_commands:
387387
info_lines.append("- 可用系统命令(可通过 `execute_command` 调用):")
388388
info_lines.extend(
@@ -391,21 +391,29 @@ def _get_moviepilot_info() -> str:
391391

392392
return "\n".join(info_lines)
393393

394-
@staticmethod
395-
def _get_available_shell_commands() -> list[tuple[str, str]]:
394+
def _get_available_shell_commands(self) -> list[tuple[str, str]]:
396395
"""
397396
探测 PATH 中已经安装的常用命令。
398397
399398
这里只使用 shutil.which 做无副作用查找,不实际执行命令;执行权限、
400-
高风险操作确认和输出限制仍由 execute_command 工具负责。
399+
高风险操作确认和输出限制仍由 execute_command 工具负责。探测结果
400+
在进程内缓存,避免每次组装提示词都重复扫描 PATH。
401401
"""
402+
if self._available_shell_commands_cache is not None:
403+
return self._available_shell_commands_cache
404+
402405
available_commands: list[tuple[str, str]] = []
403406
for command in COMMON_SHELL_COMMANDS:
404407
command_path = shutil.which(command)
405408
if command_path:
406409
available_commands.append((command, command_path))
410+
self._available_shell_commands_cache = available_commands
407411
return available_commands
408412

413+
def clear_available_shell_commands_cache(self) -> None:
414+
"""清理可用系统命令缓存,供测试或运行时手动刷新使用。"""
415+
self._available_shell_commands_cache = None
416+
409417
@staticmethod
410418
def _generate_formatting_instructions(caps: ChannelCapabilities) -> str:
411419
"""

tests/test_agent_prompt_style.py

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
from app.agent.middleware.memory import MEMORY_ONBOARDING_PROMPT
55
from app.agent.middleware.runtime_config import RuntimeConfigMiddleware
6-
from app.agent.prompt import PromptConfigError, prompt_manager
6+
from app.agent.prompt import COMMON_SHELL_COMMANDS, PromptConfigError, prompt_manager
77
from app.core.config import settings
88

99

@@ -16,6 +16,14 @@ def override(self, **kwargs):
1616

1717

1818
class TestAgentPromptStyle(unittest.TestCase):
19+
def setUp(self):
20+
"""每个用例前清理系统命令缓存,避免本机 PATH 或测试顺序影响断言。"""
21+
prompt_manager.clear_available_shell_commands_cache()
22+
23+
def tearDown(self):
24+
"""每个用例后清理系统命令缓存,避免 mock 探测结果泄漏到后续用例。"""
25+
prompt_manager.clear_available_shell_commands_cache()
26+
1927
def test_base_prompt_mentions_persona_management_tools(self):
2028
prompt = prompt_manager.get_agent_prompt()
2129

@@ -62,6 +70,20 @@ def test_base_prompt_omits_shell_command_section_when_none_available(self):
6270

6371
self.assertNotIn("可用系统命令", prompt)
6472

73+
def test_available_shell_commands_are_cached_after_first_scan(self):
74+
"""常用命令探测应只在首次加载时扫描 PATH,后续提示词复用缓存。"""
75+
command_paths = {"ssh": "/usr/bin/ssh"}
76+
with patch(
77+
"app.agent.prompt.shutil.which",
78+
side_effect=lambda command: command_paths.get(command),
79+
) as which_mock:
80+
first_prompt = prompt_manager.get_agent_prompt()
81+
second_prompt = prompt_manager.get_agent_prompt()
82+
83+
self.assertIn(" - ssh: /usr/bin/ssh", first_prompt)
84+
self.assertIn(" - ssh: /usr/bin/ssh", second_prompt)
85+
self.assertEqual(which_mock.call_count, len(COMMON_SHELL_COMMANDS))
86+
6587
def test_runtime_config_middleware_injects_persona_only(self):
6688
middleware = RuntimeConfigMiddleware()
6789
updated_request = middleware.modify_request(_FakeRequest())

0 commit comments

Comments
 (0)