3 Commits

Author SHA1 Message Date
8945e922d5 新增拍照节点,删除安全检查 2025-12-06 13:47:05 +08:00
c08cdfb339 修改简单模式验证 2025-12-03 17:13:59 +08:00
43a0636913 修改简单模式验证 2025-12-03 17:13:47 +08:00
2497 changed files with 5748 additions and 66965 deletions

View File

@@ -6,38 +6,80 @@
接下来,任务流程应该是:起飞→飞往搜索区域→搜索目标→检测到目标后跟踪→打击。同时必须包含安全监控。 接下来,任务流程应该是:起飞→飞往搜索区域→搜索目标→检测到目标后跟踪→打击。同时必须包含安全监控。
根据用户提供的参考知识,可能需要将搜索区域设置为某个中心点。比如,用户提到的“跷跷板”在(x:15, y:-8.5, z:1.2)但z坐标需要调整到至少1米这里已经是1.2,没问题。或者可能选择其他地点作为搜索中心。但用户没有明确说明,可能需要假设搜索区域是这些地点附近,或者使用一个综合的中心 根据用户提供的参考知识,可能需要将搜索区域设置为某个中心点。比如,用户提到的“跷跷板”在(x:15, y:-8.5, z:1.2)但z坐标需要调整到至少1米所以可能设置为z=2。或者选择其他地点作为搜索中心。但用户没有明确说明,可能需要假设搜索区域是这些地点附近,或者使用其中一个作为中心。
不过用户可能希望无人机先飞往某个特定的搜索区。比如参考知识中的“学生宿舍”可能是一个可能的区域但需要确认。或者用户可能希望无人机在某个中心点周围进行搜索。例如使用search_pattern的中心点可能选在某个已知地点比如“跷跷板”附近或者综合多个点 另外用户提到要搜索并锁定危险性最高的气球所以需要使用search_pattern或者object_detect。但因为是未知区域可能更适合使用search_pattern或者先飞到某个区域再进行检测
但用户没有明确指定搜索区域所以可能需要使用search_pattern的中心点为某个已知地点比如“跷跷板”的坐标或者选择一个合理的中心点。例如假设搜索区域是“跷跷板”所在的位置那么中心坐标为(15, -8.5, 1.2)。或者可能需要将搜索区域设置为多个地点的组合,但用户没有说明,所以可能需要选择一个中心点 根据任务范式可能需要先飞到某个坐标点然后进行搜索。例如使用fly_to_waypoint飞到某个中心点然后执行search_pattern。或者直接使用search_pattern覆盖多个区域
接下来考虑使用search_pattern来搜索因为目标位置未知。参数中需要指定pattern_type比如spiral或grid。假设选择spiral模式覆盖更大的区域中心可能选在某个已知地点,比如“跷跷板”的坐标,或者综合多个点。但用户没有明确,所以可能需要选择一个合理的中心点,比如“跷跷板”的坐标 不过参考知识中的三个地点可能作为搜索区域中心可能需要将搜索区域设置为这些点的附近。但用户没有明确说明,可能需要选择一个中心点,比如“学生宿舍”的坐标,或者综合考虑
然后检测到目标后需要跟踪30秒再打击。所以流程是起飞→飞往搜索区→搜索→检测→跟踪→打击→降落 另外用户提到要锁定危险性最高的气球所以需要检测到红色气球。因此在search_pattern中target_class应为balloon并且description为“红色气球危险性高于蓝色气球高于绿色气球”或者在object_detect中设置description参数为“红>蓝>绿”
安全监控必须包含在Parallel节点中监控电池和GPS并在紧急情况下返航 但根据搜索模式search_pattern的参数需要target_class和description。所以可能需要设置target_class为balloondescription为“红>蓝>绿”
现在,构建行为树的结构 接下来,流程应该是
根节点是Parallel包含主任务和安全监控。主任务是Sequence包含起飞、飞往搜索区、搜索、检测、跟踪、打击、降落。安全监控是Selector包含电池和GPS条件以及紧急处理Sequence 1. 起飞到一定高度
2. 飞往搜索区域的中心点比如学生宿舍的坐标调整z到2
3. 执行search_pattern搜索红色气球。
4. 一旦检测到目标,进行跟踪。
5. 跟踪30秒后打击。
在搜索部分使用search_pattern参数包括pattern_type为spiral中心点选在某个已知地点比如“跷跷板”的坐标(15, -8.5, 1.2)半径可能设为50米覆盖较大区域。目标类别是balloon描述为“红色气球危险性高于蓝色气球高于绿色气球”但参数中description需要描述目标属性比如“红>蓝>绿” 同时安全监控必须包含电池和GPS检查以及紧急处理
检测到目标后使用track_object参数target_class为balloondescription为“红>蓝>绿”track_time为30秒。然后strike_target最后评估战损 现在需要构建行为树结构。根节点是Parallel包含主任务和安全监控。主任务是Sequence包含起飞、飞往搜索点、搜索、跟踪、打击、降落
需要确保所有参数符合约束比如z坐标在1-5000米之间半径在5-1000米之间速度等参数在合理范围 在搜索部分可能需要使用search_pattern或者先飞到某个点再进行object_detect。但根据用户指令可能更适合使用search_pattern因为需要在未知区域搜索
现在将各个步骤的参数填入JSON中。例如起飞到2米飞往搜索区的中心点可能选在“跷跷板”的坐标(15, -8.5, 1.2)但z需要调整到至少1米这里已经是1.2。所以fly_to_waypoint的参数x=15y=-8.5z=1.2接受半径2米。 所以,主任务步骤:
搜索使用search_patternpattern_type为spiral中心点为(15, -8.5, 1.2)半径设为50米覆盖区域。目标类别是balloondescription为“红>蓝>绿”。 - preflight_checks
- takeoff
- fly_to_waypoint到某个中心点比如学生宿舍的坐标调整z为2
- search_pattern参数为balloondescription为“红>蓝>绿”pattern_type可能是spiral或grid假设用spiral中心点为学生宿舍的坐标半径适当比如50米。
- 然后当检测到目标后进行track_object跟踪30秒。
- 然后strike_target打击。
- 最后降落。
检测到目标后使用track_object参数target_class为balloondescription为“红>蓝>绿”track_time为30秒min_confidence设为0.7safe_distance设为15米 但需要确保在search_pattern之后检测到目标然后进入跟踪。或者可能需要使用Selector来选择直接检测或搜索
然后strike_target参数target_class为balloondescription为“红>蓝>绿”count为1 但根据用户指令可能需要先飞到某个点再执行search_pattern。或者直接使用search_pattern覆盖多个区域
最后,降落 参考知识中的三个地点可能作为搜索区域的中心,但用户没有明确说明,可能需要选择其中一个作为搜索中心。比如学生宿舍的坐标(x:5, y:3, z:2)调整z为2
安全监控部分电池阈值设为0.35GPS的min_satellites设为8紧急处理Sequence包括emergency_return和land 所以fly_to_waypoint的参数是x=5, y=3, z=2, acceptance_radius=2.0
现在将所有步骤的参数填入JSON中确保符合所有规则。 然后执行search_pattern参数为balloondescription为“红>蓝>绿”pattern_type为spiral中心点为学生宿舍的坐标半径比如50米。
之后当检测到目标后进行track_object参数为balloondescription为“红>蓝>绿”track_time=30.0min_confidence=0.7safe_distance=15.0。
然后strike_target参数为balloondescription为“红>蓝>绿”count=1。
最后降落。
同时安全监控需要包含电池和GPS检查以及紧急处理。
现在需要构建JSON结构确保所有参数符合约束并且流程正确。
可能的搜索中心是学生宿舍的坐标调整z为2。然后search_pattern的参数为balloondescription为“红>蓝>绿”pattern_type为spiralradius为50米center_x=5, center_y=3, center_z=2。
然后当检测到目标后进行track_object跟踪30秒然后打击。
在行为树中主任务Sequence的步骤包括
- preflight_checks
- takeoff (altitude=2.0)
- fly_to_waypoint到学生宿舍的坐标调整z为2
- search_pattern参数为balloondescription为“红>蓝>绿”pattern_type为spiralradius=50center_x=5, center_y=3, center_z=2
- 然后当检测到目标后进行track_object
- strike_target
- land
同时安全监控的Selector包含电池和GPS条件以及紧急处理。
现在需要检查所有参数是否符合约束例如z=2符合≥1。
其他参数如radius=50符合[5,1000]。
所以生成的JSON结构应该符合这些要求并且流程正确。

