From b1700a826043bd0bae463fc7aa4615350220ef34 Mon Sep 17 00:00:00 2001 From: huangfu <3045324663@qq.com> Date: Thu, 26 Feb 2026 20:43:26 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96=E6=B5=8B=E8=AF=95=E8=84=9A?= =?UTF-8?q?=E6=9C=ACstage=E5=88=86=E5=88=AB=E8=BE=93=E5=87=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../py_tree_generator.cpython-313.pyc | Bin 38579 -> 39267 bytes .../llm/__pycache__/gateway.cpython-313.pyc | Bin 5574 -> 5449 bytes .../__pycache__/tool_runtime.cpython-313.pyc | Bin 8255 -> 6885 bytes backend_service/src/llm/gateway.py | 7 +- .../__pycache__/orchestrator.cpython-313.pyc | Bin 1986 -> 1986 bytes .../__pycache__/stages.cpython-313.pyc | Bin 17936 -> 16952 bytes backend_service/src/pipeline/orchestrator.py | 4 +- backend_service/src/pipeline/stages.py | 13 +-- .../src/prompts/scene_classifier_prompt.txt | 17 ++-- backend_service/src/py_tree_generator.py | 86 +++++++++++++++++- .../stage_debugger.cpython-313.pyc | Bin 12154 -> 15717 bytes tools/test_validate/modules/stage_debugger.py | 63 ++++++++++++- 12 files changed, 167 insertions(+), 23 deletions(-) diff --git a/backend_service/src/__pycache__/py_tree_generator.cpython-313.pyc b/backend_service/src/__pycache__/py_tree_generator.cpython-313.pyc index cd04417b040106b30c6d9a66745b2c3a4b15edcf..30f461e7c5d61e483c42860d633337989e2da346 100644 GIT binary patch delta 3553 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 D%s_+1 delta 2936 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 delta 1345 zcmaKrOH3O_7{_P!X|M6x4j8Y&Ji?=3NNPx88Xkcrl4)!U5quextUVQ)rP1@Ra^DuKGa;Qsxx+6ZXK9Wf2`6?Y6s&Xt z>I+F%q5(HhEg~uDPN-N-c(A7vXrK!qxdk9clwVQ{t$)+d zM*u;SN1V>7G>r~ba8@vdcJd{!ku`oD3goV9fcOPpfM$4+L$WTgI>I3baw262l3hMP zt_e+zF4TbBLvRQpbyPA%T}*k&T_FfhkT>{7*hF}td)aGB$frx=bUoP@07y4imqwA& z>gu?rJ2-*7x)kMWUaIvtR#(yI!j#gXx|eDvPldizLaOS3&TSeGXXE34+M$@Rc2Ji+x!F?dm5Lv9T!$6l>QFbXR5TFm zFKdx~EwT;wwKHXHY+oBIYnS%5OJ(iyqlJz5kw@E%A9#8XL+uZyx28+o7e2#}Mh`+W zF8qXIJfj$r09iiQznjEw5y)Y@vYh1hcALV@$bNkf+CrUuMl$7%nrgXN(;T7s;S^ z#Gj?XU}Y?|HtaFaH%G}Ki|l~Pb7AgIAgEw;l#q>3gsrd7LK(k5=2hI)v5X(^~C!iQ>UdO%kiOJ}s;E;37%;GSB z6N&=W2aE|oW1Jf>%po1ZYey2Lp}zmhv?I~@S+OPLVGLBr!vs)cj<|}W7;yOlfU8zM z_zG+~SJ_>5T^E^a(;@wlx)CQb7%MM4&I*lz$V~5Vlq6G298_z7ot$06jRbaT)*R6k z?>_s=tMhOdOUXiUH@Li7Uuk(^PVu*7Yu-tYt2{JV*i~xPR{hpWb@k2?{>nVUhsJcK z%`SHxc64J~AK%i)->Tbsc}p+v7^yG#F==-`XP=Ss6W(H-1zYO2yTS?H)AJF}?*?kk z=7awftl{gCEKzWCQbC&osHi}Pk1yaX%x!a_TYQCUf6o|P8JbI^? Fz+WLtno0lw delta 1922 zcma)6|4&<06ubB9ydHD)n2A@pYT5}M1)5|JLW2U(X`BbUhUhY{AbC8J^ z^JY|$ge#H3C&=j>BxNv@c4pZ}%WZoG+?CzE?XBIRgYL?JKj`l}K@Psn{H-81?x8^x6{~-8 zc_uxhwRen#p#4q!U~(xUYMAbQc{M(CJ;H*=hv(&a=(O# zC^0da7{3v_c`5$h$m-YgiHYxHH*c+eGIf99{3djqa-7y!i(Fm16kHsuKm;f z%lRdX`6W}HrF{2NZu!su3;IK&w1^Lo(jY$2Ba=nuGI{ZELI#&V0(D6)CBqh#@M2R# z;*ycL#U)K-7Z~{rh7>^B;|wtr7_z1q+O|Q^IGR!>tadAcN~x;0Ws*umDk_baOEs6E z%BxCsf)1$3KxRIwbUs>ZP>r=%hxO1Vv!EG@Zgo)O)lgH8h3+@!1&q(|Uha7~rK13< zPb(s9N+TA8a5lrzhyX~=BM4k!9dsC>hwH)ILb;)vO&>1G)Vd5+k?}DJ+);zI|tyqbZ z0S0|$Jyk(Mplz$^e{F0ZfM4h&Py;nR6R}Nmhc2=}AAx|UBt?Fi0aAELWA+tg8@!hP zaM+~Xc@CR$+cq-H!e-I)c`uq+ww c_u7lO06k)F;U?&`z1ZmEIm@4jN$Ju21JZ`B^8f$< diff --git a/backend_service/src/llm/gateway.py b/backend_service/src/llm/gateway.py index 4b11d073..304d3773 100644 --- a/backend_service/src/llm/gateway.py +++ b/backend_service/src/llm/gateway.py @@ -15,14 +15,17 @@ class LLMGateway: self.generator = generator 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" 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( model=self.generator.classifier_model, messages=[ {"role": "system", "content": self.generator.scene_classifier_prompt or "你是一个分类器,只输出JSON。"}, - {"role": "user", "content": user_prompt}, + {"role": "user", "content": user_content}, ], temperature=0.0, response_format={"type": "json_object"}, diff --git a/backend_service/src/pipeline/__pycache__/orchestrator.cpython-313.pyc b/backend_service/src/pipeline/__pycache__/orchestrator.cpython-313.pyc index c51df30c5ff583553d4805d5cb3f98aebb530eca..8b7c4f030db1f0c1e2db874a7d03ae74294f2cf4 100644 GIT binary patch delta 20 acmX@ae~6#^GcPX}0}y;RTd@k+ zQ7FAP23>R$L3UHS=`Ny+pa_b(kQzbV1?yr?4ML>v`!vnIi|4%G^S;mXKIfeI+wgn} zY;~*EOz8O%doZ!k@zl1U!czH>8aKd5`MdYE2`0;rLj?mYK5IQ`f=W5x(Ja8*^0io0 zXVFugKnT>=b$Hg-jz2oxARu%pcqS!@gM<`xSc|K8KOv(duGock64kL~gd89RKIteJ z`UQ~^{rVP)1tBkEEzux~y!z0kC=uQEh>?$wybZrOoOsvWf-@c$-bq?jm3gu=xuvD4pB?eX8==B8neU9DPPu5l$NEE^*Ex_t3KFNAu75lO(#qn%Y zV2#oLDHs=)cND=r%WR5|a?3URnmrA5>^zdYp^D~}h?hdVmBJwSMc+y{q(tAZNl-+$ z7Gly-=4jY*WCZ&V8c)j&Sryj*(`W2&>G(vkG*m2U!!-ZVZN-lE7$JV5t+NidVbyLM a+?PwjPdt^o00dv;dhG)OxR(h-p6m}Nbi%;^ delta 1564 zcmaJ>YiLtv82-MKV7OR z+*a6TQ@?x9wz0{9HQ|3UKIp6j{=GprUqF^6b)yzqO=w+ze_u6W`!{Orc= zVFvzMi0y5Y!5<4}y91P3iSuLtz=xv>G-~Ta>(5&ug<#O6|laQE(IL+(GT6m+_sPxllWt&Y{YE5jCNMR5Mu?F;8 z1Bbrya#YNUV8|>M@`beKCL9;fNV8kUzQeSYhCqHhe;-nxlthOfMP#z7fOcvjOMy+&>rBkpj+XAwx4l3!Z!pW z1jyy7&{GK^aVk}TXDaY)1-`znx)AB^`IN-p#xPAv^T|_FC&!(4bC*`XoL&9=#@#}0 z^{tQBKRkEu>ZP@-^Y>=1ti5?F

