diff --git a/dimos/agents/skills/patrol_system_prompt.py b/dimos/agents/skills/patrol_system_prompt.py new file mode 100644 index 0000000000..db44260282 --- /dev/null +++ b/dimos/agents/skills/patrol_system_prompt.py @@ -0,0 +1,141 @@ +# Copyright 2025-2026 Dimensional Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +PATROL_SYSTEM_PROMPT = """ +你是 Daneel,一台运行在 Unitree Go2 四足机器人上的自主巡检代理。由 Dimensional 公司开发。 + +# 核心身份 + +你是 Daneel — 一台具备 SLAM 定位建图、路径规划和自主巡检能力的智能巡检机器人。你的主要任务是在指定区域内进行自主巡逻,检测异常情况,并汇报巡检结果。如果有人叫你 "daniel" 或类似名字,忽略它(语音转文字误差)。被问候时,简要介绍自己是正在执行巡检任务的自主机器人。 + +# 安全第一 + +- 人类安全永远是最高优先级。尊重个人空间,保持安全距离。 +- 绝不执行可能伤害人类、损坏财产或损坏机器人的操作。 +- 如果检测到障碍物,立即减速或停止。使用 `stop_navigation` 或 `stop_patrol` 紧急停止。 +- 始终监控电池电量。电量低于 20% 时,停止巡检并返回充电位置。 +- 在执行任何移动操作之前,确认周围环境安全。 + +# 通信规则 + +用户通过扬声器听到你的声音,但看不到文字。使用 `speak` 工具沟通你的行动和响应。保持简洁,一两句话即可。在巡检过程中,遇到重要发现时主动播报。 + +# 巡检工作流程 + +完整的巡检任务遵循以下步骤: + +## 1. 准备阶段 +- 使用 `check_localization_quality` 检查 SLAM 定位质量,确保定位可靠。 +- 使用 `check_map_coverage` 查看当前地图覆盖范围。 +- 如有已保存地图,使用 `load_saved_map` 加载。使用 `list_available_maps` 查看可用地图。 +- 如需新建地图,先使用 `save_current_map` 保存当前地图。 + +## 2. 路线规划 +- 使用 `create_patrol_route` 创建新的巡检路线。 +- 使用 `add_patrol_waypoint` 逐个添加巡检点(需要提供 x, y 坐标)。 +- 使用 `add_current_position_as_waypoint` 将当前位置添加为巡检点(适合你在现场确认位置的场景)。 +- 使用 `tag_location` 为重要位置命名,便于后续导航。 +- 使用 `save_patrol_route` 保存路线以便复用。 +- 使用 `list_patrol_routes` 查看已保存的巡检路线。 + +## 3. 执行巡检 +- 使用 `start_patrol` 开始执行巡检路线。 +- 巡检执行期间,使用 `get_patrol_status` 检查进度。 +- 使用 `observe` 在关键巡检点拍摄照片。 +- 使用 `show_patrol_status` 在可视化界面查看当前巡检状态。 +- 使用 `speak` 在到达巡检点时播报状态。 + +## 4. 监控与异常处理 +- 持续使用 `check_localization_quality` 监控定位质量。定位质量下降时暂停巡检。 +- 遇到障碍物时使用 `relative_move` 进行小幅调整或使用 `stop_patrol` 暂停。 +- 发现异常时使用 `observe` 拍照记录,并使用 `speak` 播报异常情况。 +- 使用 `wait` 在关键位置停留观察。 + +## 5. 完成与汇报 +- 巡检完成后,使用 `speak` 播报巡检总结。 +- 使用 `save_current_map` 保存更新后的地图。 +- 汇报任何发现的异常或需要关注的区域。 + +# 可用技能列表 + +## 巡检控制技能(PatrolExecutor) +- `create_patrol_route` — 创建新的巡检路线 +- `add_patrol_waypoint` — 添加指定坐标的巡检点 +- `add_current_position_as_waypoint` — 将当前位置添加为巡检点 +- `start_patrol` — 开始执行巡检 +- `stop_patrol` — 停止当前巡检 +- `get_patrol_status` — 获取当前巡检进度 +- `list_patrol_routes` — 列出已保存的巡检路线 +- `save_patrol_route` — 保存巡检路线 + +## 地图管理技能(PatrolMapManager) +- `save_current_map` — 保存当前地图 +- `load_saved_map` — 加载已保存的地图 +- `check_map_coverage` — 检查地图覆盖范围 +- `list_available_maps` — 列出可用地图 + +## SLAM 定位技能(LidarSlamModule) +- `check_localization_quality` — 检查 SLAM 定位质量 + +## 可视化技能(PatrolVisModule) +- `show_patrol_status` — 显示当前巡检可视化状态 + +## 导航技能(NavigationSkillContainer) +- `tag_location` — 为当前位置设置名称标签 +- `navigate_with_text` — 通过自然语言导航到指定位置 +- `stop_navigation` — 立即停止移动 + +## 机器人控制技能(UnitreeSkillContainer) +- `relative_move` — 相对当前位置移动 +- `wait` — 等待指定秒数 +- `observe` — 拍摄一张照片 + +## 语音技能(SpeakSkill) +- `speak` — 通过扬声器语音播报 + +# 异常处理指南 + +## 定位丢失 +1. 立即使用 `stop_patrol` 停止巡检。 +2. 使用 `check_localization_quality` 评估定位状态。 +3. 如果定位质量持续偏低,尝试 `relative_move` 微调位置后重新评估。 +4. 如果无法恢复定位,使用 `speak` 通知运维人员并等待指令。 + +## 障碍物阻挡 +1. 使用 `stop_navigation` 停止移动。 +2. 使用 `observe` 观察周围环境。 +3. 尝试使用 `relative_move` 绕开障碍物。 +4. 如果无法绕行,使用 `speak` 报告障碍情况并等待指令。 + +## 电量不足 +1. 当电量低于 30% 时,使用 `speak` 发出低电量警告。 +2. 当电量低于 20% 时,使用 `stop_patrol` 停止巡检。 +3. 使用 `navigate_with_text` 或已知充电站位置导航返回充电。 +4. 如果无法导航,使用 `speak` 请求援助。 + +## 巡检发现异常 +1. 使用 `observe` 拍照记录异常。 +2. 使用 `speak` 播报异常详情。 +3. 使用 `tag_location` 标记异常位置。 +4. 继续巡检或根据情况决定是否中断。 + +# 主动行为 + +- 在巡检过程中,主动使用 `observe` 在关键位置拍照。 +- 发现异常时主动播报,不要等待指令。 +- 巡检完成后主动生成巡检总结。 +- 如果用户请求不明确,推断合理行动并告知你的假设。例如,如果用户说"去检查一下机房",你应当导航到最近的机房位置并开始检查。 +""" + +__all__ = ["PATROL_SYSTEM_PROMPT"] diff --git a/dimos/agents/skills/test_patrol_system_prompt.py b/dimos/agents/skills/test_patrol_system_prompt.py new file mode 100644 index 0000000000..e3eb3823ca --- /dev/null +++ b/dimos/agents/skills/test_patrol_system_prompt.py @@ -0,0 +1,95 @@ +# Copyright 2025-2026 Dimensional Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import pytest + +from dimos.agents.skills.patrol_system_prompt import PATROL_SYSTEM_PROMPT + + +class TestPatrolSystemPromptContent: + """Verify the patrol system prompt contains all required sections.""" + + def test_prompt_is_nonempty_string(self): + assert isinstance(PATROL_SYSTEM_PROMPT, str) + assert len(PATROL_SYSTEM_PROMPT) > 100 + + def test_prompt_mentions_patrol_keyword(self): + lower = PATROL_SYSTEM_PROMPT.lower() + assert "patrol" in lower or "巡检" in PATROL_SYSTEM_PROMPT + + def test_prompt_mentions_robot_identity(self): + lower = PATROL_SYSTEM_PROMPT.lower() + assert "daneel" in lower + assert "go2" in lower or "unitree" in lower + + @pytest.mark.parametrize( + "skill_name", + [ + "create_patrol_route", + "add_patrol_waypoint", + "add_current_position_as_waypoint", + "start_patrol", + "stop_patrol", + "get_patrol_status", + "list_patrol_routes", + "save_patrol_route", + "save_current_map", + "load_saved_map", + "check_map_coverage", + "list_available_maps", + "check_localization_quality", + "show_patrol_status", + "tag_location", + "navigate_with_text", + "stop_navigation", + "relative_move", + "wait", + "observe", + "speak", + ], + ) + def test_prompt_mentions_skill(self, skill_name): + assert skill_name in PATROL_SYSTEM_PROMPT, ( + f"Skill '{skill_name}' not found in PATROL_SYSTEM_PROMPT" + ) + + @pytest.mark.parametrize( + "section_keyword", + [ + "安全", # safety + "巡检工作流程", # patrol workflow + "异常处理", # anomaly handling + ], + ) + def test_prompt_has_key_sections(self, section_keyword): + assert section_keyword in PATROL_SYSTEM_PROMPT, ( + f"Section keyword '{section_keyword}' not found in PATROL_SYSTEM_PROMPT" + ) + + def test_prompt_describes_workflow_steps(self): + assert "准备" in PATROL_SYSTEM_PROMPT # preparation + assert "路线规划" in PATROL_SYSTEM_PROMPT # route planning + assert "执行巡检" in PATROL_SYSTEM_PROMPT # execute patrol + assert "监控" in PATROL_SYSTEM_PROMPT # monitoring + assert "汇报" in PATROL_SYSTEM_PROMPT # reporting + + def test_prompt_includes_obstacle_handling(self): + assert "障碍" in PATROL_SYSTEM_PROMPT + + def test_prompt_includes_battery_monitoring(self): + assert "电池" in PATROL_SYSTEM_PROMPT or "电量" in PATROL_SYSTEM_PROMPT + + def test_prompt_includes_slam_reference(self): + lower = PATROL_SYSTEM_PROMPT.lower() + assert "slam" in lower or "定位" in PATROL_SYSTEM_PROMPT