Binary file not shown.

Before

Width:  |  Height:  |  Size: 233 KiB

After

Width:  |  Height:  |  Size: 98 KiB

View File

@@ -1,8 +1,8 @@
你是一个严格的任务分类器。只输出一个JSON对象不要输出解释或多余文本。 你是一个严格的任务分类器。只输出一个JSON对象不要输出解释或多余文本。
根据用户指令与下述可用节点定义,判断其为“简单”或“复杂”。 根据用户指令与下述可用节点定义,判断其为“简单”或“复杂”。
- 简单:单一原子动作即可完成(例如起飞”“飞机自检”“移动到某地(已给定坐标)”“对着某点环绕XY圈对着学生宿舍环绕三十两圈’)”等),且无需行为树与安全并行监控 - 简单:单一原子动作即可完成(例如"起飞""飞机自检""移动到某地(已给定坐标)""对着某点环绕XY圈'对着学生宿舍环绕三十两圈'"等),且无需行为树。
- 复杂:需要多步流程、搜索/检测/跟踪/评估、战损确认、或需要模板化任务结构与安全并行监控 - 复杂:需要多步流程、搜索/检测/跟踪/评估、战损确认、或需要模板化任务结构。
输出格式(严格遵守): 输出格式(严格遵守):
{"mode":"simple"} 或 {"mode":"complex"} {"mode":"simple"} 或 {"mode":"complex"}
@@ -14,11 +14,11 @@
{"name": "takeoff"}, {"name": "land"}, {"name": "fly_to_waypoint"}, {"name": "move_direction"}, {"name": "orbit_around_point"}, {"name": "orbit_around_target"}, {"name": "loiter"}, {"name": "takeoff"}, {"name": "land"}, {"name": "fly_to_waypoint"}, {"name": "move_direction"}, {"name": "orbit_around_point"}, {"name": "orbit_around_target"}, {"name": "loiter"},
{"name": "object_detect"}, {"name": "strike_target"}, {"name": "battle_damage_assessment"}, {"name": "object_detect"}, {"name": "strike_target"}, {"name": "battle_damage_assessment"},
{"name": "search_pattern"}, {"name": "track_object"}, {"name": "deliver_payload"}, {"name": "search_pattern"}, {"name": "track_object"}, {"name": "deliver_payload"},
{"name": "preflight_checks"}, {"name": "emergency_return"} {"name": "preflight_checks"}, {"name": "take_picture"}
], ],
"conditions": [ "conditions": [
{"name": "battery_above"}, {"name": "at_waypoint"}, {"name": "object_detected"}, {"name": "at_waypoint"}, {"name": "object_detected"},
{"name": "target_destroyed"}, {"name": "time_elapsed"}, {"name": "gps_status"} {"name": "target_destroyed"}, {"name": "time_elapsed"}
] ]
} }
``` ```