I5Pvk_GNl20GG=c94xCy*N;5>ovFjQ2j@$}gE zL}L168mE07ISo}P6!lvK(qn}>Jth2sjTCtcolYl8Mvp4h`1MHnDD+dbB~w*5meo6p z>YYnx?x;KR2g>_~@n?D8aFuJG56^`cUMq2JWv)Hvp4tD4zG=bpk-V&LE$Ulyo@?IA z-h6k-A6o7`Qt}^JG4CjwyNc$n{GOsYkfm0dY>SSIj$C8DtJKt;rC03E#fgg(OMI^F zn*Xvt&);;F{Fz9}9?jC()SUX6@&60EOa5rd-doGAI>Re2Z`svbboJg>$cA7;R#`T< ziw5^nFn7FU*pu%l8U{BQU~HV%T+l2#hHeYD$Chc^i+2o%tA+Jw7uItG+-?w;u#34mNV&Mk2=OW&J*u%+GdL)$=j^ddP5*;yPJNFO(c6{aW9jc8_ ftfytr;6MaEK$(G8A&u4s2K7l9v_1d?#m@c(+|+-y diff --git a/backend_service/src/pipeline/orchestrator.py b/backend_service/src/pipeline/orchestrator.py index aabe266d..5b82cb0b 100644 --- a/backend_service/src/pipeline/orchestrator.py +++ b/backend_service/src/pipeline/orchestrator.py @@ -9,8 +9,8 @@ class GenerationOrchestrator: def __init__(self, generator: Any): self.stages = PipelineStages(generator) - async def generate(self, user_prompt: str) -> Dict: - understanding = self.stages.stage1_task_understanding(user_prompt) + async def generate(self, user_prompt: str, drone_state: str = "on_ground") -> Dict: + understanding = self.stages.stage1_task_understanding(user_prompt, drone_state=drone_state) context = self.stages.stage2_context_binding(user_prompt, understanding) if understanding.scene_mode == "simple": diff --git a/backend_service/src/pipeline/stages.py b/backend_service/src/pipeline/stages.py index 055dd4ac..ca53d3b0 100644 --- a/backend_service/src/pipeline/stages.py +++ b/backend_service/src/pipeline/stages.py @@ -71,18 +71,15 @@ class PipelineStages: def __init__(self, generator: Any): 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 ==========") - 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) risk_flags = _extract_risk_flags(user_prompt) 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} logging.info(f"Task Understanding Results: mode={scene_mode}, state={drone_state}, intent={intent_type}, risks={risk_flags}") diff --git a/backend_service/src/prompts/scene_classifier_prompt.txt b/backend_service/src/prompts/scene_classifier_prompt.txt index cde48caf..0a84ac4c 100644 --- a/backend_service/src/prompts/scene_classifier_prompt.txt +++ b/backend_service/src/prompts/scene_classifier_prompt.txt @@ -1,9 +1,12 @@ -你是指令分类器。只输出一个JSON,无其它内容。 -输入:无人机状态{on_ground/in_air}+指令。 +你是指令分类器,仅输出JSON对象,无任何多余内容。 +输入:无人机状态(on_ground/in_air)+指令。 输出仅三选一:{"mode":"simple"}、{"mode":"scene1"}、{"mode":"scene4"}。 -规则: -1. 指令含“面前”→scene1 -2. 状态=in_air,指令是:飞到某地/飞到某地+方位距离/往某方向飞X米/降落/旋转/悬停 →simple -3. 状态=on_ground,指令含去/飞到/回到某地 →非simple -4. 多动作/序列任务→scene4 \ No newline at end of file +规则(必须严格执行): +1. 指令包含“面前”→输出{"mode":"scene1"}; +2. 无人机状态=in_air且指令包含“飞到”→输出{"mode":"simple"}; +3. 无人机状态=in_air且指令包含“往某方向飞”或“降落”或“旋转”或“悬停”→输出{"mode":"simple"}; +4. 无人机状态=on_ground且指令包含“起飞”或“旋转”或“悬停”→输出{"mode":"simple"}; +5. 无人机状态=on_ground且指令包含“飞到”或“去”或“回到”→输出{"mode":"scene4"}; +6. 指令包含“先”或“再”或“搜索”或“监控”或“拍照”或“返航”或“确认”→输出{"mode":"scene4"}; +7. 其他所有情况→输出{"mode":"scene4"}。 \ No newline at end of file diff --git a/backend_service/src/py_tree_generator.py b/backend_service/src/py_tree_generator.py index 6a971778..87ef1343 100644 --- a/backend_service/src/py_tree_generator.py +++ b/backend_service/src/py_tree_generator.py @@ -790,9 +790,89 @@ class PyTreeGenerator: except Exception as e: logging.warning(f"保存推理链Markdown失败: {e}") - async def generate(self, user_prompt: str) -> Dict[str, Any]: - logging.info(f"接收到用户请求: {user_prompt}") - return await self.orchestrator.generate(user_prompt) + async def generate(self, user_prompt: str, drone_state: str = "on_ground") -> Dict[str, Any]: + logging.info(f"接收到用户请求: {user_prompt}, drone_state={drone_state}") + 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 py_tree_generator = PyTreeGenerator() diff --git a/tools/test_validate/modules/__pycache__/stage_debugger.cpython-313.pyc b/tools/test_validate/modules/__pycache__/stage_debugger.cpython-313.pyc index 0659022fdae8f453e20797d756f5a53c7aa767d7..9d0d4d3d18c4ce63fe0b46242ae60d2d02ce390a 100644 GIT binary patch delta 4971 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 diff --git a/tools/test_validate/modules/stage_debugger.py b/tools/test_validate/modules/stage_debugger.py index f9fe6f05..c41c4246 100644 --- a/tools/test_validate/modules/stage_debugger.py +++ b/tools/test_validate/modules/stage_debugger.py @@ -39,6 +39,52 @@ def _select_stage(): 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: """从指令中简单推断 drone_state(批量模式下若未单独指定则用此)""" if "在空中" in prompt or "已起飞" in prompt: @@ -75,8 +121,23 @@ def run_stage_debug_single(): print(f"✅ 请求成功 (耗时: {result['latency']:.2f}s)") data = result["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("【上游 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(json.dumps(output, ensure_ascii=False, indent=2)) + 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) # 保存到 validation/temporary