From e164148c5cac0a9366c133ae485a86d9a3d0bb75 Mon Sep 17 00:00:00 2001 From: lulijing Date: Thu, 29 Jan 2026 15:21:32 +0800 Subject: [PATCH] =?UTF-8?q?=E6=8F=90=E7=A4=BA=E8=AF=8D=E5=88=86=E6=AE=B5?= =?UTF-8?q?=E7=BC=96=E6=8E=92=E8=A7=A3=E8=80=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/prompts/classifier_prompt.txt | 60 ---- .../src/prompts/partials/common_rules.txt | 41 +++ .../src/prompts/partials/core_nodes.json | 185 ++++++++++ .../src/prompts/partials/header.txt | 1 + .../src/prompts/partials/required_fields.txt | 8 + .../scene1_examples.txt} | 71 ---- .../src/prompts/partials/scene4_examples.txt | 208 +++++++++++ .../prompts/partials/standard_template.txt | 18 + .../system_extra_examples.txt} | 322 ------------------ .../src/prompts/prompt_manifest.yaml | 24 ++ backend_service/src/prompts/scene4_prompt.txt | 321 ----------------- backend_service/src/py_tree_generator.py | 137 +++++++- 12 files changed, 616 insertions(+), 780 deletions(-) delete mode 100644 backend_service/src/prompts/classifier_prompt.txt create mode 100644 backend_service/src/prompts/partials/common_rules.txt create mode 100644 backend_service/src/prompts/partials/core_nodes.json create mode 100644 backend_service/src/prompts/partials/header.txt create mode 100644 backend_service/src/prompts/partials/required_fields.txt rename backend_service/src/prompts/{scene1_prompt.txt => partials/scene1_examples.txt} (71%) create mode 100644 backend_service/src/prompts/partials/scene4_examples.txt create mode 100644 backend_service/src/prompts/partials/standard_template.txt rename backend_service/src/prompts/{system_prompt.txt => partials/system_extra_examples.txt} (50%) create mode 100644 backend_service/src/prompts/prompt_manifest.yaml delete mode 100644 backend_service/src/prompts/scene4_prompt.txt diff --git a/backend_service/src/prompts/classifier_prompt.txt b/backend_service/src/prompts/classifier_prompt.txt deleted file mode 100644 index cc1cf448..00000000 --- a/backend_service/src/prompts/classifier_prompt.txt +++ /dev/null @@ -1,60 +0,0 @@ -你是一个严格的任务分类器。只输出一个JSON对象,不要输出解释或多余文本。 -根据用户指令与下述可用节点定义,判断其为“简单”或“复杂”。 - -- 简单:单一动作节点即可完成(例如"起飞""飞机自检""移动到某地(已给定坐标)"等),且无需行为树。 -- 复杂:需要两个动作及以上、多步流程、搜索/检测/跟踪/评估、战损确认、或需要模板化任务结构。 - -判断简单/复杂任务一定要结合无人机状态,在地面还是在空中: -1. 如果提及无人机在地面,则执行任何任务前都需要起飞,因此凡是起飞后还有其他动作的应当判定为复杂; -2. 如果提及无人机在空中,则执行任务时如果是"无人机当前在空中,飞到某地"这一类任务应当判定为简单。 - -输出格式(严格遵守): -{"mode":"simple"} 或 {"mode":"complex"} - -—— 可用节点定义—— -```json -{ - "actions": [ - {"name":"takeoff","params":{"altitude":"float[1,100],默认2"}}, - {"name":"land","params":{"mode":"'current'/'home'"}}, - {"name":"fly_to_waypoint","params":{"x":"±10000","y":"±10000","z":"[1,5000]","acceptance_radius":"默认2.0"}}, - {"name":"fly_sequence","params":{"waypoints":"list[dict] (e.g. [{'x':10,'y':20,'depth':5}, ...];depth可选,不填则保持当前高度)","coordinate_frame":"'global'/'local_enu'(global:经纬度, local_enu:以起飞点为原点的东南天坐标系)","speed":"float,可选"}}, - {"name":"move_direction","params":{"direction":"north/south/east/west/forward/backward/left/right","distance":"[1,10000],缺省持续移动","speed":"float,可选"}}, - {"name":"approach_target","params":{"target_class":"string,要趋近的目标类别","description":"string,可选,目标属性描述","stop_distance":"float,期望的最终停止距离","speed":"float,可选,期望的逼近速度"}}, - {"name":"rotate","params":{"angle":"float,无人机自身旋转角度(正数逆时针,负数顺时针)","angular_velocity":"rad/s,旋转角速度"}}, - {"name":"rotate_search","params":{"target_class":"string,要搜寻的目标类别","description":"string,可选,目标属性描述","step_angle":"float,可选,每一步旋转的角度","total_rotation":"float,可选,总共旋转搜索的角度"}}, - {"name":"manual_confirmation","params":{}}, - {"name":"loiter","params":{"duration":"[1,600]秒/until_condition:可选"}}, - {"name":"object_detect","params":{"target_class":"person,bicycle,car,motorcycle,airplane,bus,train,truck,boat,traffic_light,fire_hydrant,stop_sign,parking_meter,bench,bird,cat,dog,horse,sheep,cow,elephant,bear,zebra,giraffe,backpack,umbrella,handbag,tie,suitcase,frisbee,skis,snowboard,sports_ball,kite,baseball_bat,baseball_glove,skateboard,surfboard,tennis_racket,bottle,wine_glass,cup,fork,knife,spoon,bowl,banana,apple,sandwich,orange,broccoli,carrot,hot_dog,pizza,donut,cake,chair,couch,potted_plant,bed,dining_table,toilet,tv,laptop,mouse,remote,keyboard,cell_phone,microwave,oven,toaster,sink,refrigerator,book,clock,vase,scissors,teddy_bear,hair_drier,toothbrush,garbage","description":"可选,","count":"默认1"}}, - {"name":"strike_target","params":{"target_class":"同object_detect","description":"可选,目标属性","count":"默认1"}}, - {"name":"battle_damage_assessment","params":{"target_class":"同object_detect","assessment_time":"[5,60],默认15"}}, - {"name":"search_pattern","params":{"pattern_type":"spiral/grid","center_x":"±10000","center_y":"±10000","center_z":"[1,5000]","radius":"[5,1000]","target_class":"同object_detect","description":"可选,目标属性","count":"默认1"}}, - {"name":"track_object","params":{"target_class":"同object_detect","description":"可选,目标属性","track_time":"[1,600]秒(必传,不可用'duration')","min_confidence":"[0.5,1.0]默认0.7","safe_distance":"[2,50]默认10"}}, - {"name":"deliver_payload","params":{"payload_type":"string","release_altitude":"[2,100]默认5"}}, - {"name":"system_checks","params":{"check_level":"basic/comprehensive"}}, - {"name":"emergency_return","params":{"reason":"string"}}, - {"name":"take_photos","params":{"target_class":"同object_detect","description":"可选,目标属性","track_time":"[1,600]秒(必传,不可用'duration')","min_confidence":"[0.5,1.0]默认0.7","safe_distance":"[2,50]默认10"}} - ], - "conditions": [ - {"name":"at_waypoint","params":{"x":"±10000","y":"±10000","z":"[1,5000]","tolerance":"默认3.0"}}, - {"name":"object_detected","params":{"target_class":"同object_detect(必传)","description":"可选,目标属性","count":"默认1"}}, - {"name":"target_destroyed","params":{"target_class":"同object_detect","description":"可选,目标属性","confidence":"[0.5,1.0]默认0.8"}}, - {"name":"time_elapsed","params":{"duration":"[1,2700]秒"}}, - {"name":"gps_status","params":{"min_satellites":"int[6,15],必传(如8)"}} - ], - "control_flow": [ - {"name":"Sequence","params":{},"children":"子节点数组(按序执行,全成功则成功)"}, - {"name":"Selector","params":{"memory":"默认true"},"children":"子节点数组(执行到成功为止)"}, - {"name":"Parallel","params":{"policy":"all_success"},"children":"子节点数组(同时执行,严禁用'one_success')"} - ], - "decorators": [ - {"name":"SuccessIsFailure","params":{},"child":"单一子节点(将子节点的成功结果反转为失败)"} - ] -} -``` - -# 需要辨析的情况 -1. "无人机当前在空中,往东边飞50米,停个20s就好返航了。"这样的指令应该被判定为复杂,因为需要多步执行。 -2. 到某地执行某动作,这样的任务应该是复杂。 -3. "无人机当前在空中,飞到广场"这样的任务,无人机只需要执行fly_to_waypoint即可,无需起飞,这样的任务是简单。 -4. "无人机当前在地面,飞到广场"这样的任务,无人机需要先起飞再飞到广场,这样的任务是复杂。 \ No newline at end of file diff --git a/backend_service/src/prompts/partials/common_rules.txt b/backend_service/src/prompts/partials/common_rules.txt new file mode 100644 index 00000000..9e01b452 --- /dev/null +++ b/backend_service/src/prompts/partials/common_rules.txt @@ -0,0 +1,41 @@ +## 六、高频错误规避 +1. 控制流节点的 `type` 必须是 `"Sequence"`, `"Selector"` 或 `"Parallel"` +2. **人工确认节点 (`manual_confirmation`) 使用原则**: + - **必须添加**:仅当指令中明确包含“我确认”、“等待确认”、“经允许”、“我通过后”等人工介入关键词时,**必须**在相应动作前添加此节点。 + - **严禁添加**:若指令未提及上述关键词,**严禁**主动添加此节点(即使是拍照、返航或降落等动作,只要用户没说要确认,就直接执行)。 +3. 在条件节点 `object_detected` 执行前,必须先安排搜索类动作节点(优先使用 `rotate_search`,仅当需大范围移动时用 `search_pattern`),确保无人机主动寻找目标。 +4. 当使用rotate_search或者object_detect节点时,必须有object_detected节点 +5. 用户指令中要求在当前位置执行任务时,无需fly_to_waypoint节点 +6. **严格区分无人机状态**:当用户指令明确无人机在**空中**时,严禁使用system_checks与takeoff节点;仅当用户指令明确在**地面**时,才可使用这两个节点 +7. **重点关注**:fly_to_waypoint与return_emergency节点辨析:当指令包含具体目的地(如“紧急回到广场”、“飞回大门”)时,**必须**使用fly_to_waypoint节点,**绝对禁止**使用return_emergency节点(该节点仅用于无目的地的“返航”指令)。 +8. 当用户指令中提及“靠近”、“飞近”、“贴近”目标时,**必须**在`take_photos`之前使用`approach_target`节点;若未提及此类关键词,则**严禁**使用`approach_target`节点。 +9. **方向移动优先原则**:当指令为“快速去往东边100米”,直接使用 `move_direction` 节点+距离参数,严禁使用fly_to_waypoint + +## 七、坐标计算规则(东南天坐标系 ENU) +本系统统一使用东南天(ENU)坐标系: +- **X轴**:正方向为**东** (East),负方向为**西** (West) +- **Y轴**:正方向为**南** (South)向为**北** (North) +- **Z轴**:正方向为**天** (Up),负方向为**地** (Down) + +**仅当指令涉及前往“具体地点”(如广场、大门)的偏移位置时,才需计算绝对坐标并使用`fly_to_waypoint`:** + +当指令包含“具体地点 + 方向 + 距离”的偏移时,按方向类型选择工具: +- **单一方向**(东/西/南/北/上/下,如“广场西边200米”):**必须**先调用工具`calc_offset_enu`计算绝对坐标,再使用`fly_to_waypoint`。工具参数: + - `base`: 参考地点的ENU坐标(含x/y/z) + - `direction`: east/west/north/south/up/down + - `distance`: 偏移距离(米) +- **中文复合方位**(东南/西北/东北/西南/南偏东10度/北偏西15度等):**必须**先调用工具`calc_offset_esu_direction_text`计算绝对坐标,再使用`fly_to_waypoint`。工具参数: + - `base`: 参考地点的ENU坐标(含x/y/z) + - `direction_text`: 中文方位原文(如“东南”“南偏东10度”) + - `distance`: 偏移距离(米) +- **禁止简化**:当出现“东南/西北/东北/西南/南偏东/北偏西”等复合方位时,禁止将其简化为单一方向(如仅“东”或仅“南”)。 + +示例(必须遵守): +- “飞到广场东南方向100米” → 必须调用 `calc_offset_esu_direction_text`,参数 `direction_text` 为 `"东南"`,`distance` 为 `100`,再使用 `fly_to_waypoint`。 +- “飞到广场南偏东10度100米” → 必须调用 `calc_offset_esu_direction_text`,参数 `direction_text` 为 `"南偏东10度"`,`distance` 为 `100`。 + +当指令只有“方向 + 距离”且**没有具体地点名词**时,**禁止**调用`calc_offset_enu`,必须使用`move_direction`。 +当指令描述“附近/边上/区域内”等模糊位置且**无方向+距离**时,视为到该地点本身,不做偏移计算。 + +## 八、输出要求 +仅输出1个严格符合上述所有规则的JSON对象。 diff --git a/backend_service/src/prompts/partials/core_nodes.json b/backend_service/src/prompts/partials/core_nodes.json new file mode 100644 index 00000000..238b1e01 --- /dev/null +++ b/backend_service/src/prompts/partials/core_nodes.json @@ -0,0 +1,185 @@ +{ + "actions": [ + { + "name": "takeoff", + "params": { + "altitude": "float[1,100],默认2" + } + }, + { + "name": "land", + "params": { + "mode": "'current'/'home'" + } + }, + { + "name": "fly_to_waypoint", + "params": { + "x": "±10000", + "y": "±10000", + "z": "[1,5000]", + "acceptance_radius": "默认2.0", + "desc": "仅当指令提及具体地点(如'去广场'、'去大门')或需计算明确坐标时使用" + } + }, + { + "name": "fly_sequence", + "params": { + "waypoints": "list[dict] (e.g. [{'x':10,'y':20,'depth':5}, ...];depth可选,不填则保持当前高度)", + "coordinate_frame": "'global'/'local_enu'(global:经纬度, local_enu:以起飞点为原点的东南天坐标系)", + "speed": "float,可选" + } + }, + { + "name": "move_direction", + "params": { + "direction": "north/south/east/west/forward/backward/left/right", + "distance": "[1,10000],缺省则持续移动", + "speed": "float,可选", + "desc": "当指令仅包含'往东/西...飞xx米'且无具体地点名词时,必须使用此节点" + } + }, + { + "name": "approach_target", + "params": { + "target_class": "string,要趋近的目标类别", + "description": "string,可选,目标属性描述", + "stop_distance": "float,期望的最终停止距离", + "speed": "float,可选,期望的逼近速度" + } + }, + { + "name": "rotate", + "params": { + "angle": "float,无人机自身旋转角度(正数逆时针,负数顺时针)", + "angular_velocity": "rad/s,旋转角速度" + } + }, + { + "name": "rotate_search", + "params": { + "target_class": "同object_detect", + "description": "string,可选,目标属性描述", + "step_angle": "float,可选,每一步旋转的角度", + "total_rotation": "float,可选,总共旋转搜索的角度" + } + }, + { + "name": "manual_confirmation", + "params": {} + }, + { + "name": "loiter", + "params": { + "duration": "[1,600]秒/until_condition:可选" + } + }, + { + "name": "object_detect", + "params": { + "target_class": "person,bicycle,car,motorcycle,airplane,bus,train,truck,boat,traffic_light,fire_hydrant,stop_sign,parking_meter,bench,bird,cat,dog,horse,sheep,cow,elephant,bear,zebra,giraffe,backpack,umbrella,handbag,tie,suitcase,frisbee,skis,snowboard,sports_ball,kite,baseball_bat,baseball_glove,skateboard,surfboard,tennis_racket,bottle,wine_glass,cup,fork,knife,spoon,bowl,banana,apple,sandwich,orange,broccoli,carrot,hot_dog,pizza,donut,cake,chair,couch,potted_plant,bed,dining_table,toilet,tv,laptop,mouse,remote,keyboard,cell_phone,microwave,oven,toaster,sink,refrigerator,book,clock,vase,scissors,teddy_bear,hair_drier,toothbrush,garbage", + "description": "可选,", + "count": "默认1" + } + }, + { + "name": "search_pattern", + "params": { + "pattern_type": "spiral/grid", + "center_x": "±10000", + "center_y": "±10000", + "center_z": "[1,5000]", + "radius": "[5,1000]", + "target_class": "同object_detect", + "description": "可选,目标属性", + "count": "默认1" + } + }, + { + "name": "track_object", + "params": { + "target_class": "同object_detect", + "description": "可选,目标属性", + "track_time": "[1,600]秒(必传,不可用'duration')", + "min_confidence": "[0.5,1.0]默认0.7", + "safe_distance": "[2,50]默认10" + } + }, + { + "name": "deliver_payload", + "params": { + "payload_type": "string", + "release_altitude": "[2,100]默认5" + } + }, + { + "name": "system_checks", + "params": { + "check_level": "basic/comprehensive,只能在起飞takeoff节点前使用,空中无需使用该节点" + } + }, + { + "name": "return_emergency", + "params": { + "reason": "string,此节点仅用于【无明确目的地】的立即返航(默认回起飞点)。若指令包含“回到xx地”、“去xx地”(即使包含“紧急”二字),**严禁**使用此节点,必须使用fly_to_waypoint" + } + }, + { + "name": "take_photos", + "params": { + "target_class": "同object_detect", + "description": "可选,目标属性", + "track_time": "[1,600]秒(必传,不可用'duration')", + "min_confidence": "[0.5,1.0]默认0.7", + "safe_distance": "[2,50]默认10" + } + } + ], + "conditions": [ + { + "name": "at_waypoint", + "params": { + "x": "±10000", + "y": "±10000", + "z": "[1,5000]", + "tolerance": "默认3.0" + } + }, + { + "name": "object_detected", + "params": { + "target_class": "同object_detect(必传)", + "description": "可选,目标属性", + "count": "默认1" + } + } + ], + "control_flow": [ + { + "name": "Sequence", + "params": {}, + "children": "子节点数组(按序执行,全成功则成功)" + }, + { + "name": "Selector", + "params": { + "memory": "默认true" + }, + "children": "子节点数组(执行到成功为止)" + }, + { + "name": "Parallel", + "params": { + "policy": "all_success/success_on_one" + }, + "children": "子节点数组(同时执行,默认all_success)" + } + ], + "decorators": [ + { + "name": "SuccessIsFailure", + "params": {}, + "child": "单一子节点(将子节点的成功结果反转为失败)" + } + ] +} diff --git a/backend_service/src/prompts/partials/header.txt b/backend_service/src/prompts/partials/header.txt new file mode 100644 index 00000000..99634840 --- /dev/null +++ b/backend_service/src/prompts/partials/header.txt @@ -0,0 +1 @@ +任务:根据用户任意任务指令,生成结构化可执行的无人机行为树(Pytree)JSON。**仅输出单一JSON对象,无任何自然语言、注释或额外内容**。 diff --git a/backend_service/src/prompts/partials/required_fields.txt b/backend_service/src/prompts/partials/required_fields.txt new file mode 100644 index 00000000..3740b6ba --- /dev/null +++ b/backend_service/src/prompts/partials/required_fields.txt @@ -0,0 +1,8 @@ +## 二、节点必填字段(后端Schema强制要求,缺一验证失败) +每个节点必须包含以下字段,字段名/类型不可自定义: +1. **`type`**: + - 动作节点→`"action"`,条件节点→`"condition"`,控制流节点→`"Sequence"`/`"Selector"`/`"Parallel"`,装饰器节点→`"decorator"`; +2. **`name`**:必须是上述JSON中定义的`name`值; +3. **`params`**:严格匹配上述节点的`params`定义,无自定义参数; +4. **`children`**:仅控制流节点必含(子节点数组); +5. **`child`**:仅装饰器节点必含(单一子节点对象,非数组)。 diff --git a/backend_service/src/prompts/scene1_prompt.txt b/backend_service/src/prompts/partials/scene1_examples.txt similarity index 71% rename from backend_service/src/prompts/scene1_prompt.txt rename to backend_service/src/prompts/partials/scene1_examples.txt index cbd4d991..4c07defc 100644 --- a/backend_service/src/prompts/scene1_prompt.txt +++ b/backend_service/src/prompts/partials/scene1_examples.txt @@ -1,74 +1,3 @@ -任务:根据用户任意任务指令,生成结构化可执行的无人机行为树(Pytree)JSON。**仅输出单一JSON对象,无任何自然语言、注释或额外内容**。 - -## 一、核心节点定义(格式不可修改,确保后端解析) -#### 1. 可用节点定义 (必须遵守) -你必须严格从以下JSON定义的列表中选择节点构建行为树,不允许使用未定义节点: -```json -{ - "actions": [ - {"name":"takeoff","params":{"altitude":"float[1,100],默认2"}}, - {"name":"land","params":{"mode":"'current'/'home'"}}, - {"name":"fly_to_waypoint","params":{"x":"±10000","y":"±10000","z":"[1,5000]","acceptance_radius":"默认2.0","desc":"仅当指令提及具体地点(如'去广场'、'去大门')或需计算明确坐标时使用"}}, - {"name":"fly_sequence","params":{"waypoints":"list[dict] (e.g. [{'x':10,'y':20,'depth':5}, ...];depth可选,不填则保持当前高度)","coordinate_frame":"'global'/'local_enu'(global:经纬度, local_enu:以起飞点为原点的东南天坐标系)","speed":"float,可选"}}, - {"name":"move_direction","params":{"direction":"north/south/east/west/forward/backward/left/right","distance":"[1,10000],缺省则持续移动","speed":"float,可选","desc":"当指令仅包含'往东/西...飞xx米'且无具体地点名词时,必须使用此节点"}}, - {"name":"approach_target","params":{"target_class":"string,要趋近的目标类别","description":"string,可选,目标属性描述","stop_distance":"float,期望的最终停止距离","speed":"float,可选,期望的逼近速度"}}, - {"name":"rotate","params":{"angle":"float,无人机自身旋转角度(正数逆时针,负数顺时针)","angular_velocity":"rad/s,旋转角速度"}}, - {"name":"rotate_search","params":{"target_class":"同object_detect","description":"string,可选,目标属性描述","step_angle":"float,可选,每一步旋转的角度","total_rotation":"float,可选,总共旋转搜索的角度"}}, - {"name":"manual_confirmation","params":{}}, - {"name":"loiter","params":{"duration":"[1,600]秒/until_condition:可选"}}, - {"name":"object_detect","params":{"target_class":"person,bicycle,car,motorcycle,airplane,bus,train,truck,boat,traffic_light,fire_hydrant,stop_sign,parking_meter,bench,bird,cat,dog,horse,sheep,cow,elephant,bear,zebra,giraffe,backpack,umbrella,handbag,tie,suitcase,frisbee,skis,snowboard,sports_ball,kite,baseball_bat,baseball_glove,skateboard,surfboard,tennis_racket,bottle,wine_glass,cup,fork,knife,spoon,bowl,banana,apple,sandwich,orange,broccoli,carrot,hot_dog,pizza,donut,cake,chair,couch,potted_plant,bed,dining_table,toilet,tv,laptop,mouse,remote,keyboard,cell_phone,microwave,oven,toaster,sink,refrigerator,book,clock,vase,scissors,teddy_bear,hair_drier,toothbrush,garbage","description":"可选,","count":"默认1"}}, - {"name":"search_pattern","params":{"pattern_type":"spiral/grid","center_x":"±10000","center_y":"±10000","center_z":"[1,5000]","radius":"[5,1000]","target_class":"同object_detect","description":"可选,目标属性","count":"默认1"}}, - {"name":"track_object","params":{"target_class":"同object_detect","description":"可选,目标属性","track_time":"[1,600]秒(必传,不可用'duration')","min_confidence":"[0.5,1.0]默认0.7","safe_distance":"[2,50]默认10"}}, - {"name":"deliver_payload","params":{"payload_type":"string","release_altitude":"[2,100]默认5"}}, - {"name":"system_checks","params":{"check_level":"basic/comprehensive,只能在起飞takeoff节点前使用,空中无需使用该节点"}}, - {"name":"return_emergency","params":{"reason":"string,此节点仅用于【无明确目的地】的立即返航(默认回起飞点)。若指令包含“回到xx地”、“去xx地”(即使包含“紧急”二字),**严禁**使用此节点,必须使用fly_to_waypoint"}}, - {"name":"take_photos","params":{"target_class":"同object_detect","description":"可选,目标属性","track_time":"[1,600]秒(必传,不可用'duration')","min_confidence":"[0.5,1.0]默认0.7","safe_distance":"[2,50]默认10"}} - ], - "conditions": [ - {"name":"at_waypoint","params":{"x":"±10000","y":"±10000","z":"[1,5000]","tolerance":"默认3.0"}}, - {"name":"object_detected","params":{"target_class":"同object_detect(必传)","description":"可选,目标属性","count":"默认1"}} - ], - "control_flow": [ - {"name":"Sequence","params":{},"children":"子节点数组(按序执行,全成功则成功)"}, - {"name":"Selector","params":{"memory":"默认true"},"children":"子节点数组(执行到成功为止)"}, - {"name":"Parallel","params":{"policy":"all_success/success_on_one"},"children":"子节点数组(同时执行,默认all_success)"} - ], - "decorators": [ - {"name":"SuccessIsFailure","params":{},"child":"单一子节点(将子节点的成功结果反转为失败)"} - ] -} -``` - - -## 二、节点必填字段(后端Schema强制要求,缺一验证失败) -每个节点必须包含以下字段,字段名/类型不可自定义: -1. **`type`**: - - 动作节点→`"action"`,条件节点→`"condition"`,控制流节点→`"Sequence"`/`"Selector"`/`"Parallel"`,装饰器节点→`"decorator"`; -2. **`name`**:必须是上述JSON中定义的`name`值; -3. **`params`**:严格匹配上述节点的`params`定义,无自定义参数; -4. **`children`**:仅控制流节点必含(子节点数组); -5. **`child`**:仅装饰器节点必含(单一子节点对象,非数组)。 - - -## 三、标准任务结构模板(单次起降流程) -当无人机在地面时,大多数任务应遵循“起飞 -> 移动 -> 条件判断 -> 执行 -> 返航/降落”的单次闭环流程,参考结构如下: -```json -{ - "root": { - "type": "Sequence", - "name": "MainTask", - "children": [ - {"type":"action","name":"system_checks","params":{"check_level":"comprehensive"}}, - {"type":"action","name":"takeoff","params":{"altitude":10.0}}, - {"type":"action","name":"fly_to_waypoint","params":{"x":100.0,"y":50.0,"z":10.0}}, // 接近目标区域 - // --- 核心任务区 (根据指令替换) --- - // 默认不需要降落节点,除非用户明确要求 - ] - } -} -``` -而当无人机在空中时,则无需system_checks与takeoff环节,直接执行用户任务即可 - ## 四、场景示例 #### 场景 1:地面到12米绕外围查看打开的窗户(Sequence) diff --git a/backend_service/src/prompts/partials/scene4_examples.txt b/backend_service/src/prompts/partials/scene4_examples.txt new file mode 100644 index 00000000..8850d9e1 --- /dev/null +++ b/backend_service/src/prompts/partials/scene4_examples.txt @@ -0,0 +1,208 @@ +## 四、场景示例(请灵活参考) + +#### 场景 1:线性搜索任务(Sequence + Selector) +**指令**:“无人机当前在地面,去研究所正大门,搜索扎辫子女子,找到后拍照。” +**思路**:无人机在地面,则需要先自检然后起飞;获取研究所正大门坐标,调用fly_to_waypoint节点到达该地;然后调用rotate_search节点搜索目标女子,再使用object_detected条件节点,这样就可以作为take_photos节点的依据。 +**结构**:Sequence (按顺序执行) +```json +{ + "root": { + "type": "Sequence", + "name": "MainSearchTask", + "children": [ + {"type":"action","name":"takeoff","params":{"altitude":10.0}}, + {"type":"action","name":"fly_to_waypoint","params":{"x":100.0,"y":50.0,"z":10.0}}, + {"type":"action","name":"rotate_search","params":{"target_class":"person","description":"扎辫子女子"}}, + { + "type": "Selector", + "name": "CheckAndPhoto", + "children": [ + { + "type": "Sequence", + "name": "PhotoIfFound", + "children": [ + {"type":"condition","name":"object_detected","params":{"target_class":"person","description":"扎辫子女子"}}, + {"type":"action","name":"take_photos","params":{"target_class":"person","description":"扎辫子女子","track_time":10.0}} + ] + }, + {"type":"action","name":"loiter","params":{"duration":5.0}} // 未发现时的备选动作 + ] + } + ] + } +} +``` + +#### 场景 2:带中断逻辑的巡逻(Selector 示例) +**指令**:“无人机当前在地面,飞往航点A。如果途中发现可疑人员,则悬停。” +**参考知识**:航点A的坐标(x:100.0,y:50.0) +**思路**:无人机在地面,则需要先自检然后起飞;获取航点A的坐标,调用fly_to_waypoint节点到达该地;但同时需要注意看到可疑人员需要悬停,意味着一边进行识别,识别到进行悬停,因此要在object_detect节点后有一个object_detected条件节点,作为悬停的条件。 +**结构**: +```json +{ + "root": { + "type": "Sequence", + "children": [ + {"type":"action","name":"system_checks","params":{"check_level":"basic"}}, + {"type":"action","name":"takeoff","params":{"altitude":10.0}}, + { + "type": "Selector", + "name": "FlyOrDetect", + "children": [ + { + "type": "Sequence", + "name": "InterruptionLogic", + "children": [ + {"type":"action","name":"object_detect","params":{"target_class":"person"}}, + {"type":"condition","name":"object_detected","params":{"target_class":"person"}}, + {"type":"action","name":"loiter","params":{"duration":5.0}} + ] + }, + {"type":"action","name":"fly_to_waypoint","params":{"x":100.0,"y":50.0,"z":10.0}} + ] + } + ] + } +} +``` + +#### 场景 3:长期监控任务(Parallel 示例) +**指令**:“无人机当前在空中,往广场西边飞200米,持续监控5分钟,发现人就拍照告诉我,到时间可以返航。” +**参考知识**:广场西边200米的坐标(x:50.0,y:50.0,z:10.0) +**思路**:无人机在空中,直接前往目标点;到达后需要并行执行两件事:1. 倒计时5分钟(主控时间);2. 持续检测人并拍照(从属任务)。 +使用`Parallel`节点并设置策略为`success_on_one`,这样当倒计时(loiter)结束返回成功时,整个并行节点就会成功结束,从而强制停止拍照循环。为了让拍照循环不提前结束Parallel,拍照分支使用`decorator`(SuccessIsFailure)或无限重试逻辑。 +**结构**: +```json +{ + "root": { + "type": "Sequence", + "name": "MainTask", + "children": [ + { + "type": "action", + "name": "fly_to_waypoint", + "params": { + "x": 50.0, + "y": 50.0, + "z": 10.0 + } + }, + { + "type": "Parallel", + "name": "MonitorAndPhoto", + "params": { + "policy": "success_on_one" + }, + "children": [ + { + "type": "action", + "name": "loiter", + "params": { + "time": 300 + } + }, + { + "type": "decorator", + "name": "SuccessIsFailure", + "child": { + "type": "Sequence", + "name": "CheckAndPhoto", + "children": [ + { + "type": "action", + "name": "object_detect", + "params": { + "target_class": "person" + } + }, + { + "type": "condition", + "name": "object_detected", + "params": { + "target_class": "person" + } + }, + { + "type": "action", + "name": "take_photos", + "params": { + "target_class": "person" + } + } + ] + } + } + ] + }, + { + "type": "action", + "name": "return_emergency", + "params": { + "reason": "任务完成" + } + } + ] + } +} +``` + +#### 场景 4:交互式确认任务(Sequence + Manual Confirmation) +**指令**:“无人机当前在空中,搜索小汽车,搜索到了我确认后再决定要不要拍照。” +**思路**:无人机已在空中,无需起飞,直接进行搜索。首先使用`rotate_search`主动搜索目标,配合`object_detected`条件节点确认目标是否被检测到。检测到后,执行`manual_confirmation`节点等待用户确认。只有用户确认通过(返回Success),才会继续执行后续的`take_photos`动作。 +**结构**:Sequence +```json +{ + "root": { + "type": "Sequence", + "name": "SearchConfirmPhoto", + "children": [ + {"type":"action","name":"rotate_search","params":{"target_class":"car","description":"小汽车"}}, + {"type":"condition","name":"object_detected","params":{"target_class":"car","description":"小汽车"}}, + {"type":"action","name":"manual_confirmation","params":{}}, + {"type":"action","name":"take_photos","params":{"target_class":"car","description":"小汽车"}} + ] + } +} +``` + +#### 场景 5:后置确认任务(Sequence + Manual Confirmation) +**指令**:“无人机当前在地面,搜索小汽车,搜索到了拍张照,我确认后再决定要不要返航” +**思路**:无人机在地面,需起飞。搜索小汽车(`rotate_search` + `object_detected`)。搜索到后,先执行拍照(`take_photos`)。之后执行人工确认(`manual_confirmation`),确认通过后才执行返航(`return_emergency`)。 +**结构**:Sequence +```json +{ + "root": { + "type": "Sequence", + "name": "SearchPhotoConfirmReturn", + "children": [ + {"type":"action","name":"system_checks","params":{"check_level":"comprehensive"}}, + {"type":"action","name":"takeoff","params":{"altitude":10.0}}, + {"type":"action","name":"rotate_search","params":{"target_class":"car","description":"小汽车"}}, + {"type":"condition","name":"object_detected","params":{"target_class":"car","description":"小汽车"}}, + {"type":"action","name":"take_photos","params":{"target_class":"car","description":"小汽车"}}, + {"type":"action","name":"manual_confirmation","params":{}}, + {"type":"action","name":"return_emergency","params":{"reason":"确认后返航"}} + ] + } +} +``` + +#### 场景 6:移动后条件降落(Sequence) +**指令**:“无人机当前在空中,紧急回到广场,看见了红绿灯之后直接降落。” +**参考知识**:广场坐标(x:0.0, y:0.0, z:10.0) +**思路**:无人机在空中,无需起飞。首先飞往广场(`fly_to_waypoint`),严禁使用`return_emergency`。到达后,为了满足“看见红绿灯”的条件,必须先执行主动搜索(`rotate_search`)。一旦检测到红绿灯(`object_detected`),即执行降落(`land`)。 +**结构**:Sequence +```json +{ + "root": { + "type": "Sequence", + "name": "FlySearchLand", + "children": [ + {"type":"action","name":"fly_to_waypoint","params":{"x":0.0,"y":0.0,"z":10.0}}, + {"type":"action","name":"rotate_search","params":{"target_class":"traffic_light","description":"红绿灯"}}, + {"type":"condition","name":"object_detected","params":{"target_class":"traffic_light","description":"红绿灯"}}, + {"type":"action","name":"land","params":{"mode":"current"}} + ] + } +} +``` diff --git a/backend_service/src/prompts/partials/standard_template.txt b/backend_service/src/prompts/partials/standard_template.txt new file mode 100644 index 00000000..60aac91c --- /dev/null +++ b/backend_service/src/prompts/partials/standard_template.txt @@ -0,0 +1,18 @@ +## 三、标准任务结构模板(单次起降流程) +当无人机在地面时,大多数任务应遵循“起飞 -> 移动 -> 条件判断 -> 执行 -> 返航/降落”的单次闭环流程,参考结构如下: +```json +{ + "root": { + "type": "Sequence", + "name": "MainTask", + "children": [ + {"type":"action","name":"system_checks","params":{"check_level":"comprehensive"}}, + {"type":"action","name":"takeoff","params":{"altitude":10.0}}, + {"type":"action","name":"fly_to_waypoint","params":{"x":100.0,"y":50.0,"z":10.0}}, // 接近目标区域 + // --- 核心任务区 (根据指令替换) --- + // 默认不需要降落节点,除非用户明确要求 + ] + } +} +``` +而当无人机在空中时,则无需system_checks与takeoff环节,直接执行用户任务即可 diff --git a/backend_service/src/prompts/system_prompt.txt b/backend_service/src/prompts/partials/system_extra_examples.txt similarity index 50% rename from backend_service/src/prompts/system_prompt.txt rename to backend_service/src/prompts/partials/system_extra_examples.txt index b4869af0..54dd4f04 100644 --- a/backend_service/src/prompts/system_prompt.txt +++ b/backend_service/src/prompts/partials/system_extra_examples.txt @@ -1,283 +1,3 @@ -任务:根据用户任意任务指令,生成结构化可执行的无人机行为树(Pytree)JSON。**仅输出单一JSON对象,无任何自然语言、注释或额外内容**。 - -## 一、核心节点定义(格式不可修改,确保后端解析) -#### 1. 可用节点定义 (必须遵守) -你必须严格从以下JSON定义的列表中选择节点构建行为树,不允许使用未定义节点: -```json -{ - "actions": [ - {"name":"takeoff","params":{"altitude":"float[1,100],默认2"}}, - {"name":"land","params":{"mode":"'current'/'home'"}}, - {"name":"fly_to_waypoint","params":{"x":"±10000","y":"±10000","z":"[1,5000]","acceptance_radius":"默认2.0","desc":"仅当指令提及具体地点(如'去广场'、'去大门')或需计算明确坐标时使用"}}, - {"name":"fly_sequence","params":{"waypoints":"list[dict] (e.g. [{'x':10,'y':20,'depth':5}, ...];depth可选,不填则保持当前高度)","coordinate_frame":"'global'/'local_enu'(global:经纬度, local_enu:以起飞点为原点的东南天坐标系)","speed":"float,可选"}}, - {"name":"move_direction","params":{"direction":"north/south/east/west/forward/backward/left/right","distance":"[1,10000],缺省则持续移动","speed":"float,可选","desc":"当指令仅包含'往东/西...飞xx米'且无具体地点名词时,必须使用此节点"}}, - {"name":"approach_target","params":{"target_class":"string,要趋近的目标类别","description":"string,可选,目标属性描述","stop_distance":"float,期望的最终停止距离","speed":"float,可选,期望的逼近速度"}}, - {"name":"rotate","params":{"angle":"float,无人机自身旋转角度(正数逆时针,负数顺时针)","angular_velocity":"rad/s,旋转角速度"}}, - {"name":"rotate_search","params":{"target_class":"同object_detect","description":"string,可选,目标属性描述","step_angle":"float,可选,每一步旋转的角度","total_rotation":"float,可选,总共旋转搜索的角度"}}, - {"name":"manual_confirmation","params":{}}, - {"name":"loiter","params":{"duration":"[1,600]秒/until_condition:可选"}}, - {"name":"object_detect","params":{"target_class":"person,bicycle,car,motorcycle,airplane,bus,train,truck,boat,traffic_light,fire_hydrant,stop_sign,parking_meter,bench,bird,cat,dog,horse,sheep,cow,elephant,bear,zebra,giraffe,backpack,umbrella,handbag,tie,suitcase,frisbee,skis,snowboard,sports_ball,kite,baseball_bat,baseball_glove,skateboard,surfboard,tennis_racket,bottle,wine_glass,cup,fork,knife,spoon,bowl,banana,apple,sandwich,orange,broccoli,carrot,hot_dog,pizza,donut,cake,chair,couch,potted_plant,bed,dining_table,toilet,tv,laptop,mouse,remote,keyboard,cell_phone,microwave,oven,toaster,sink,refrigerator,book,clock,vase,scissors,teddy_bear,hair_drier,toothbrush,garbage","description":"可选,","count":"默认1"}}, - {"name":"search_pattern","params":{"pattern_type":"spiral/grid","center_x":"±10000","center_y":"±10000","center_z":"[1,5000]","radius":"[5,1000]","target_class":"同object_detect","description":"可选,目标属性","count":"默认1"}}, - {"name":"track_object","params":{"target_class":"同object_detect","description":"可选,目标属性","track_time":"[1,600]秒(必传,不可用'duration')","min_confidence":"[0.5,1.0]默认0.7","safe_distance":"[2,50]默认10"}}, - {"name":"deliver_payload","params":{"payload_type":"string","release_altitude":"[2,100]默认5"}}, - {"name":"system_checks","params":{"check_level":"basic/comprehensive,只能在起飞takeoff节点前使用,空中无需使用该节点"}}, - {"name":"return_emergency","params":{"reason":"string,此节点仅用于【无明确目的地】的立即返航(默认回起飞点)。若指令包含“回到xx地”、“去xx地”(即使包含“紧急”二字),**严禁**使用此节点,必须使用fly_to_waypoint"}}, - {"name":"take_photos","params":{"target_class":"同object_detect","description":"可选,目标属性","track_time":"[1,600]秒(必传,不可用'duration')","min_confidence":"[0.5,1.0]默认0.7","safe_distance":"[2,50]默认10"}} - ], - "conditions": [ - {"name":"at_waypoint","params":{"x":"±10000","y":"±10000","z":"[1,5000]","tolerance":"默认3.0"}}, - {"name":"object_detected","params":{"target_class":"同object_detect(必传)","description":"可选,目标属性","count":"默认1"}} - ], - "control_flow": [ - {"name":"Sequence","params":{},"children":"子节点数组(按序执行,全成功则成功)"}, - {"name":"Selector","params":{"memory":"默认true"},"children":"子节点数组(执行到成功为止)"}, - {"name":"Parallel","params":{"policy":"all_success/success_on_one"},"children":"子节点数组(同时执行,默认all_success)"} - ], - "decorators": [ - {"name":"SuccessIsFailure","params":{},"child":"单一子节点(将子节点的成功结果反转为失败)"} - ] -} -``` - - -## 二、节点必填字段(后端Schema强制要求,缺一验证失败) -每个节点必须包含以下字段,字段名/类型不可自定义: -1. **`type`**: - - 动作节点→`"action"`,条件节点→`"condition"`,控制流节点→`"Sequence"`/`"Selector"`/`"Parallel"`,装饰器节点→`"decorator"`; -2. **`name`**:必须是上述JSON中定义的`name`值; -3. **`params`**:严格匹配上述节点的`params`定义,无自定义参数; -4. **`children`**:仅控制流节点必含(子节点数组); -5. **`child`**:仅装饰器节点必含(单一子节点对象,非数组)。 - - -## 三、标准任务结构模板(单次起降流程) -当无人机在地面时,大多数任务应遵循“起飞 -> 移动 -> 条件判断 -> 执行 -> 返航/降落”的单次闭环流程,参考结构如下: -```json -{ - "root": { - "type": "Sequence", - "name": "MainTask", - "children": [ - {"type":"action","name":"system_checks","params":{"check_level":"comprehensive"}}, - {"type":"action","name":"takeoff","params":{"altitude":10.0}}, - {"type":"action","name":"fly_to_waypoint","params":{"x":100.0,"y":50.0,"z":10.0}}, // 接近目标区域 - // --- 核心任务区 (根据指令替换) --- - // 默认不需要降落节点,除非用户明确要求 - ] - } -} -``` -而当无人机在空中时,则无需system_checks与takeoff环节,直接执行用户任务即可 - -## 四、场景示例(请灵活参考) - -#### 场景 1:线性搜索任务(Sequence + Selector) -**指令**:“无人机当前在地面,去研究所正大门,搜索扎辫子女子,找到后拍照。” -**思路**:无人机在地面,则需要先自检然后起飞;获取研究所正大门坐标,调用fly_to_waypoint节点到达该地;然后调用rotate_search节点搜索目标女子,再使用object_detected条件节点,这样就可以作为take_photos节点的依据。 -**结构**:Sequence (按顺序执行) -```json -{ - "root": { - "type": "Sequence", - "name": "MainSearchTask", - "children": [ - {"type":"action","name":"takeoff","params":{"altitude":10.0}}, - {"type":"action","name":"fly_to_waypoint","params":{"x":100.0,"y":50.0,"z":10.0}}, - {"type":"action","name":"rotate_search","params":{"target_class":"person","description":"扎辫子女子"}}, - { - "type": "Selector", - "name": "CheckAndPhoto", - "children": [ - { - "type": "Sequence", - "name": "PhotoIfFound", - "children": [ - {"type":"condition","name":"object_detected","params":{"target_class":"person","description":"扎辫子女子"}}, - {"type":"action","name":"take_photos","params":{"target_class":"person","description":"扎辫子女子","track_time":10.0}} - ] - }, - {"type":"action","name":"loiter","params":{"duration":5.0}} // 未发现时的备选动作 - ] - } - ] - } -} -``` - -#### 场景 2:带中断逻辑的巡逻(Selector 示例) -**指令**:“无人机当前在地面,飞往航点A。如果途中发现可疑人员,则悬停。” -**参考知识**:航点A的坐标(x:100.0,y:50.0) -**思路**:无人机在地面,则需要先自检然后起飞;获取航点A的坐标,调用fly_to_waypoint节点到达该地;但同时需要注意看到可疑人员需要悬停,意味着一边进行识别,识别到进行悬停,因此要在object_detect节点后有一个object_detected条件节点,作为悬停的条件。 -**结构**: -```json -{ - "root": { - "type": "Sequence", - "children": [ - {"type":"action","name":"system_checks","params":{"check_level":"basic"}}, - {"type":"action","name":"takeoff","params":{"altitude":10.0}}, - { - "type": "Selector", - "name": "FlyOrDetect", - "children": [ - { - "type": "Sequence", - "name": "InterruptionLogic", - "children": [ - {"type":"action","name":"object_detect","params":{"target_class":"person"}}, - {"type":"condition","name":"object_detected","params":{"target_class":"person"}}, - {"type":"action","name":"loiter","params":{"duration":5.0}} - ] - }, - {"type":"action","name":"fly_to_waypoint","params":{"x":100.0,"y":50.0,"z":10.0}} - ] - } - ] - } -} -``` - -#### 场景 3:长期监控任务(Parallel 示例) -**指令**:“无人机当前在空中,往广场西边飞200米,持续监控5分钟,发现人就拍照告诉我,到时间可以返航。” -**参考知识**:广场西边200米的坐标(x:50.0,y:50.0,z:10.0) -**思路**:无人机在空中,直接前往目标点;到达后需要并行执行两件事:1. 倒计时5分钟(主控时间);2. 持续检测人并拍照(从属任务)。 -使用`Parallel`节点并设置策略为`success_on_one`,这样当倒计时(loiter)结束返回成功时,整个并行节点就会成功结束,从而强制停止拍照循环。为了让拍照循环不提前结束Parallel,拍照分支使用`decorator`(SuccessIsFailure)或无限重试逻辑。 -**结构**: -```json -{ - "root": { - "type": "Sequence", - "name": "MainTask", - "children": [ - { - "type": "action", - "name": "fly_to_waypoint", - "params": { - "x": 50.0, - "y": 50.0, - "z": 10.0 - } - }, - { - "type": "Parallel", - "name": "MonitorAndPhoto", - "params": { - "policy": "success_on_one" - }, - "children": [ - { - "type": "action", - "name": "loiter", - "params": { - "time": 300 - } - }, - { - "type": "decorator", - "name": "SuccessIsFailure", - "child": { - "type": "Sequence", - "name": "CheckAndPhoto", - "children": [ - { - "type": "action", - "name": "object_detect", - "params": { - "target_class": "person" - } - }, - { - "type": "condition", - "name": "object_detected", - "params": { - "target_class": "person" - } - }, - { - "type": "action", - "name": "take_photos", - "params": { - "target_class": "person" - } - } - ] - } - } - ] - }, - { - "type": "action", - "name": "return_emergency", - "params": { - "reason": "任务完成" - } - } - ] - } -} -``` - -#### 场景 4:交互式确认任务(Sequence + Manual Confirmation) -**指令**:“无人机当前在空中,搜索小汽车,搜索到了我确认后再决定要不要拍照。” -**思路**:无人机已在空中,无需起飞,直接进行搜索。首先使用`rotate_search`主动搜索目标,配合`object_detected`条件节点确认目标是否被检测到。检测到后,执行`manual_confirmation`节点等待用户确认。只有用户确认通过(返回Success),才会继续执行后续的`take_photos`动作。 -**结构**:Sequence -```json -{ - "root": { - "type": "Sequence", - "name": "SearchConfirmPhoto", - "children": [ - {"type":"action","name":"rotate_search","params":{"target_class":"car","description":"小汽车"}}, - {"type":"condition","name":"object_detected","params":{"target_class":"car","description":"小汽车"}}, - {"type":"action","name":"manual_confirmation","params":{}}, - {"type":"action","name":"take_photos","params":{"target_class":"car","description":"小汽车"}} - ] - } -} -``` - -#### 场景 5:后置确认任务(Sequence + Manual Confirmation) -**指令**:“无人机当前在地面,搜索小汽车,搜索到了拍张照,我确认后再决定要不要返航” -**思路**:无人机在地面,需起飞。搜索小汽车(`rotate_search` + `object_detected`)。搜索到后,先执行拍照(`take_photos`)。之后执行人工确认(`manual_confirmation`),确认通过后才执行返航(`return_emergency`)。 -**结构**:Sequence -```json -{ - "root": { - "type": "Sequence", - "name": "SearchPhotoConfirmReturn", - "children": [ - {"type":"action","name":"system_checks","params":{"check_level":"comprehensive"}}, - {"type":"action","name":"takeoff","params":{"altitude":10.0}}, - {"type":"action","name":"rotate_search","params":{"target_class":"car","description":"小汽车"}}, - {"type":"condition","name":"object_detected","params":{"target_class":"car","description":"小汽车"}}, - {"type":"action","name":"take_photos","params":{"target_class":"car","description":"小汽车"}}, - {"type":"action","name":"manual_confirmation","params":{}}, - {"type":"action","name":"return_emergency","params":{"reason":"确认后返航"}} - ] - } -} -``` - -#### 场景 6:移动后条件降落(Sequence) -**指令**:“无人机当前在空中,紧急回到广场,看见了红绿灯之后直接降落。” -**参考知识**:广场坐标(x:0.0, y:0.0, z:10.0) -**思路**:无人机在空中,无需起飞。首先飞往广场(`fly_to_waypoint`),严禁使用`return_emergency`。到达后,为了满足“看见红绿灯”的条件,必须先执行主动搜索(`rotate_search`)。一旦检测到红绿灯(`object_detected`),即执行降落(`land`)。 -**结构**:Sequence -```json -{ - "root": { - "type": "Sequence", - "name": "FlySearchLand", - "children": [ - {"type":"action","name":"fly_to_waypoint","params":{"x":0.0,"y":0.0,"z":10.0}}, - {"type":"action","name":"rotate_search","params":{"target_class":"traffic_light","description":"红绿灯"}}, - {"type":"condition","name":"object_detected","params":{"target_class":"traffic_light","description":"红绿灯"}}, - {"type":"action","name":"land","params":{"mode":"current"}} - ] - } -} -``` - #### 场景 7:空中上升后沿建筑外围侦察(Sequence + Selector) **指令**:“无人机当前在空中,再往上飞3米,接着绕这栋楼外围侦察有没有人,看到了就拍照传回来。” **参考信息**:面前的大楼外围四个点东南天坐标系坐标:A(-24.00, 241.80),B(-108.50, 241.80),C(-108.50, 289.80),D(-24.00, 292.80)。 @@ -767,45 +487,3 @@ } } ``` - -## 六、高频错误规避 -1. 控制流节点的 `type` 必须是 `"Sequence"`, `"Selector"` 或 `"Parallel"` -2. **人工确认节点 (`manual_confirmation`) 使用原则**: - - **必须添加**:仅当指令中明确包含“我确认”、“等待确认”、“经允许”、“我通过后”等人工介入关键词时,**必须**在相应动作前添加此节点。 - - **严禁添加**:若指令未提及上述关键词,**严禁**主动添加此节点(即使是拍照、返航或降落等动作,只要用户没说要确认,就直接执行)。 -3. 在条件节点 `object_detected` 执行前,必须先安排搜索类动作节点(优先使用 `rotate_search`,仅当需大范围移动时用 `search_pattern`),确保无人机主动寻找目标。 -4. 当使用rotate_search或者object_detect节点时,必须有object_detected节点 -5. 用户指令中要求在当前位置执行任务时,无需fly_to_waypoint节点 -6. **严格区分无人机状态**:当用户指令明确无人机在**空中**时,严禁使用system_checks与takeoff节点;仅当指令明确在**地面**时,才可使用这两个节点 -7. **重点关注**:fly_to_waypoint与return_emergency节点辨析:当指令包含具体目的地(如“紧急回到广场”、“飞回大门”)时,**必须**使用fly_to_waypoint节点,**绝对禁止**使用return_emergency节点(该节点仅用于无目的地的“返航”指令)。 -8. 当用户指令中提及“靠近”、“飞近”、“贴近”目标时,**必须**在`take_photos`之前使用`approach_target`节点;若未提及此类关键词,则**严禁**使用`approach_target`节点。 -9. **方向移动优先原则**:当指令为“快速去往东边100米”,直接使用 `move_direction` 节点+距离参数,严禁使用fly_to_waypoint - -## 七、坐标计算规则(东南天坐标系 ENU) -本系统统一使用东南天(ENU)坐标系: -- **X轴**:正方向为**东** (East),负方向为**西** (West) -- **Y轴**:正方向为**南** (South)向为**北** (North) -- **Z轴**:正方向为**天** (Up),负方向为**地** (Down) - -**仅当指令涉及前往“具体地点”(如广场、大门)的偏移位置时,才需计算绝对坐标并使用`fly_to_waypoint`:** - -当指令包含“具体地点 + 方向 + 距离”的偏移时,按方向类型选择工具: -- **单一方向**(东/西/南/北/上/下,如“广场西边200米”):**必须**先调用工具`calc_offset_enu`计算绝对坐标,再使用`fly_to_waypoint`。工具参数: - - `base`: 参考地点的ENU坐标(含x/y/z) - - `direction`: east/west/north/south/up/down - - `distance`: 偏移距离(米) -- **中文复合方位**(东南/西北/东北/西南/南偏东10度/北偏西15度等):**必须**先调用工具`calc_offset_esu_direction_text`计算绝对坐标,再使用`fly_to_waypoint`。工具参数: - - `base`: 参考地点的ENU坐标(含x/y/z) - - `direction_text`: 中文方位原文(如“东南”“南偏东10度”) - - `distance`: 偏移距离(米) -- **禁止简化**:当出现“东南/西北/东北/西南/南偏东/北偏西”等复合方位时,禁止将其简化为单一方向(如仅“东”或仅“南”)。 - -示例(必须遵守): -- “飞到广场东南方向100米” → 必须调用 `calc_offset_esu_direction_text`,参数 `direction_text` 为 `"东南"`,`distance` 为 `100`,再使用 `fly_to_waypoint`。 -- “飞到广场南偏东10度100米” → 必须调用 `calc_offset_esu_direction_text`,参数 `direction_text` 为 `"南偏东10度"`,`distance` 为 `100`。 - -当指令只有“方向 + 距离”且**没有具体地点名词**时,**禁止**调用`calc_offset_enu`,必须使用`move_direction`。 -当指令描述“附近/边上/区域内”等模糊位置且**无方向+距离**时,视为到该地点本身,不做偏移计算。 - -## 八、输出要求 -仅输出1个严格符合上述所有规则的JSON对象。 diff --git a/backend_service/src/prompts/prompt_manifest.yaml b/backend_service/src/prompts/prompt_manifest.yaml new file mode 100644 index 00000000..ee149ecc --- /dev/null +++ b/backend_service/src/prompts/prompt_manifest.yaml @@ -0,0 +1,24 @@ +scenes: + system: + - header.txt + - core_nodes.json + - required_fields.txt + - standard_template.txt + - scene4_examples.txt + - system_extra_examples.txt + - common_rules.txt + scene1: + - header.txt + - core_nodes.json + - required_fields.txt + - standard_template.txt + - scene1_examples.txt + scene4: + - header.txt + - core_nodes.json + - required_fields.txt + - standard_template.txt + - scene4_examples.txt + - common_rules.txt + simple: + - simple_mode_prompt.txt diff --git a/backend_service/src/prompts/scene4_prompt.txt b/backend_service/src/prompts/scene4_prompt.txt deleted file mode 100644 index fd88d7e8..00000000 --- a/backend_service/src/prompts/scene4_prompt.txt +++ /dev/null @@ -1,321 +0,0 @@ -任务:根据用户任意任务指令,生成结构化可执行的无人机行为树(Pytree)JSON。**仅输出单一JSON对象,无任何自然语言、注释或额外内容**。 - -## 一、核心节点定义(格式不可修改,确保后端解析) -#### 1. 可用节点定义 (必须遵守) -你必须严格从以下JSON定义的列表中选择节点构建行为树,不允许使用未定义节点: -```json -{ - "actions": [ - {"name":"takeoff","params":{"altitude":"float[1,100],默认2"}}, - {"name":"land","params":{"mode":"'current'/'home'"}}, - {"name":"fly_to_waypoint","params":{"x":"±10000","y":"±10000","z":"[1,5000]","acceptance_radius":"默认2.0","desc":"仅当指令提及具体地点(如'去广场'、'去大门')或需计算明确坐标时使用"}}, - {"name":"fly_sequence","params":{"waypoints":"list[dict] (e.g. [{'x':10,'y':20,'depth':5}, ...];depth可选,不填则保持当前高度)","coordinate_frame":"'global'/'local_enu'(global:经纬度, local_enu:以起飞点为原点的东南天坐标系)","speed":"float,可选"}}, - {"name":"move_direction","params":{"direction":"north/south/east/west/forward/backward/left/right","distance":"[1,10000],缺省则持续移动","speed":"float,可选","desc":"当指令仅包含'往东/西...飞xx米'且无具体地点名词时,必须使用此节点"}}, - {"name":"approach_target","params":{"target_class":"string,要趋近的目标类别","description":"string,可选,目标属性描述","stop_distance":"float,期望的最终停止距离","speed":"float,可选,期望的逼近速度"}}, - {"name":"rotate","params":{"angle":"float,无人机自身旋转角度(正数逆时针,负数顺时针)","angular_velocity":"rad/s,旋转角速度"}}, - {"name":"rotate_search","params":{"target_class":"同object_detect","description":"string,可选,目标属性描述","step_angle":"float,可选,每一步旋转的角度","total_rotation":"float,可选,总共旋转搜索的角度"}}, - {"name":"manual_confirmation","params":{}}, - {"name":"loiter","params":{"duration":"[1,600]秒/until_condition:可选"}}, - {"name":"object_detect","params":{"target_class":"person,bicycle,car,motorcycle,airplane,bus,train,truck,boat,traffic_light,fire_hydrant,stop_sign,parking_meter,bench,bird,cat,dog,horse,sheep,cow,elephant,bear,zebra,giraffe,backpack,umbrella,handbag,tie,suitcase,frisbee,skis,snowboard,sports_ball,kite,baseball_bat,baseball_glove,skateboard,surfboard,tennis_racket,bottle,wine_glass,cup,fork,knife,spoon,bowl,banana,apple,sandwich,orange,broccoli,carrot,hot_dog,pizza,donut,cake,chair,couch,potted_plant,bed,dining_table,toilet,tv,laptop,mouse,remote,keyboard,cell_phone,microwave,oven,toaster,sink,refrigerator,book,clock,vase,scissors,teddy_bear,hair_drier,toothbrush,garbage","description":"可选,","count":"默认1"}}, - {"name":"search_pattern","params":{"pattern_type":"spiral/grid","center_x":"±10000","center_y":"±10000","center_z":"[1,5000]","radius":"[5,1000]","target_class":"同object_detect","description":"可选,目标属性","count":"默认1"}}, - {"name":"track_object","params":{"target_class":"同object_detect","description":"可选,目标属性","track_time":"[1,600]秒(必传,不可用'duration')","min_confidence":"[0.5,1.0]默认0.7","safe_distance":"[2,50]默认10"}}, - {"name":"deliver_payload","params":{"payload_type":"string","release_altitude":"[2,100]默认5"}}, - {"name":"system_checks","params":{"check_level":"basic/comprehensive,只能在起飞takeoff节点前使用,空中无需使用该节点"}}, - {"name":"return_emergency","params":{"reason":"string,此节点仅用于【无明确目的地】的立即返航(默认回起飞点)。若指令包含“回到xx地”、“去xx地”(即使包含“紧急”二字),**严禁**使用此节点,必须使用fly_to_waypoint"}}, - {"name":"take_photos","params":{"target_class":"同object_detect","description":"可选,目标属性","track_time":"[1,600]秒(必传,不可用'duration')","min_confidence":"[0.5,1.0]默认0.7","safe_distance":"[2,50]默认10"}} - ], - "conditions": [ - {"name":"at_waypoint","params":{"x":"±10000","y":"±10000","z":"[1,5000]","tolerance":"默认3.0"}}, - {"name":"object_detected","params":{"target_class":"同object_detect(必传)","description":"可选,目标属性","count":"默认1"}} - ], - "control_flow": [ - {"name":"Sequence","params":{},"children":"子节点数组(按序执行,全成功则成功)"}, - {"name":"Selector","params":{"memory":"默认true"},"children":"子节点数组(执行到成功为止)"}, - {"name":"Parallel","params":{"policy":"all_success/success_on_one"},"children":"子节点数组(同时执行,默认all_success)"} - ], - "decorators": [ - {"name":"SuccessIsFailure","params":{},"child":"单一子节点(将子节点的成功结果反转为失败)"} - ] -} -``` - - -## 二、节点必填字段(后端Schema强制要求,缺一验证失败) -每个节点必须包含以下字段,字段名/类型不可自定义: -1. **`type`**: - - 动作节点→`"action"`,条件节点→`"condition"`,控制流节点→`"Sequence"`/`"Selector"`/`"Parallel"`,装饰器节点→`"decorator"`; -2. **`name`**:必须是上述JSON中定义的`name`值; -3. **`params`**:严格匹配上述节点的`params`定义,无自定义参数; -4. **`children`**:仅控制流节点必含(子节点数组); -5. **`child`**:仅装饰器节点必含(单一子节点对象,非数组)。 - - -## 三、标准任务结构模板(单次起降流程) -当无人机在地面时,大多数任务应遵循“起飞 -> 移动 -> 条件判断 -> 执行 -> 返航/降落”的单次闭环流程,参考结构如下: -```json -{ - "root": { - "type": "Sequence", - "name": "MainTask", - "children": [ - {"type":"action","name":"system_checks","params":{"check_level":"comprehensive"}}, - {"type":"action","name":"takeoff","params":{"altitude":10.0}}, - {"type":"action","name":"fly_to_waypoint","params":{"x":100.0,"y":50.0,"z":10.0}}, // 接近目标区域 - // --- 核心任务区 (根据指令替换) --- - // 默认不需要降落节点,除非用户明确要求 - ] - } -} -``` -而当无人机在空中时,则无需system_checks与takeoff环节,直接执行用户任务即可 - -## 四、场景示例(请灵活参考) - -#### 场景 1:线性搜索任务(Sequence + Selector) -**指令**:“无人机当前在地面,去研究所正大门,搜索扎辫子女子,找到后拍照。” -**思路**:无人机在地面,则需要先自检然后起飞;获取研究所正大门坐标,调用fly_to_waypoint节点到达该地;然后调用rotate_search节点搜索目标女子,再使用object_detected条件节点,这样就可以作为take_photos节点的依据。 -**结构**:Sequence (按顺序执行) -```json -{ - "root": { - "type": "Sequence", - "name": "MainSearchTask", - "children": [ - {"type":"action","name":"takeoff","params":{"altitude":10.0}}, - {"type":"action","name":"fly_to_waypoint","params":{"x":100.0,"y":50.0,"z":10.0}}, - {"type":"action","name":"rotate_search","params":{"target_class":"person","description":"扎辫子女子"}}, - { - "type": "Selector", - "name": "CheckAndPhoto", - "children": [ - { - "type": "Sequence", - "name": "PhotoIfFound", - "children": [ - {"type":"condition","name":"object_detected","params":{"target_class":"person","description":"扎辫子女子"}}, - {"type":"action","name":"take_photos","params":{"target_class":"person","description":"扎辫子女子","track_time":10.0}} - ] - }, - {"type":"action","name":"loiter","params":{"duration":5.0}} // 未发现时的备选动作 - ] - } - ] - } -} -``` - -#### 场景 2:带中断逻辑的巡逻(Selector 示例) -**指令**:“无人机当前在地面,飞往航点A。如果途中发现可疑人员,则悬停。” -**参考知识**:航点A的坐标(x:100.0,y:50.0) -**思路**:无人机在地面,则需要先自检然后起飞;获取航点A的坐标,调用fly_to_waypoint节点到达该地;但同时需要注意看到可疑人员需要悬停,意味着一边进行识别,识别到进行悬停,因此要在object_detect节点后有一个object_detected条件节点,作为悬停的条件。 -**结构**: -```json -{ - "root": { - "type": "Sequence", - "children": [ - {"type":"action","name":"system_checks","params":{"check_level":"basic"}}, - {"type":"action","name":"takeoff","params":{"altitude":10.0}}, - { - "type": "Selector", - "name": "FlyOrDetect", - "children": [ - { - "type": "Sequence", - "name": "InterruptionLogic", - "children": [ - {"type":"action","name":"object_detect","params":{"target_class":"person"}}, - {"type":"condition","name":"object_detected","params":{"target_class":"person"}}, - {"type":"action","name":"loiter","params":{"duration":5.0}} - ] - }, - {"type":"action","name":"fly_to_waypoint","params":{"x":100.0,"y":50.0,"z":10.0}} - ] - } - ] - } -} -``` - -#### 场景 3:长期监控任务(Parallel 示例) -**指令**:“无人机当前在空中,往广场西边飞200米,持续监控5分钟,发现人就拍照告诉我,到时间可以返航。” -**参考知识**:广场西边200米的坐标(x:50.0,y:50.0,z:10.0) -**思路**:无人机在空中,直接前往目标点;到达后需要并行执行两件事:1. 倒计时5分钟(主控时间);2. 持续检测人并拍照(从属任务)。 -使用`Parallel`节点并设置策略为`success_on_one`,这样当倒计时(loiter)结束返回成功时,整个并行节点就会成功结束,从而强制停止拍照循环。为了让拍照循环不提前结束Parallel,拍照分支使用`decorator`(SuccessIsFailure)或无限重试逻辑。 -**结构**: -```json -{ - "root": { - "type": "Sequence", - "name": "MainTask", - "children": [ - { - "type": "action", - "name": "fly_to_waypoint", - "params": { - "x": 50.0, - "y": 50.0, - "z": 10.0 - } - }, - { - "type": "Parallel", - "name": "MonitorAndPhoto", - "params": { - "policy": "success_on_one" - }, - "children": [ - { - "type": "action", - "name": "loiter", - "params": { - "time": 300 - } - }, - { - "type": "decorator", - "name": "SuccessIsFailure", - "child": { - "type": "Sequence", - "name": "CheckAndPhoto", - "children": [ - { - "type": "action", - "name": "object_detect", - "params": { - "target_class": "person" - } - }, - { - "type": "condition", - "name": "object_detected", - "params": { - "target_class": "person" - } - }, - { - "type": "action", - "name": "take_photos", - "params": { - "target_class": "person" - } - } - ] - } - } - ] - }, - { - "type": "action", - "name": "return_emergency", - "params": { - "reason": "任务完成" - } - } - ] - } -} -``` - -#### 场景 4:交互式确认任务(Sequence + Manual Confirmation) -**指令**:“无人机当前在空中,搜索小汽车,搜索到了我确认后再决定要不要拍照。” -**思路**:无人机已在空中,无需起飞,直接进行搜索。首先使用`rotate_search`主动搜索目标,配合`object_detected`条件节点确认目标是否被检测到。检测到后,执行`manual_confirmation`节点等待用户确认。只有用户确认通过(返回Success),才会继续执行后续的`take_photos`动作。 -**结构**:Sequence -```json -{ - "root": { - "type": "Sequence", - "name": "SearchConfirmPhoto", - "children": [ - {"type":"action","name":"rotate_search","params":{"target_class":"car","description":"小汽车"}}, - {"type":"condition","name":"object_detected","params":{"target_class":"car","description":"小汽车"}}, - {"type":"action","name":"manual_confirmation","params":{}}, - {"type":"action","name":"take_photos","params":{"target_class":"car","description":"小汽车"}} - ] - } -} -``` - -#### 场景 5:后置确认任务(Sequence + Manual Confirmation) -**指令**:“无人机当前在地面,搜索小汽车,搜索到了拍张照,我确认后再决定要不要返航” -**思路**:无人机在地面,需起飞。搜索小汽车(`rotate_search` + `object_detected`)。搜索到后,先执行拍照(`take_photos`)。之后执行人工确认(`manual_confirmation`),确认通过后才执行返航(`return_emergency`)。 -**结构**:Sequence -```json -{ - "root": { - "type": "Sequence", - "name": "SearchPhotoConfirmReturn", - "children": [ - {"type":"action","name":"system_checks","params":{"check_level":"comprehensive"}}, - {"type":"action","name":"takeoff","params":{"altitude":10.0}}, - {"type":"action","name":"rotate_search","params":{"target_class":"car","description":"小汽车"}}, - {"type":"condition","name":"object_detected","params":{"target_class":"car","description":"小汽车"}}, - {"type":"action","name":"take_photos","params":{"target_class":"car","description":"小汽车"}}, - {"type":"action","name":"manual_confirmation","params":{}}, - {"type":"action","name":"return_emergency","params":{"reason":"确认后返航"}} - ] - } -} -``` - -#### 场景 6:移动后条件降落(Sequence) -**指令**:“无人机当前在空中,紧急回到广场,看见了红绿灯之后直接降落。” -**参考知识**:广场坐标(x:0.0, y:0.0, z:10.0) -**思路**:无人机在空中,无需起飞。首先飞往广场(`fly_to_waypoint`),严禁使用`return_emergency`。到达后,为了满足“看见红绿灯”的条件,必须先执行主动搜索(`rotate_search`)。一旦检测到红绿灯(`object_detected`),即执行降落(`land`)。 -**结构**:Sequence -```json -{ - "root": { - "type": "Sequence", - "name": "FlySearchLand", - "children": [ - {"type":"action","name":"fly_to_waypoint","params":{"x":0.0,"y":0.0,"z":10.0}}, - {"type":"action","name":"rotate_search","params":{"target_class":"traffic_light","description":"红绿灯"}}, - {"type":"condition","name":"object_detected","params":{"target_class":"traffic_light","description":"红绿灯"}}, - {"type":"action","name":"land","params":{"mode":"current"}} - ] - } -} -``` - -## 六、高频错误规避 -1. 控制流节点的 `type` 必须是 `"Sequence"`, `"Selector"` 或 `"Parallel"` -2. **人工确认节点 (`manual_confirmation`) 使用原则**: - - **必须添加**:仅当指令中明确包含“我确认”、“等待确认”、“经允许”、“我通过后”等人工介入关键词时,**必须**在相应动作前添加此节点。 - - **严禁添加**:若指令未提及上述关键词,**严禁**主动添加此节点(即使是拍照、返航或降落等动作,只要用户没说要确认,就直接执行)。 -3. 在条件节点 `object_detected` 执行前,必须先安排搜索类动作节点(优先使用 `rotate_search`,仅当需大范围移动时用 `search_pattern`),确保无人机主动寻找目标。 -4. 当使用rotate_search或者object_detect节点时,必须有object_detected节点 -5. 用户指令中要求在当前位置执行任务时,无需fly_to_waypoint节点 -6. **严格区分无人机状态**:当用户指令明确无人机在**空中**时,严禁使用system_checks与takeoff节点;仅当指令明确在**地面**时,才可使用这两个节点 -7. **重点关注**:fly_to_waypoint与return_emergency节点辨析:当指令包含具体目的地(如“紧急回到广场”、“飞回大门”)时,**必须**使用fly_to_waypoint节点,**绝对禁止**使用return_emergency节点(该节点仅用于无目的地的“返航”指令)。 -8. 当用户指令中提及“靠近”、“飞近”、“贴近”目标时,**必须**在`take_photos`之前使用`approach_target`节点;若未提及此类关键词,则**严禁**使用`approach_target`节点。 -9. **方向移动优先原则**:当指令为“快速去往东边100米”,直接使用 `move_direction` 节点+距离参数,严禁使用fly_to_waypoint - -## 七、坐标计算规则(东南天坐标系 ENU) -本系统统一使用东南天(ENU)坐标系: -- **X轴**:正方向为**东** (East),负方向为**西** (West) -- **Y轴**:正方向为**南** (South)向为**北** (North) -- **Z轴**:正方向为**天** (Up),负方向为**地** (Down) - -**仅当指令涉及前往“具体地点”(如广场、大门)的偏移位置时,才需计算绝对坐标并使用`fly_to_waypoint`:** - -当指令包含“具体地点 + 方向 + 距离”的偏移时,按方向类型选择工具: -- **单一方向**(东/西/南/北/上/下,如“广场西边200米”):**必须**先调用工具`calc_offset_enu`计算绝对坐标,再使用`fly_to_waypoint`。工具参数: - - `base`: 参考地点的ENU坐标(含x/y/z) - - `direction`: east/west/north/south/up/down - - `distance`: 偏移距离(米) -- **中文复合方位**(东南/西北/东北/西南/南偏东10度/北偏西15度等):**必须**先调用工具`calc_offset_esu_direction_text`计算绝对坐标,再使用`fly_to_waypoint`。工具参数: - - `base`: 参考地点的ENU坐标(含x/y/z) - - `direction_text`: 中文方位原文(如“东南”“南偏东10度”) - - `distance`: 偏移距离(米) -- **禁止简化**:当出现“东南/西北/东北/西南/南偏东/北偏西”等复合方位时,禁止将其简化为单一方向(如仅“东”或仅“南”)。 - -示例(必须遵守): -- “飞到广场东南方向100米” → 必须调用 `calc_offset_esu_direction_text`,参数 `direction_text` 为 `"东南"`,`distance` 为 `100`,再使用 `fly_to_waypoint`。 -- “飞到广场南偏东10度100米” → 必须调用 `calc_offset_esu_direction_text`,参数 `direction_text` 为 `"南偏东10度"`,`distance` 为 `100`。 - -当指令只有“方向 + 距离”且**没有具体地点名词**时,**禁止**调用`calc_offset_enu`,必须使用`move_direction`。 -当指令描述“附近/边上/区域内”等模糊位置且**无方向+距离**时,视为到该地点本身,不做偏移计算。 - -## 八、输出要求 -仅输出1个严格符合上述所有规则的JSON对象。 diff --git a/backend_service/src/py_tree_generator.py b/backend_service/src/py_tree_generator.py index eb104dc4..11244890 100644 --- a/backend_service/src/py_tree_generator.py +++ b/backend_service/src/py_tree_generator.py @@ -10,7 +10,8 @@ from openai import OpenAIError import jsonschema import requests import platform # 新增:用于选择合适的中文字体 -from .tools.coordinate_tools import calc_offset_enu +import yaml +from .tools.coordinate_tools import calc_offset_enu, calc_offset_esu_direction_text # --- 自定义远程嵌入函数 (与ingest.py中定义一致) --- from chromadb.api.types import Documents, EmbeddingFunction, Embeddings, Embeddable @@ -580,6 +581,8 @@ class PyTreeGenerator: def __init__(self): self.base_dir = os.path.dirname(os.path.abspath(__file__)) self.prompts_dir = os.path.join(self.base_dir, 'prompts') + self._prompt_manifest_cache = None + self._prompt_manifest_mtime = None # Updated output directory for visualizations self.vis_dir = os.path.abspath(os.path.join(self.base_dir, '..', 'generated_visualizations')) @@ -594,11 +597,11 @@ class PyTreeGenerator: self.reasoning_preview_lines = int(os.getenv("REASONING_PREVIEW_LINES", "20")) except Exception: self.reasoning_preview_lines = 20 - # 加载提示词:复杂模式复用现有 system_prompt.txt;场景1/4与分类器独立提示词 - self.complex_prompt = self._load_prompt("system_prompt.txt") - self.scene1_prompt = self._load_prompt("scene1_prompt.txt") - self.scene4_prompt = self._load_prompt("scene4_prompt.txt") - self.simple_prompt = self._load_prompt("simple_mode_prompt.txt") + # 加载提示词:优先使用清单拼接,失败回退到单文件 + self.complex_prompt = self._load_prompt_from_manifest("system", "system_prompt.txt") + self.scene1_prompt = self._load_prompt_from_manifest("scene1", "scene1_prompt.txt") + self.scene4_prompt = self._load_prompt_from_manifest("scene4", "scene4_prompt.txt") + self.simple_prompt = self._load_prompt_from_manifest("simple", "simple_mode_prompt.txt") self.classifier_prompt = self._load_prompt("classifier_prompt.txt") self.scene_classifier_prompt = self._load_prompt("scene_classifier_prompt.txt") # 兼容旧变量名 @@ -648,6 +651,97 @@ class PyTreeGenerator: logging.error(f"提示词文件未找到 -> {file_name}") return "" + def _load_prompt_manifest(self) -> Optional[dict]: + manifest_path = os.path.join(self.prompts_dir, "prompt_manifest.yaml") + try: + manifest_mtime = os.path.getmtime(manifest_path) + except FileNotFoundError: + return None + + if ( + self._prompt_manifest_cache is not None + and self._prompt_manifest_mtime == manifest_mtime + ): + return self._prompt_manifest_cache + + try: + with open(manifest_path, "r", encoding="utf-8") as f: + manifest = yaml.safe_load(f) or {} + except Exception as exc: + logging.error(f"提示词清单加载失败 -> {exc}") + return None + + if not isinstance(manifest, dict): + logging.error("提示词清单格式错误:顶层必须为映射") + return None + + self._prompt_manifest_cache = manifest + self._prompt_manifest_mtime = manifest_mtime + return manifest + + def _resolve_prompt_fragment(self, fragment: str) -> str: + if os.path.isabs(fragment): + return fragment + if "/" in fragment: + return os.path.join(self.prompts_dir, fragment) + partial_path = os.path.join(self.prompts_dir, "partials", fragment) + if os.path.exists(partial_path): + return partial_path + return os.path.join(self.prompts_dir, fragment) + + def _load_fragment_text(self, fragment_path: str) -> str: + if fragment_path.lower().endswith(".json"): + try: + with open(fragment_path, "r", encoding="utf-8") as f: + payload = json.load(f) + except Exception as exc: + raise ValueError(f"JSON片段解析失败: {exc}") from exc + + json_text = json.dumps(payload, ensure_ascii=False, indent=2) + if os.path.basename(fragment_path) == "core_nodes.json": + return "\n".join( + [ + "## 一、核心节点定义(格式不可修改,确保后端解析)", + "#### 1. 可用节点定义 (必须遵守)", + "你必须严格从以下JSON定义的列表中选择节点构建行为树,不允许使用未定义节点:", + "```json", + json_text, + "```", + ] + ) + return "\n".join(["```json", json_text, "```"]) + + with open(fragment_path, "r", encoding="utf-8") as f: + return f.read() + + def _load_prompt_from_manifest(self, scene_key: str, fallback_file: str) -> str: + manifest = self._load_prompt_manifest() + if not manifest: + return self._load_prompt(fallback_file) + + scene_map = manifest.get("scenes", manifest) + fragments = scene_map.get(scene_key) + if not fragments: + logging.warning(f"提示词清单缺少场景配置 -> {scene_key}") + return self._load_prompt(fallback_file) + if not isinstance(fragments, list) or not all(isinstance(item, str) for item in fragments): + logging.warning(f"提示词清单场景配置非法 -> {scene_key}") + return self._load_prompt(fallback_file) + + contents = [] + for fragment in fragments: + fragment_path = self._resolve_prompt_fragment(fragment) + try: + contents.append(self._load_fragment_text(fragment_path).strip("\n")) + except FileNotFoundError: + logging.error(f"提示词片段未找到 -> {fragment_path}") + return self._load_prompt(fallback_file) + except ValueError as exc: + logging.error(f"提示词片段解析失败 -> {exc}") + return self._load_prompt(fallback_file) + + return "\n\n".join(contents).strip("\n") + def _get_tool_definitions(self) -> list[dict]: return [ { @@ -679,6 +773,33 @@ class PyTreeGenerator: "required": ["base", "direction", "distance"] } } + }, + { + "type": "function", + "function": { + "name": "calc_offset_esu_direction_text", + "description": "根据ESU坐标系基准点、中文方位与距离计算偏移后的坐标。", + "parameters": { + "type": "object", + "properties": { + "base": { + "type": "object", + "properties": { + "x": {"type": "number"}, + "y": {"type": "number"}, + "z": {"type": "number"} + }, + "required": ["x", "y", "z"] + }, + "direction_text": { + "type": "string", + "description": "中文方位,如“南偏东10度”“北偏东10度”“东南”“西北”等" + }, + "distance": {"type": "number", "minimum": 0} + }, + "required": ["base", "direction_text", "distance"] + } + } } ] @@ -721,6 +842,8 @@ class PyTreeGenerator: try: if tool_name == "calc_offset_enu": result = calc_offset_enu(**args) + elif tool_name == "calc_offset_esu_direction_text": + result = calc_offset_esu_direction_text(**args) else: result = {"error": f"unsupported tool: {tool_name}"} tool_messages.append( @@ -767,6 +890,8 @@ class PyTreeGenerator: "东侧", "西侧", "南侧", "北侧", "往东", "往西", "往南", "往北", "向东", "向西", "向南", "向北", + "东南", "西南", "东北", "西北", + "南偏东", "南偏西", "北偏东", "北偏西", ] has_direction = any(pat in user_prompt for pat in direction_patterns) has_distance = re.search(r"\d+(\.\d+)?\s*(米|m)", user_prompt) is not None