View File

@@ -4,8 +4,7 @@
- 只输出一个JSON对象不要任何解释或多余文本。 - 只输出一个JSON对象不要任何解释或多余文本。
- JSON结构 - JSON结构
{"root":{"type":"action","name":"<action_name>","params":{...}}} {"root":{"type":"action","name":"<action_name>","params":{...}}}
- <action_name> 与参数定义、取值范围必须与“复杂模式”提示词system_prompt.txt中的定义完全一致 - root节点必须是action类型节点不能是控制流节点
- 简单模式下root节点必须是action类型节点不能是控制流节点。
示例: 示例:
- “起飞到10米” → {"root":{"type":"action","name":"takeoff","params":{"altitude":10.0}}} - “起飞到10米” → {"root":{"type":"action","name":"takeoff","params":{"altitude":10.0}}}
@@ -30,15 +29,13 @@
{"name": "track_object", "description": "持续跟踪目标。", "params": {"target_class": "string, 取值同object_detect列表", "description": "string, 可选", "track_time": "float[1,600], 默认30.0", "min_confidence": "float[0.5-1.0], 默认0.7", "safe_distance": "float[2-50], 默认10.0"}}, {"name": "track_object", "description": "持续跟踪目标。", "params": {"target_class": "string, 取值同object_detect列表", "description": "string, 可选", "track_time": "float[1,600], 默认30.0", "min_confidence": "float[0.5-1.0], 默认0.7", "safe_distance": "float[2-50], 默认10.0"}},
{"name": "deliver_payload", "description": "投放物资。", "params": {"payload_type": "string", "release_altitude": "float[2,100], 默认5.0"}}, {"name": "deliver_payload", "description": "投放物资。", "params": {"payload_type": "string", "release_altitude": "float[2,100], 默认5.0"}},
{"name": "preflight_checks", "description": "飞行前系统自检。", "params": {"check_level": "string: basic|comprehensive"}}, {"name": "preflight_checks", "description": "飞行前系统自检。", "params": {"check_level": "string: basic|comprehensive"}},
{"name": "emergency_return", "description": "执行紧急返航程序。", "params": {"reason": "string"}} {"name": "take_picture", "description": "使用机载相机拍摄照片。", "params": {}}
], ],
"conditions": [ "conditions": [
{"name": "battery_above", "description": "电池电量高于阈值。", "params": {"threshold": "float[0.0,1.0]"}},
{"name": "at_waypoint", "description": "在指定坐标容差范围内。", "params": {"x": "float", "y": "float", "z": "float", "tolerance": "float, 可选, 默认3.0"}}, {"name": "at_waypoint", "description": "在指定坐标容差范围内。", "params": {"x": "float", "y": "float", "z": "float", "tolerance": "float, 可选, 默认3.0"}},
{"name": "object_detected", "description": "检测到特定目标。", "params": {"target_class": "string", "description": "string, 可选", "count": "int, 可选, 默认1"}}, {"name": "object_detected", "description": "检测到特定目标。", "params": {"target_class": "string", "description": "string, 可选", "count": "int, 可选, 默认1"}},
{"name": "target_destroyed", "description": "目标已被摧毁。", "params": {"target_class": "string", "description": "string, 可选", "confidence": "float[0.5-1.0], 默认0.8"}}, {"name": "target_destroyed", "description": "目标已被摧毁。", "params": {"target_class": "string", "description": "string, 可选", "confidence": "float[0.5-1.0], 默认0.8"}},
{"name": "time_elapsed", "description": "时间经过。", "params": {"duration": "float[1,2700]"}}, {"name": "time_elapsed", "description": "时间经过。", "params": {"duration": "float[1,2700]"}}
{"name": "gps_status", "description": "GPS状态良好。", "params": {"min_satellites": "int[6,15], 默认10"}}
] ]
} }
``` ```

