Compare commits
3 Commits
fa5e5e7c23
...
8823c1fe0c
| Author | SHA1 | Date | |
|---|---|---|---|
| 8823c1fe0c | |||
| 1b4847707e | |||
| b1700a8260 |
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -15,14 +15,17 @@ class LLMGateway:
|
|||||||
self.generator = generator
|
self.generator = generator
|
||||||
self.stage1_enable_thinking = os.getenv("STAGE1_ENABLE_THINKING", "true").lower() in ("1", "true", "yes")
|
self.stage1_enable_thinking = os.getenv("STAGE1_ENABLE_THINKING", "true").lower() in ("1", "true", "yes")
|
||||||
|
|
||||||
def classify_scene(self, user_prompt: str) -> str:
|
def classify_scene(self, user_prompt: str, drone_state: str = "on_ground") -> str:
|
||||||
scene_mode = "scene1"
|
scene_mode = "scene1"
|
||||||
try:
|
try:
|
||||||
|
user_content = user_prompt
|
||||||
|
if drone_state and drone_state in ("on_ground", "in_air"):
|
||||||
|
user_content = f"无人机当前状态:{drone_state}\n\n用户指令:{user_prompt}"
|
||||||
classifier_resp = self.generator.classifier_client.chat.completions.create(
|
classifier_resp = self.generator.classifier_client.chat.completions.create(
|
||||||
model=self.generator.classifier_model,
|
model=self.generator.classifier_model,
|
||||||
messages=[
|
messages=[
|
||||||
{"role": "system", "content": self.generator.scene_classifier_prompt or "你是一个分类器,只输出JSON。"},
|
{"role": "system", "content": self.generator.scene_classifier_prompt or "你是一个分类器,只输出JSON。"},
|
||||||
{"role": "user", "content": user_prompt},
|
{"role": "user", "content": user_content},
|
||||||
],
|
],
|
||||||
temperature=0.0,
|
temperature=0.0,
|
||||||
response_format={"type": "json_object"},
|
response_format={"type": "json_object"},
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
@@ -9,8 +9,8 @@ class GenerationOrchestrator:
|
|||||||
def __init__(self, generator: Any):
|
def __init__(self, generator: Any):
|
||||||
self.stages = PipelineStages(generator)
|
self.stages = PipelineStages(generator)
|
||||||
|
|
||||||
async def generate(self, user_prompt: str) -> Dict:
|
async def generate(self, user_prompt: str, drone_state: str = "on_ground") -> Dict:
|
||||||
understanding = self.stages.stage1_task_understanding(user_prompt)
|
understanding = self.stages.stage1_task_understanding(user_prompt, drone_state=drone_state)
|
||||||
context = self.stages.stage2_context_binding(user_prompt, understanding)
|
context = self.stages.stage2_context_binding(user_prompt, understanding)
|
||||||
|
|
||||||
if understanding.scene_mode == "simple":
|
if understanding.scene_mode == "simple":
|
||||||
|
|||||||
@@ -71,18 +71,15 @@ class PipelineStages:
|
|||||||
def __init__(self, generator: Any):
|
def __init__(self, generator: Any):
|
||||||
self.generator = generator
|
self.generator = generator
|
||||||
|
|
||||||
def stage1_task_understanding(self, user_prompt: str) -> TaskUnderstanding:
|
def stage1_task_understanding(self, user_prompt: str, drone_state: str = "on_ground") -> TaskUnderstanding:
|
||||||
logging.info("========== [Stage 1] Task Understanding ==========")
|
logging.info("========== [Stage 1] Task Understanding ==========")
|
||||||
scene_mode = self.generator.llm_gateway.classify_scene(user_prompt)
|
if drone_state not in ("on_ground", "in_air"):
|
||||||
|
drone_state = "on_ground"
|
||||||
|
scene_mode = self.generator.llm_gateway.classify_scene(user_prompt, drone_state=drone_state)
|
||||||
intent_type = _infer_intent_type(user_prompt, scene_mode)
|
intent_type = _infer_intent_type(user_prompt, scene_mode)
|
||||||
risk_flags = _extract_risk_flags(user_prompt)
|
risk_flags = _extract_risk_flags(user_prompt)
|
||||||
requires_relative = "relative_reference_detected" in risk_flags
|
requires_relative = "relative_reference_detected" in risk_flags
|
||||||
|
|
||||||
# 简单提取状态
|
|
||||||
drone_state: DroneState = "on_ground"
|
|
||||||
if "在空中" in user_prompt or "已起飞" in user_prompt:
|
|
||||||
drone_state = "in_air"
|
|
||||||
|
|
||||||
entities = {"raw_prompt": user_prompt}
|
entities = {"raw_prompt": user_prompt}
|
||||||
|
|
||||||
logging.info(f"Task Understanding Results: mode={scene_mode}, state={drone_state}, intent={intent_type}, risks={risk_flags}")
|
logging.info(f"Task Understanding Results: mode={scene_mode}, state={drone_state}, intent={intent_type}, risks={risk_flags}")
|
||||||
|
|||||||
@@ -125,8 +125,8 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "return_emergency",
|
"name": "return",
|
||||||
"desc": "紧急返航",
|
"desc": "返航,回到起飞点",
|
||||||
"params": {
|
"params": {
|
||||||
"reason": "string"
|
"reason": "string"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,8 +20,8 @@
|
|||||||
- 指令明确“在当前位置 / 原地 / 不用过去 / 就在这”时,无需 fly_to_waypoint。
|
- 指令明确“在当前位置 / 原地 / 不用过去 / 就在这”时,无需 fly_to_waypoint。
|
||||||
|
|
||||||
5. 严格区分无人机状态:
|
5. 严格区分无人机状态:
|
||||||
- 指令明确“当前在空中”:严禁使用 system_checks 与 takeoff。
|
- 指令明确“当前在空中”:严禁使用 takeoff。
|
||||||
- 指令明确“当前在地面”:如需要飞行任务,优先 system_checks -> takeoff 再执行后续动作。
|
- 指令明确“当前在地面”:如需要飞行任务,优先 takeoff 再执行后续动作。
|
||||||
|
|
||||||
6. fly_to_waypoint 与 return_emergency 辨析:
|
6. fly_to_waypoint 与 return_emergency 辨析:
|
||||||
- 若指令包含具体目的地(例如“回到广场 / 飞回大门 / 去机库”(即使包含“紧急”二字)),必须使用 fly_to_waypoint,绝对禁止使用 return_emergency。
|
- 若指令包含具体目的地(例如“回到广场 / 飞回大门 / 去机库”(即使包含“紧急”二字)),必须使用 fly_to_waypoint,绝对禁止使用 return_emergency。
|
||||||
|
|||||||
@@ -9,7 +9,6 @@
|
|||||||
"type": "Sequence",
|
"type": "Sequence",
|
||||||
"name": "FlyPerimeterAndPhotoWindows",
|
"name": "FlyPerimeterAndPhotoWindows",
|
||||||
"children": [
|
"children": [
|
||||||
{"type":"action","name":"system_checks","params":{"check_level":"comprehensive"}},
|
|
||||||
{"type":"action","name":"takeoff","params":{"altitude":10.0}},
|
{"type":"action","name":"takeoff","params":{"altitude":10.0}},
|
||||||
{
|
{
|
||||||
"type": "Parallel",
|
"type": "Parallel",
|
||||||
@@ -59,7 +58,6 @@
|
|||||||
"type": "Sequence",
|
"type": "Sequence",
|
||||||
"name": "FlyPerimeterAndPhotoOpenWindows",
|
"name": "FlyPerimeterAndPhotoOpenWindows",
|
||||||
"children": [
|
"children": [
|
||||||
{"type":"action","name":"system_checks","params":{"check_level":"comprehensive"}},
|
|
||||||
{"type":"action","name":"takeoff","params":{"altitude":10.0}},
|
{"type":"action","name":"takeoff","params":{"altitude":10.0}},
|
||||||
{
|
{
|
||||||
"type": "Parallel",
|
"type": "Parallel",
|
||||||
@@ -158,7 +156,6 @@
|
|||||||
"type": "Sequence",
|
"type": "Sequence",
|
||||||
"name": "FlyPerimeterAndPhotoGarbageObserve",
|
"name": "FlyPerimeterAndPhotoGarbageObserve",
|
||||||
"children": [
|
"children": [
|
||||||
{"type":"action","name":"system_checks","params":{"check_level":"comprehensive"}},
|
|
||||||
{"type":"action","name":"takeoff","params":{"altitude":10.0}},
|
{"type":"action","name":"takeoff","params":{"altitude":10.0}},
|
||||||
{
|
{
|
||||||
"type": "Parallel",
|
"type": "Parallel",
|
||||||
@@ -208,7 +205,6 @@
|
|||||||
"type": "Sequence",
|
"type": "Sequence",
|
||||||
"name": "FlyPerimeterAndPhotoGarbage",
|
"name": "FlyPerimeterAndPhotoGarbage",
|
||||||
"children": [
|
"children": [
|
||||||
{"type":"action","name":"system_checks","params":{"check_level":"comprehensive"}},
|
|
||||||
{"type":"action","name":"takeoff","params":{"altitude":10.0}},
|
{"type":"action","name":"takeoff","params":{"altitude":10.0}},
|
||||||
{
|
{
|
||||||
"type": "Parallel",
|
"type": "Parallel",
|
||||||
@@ -307,7 +303,6 @@
|
|||||||
"type": "Sequence",
|
"type": "Sequence",
|
||||||
"name": "FlyPerimeterAndPhotoPerson",
|
"name": "FlyPerimeterAndPhotoPerson",
|
||||||
"children": [
|
"children": [
|
||||||
{"type":"action","name":"system_checks","params":{"check_level":"comprehensive"}},
|
|
||||||
{"type":"action","name":"takeoff","params":{"altitude":10.0}},
|
{"type":"action","name":"takeoff","params":{"altitude":10.0}},
|
||||||
{
|
{
|
||||||
"type": "Parallel",
|
"type": "Parallel",
|
||||||
@@ -357,7 +352,6 @@
|
|||||||
"type": "Sequence",
|
"type": "Sequence",
|
||||||
"name": "FlyPerimeterCounterClockwiseAndPhotoPerson",
|
"name": "FlyPerimeterCounterClockwiseAndPhotoPerson",
|
||||||
"children": [
|
"children": [
|
||||||
{"type":"action","name":"system_checks","params":{"check_level":"comprehensive"}},
|
|
||||||
{"type":"action","name":"takeoff","params":{"altitude":10.0}},
|
{"type":"action","name":"takeoff","params":{"altitude":10.0}},
|
||||||
{
|
{
|
||||||
"type": "Parallel",
|
"type": "Parallel",
|
||||||
|
|||||||
@@ -43,7 +43,6 @@
|
|||||||
"root": {
|
"root": {
|
||||||
"type": "Sequence",
|
"type": "Sequence",
|
||||||
"children": [
|
"children": [
|
||||||
{"type":"action","name":"system_checks","params":{"check_level":"basic"}},
|
|
||||||
{"type":"action","name":"takeoff","params":{"altitude":10.0}},
|
{"type":"action","name":"takeoff","params":{"altitude":10.0}},
|
||||||
{
|
{
|
||||||
"type": "Selector",
|
"type": "Selector",
|
||||||
@@ -175,7 +174,6 @@
|
|||||||
"type": "Sequence",
|
"type": "Sequence",
|
||||||
"name": "SearchPhotoConfirmReturn",
|
"name": "SearchPhotoConfirmReturn",
|
||||||
"children": [
|
"children": [
|
||||||
{"type":"action","name":"system_checks","params":{"check_level":"comprehensive"}},
|
|
||||||
{"type":"action","name":"takeoff","params":{"altitude":10.0}},
|
{"type":"action","name":"takeoff","params":{"altitude":10.0}},
|
||||||
{"type":"action","name":"rotate_search","params":{"target_class":"car","description":"小汽车"}},
|
{"type":"action","name":"rotate_search","params":{"target_class":"car","description":"小汽车"}},
|
||||||
{"type":"condition","name":"object_detected","params":{"target_class":"car","description":"小汽车"}},
|
{"type":"condition","name":"object_detected","params":{"target_class":"car","description":"小汽车"}},
|
||||||
|
|||||||
@@ -69,7 +69,6 @@
|
|||||||
"type": "Sequence",
|
"type": "Sequence",
|
||||||
"name": "FlyPerimeterAndPhotoGarbage",
|
"name": "FlyPerimeterAndPhotoGarbage",
|
||||||
"children": [
|
"children": [
|
||||||
{"type":"action","name":"system_checks","params":{"check_level":"comprehensive"}},
|
|
||||||
{"type":"action","name":"takeoff","params":{"altitude":10.0}},
|
{"type":"action","name":"takeoff","params":{"altitude":10.0}},
|
||||||
{
|
{
|
||||||
"type": "Parallel",
|
"type": "Parallel",
|
||||||
@@ -123,7 +122,6 @@
|
|||||||
"type": "Sequence",
|
"type": "Sequence",
|
||||||
"name": "FlyPerimeterAndPhotoWindows",
|
"name": "FlyPerimeterAndPhotoWindows",
|
||||||
"children": [
|
"children": [
|
||||||
{"type":"action","name":"system_checks","params":{"check_level":"comprehensive"}},
|
|
||||||
{"type":"action","name":"takeoff","params":{"altitude":10.0}},
|
{"type":"action","name":"takeoff","params":{"altitude":10.0}},
|
||||||
{
|
{
|
||||||
"type": "Parallel",
|
"type": "Parallel",
|
||||||
@@ -177,7 +175,6 @@
|
|||||||
"type": "Sequence",
|
"type": "Sequence",
|
||||||
"name": "FlyPerimeterAndPhotoOpenWindows",
|
"name": "FlyPerimeterAndPhotoOpenWindows",
|
||||||
"children": [
|
"children": [
|
||||||
{"type":"action","name":"system_checks","params":{"check_level":"comprehensive"}},
|
|
||||||
{"type":"action","name":"takeoff","params":{"altitude":10.0}},
|
{"type":"action","name":"takeoff","params":{"altitude":10.0}},
|
||||||
{
|
{
|
||||||
"type": "Parallel",
|
"type": "Parallel",
|
||||||
@@ -284,7 +281,6 @@
|
|||||||
"type": "Sequence",
|
"type": "Sequence",
|
||||||
"name": "FlyPerimeterAndPhotoGarbageObserve",
|
"name": "FlyPerimeterAndPhotoGarbageObserve",
|
||||||
"children": [
|
"children": [
|
||||||
{"type":"action","name":"system_checks","params":{"check_level":"comprehensive"}},
|
|
||||||
{"type":"action","name":"takeoff","params":{"altitude":10.0}},
|
{"type":"action","name":"takeoff","params":{"altitude":10.0}},
|
||||||
{
|
{
|
||||||
"type": "Parallel",
|
"type": "Parallel",
|
||||||
@@ -391,7 +387,6 @@
|
|||||||
"type": "Sequence",
|
"type": "Sequence",
|
||||||
"name": "FlyPerimeterAndPhotoPerson",
|
"name": "FlyPerimeterAndPhotoPerson",
|
||||||
"children": [
|
"children": [
|
||||||
{"type":"action","name":"system_checks","params":{"check_level":"comprehensive"}},
|
|
||||||
{"type":"action","name":"takeoff","params":{"altitude":10.0}},
|
{"type":"action","name":"takeoff","params":{"altitude":10.0}},
|
||||||
{
|
{
|
||||||
"type": "Parallel",
|
"type": "Parallel",
|
||||||
@@ -445,7 +440,6 @@
|
|||||||
"type": "Sequence",
|
"type": "Sequence",
|
||||||
"name": "FlyPerimeterCounterClockwiseAndPhotoPerson",
|
"name": "FlyPerimeterCounterClockwiseAndPhotoPerson",
|
||||||
"children": [
|
"children": [
|
||||||
{"type":"action","name":"system_checks","params":{"check_level":"comprehensive"}},
|
|
||||||
{"type":"action","name":"takeoff","params":{"altitude":10.0}},
|
{"type":"action","name":"takeoff","params":{"altitude":10.0}},
|
||||||
{
|
{
|
||||||
"type": "Parallel",
|
"type": "Parallel",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
## 三、顶层 JSON 结构规范与模板
|
## 三、顶层 JSON 结构规范与模板
|
||||||
|
|
||||||
当无人机状态为 **in_air(空中)** 时,通常的流程包含:方向移动或直接飞行至目标点 -> (执行任务)。**严禁**使用 `system_checks` 与 `takeoff`。
|
当无人机状态为 **in_air(空中)** 时,通常的流程包含:方向移动或直接飞行至目标点 -> (执行任务)。**严禁**使用 `takeoff`。
|
||||||
如果指令要求“沿外围”,需使用 `fly_sequence`。
|
如果指令要求“沿外围”,需使用 `fly_sequence`。
|
||||||
|
|
||||||
你必须同时输出 `macro_tree`(必须剔除 params)和 `parameter_requests` 两个字段。
|
你必须同时输出 `macro_tree`(必须剔除 params)和 `parameter_requests` 两个字段。
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
## 三、顶层 JSON 结构规范与模板
|
## 三、顶层 JSON 结构规范与模板
|
||||||
|
|
||||||
当无人机状态为 **on_ground(地面)** 时,通常的流程包含:`system_checks` -> `takeoff` -> (飞行至目标点等后续动作)。
|
当无人机状态为 **on_ground(地面)** 时,通常的流程包含:`takeoff` -> (飞行至目标点等后续动作)。
|
||||||
如果指令要求“沿外围”,需使用 `fly_sequence`。
|
如果指令要求“沿外围”,需使用 `fly_sequence`。
|
||||||
|
|
||||||
你必须同时输出 `macro_tree`(必须剔除 params)和 `parameter_requests` 两个字段。
|
你必须同时输出 `macro_tree`(必须剔除 params)和 `parameter_requests` 两个字段。
|
||||||
@@ -13,7 +13,6 @@
|
|||||||
"type": "Sequence",
|
"type": "Sequence",
|
||||||
"name": "MainTask",
|
"name": "MainTask",
|
||||||
"children": [
|
"children": [
|
||||||
{"type": "action", "name": "system_checks"},
|
|
||||||
{"type": "action", "name": "takeoff"},
|
{"type": "action", "name": "takeoff"},
|
||||||
{"type": "action", "name": "fly_to_waypoint"}
|
{"type": "action", "name": "fly_to_waypoint"}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
你是指令分类器。只输出一个JSON,无其它内容。
|
你是指令分类器,仅输出JSON对象,无任何多余内容。
|
||||||
输入:无人机状态{on_ground/in_air}+指令。
|
输入:无人机状态(on_ground/in_air)+指令。
|
||||||
输出仅三选一:{"mode":"simple"}、{"mode":"scene1"}、{"mode":"scene4"}。
|
输出仅三选一:{"mode":"simple"}、{"mode":"scene1"}、{"mode":"scene4"}。
|
||||||
|
|
||||||
规则:
|
规则(必须严格执行):
|
||||||
1. 指令含“面前”→scene1
|
1. 指令包含“面前”→输出{"mode":"scene1"};
|
||||||
2. 状态=in_air,指令是:飞到某地/飞到某地+方位距离/往某方向飞X米/降落/旋转/悬停 →simple
|
2. 无人机状态=in_air且指令包含“飞到”→输出{"mode":"simple"};
|
||||||
3. 状态=on_ground,指令含去/飞到/回到某地 →非simple
|
3. 无人机状态=in_air且指令包含“往某方向飞”或“降落”或“旋转”或“悬停”→输出{"mode":"simple"};
|
||||||
4. 多动作/序列任务→scene4
|
4. 无人机状态=on_ground且指令包含“起飞”或“旋转”或“悬停”→输出{"mode":"simple"};
|
||||||
|
5. 无人机状态=on_ground且指令包含“飞到”或“去”或“回到”→输出{"mode":"scene4"};
|
||||||
|
6. 指令包含“先”或“再”或“搜索”或“监控”或“拍照”或“返航”或“确认”→输出{"mode":"scene4"};
|
||||||
|
7. 其他所有情况→输出{"mode":"scene4"}。
|
||||||
@@ -1,248 +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,'z':5}, ...])","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}}, // 接近目标区域
|
|
||||||
// --- 核心任务区 (根据指令替换) ---
|
|
||||||
// --- 任务1执行---
|
|
||||||
{"type":"action","name":"rotate_search","params":{"target_class":"person","description":"目标描述"}},
|
|
||||||
{"type":"action","name":"object_detect","params":{"target_class":"person","description":"目标描述"}},
|
|
||||||
// ----条件判断(根据指令替换) -----
|
|
||||||
{"type":"condition","name":"object_detected","params":{"target_class":"person","description":"扎辫子女子"}},
|
|
||||||
// --- 任务2执行---
|
|
||||||
{"type":"action","name":"take_photos","params":{"target_class":"person","description":"扎辫子女子"}},
|
|
||||||
// 默认不需要降落节点,除非用户明确要求
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## 四、场景示例(请灵活参考)
|
|
||||||
|
|
||||||
#### 场景 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": {
|
|
||||||
"name": "主任务-监控与拍照",
|
|
||||||
"type": "Sequence",
|
|
||||||
"children": [
|
|
||||||
{
|
|
||||||
"name": "fly_to_waypoint",
|
|
||||||
"type": "action",
|
|
||||||
"params": {"x": 50.0, "y": 50.0, "z": 10.0}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "并行监控5分钟",
|
|
||||||
"type": "Parallel",
|
|
||||||
"params": { "policy": "success_on_one" },
|
|
||||||
"children": [
|
|
||||||
{
|
|
||||||
"name": "loiter",
|
|
||||||
"type": "action",
|
|
||||||
"params": { "duration": 300 }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "SuccessIsFailure",
|
|
||||||
"type": "decorator",
|
|
||||||
"child": {
|
|
||||||
"name": "check_and_photo",
|
|
||||||
"type": "Sequence",
|
|
||||||
"children": [
|
|
||||||
{"name": "object_detect", "type": "action", "params": { "target_class": "person" }},
|
|
||||||
{"name": "object_detected", "type": "condition", "params": { "target_class": "person" }},
|
|
||||||
{"name": "take_photos", "type": "action", "params": { "target_class": "person" }}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "return_emergency",
|
|
||||||
"type": "action",
|
|
||||||
"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":"小汽车"}}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## 六、高频错误规避
|
|
||||||
1. 控制流节点的 `type` 必须是 `"Sequence"`, `"Selector"` 或 `"Parallel"`
|
|
||||||
2. 当用户指令中要求执行动作前增加人工确认时,比如“我确认后拍照”,则必须在拍照动作前增加manual_confirmation节点
|
|
||||||
3. 在条件节点执行前,必须有相应动作节点!如object_detected节点前必须是rotate_search等搜索类节点
|
|
||||||
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`: 偏移距离(米)
|
|
||||||
|
|
||||||
当指令只有“方向 + 距离”且**没有具体地点名词**时,**禁止**调用`calc_offset_enu`,必须使用`move_direction`。
|
|
||||||
当指令描述“附近/边上/区域内”等模糊位置且**无方向+距离**时,视为到该地点本身,不做偏移计算。
|
|
||||||
|
|
||||||
## 八、输出要求
|
|
||||||
仅输出1个严格符合上述所有规则的JSON对象。
|
|
||||||
|
|
||||||
## 九、vLLM 工具调用输出规则(必须遵守)
|
|
||||||
当且仅当需要调用工具时,允许输出 `tool_call` XML(而不是JSON)。工具调用完成后,必须输出最终的行为树JSON对象。
|
|
||||||
@@ -790,9 +790,89 @@ class PyTreeGenerator:
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.warning(f"保存推理链Markdown失败: {e}")
|
logging.warning(f"保存推理链Markdown失败: {e}")
|
||||||
|
|
||||||
async def generate(self, user_prompt: str) -> Dict[str, Any]:
|
async def generate(self, user_prompt: str, drone_state: str = "on_ground") -> Dict[str, Any]:
|
||||||
logging.info(f"接收到用户请求: {user_prompt}")
|
logging.info(f"接收到用户请求: {user_prompt}, drone_state={drone_state}")
|
||||||
return await self.orchestrator.generate(user_prompt)
|
return await self.orchestrator.generate(user_prompt, drone_state=drone_state)
|
||||||
|
|
||||||
|
def run_debug_stage(
|
||||||
|
self, user_prompt: str, drone_state: str = "on_ground", target_stage: int = 1
|
||||||
|
) -> Dict[str, Any]:
|
||||||
|
"""
|
||||||
|
分阶段调试:运行到指定 stage 并返回该 stage 的输出。
|
||||||
|
当 target_stage >= 2 时,响应中附带 upstream(前一 stage 的 output)。
|
||||||
|
当 target_stage >= 3 时,响应中附带 stage1 与 stage2 的 output。
|
||||||
|
以此类推,便于追溯完整流水线。
|
||||||
|
"""
|
||||||
|
if target_stage < 1 or target_stage > 6:
|
||||||
|
return {"error": f"target_stage 必须在 1-6 之间,当前为 {target_stage}"}
|
||||||
|
|
||||||
|
upstream: Dict[str, Any] = {}
|
||||||
|
|
||||||
|
understanding = self.orchestrator.stages.stage1_task_understanding(
|
||||||
|
user_prompt, drone_state=drone_state
|
||||||
|
)
|
||||||
|
if target_stage == 1:
|
||||||
|
return {
|
||||||
|
"target_stage": 1,
|
||||||
|
"stage_name": "TaskUnderstanding",
|
||||||
|
"output": understanding.model_dump(),
|
||||||
|
"upstream": upstream,
|
||||||
|
}
|
||||||
|
|
||||||
|
upstream["stage1"] = understanding.model_dump()
|
||||||
|
context = self.orchestrator.stages.stage2_context_binding(user_prompt, understanding)
|
||||||
|
if target_stage == 2:
|
||||||
|
return {
|
||||||
|
"target_stage": 2,
|
||||||
|
"stage_name": "ContextBinding",
|
||||||
|
"output": context.model_dump(),
|
||||||
|
"upstream": upstream,
|
||||||
|
}
|
||||||
|
|
||||||
|
upstream["stage2"] = context.model_dump()
|
||||||
|
draft = self.orchestrator.stages.stage3_macro_planning(
|
||||||
|
user_prompt, understanding, context
|
||||||
|
)
|
||||||
|
if target_stage == 3:
|
||||||
|
return {
|
||||||
|
"target_stage": 3,
|
||||||
|
"stage_name": "BTDraft",
|
||||||
|
"output": draft.model_dump(),
|
||||||
|
"upstream": upstream,
|
||||||
|
}
|
||||||
|
|
||||||
|
upstream["stage3"] = draft.model_dump()
|
||||||
|
resolved_data = self.orchestrator.stages.stage4_middleware_resolution(draft)
|
||||||
|
if target_stage == 4:
|
||||||
|
return {
|
||||||
|
"target_stage": 4,
|
||||||
|
"stage_name": "MiddlewareResolution",
|
||||||
|
"output": {"resolved_data": resolved_data},
|
||||||
|
"upstream": upstream,
|
||||||
|
}
|
||||||
|
|
||||||
|
upstream["stage4"] = {"resolved_data": resolved_data}
|
||||||
|
final_tree = self.orchestrator.stages.stage5_micro_filling(
|
||||||
|
draft, resolved_data, understanding
|
||||||
|
)
|
||||||
|
if target_stage == 5:
|
||||||
|
return {
|
||||||
|
"target_stage": 5,
|
||||||
|
"stage_name": "MicroFilling",
|
||||||
|
"output": {"final_tree": final_tree},
|
||||||
|
"upstream": upstream,
|
||||||
|
}
|
||||||
|
|
||||||
|
upstream["stage5"] = {"final_tree": final_tree}
|
||||||
|
payload = self.orchestrator.stages.stage6_validate_and_postprocess(
|
||||||
|
user_prompt, understanding, context, draft, final_tree
|
||||||
|
)
|
||||||
|
return {
|
||||||
|
"target_stage": 6,
|
||||||
|
"stage_name": "ValidateAndPostprocess",
|
||||||
|
"output": payload,
|
||||||
|
"upstream": upstream,
|
||||||
|
}
|
||||||
|
|
||||||
# Create a single instance for the application
|
# Create a single instance for the application
|
||||||
py_tree_generator = PyTreeGenerator()
|
py_tree_generator = PyTreeGenerator()
|
||||||
|
|||||||
Binary file not shown.
@@ -39,6 +39,52 @@ def _select_stage():
|
|||||||
print("❌ 请输入 1-6 之间的数字")
|
print("❌ 请输入 1-6 之间的数字")
|
||||||
|
|
||||||
|
|
||||||
|
def _summarize_system_prompt_origin(obj: dict, upstream: dict) -> str:
|
||||||
|
"""
|
||||||
|
根据上游 stage1 推断 system_prompt 的拼接顺序,仅展示文件名,不输出全文。
|
||||||
|
"""
|
||||||
|
stage1 = upstream.get("stage1") or {}
|
||||||
|
scene_mode = stage1.get("scene_mode", "scene4")
|
||||||
|
drone_state = stage1.get("drone_state", "on_ground")
|
||||||
|
intent_type = stage1.get("intent_type", "generic_mission")
|
||||||
|
|
||||||
|
if scene_mode == "simple":
|
||||||
|
return "【system_prompt 来源】simple_mode_prompt.txt"
|
||||||
|
|
||||||
|
parts = [
|
||||||
|
"macro_header.txt",
|
||||||
|
"core_nodes.json (裁剪后)",
|
||||||
|
f"template_{'ground' if drone_state == 'on_ground' else 'air'}.txt",
|
||||||
|
"common_rules.txt",
|
||||||
|
]
|
||||||
|
if intent_type == "generic_mission":
|
||||||
|
parts.append("[可选] system_extra_examples.txt")
|
||||||
|
parts.append("任务意图标签 (代码生成)")
|
||||||
|
return "【system_prompt 来源】" + " → ".join(parts)
|
||||||
|
|
||||||
|
|
||||||
|
def _summarize_output_for_display(obj: dict, upstream: dict) -> dict:
|
||||||
|
"""将 output/upstream 中的 system_prompt、final_prompt 替换为简要说明,便于终端查看"""
|
||||||
|
if not isinstance(obj, dict):
|
||||||
|
return obj
|
||||||
|
out = {}
|
||||||
|
for k, v in obj.items():
|
||||||
|
if k == "system_prompt" and isinstance(v, str) and len(v) > 200:
|
||||||
|
out[k] = _summarize_system_prompt_origin(obj, upstream) + f"\n(全文约 {len(v)} 字符,已保存至 response.json)"
|
||||||
|
elif k == "final_prompt" and isinstance(v, str) and len(v) > 200:
|
||||||
|
out[k] = f"【final_prompt】= system_prompt + user_prompt (全文约 {len(v)} 字符,已保存至 response.json)"
|
||||||
|
elif isinstance(v, dict):
|
||||||
|
out[k] = _summarize_output_for_display(v, upstream)
|
||||||
|
elif isinstance(v, list):
|
||||||
|
out[k] = [
|
||||||
|
_summarize_output_for_display(item, upstream) if isinstance(item, dict) else item
|
||||||
|
for item in v
|
||||||
|
]
|
||||||
|
else:
|
||||||
|
out[k] = v
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
def _infer_drone_state_from_prompt(prompt: str) -> str:
|
def _infer_drone_state_from_prompt(prompt: str) -> str:
|
||||||
"""从指令中简单推断 drone_state(批量模式下若未单独指定则用此)"""
|
"""从指令中简单推断 drone_state(批量模式下若未单独指定则用此)"""
|
||||||
if "在空中" in prompt or "已起飞" in prompt:
|
if "在空中" in prompt or "已起飞" in prompt:
|
||||||
@@ -75,8 +121,23 @@ def run_stage_debug_single():
|
|||||||
print(f"✅ 请求成功 (耗时: {result['latency']:.2f}s)")
|
print(f"✅ 请求成功 (耗时: {result['latency']:.2f}s)")
|
||||||
data = result["data"]
|
data = result["data"]
|
||||||
output = data.get("output", data)
|
output = data.get("output", data)
|
||||||
|
upstream = data.get("upstream") or {}
|
||||||
|
target_stage = data.get("target_stage", 0)
|
||||||
|
if target_stage >= 2 and not upstream:
|
||||||
|
print("\n⚠️ 未获取到上游 Stage 输出。请重启后端服务 (如 start_all.sh) 后重试。")
|
||||||
|
elif upstream:
|
||||||
print("\n" + "=" * 60)
|
print("\n" + "=" * 60)
|
||||||
print(json.dumps(output, ensure_ascii=False, indent=2))
|
print("【上游 Stage 输出】")
|
||||||
|
print("=" * 60)
|
||||||
|
for k, v in sorted(upstream.items()):
|
||||||
|
print(f"\n--- {k} ---")
|
||||||
|
summarized = _summarize_output_for_display(v, upstream)
|
||||||
|
print(json.dumps(summarized, ensure_ascii=False, indent=2))
|
||||||
|
print("\n" + "=" * 60)
|
||||||
|
print(f"【Stage{data.get('target_stage', '?')} 输出】")
|
||||||
|
print("=" * 60)
|
||||||
|
summarized_output = _summarize_output_for_display(output, upstream)
|
||||||
|
print(json.dumps(summarized_output, ensure_ascii=False, indent=2))
|
||||||
print("=" * 60)
|
print("=" * 60)
|
||||||
|
|
||||||
# 保存到 validation/temporary
|
# 保存到 validation/temporary
|
||||||
|
|||||||
Reference in New Issue
Block a user