View File

@@ -20,15 +20,13 @@
{"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":"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":"deliver_payload","params":{"payload_type":"string","release_altitude":"[2,100]默认5"}},
{"name":"preflight_checks","params":{"check_level":"basic/comprehensive"}}, {"name":"preflight_checks","params":{"check_level":"basic/comprehensive"}},
{"name":"emergency_return","params":{"reason":"string"}} {"name":"take_picture","params":{}}
], ],
"conditions": [ "conditions": [
{"name":"battery_above","params":{"threshold":"[0.0,1.0],必传"}},
{"name":"at_waypoint","params":{"x":"±10000","y":"±10000","z":"[1,5000]","tolerance":"默认3.0"}}, {"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":"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":"target_destroyed","params":{"target_class":"同object_detect","description":"可选,目标属性","confidence":"[0.5,1.0]默认0.8"}},
{"name":"time_elapsed","params":{"duration":"[1,2700]秒"}}, {"name":"time_elapsed","params":{"duration":"[1,2700]秒"}}
{"name":"gps_status","params":{"min_satellites":"int[6,15]必传如8"}}
], ],
"control_flow": [ "control_flow": [
{"name":"Sequence","params":{},"children":"子节点数组(按序执行,全成功则成功)"}, {"name":"Sequence","params":{},"children":"子节点数组(按序执行,全成功则成功)"},
@@ -43,18 +41,18 @@
每个节点必须包含以下字段,字段名/类型不可自定义: 每个节点必须包含以下字段,字段名/类型不可自定义:
1. **`type`** 1. **`type`**
- 动作节点→`"action"`,条件节点→`"condition"`,控制流节点→`"Sequence"`/`"Selector"`/`"Parallel"`(与`name`字段值完全一致); - 动作节点→`"action"`,条件节点→`"condition"`,控制流节点→`"Sequence"`/`"Selector"`/`"Parallel"`(与`name`字段值完全一致);
2. **`name`**必须是上述JSON中`actions`/`conditions`/`control_flow`下的`name`值如“gps_status”不可错写为“gps_check” 2. **`name`**必须是上述JSON中`actions`/`conditions`/`control_flow`下的`name`值;
3. **`params`**:严格匹配上述节点的`params`定义无自定义参数如优先级排序不可加“priority”字段仅用`description` 3. **`params`**:严格匹配上述节点的`params`定义无自定义参数如优先级排序不可加“priority”字段仅用`description`
4. **`children`**:仅控制流节点必含(子节点数组),动作/条件节点无此字段。 4. **`children`**:仅控制流节点必含(子节点数组),动作/条件节点无此字段。
## 三、行为树固定结构(通用不变,确保安全验证 ## 三、行为树固定结构(通用不变)
根节点必须是`Parallel``children`含`MainTask`Sequence和`SafetyMonitor`Selector,结构不随任务类型(含优先级排序)修改: 根节点必须是`Parallel``children`含`MainTask`Sequence结构不随任务类型含优先级排序修改
```json ```json
{ {
"root": { "root": {
"type": "Parallel", "type": "Parallel",
"name": "MissionWithSafety", "name": "Mission",
"params": {"policy": "all_success"}, "params": {"policy": "all_success"},
"children": [ "children": [
{ {
@@ -72,24 +70,6 @@
{"type":"action","name":"strike_target","params":{"target_class":"balloon","description":"红色"}}, {"type":"action","name":"strike_target","params":{"target_class":"balloon","description":"红色"}},
{"type":"action","name":"land","params":{"mode":"home"}} {"type":"action","name":"land","params":{"mode":"home"}}
] ]
},
{
"type": "Selector",
"name": "SafetyMonitor",
"params": {"memory": true},
"children": [
{"type":"condition","name":"battery_above","params":{"threshold":0.3}},
{"type":"condition","name":"gps_status","params":{"min_satellites":8}},
{
"type":"Sequence",
"name":"EmergencyHandler",
"params": {},
"children": [
{"type":"action","name":"emergency_return","params":{"reason":"safety_breach"}},
{"type":"action","name":"land","params":{"mode":"home"}}
]
}
]
} }
] ]
} }
@@ -106,11 +86,10 @@
## 五、高频错误规避(确保验证通过) ## 五、高频错误规避(确保验证通过)
1. 优先级排序不可修改`target_class`:如民用卡车、面包车与军用卡车中,军用卡车优先`target_class`仍为`truck`,仅用`description`填排序规则; 1. 优先级排序不可修改`target_class`:如"民用卡车、面包车与军用卡车中,军用卡车优先"`target_class`仍为`truck`,仅用`description`填排序规则;
2. 在没有明确指出物体之间的优先级关系情况下,`description`字段只描述物体属性本身,严禁与用户指令中不存在的物体进行排序; 2. 在没有明确指出物体之间的优先级关系情况下,`description`字段只描述物体属性本身,严禁与用户指令中不存在的物体进行排序;
3. `track_object`必传`track_time`:不可用`duration`替代如跟踪30秒填`"track_time":30.0` 3. `track_object`必传`track_time`:不可用`duration`替代如跟踪30秒填`"track_time":30.0`
4. `gps_status`的`min_satellites`必须在6-15之间如8不可缺省 4. 无自定义节点:"锁定高优先级目标"需通过`object_detect`+`object_detected`实现,不可用"lock_high_risk_target"。
5. 无自定义节点:“锁定高优先级目标”需通过`object_detect`+`object_detected`实现不可用“lock_high_risk_target”。
## 六、输出要求 ## 六、输出要求

View File

@@ -157,32 +157,6 @@ def _find_nodes_by_name(node: Dict, target_name: str) -> List[Dict]:
return nodes_found return nodes_found
def _validate_safety_monitoring(pytree_instance: dict) -> bool:
"""验证行为树是否包含必要的安全监控"""
root_node = pytree_instance.get("root", {})
# 查找所有电池监控节点
battery_nodes = _find_nodes_by_name(root_node, "battery_above")
# 检查是否包含安全监控结构
safety_monitors = _find_nodes_by_name(root_node, "SafetyMonitor")
if not battery_nodes and not safety_monitors:
logging.warning("⚠️ 安全警告: 行为树中没有发现电池监控节点或安全监控器")
return False
# 检查电池阈值设置是否合理
for battery_node in battery_nodes:
threshold = battery_node.get("params", {}).get("threshold")
if threshold is not None:
if threshold < 0.25:
logging.warning(f"⚠️ 安全警告: 电池阈值设置过低 ({threshold})建议不低于0.25")
elif threshold > 0.5:
logging.warning(f"⚠️ 安全警告: 电池阈值设置过高 ({threshold}),可能影响任务执行")
logging.info("✅ 安全监控验证通过")
return True
def _generate_pytree_schema(allowed_actions: set, allowed_conditions: set) -> dict: def _generate_pytree_schema(allowed_actions: set, allowed_conditions: set) -> dict:
""" """
根据允许的行动和条件节点动态生成一个JSON Schema。 根据允许的行动和条件节点动态生成一个JSON Schema。
@@ -273,48 +247,6 @@ def _generate_pytree_schema(allowed_actions: set, allowed_conditions: set) -> di
} }
} }
} }
},
# 电池监控节点的参数验证
{
"if": {
"properties": {
"type": {"const": "condition"},
"name": {"const": "battery_above"}
}
},
"then": {
"properties": {
"params": {
"type": "object",
"properties": {
"threshold": {"type": "number", "minimum": 0.0, "maximum": 1.0}
},
"required": ["threshold"],
"additionalProperties": False
}
}
}
},
# GPS状态节点的参数验证
{
"if": {
"properties": {
"type": {"const": "condition"},
"name": {"const": "gps_status"}
}
},
"then": {
"properties": {
"params": {
"type": "object",
"properties": {
"min_satellites": {"type": "integer", "minimum": 6, "maximum": 15}
},
"required": ["min_satellites"],
"additionalProperties": False
}
}
}
} }
] ]
} }
@@ -337,7 +269,9 @@ def _generate_pytree_schema(allowed_actions: set, allowed_conditions: set) -> di
def _generate_simple_mode_schema(allowed_actions: set) -> dict: def _generate_simple_mode_schema(allowed_actions: set) -> dict:
""" """
生成简单模式JSON Schema{"mode":"simple","action":{...}} 生成简单模式JSON Schema{"root":{"type":"action","name":"...","params":{...}}}
简单模式与复杂模式使用相同的格式root字段但要求root必须是action类型且没有children。
严格按照提示词要求root节点必须是action类型节点不能是控制流节点即不能有children
仅校验动作名称在允许集合内,以及基本结构完整性;参数按对象形状放宽,由上游提示词与运行时再约束。 仅校验动作名称在允许集合内,以及基本结构完整性;参数按对象形状放宽,由上游提示词与运行时再约束。
""" """
schema = { schema = {
@@ -345,19 +279,20 @@ def _generate_simple_mode_schema(allowed_actions: set) -> dict:
"title": "SimpleMode", "title": "SimpleMode",
"type": "object", "type": "object",
"properties": { "properties": {
"mode": {"type": "string", "const": "simple"}, "root": {
"action": {
"type": "object", "type": "object",
"properties": { "properties": {
"name": {"type": "string", "enum": sorted(list(allowed_actions))}, "type": {"type": "string", "const": "action"}, # 必须是action类型不能是控制流节点Sequence/Selector/Parallel
"params": {"type": "object"} "name": {"type": "string", "enum": sorted(list(allowed_actions))}, # 动作名称必须在允许列表中
"params": {"type": "object"} # params是对象具体参数由提示词和运行时约束
}, },
"required": ["name"], "required": ["type", "name"], # type和name是必需的params可选
"additionalProperties": True "additionalProperties": True # 允许root节点有其他属性如额外的元数据
# 注意children字段的检查在验证后手动进行因为JSON Schema的not/allOf在检查不存在字段时可能有问题
} }
}, },
"required": ["mode", "action"], "required": ["root"], # 顶层必须有root字段
"additionalProperties": False "additionalProperties": False # 顶层只能有root字段不能有其他字段如mode等
} }
return schema return schema
@@ -368,11 +303,7 @@ def _validate_pytree_with_schema(pytree_instance: dict, schema: dict) -> bool:
try: try:
jsonschema.validate(instance=pytree_instance, schema=schema) jsonschema.validate(instance=pytree_instance, schema=schema)
logging.info("✅ JSON Schema验证成功") logging.info("✅ JSON Schema验证成功")
return True
# 额外验证安全监控
safety_valid = _validate_safety_monitoring(pytree_instance)
return True and safety_valid
except jsonschema.ValidationError as e: except jsonschema.ValidationError as e:
logging.warning("❌ Pytree验证失败") logging.warning("❌ Pytree验证失败")
logging.warning(f"错误信息: {e.message}") logging.warning(f"错误信息: {e.message}")
@@ -382,10 +313,6 @@ def _validate_pytree_with_schema(pytree_instance: dict, schema: dict) -> bool:
# 提供更具体的错误信息 # 提供更具体的错误信息
if "object_detect" in str(e.message) or "object_detected" in str(e.message): if "object_detect" in str(e.message) or "object_detected" in str(e.message):
logging.warning("💡 提示: 请确保目标类别是预定义列表中的有效值") logging.warning("💡 提示: 请确保目标类别是预定义列表中的有效值")
elif "battery_above" in str(e.message):
logging.warning("💡 提示: 电池阈值必须在0.0到1.0之间")
elif "gps_status" in str(e.message):
logging.warning("💡 提示: 最小卫星数量必须在6到15之间")
return False return False
except Exception as e: except Exception as e:
@@ -407,10 +334,10 @@ def _visualize_pytree(node: Dict, file_path: str):
# 选择合适的中文字体,避免中文乱码 # 选择合适的中文字体,避免中文乱码
def _pick_zh_font(): def _pick_zh_font():
sys = platform.system() system = platform.system()
if sys == "Windows": if system == "Windows":
return "Microsoft YaHei" return "Microsoft YaHei"
elif sys == "Darwin": elif system == "Darwin":
return "PingFang SC" return "PingFang SC"
else: else:
return "Noto Sans CJK SC" return "Noto Sans CJK SC"
@@ -440,7 +367,19 @@ def _visualize_pytree(node: Dict, file_path: str):
except Exception as e: except Exception as e:
logging.error("❌ 生成可视化图形失败") logging.error("❌ 生成可视化图形失败")
logging.error("请确保您的系统已经正确安装了Graphviz图形库。") logging.error("请确保您的系统已经正确安装了Graphviz图形库。")
logging.error("安装方法:")
logging.error(" Ubuntu/Debian: sudo apt-get install graphviz")
logging.error(" CentOS/RHEL: sudo yum install graphviz")
logging.error(" macOS: brew install graphviz")
logging.error(f"错误详情: {e}") logging.error(f"错误详情: {e}")
# 清理可能残留的源文件
try:
gv_file = f"{render_path}.gv"
if os.path.exists(gv_file):
os.remove(gv_file)
logging.info(f"已清理残留的源文件: {gv_file}")
except Exception:
pass
def _add_nodes_and_edges(node: dict, dot, parent_id: str | None = None) -> str: def _add_nodes_and_edges(node: dict, dot, parent_id: str | None = None) -> str:
"""递归辅助函数,用于添加节点和边。""" """递归辅助函数,用于添加节点和边。"""
@@ -501,11 +440,6 @@ def _add_nodes_and_edges(node: dict, dot, parent_id: str | None = None) -> str:
style = 'filled' style = 'filled'
fillcolor = '#e1d5e7' # 紫色 fillcolor = '#e1d5e7' # 紫色
# 特别标记安全相关节点
if node.get('name') in ['battery_above', 'gps_status', 'SafetyMonitor']:
border_color = '#ff0000' # 红色边框突出显示安全节点
style = 'filled,bold' # 加粗
dot.node(current_id, label=node_label, shape=shape, style=style, fillcolor=fillcolor, color=border_color) dot.node(current_id, label=node_label, shape=shape, style=style, fillcolor=fillcolor, color=border_color)
# 连接父节点 # 连接父节点
@@ -777,6 +711,13 @@ class PyTreeGenerator:
if mode == "simple": if mode == "simple":
try: try:
jsonschema.validate(instance=pytree_dict, schema=self.simple_schema) jsonschema.validate(instance=pytree_dict, schema=self.simple_schema)
# 手动检查简单模式的root节点不能有children或children必须是空数组
root_node = pytree_dict.get('root', {})
if 'children' in root_node:
children = root_node.get('children', [])
if isinstance(children, list) and len(children) > 0:
logging.warning(f"❌ 简单模式验证失败: root节点不能有children但发现 {len(children)} 个子节点")
continue
logging.info("✅ 简单模式JSON Schema验证成功") logging.info("✅ 简单模式JSON Schema验证成功")
except jsonschema.ValidationError as e: except jsonschema.ValidationError as e:
logging.warning(f"❌ 简单模式验证失败: {e.message}") logging.warning(f"❌ 简单模式验证失败: {e.message}")
@@ -784,16 +725,13 @@ class PyTreeGenerator:
# 附加元信息并生成简单可视化(单动作) # 附加元信息并生成简单可视化(单动作)
plan_id = str(uuid.uuid4()) plan_id = str(uuid.uuid4())
pytree_dict['plan_id'] = plan_id pytree_dict['plan_id'] = plan_id
# 简单模式可视化:构造一个简化节点图 # 简单模式可视化:使用root节点已经是action类型
try: try:
vis_filename = "py_tree.png" vis_filename = "py_tree.png"
vis_path = os.path.join(self.vis_dir, vis_filename) vis_path = os.path.join(self.vis_dir, vis_filename)
simple_node = { # 简单模式的root节点就是action节点直接使用
"type": "action", root_node = pytree_dict.get('root', {})
"name": pytree_dict.get('action', {}).get('name', 'action'), _visualize_pytree(root_node, os.path.splitext(vis_path)[0])
"params": pytree_dict.get('action', {}).get('params', {})
}
_visualize_pytree(simple_node, os.path.splitext(vis_path)[0])
pytree_dict['visualization_url'] = f"/static/{vis_filename}" pytree_dict['visualization_url'] = f"/static/{vis_filename}"
except Exception as e: except Exception as e:
logging.warning(f"简单模式可视化失败: {e}") logging.warning(f"简单模式可视化失败: {e}")
@@ -820,46 +758,34 @@ class PyTreeGenerator:
pytree_dict['final_prompt'] = final_prompt pytree_dict['final_prompt'] = final_prompt
return pytree_dict return pytree_dict
# 复杂模式回退:若模型误返回简单结构,则自动包装为含安全监控的行为树 # 复杂模式回退:若模型误返回简单结构root是单个action,则自动包装为完整行为树
if mode == "complex" and isinstance(pytree_dict, dict) and 'root' not in pytree_dict: if mode == "complex" and isinstance(pytree_dict, dict) and 'root' in pytree_dict:
try: root_node = pytree_dict.get('root', {})
jsonschema.validate(instance=pytree_dict, schema=self.simple_schema) # 检查是否是简单结构root是单个action节点没有children
logging.warning("⚠️ 复杂模式生成了简单结构,触发自动包装为完整行为树的回退逻辑。") if (root_node.get('type') == 'action' and
simple_action_obj = pytree_dict.get('action') or {} ('children' not in root_node or not root_node.get('children'))):
action_name = simple_action_obj.get('name') try:
action_params = simple_action_obj.get('params') if isinstance(simple_action_obj.get('params'), dict) else {} jsonschema.validate(instance=pytree_dict, schema=self.simple_schema)
logging.warning("⚠️ 复杂模式生成了简单结构单个action触发自动包装为完整行为树的回退逻辑。")
action_name = root_node.get('name')
action_params = root_node.get('params') if isinstance(root_node.get('params'), dict) else {}
safety_selector = { main_children = [{"type": "action", "name": action_name, "params": action_params}]
"type": "Selector", if action_name != "land":
"name": "SafetyMonitor", main_children.append({"type": "action", "name": "land", "params": {"mode": "home"}})
"params": {"memory": True},
"children": [
{"type": "condition", "name": "battery_above", "params": {"threshold": 0.3}},
{"type": "condition", "name": "gps_status", "params": {"min_satellites": 8}},
{"type": "Sequence", "name": "EmergencyHandler", "children": [
{"type": "action", "name": "emergency_return", "params": {"reason": "safety_breach"}},
{"type": "action", "name": "land", "params": {"mode": "home"}}
]}
]
}
main_children = [{"type": "action", "name": action_name, "params": action_params}] root_parallel = {
if action_name != "land": "type": "Parallel",
main_children.append({"type": "action", "name": "land", "params": {"mode": "home"}}) "name": "Mission",
"params": {"policy": "all_success"},
root_parallel = { "children": [
"type": "Parallel", {"type": "Sequence", "name": "MainTask", "children": main_children}
"name": "MissionWithSafety", ]
"params": {"policy": "all_success"}, }
"children": [ pytree_dict = {"root": root_parallel}
{"type": "Sequence", "name": "MainTask", "children": main_children}, except jsonschema.ValidationError:
safety_selector # 不符合简单结构,按正常复杂验证继续
] pass
}
pytree_dict = {"root": root_parallel}
except jsonschema.ValidationError:
# 不符合简单结构,按正常复杂验证继续
pass
if _validate_pytree_with_schema(pytree_dict, self.schema): if _validate_pytree_with_schema(pytree_dict, self.schema):
logging.info("✅ 成功生成并验证了Pytree") logging.info("✅ 成功生成并验证了Pytree")
plan_id = str(uuid.uuid4()) plan_id = str(uuid.uuid4())

Some files were not shown because too many files have changed in this diff Show More